瀏覽代碼

feat(llm): default Chat Completions to DeepSeek V41 Flash

Yichen Jiang 4 天之前
父節點
當前提交
bc5fd3b8dc
共有 28 個文件被更改,包括 130 次插入148 次删除
  1. 2 2
      .agents/notes/implemented/feature/2026-09-02-in-history-system-prompt-replacement.i18n.yaml
  2. 1 1
      .agents/notes/implemented/feature/2026-09-02-in-history-system-prompt-replacement.md
  3. 1 1
      .agents/notes/implemented/feature/2026-09-02-in-history-system-prompt-replacement.zh.md
  4. 5 25
      apps/web/tests/expected/onboarding-deepseek-config/models.expected.md
  5. 24 12
      apps/web/tests/onboarding-deepseek-config.e2e.ts
  6. 2 2
      docs/config-catalog.i18n.yaml
  7. 2 2
      docs/config-catalog.md
  8. 2 2
      docs/config-catalog.zh.md
  9. 2 2
      docs/user/guide/providers.i18n.yaml
  10. 1 1
      docs/user/guide/providers.md
  11. 1 1
      docs/user/guide/providers.zh.md
  12. 1 1
      packages/bundle/base/cordis.patch.yml
  13. 2 2
      packages/llm/llm-deepseek/README.i18n.yaml
  14. 3 2
      packages/llm/llm-deepseek/README.md
  15. 3 2
      packages/llm/llm-deepseek/README.zh.md
  16. 1 0
      packages/llm/llm-deepseek/package.json
  17. 4 15
      packages/llm/llm-deepseek/src/index.ts
  18. 37 3
      packages/llm/llm-deepseek/tests/adapter.e2e.ts
  19. 11 46
      packages/llm/llm-deepseek/tests/adapter.spec.ts
  20. 4 4
      packages/llm/llm-deepseek/tests/dynamic-config.spec.ts
  21. 二進制
      packages/llm/llm-deepseek/tests/fixtures/red.png
  22. 3 0
      pnpm-lock.yaml
  23. 4 4
      snapshots/sdk/system-prompt-in-history/notifications.expected.jsonl
  24. 4 4
      snapshots/sdk/system-prompt-in-history/session.v3.jsonl
  25. 3 3
      snapshots/session/system-prompt-in-history/cordis.snapshot.yml
  26. 1 5
      snapshots/session/system-prompt-in-history/cordis.yml
  27. 4 4
      snapshots/session/system-prompt-in-history/session.v3.jsonl
  28. 2 2
      snapshots/session/system-prompt-in-history/system-prompt.expected.md

+ 2 - 2
.agents/notes/implemented/feature/2026-09-02-in-history-system-prompt-replacement.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-09-02-in-history-system-prompt-replacement.md
-2026-09-02-in-history-system-prompt-replacement.md: b34a0d2b3591c1b62aba16d79963940be787f373
-2026-09-02-in-history-system-prompt-replacement.zh.md: 296932a977ae852c4ef32de1c23b2c5d9e4a1bbf
+2026-09-02-in-history-system-prompt-replacement.md: 8e5fa65ec4649537889cefc75459d2a4958165e5
+2026-09-02-in-history-system-prompt-replacement.zh.md: 73003b04de82f45154f9c673654cc492eb62a4fd

+ 1 - 1
.agents/notes/implemented/feature/2026-09-02-in-history-system-prompt-replacement.md

@@ -18,7 +18,7 @@ For a model route that declares the capability, the loop appends a new `system/m
 
 ### Capability
 
-`dsh-llm` defines `SystemPromptUpdate = 'in-history'` and carries it as an optional sibling field, `systemPromptUpdate`, on `LlmResolvedModelInfo` and `PreparedLlmCall`; `normalizeModelInfo` rejects any other value with an `LlmError` whose code is `INVALID_MODEL_INFO`. The DeepSeek adapter's catalog model (`DeepSeekCatalogModel.systemPromptUpdate`, validated by zod at load) and the replay provider's `ReplayModelConfig.systemPromptUpdate` declare it per model; absence means the model needs message 0 rewritten. No default catalog entry declares it; a deployment enables it through the `models` list in `cordis.yml`, and every `dsh-llm-pi-ai` route keeps the replace behaviour.
+`dsh-llm` defines `SystemPromptUpdate = 'in-history'` and carries it as an optional sibling field, `systemPromptUpdate`, on `LlmResolvedModelInfo` and `PreparedLlmCall`; `normalizeModelInfo` rejects any other value with an `LlmError` whose code is `INVALID_MODEL_INFO`. The DeepSeek adapter's catalog model (`DeepSeekCatalogModel.systemPromptUpdate`, validated by zod at load) and the replay provider's `ReplayModelConfig.systemPromptUpdate` declare it per model; absence means the model needs message 0 rewritten. The sole built-in entry in `dsh-llm-deepseek`, `deepseek-flash`, declares it alongside text and image input. This exact catalog entry records the model capability; names and protocol families do not imply support for other models. A deployment can replace the catalog through the `models` list in `cordis.yml`, and every `dsh-llm-pi-ai` route keeps the replace behaviour.
 
 The loop records the mode in the session: `RequestContext.systemPromptUpdate` joins provider, model, and capacity as a `request/context` field, logged whenever any of them differs from the latest snapshot. Admission reads `PreparedLlmCall.systemPromptUpdate` from the actual call prepared after `agent/request`; the preceding snapshot is not an admission input. First requests, resumed sessions, route changes, and same-route capability changes therefore use the capability of the bound adapter that will serve the call.
 

+ 1 - 1
.agents/notes/implemented/feature/2026-09-02-in-history-system-prompt-replacement.zh.md

@@ -18,7 +18,7 @@ Status: implemented
 
 ### 能力
 
-`dsh-llm` 定义 `SystemPromptUpdate = 'in-history'`,并把它作为可选的并列字段 `systemPromptUpdate` 放在 `LlmResolvedModelInfo` 与 `PreparedLlmCall` 上;`normalizeModelInfo` 用代码为 `INVALID_MODEL_INFO` 的 `LlmError` 拒绝任何其他值。DeepSeek 适配器的目录模型(`DeepSeekCatalogModel.systemPromptUpdate`,加载时由 zod 校验)与回放提供者的 `ReplayModelConfig.systemPromptUpdate` 逐模型声明它;缺省表示该模型需要重写消息 0。没有默认目录条目声明它;部署方通过 `cordis.yml` 的 `models` 列表启用,所有 `dsh-llm-pi-ai` 路由保持替换行为。
+`dsh-llm` 定义 `SystemPromptUpdate = 'in-history'`,并把它作为可选的并列字段 `systemPromptUpdate` 放在 `LlmResolvedModelInfo` 与 `PreparedLlmCall` 上;`normalizeModelInfo` 用代码为 `INVALID_MODEL_INFO` 的 `LlmError` 拒绝任何其他值。DeepSeek 适配器的目录模型(`DeepSeekCatalogModel.systemPromptUpdate`,加载时由 zod 校验)与回放提供者的 `ReplayModelConfig.systemPromptUpdate` 逐模型声明它;缺省表示该模型需要重写消息 0。`dsh-llm-deepseek` 仅内置 `deepseek-flash` 条目,在该条目上声明它,同时声明文本和图片输入。该精确目录条目记录模型能力;名称和协议类别不能推导其他模型是否支持。部署方可以通过 `cordis.yml` 的 `models` 列表替换目录,所有 `dsh-llm-pi-ai` 路由保持替换行为。
 
 循环把该模式记录进会话:`RequestContext.systemPromptUpdate` 与 provider、model、容量并列成为 `request/context` 的字段,其中任一项与最新快照不同时就记录一次。准入读取 `agent/request` 之后实际准备调用的 `PreparedLlmCall.systemPromptUpdate`;先前快照不是准入输入。因此首次请求、恢复的会话、路由变更以及同一路由的能力变更,都使用将服务该调用的绑定适配器的能力。
 

+ 5 - 25
apps/web/tests/expected/onboarding-deepseek-config/models.expected.md

@@ -35,41 +35,21 @@
           - text: 模型目录 已自定义模型目录
           - button "恢复默认模型"
           - textbox "模型 ID 1":
-            - /placeholder: 模型 ID
-            - text: deepseek-v4-pro
-          - textbox "显示名称 1":
-            - /placeholder: 显示名称
-            - text: DeepSeek-V4-Pro
-          - button "容量 1":
-            - img
-          - button "删除模型 1":
-            - img
-          - textbox "模型 ID 2":
-            - /placeholder: 模型 ID
-            - text: deepseek-v4-flash-vision-exp
-          - textbox "显示名称 2":
-            - /placeholder: 显示名称
-            - text: DeepSeek-V4-Flash-Vision-Exp
-          - button "容量 2":
-            - img
-          - button "删除模型 2":
-            - img
-          - textbox "模型 ID 3":
             - /placeholder: 模型 ID
             - text: private-preview
-          - textbox "显示名称 3":
+          - textbox "显示名称 1":
             - /placeholder: 显示名称
             - text: Private Preview
-          - button "容量 3" [expanded]:
+          - button "容量 1" [expanded]:
             - img
-          - button "删除模型 3":
+          - button "删除模型 1":
             - img
           - text: 上下文窗口
-          - textbox "上下文窗口 3":
+          - textbox "上下文窗口 1":
             - /placeholder: 1M
             - text: "131072"
           - text: 最大输出 token 数
-          - textbox "最大输出 token 数 3":
+          - textbox "最大输出 token 数 1":
             - /placeholder: 256K
             - text: 64K
           - button "添加模型":

+ 24 - 12
apps/web/tests/onboarding-deepseek-config.e2e.ts

@@ -202,15 +202,32 @@ describe.skipIf(MODE === 'record')('web e2e: first-run DeepSeek credential setup
     await deepSeek.waitFor({ timeout: 10_000 })
     await deepSeek.locator('xpath=ancestor::li').getByRole('button', { name: '编辑' }).click()
     await settings.getByText('自定义设置').click()
+    expect(await settings.getByLabel('模型 ID 1').inputValue()).toBe('deepseek-flash')
+    expect(await settings.getByLabel('显示名称 1').inputValue()).toBe('DeepSeek-V41-Flash')
+    expect(await settings.getByRole('button', { name: /删除模型/ }).count()).toBe(1)
+    await settings.getByLabel('显示名称 1').fill('Configured Flash')
+    await settings.getByRole('button', { name: '保存', exact: true }).click()
+    await settings.getByLabel('模型 ID 1').waitFor({ state: 'detached', timeout: 15_000 })
+    const savedDefaults = await readFile(join(scaffold.harnessHome, 'settings.yaml'), 'utf8')
+    expect(savedDefaults).toContain('id: deepseek-flash')
+    expect(savedDefaults).toContain('inputModalities:')
+    expect(savedDefaults).toContain('- text')
+    expect(savedDefaults).toContain('- image')
+    expect(savedDefaults).toContain('systemPromptUpdate: in-history')
+    await expect(scaffold.ctx.llm.resolveModelInfo('deepseek-official', 'deepseek-flash')).resolves.toMatchObject({
+      name: 'Configured Flash', inputModalities: ['text', 'image'], systemPromptUpdate: 'in-history',
+    })
+    await deepSeek.locator('xpath=ancestor::li').getByRole('button', { name: '编辑' }).click()
+    await settings.getByText('自定义设置').click()
     await settings.getByRole('button', { name: /删除模型/ }).first().click()
     await settings.getByRole('button', { name: '添加模型' }).click()
-    const customModelId = settings.getByLabel('模型 ID 3')
+    const customModelId = settings.getByLabel('模型 ID 1')
     await customModelId.fill('private-preview')
-    await settings.getByLabel('显示名称 3').fill('Private Preview')
+    await settings.getByLabel('显示名称 1').fill('Private Preview')
     // Capacities live behind the row's own disclosure, as in the pi-ai form.
-    await settings.getByRole('button', { name: '容量 3' }).click()
-    await settings.getByLabel('上下文窗口 3').fill('131072')
-    await settings.getByLabel('最大输出 token 数 3').fill('64K')
+    await settings.getByRole('button', { name: '容量 1' }).click()
+    await settings.getByLabel('上下文窗口 1').fill('131072')
+    await settings.getByLabel('最大输出 token 数 1').fill('64K')
 
     await expect.poll(
       () => settings.getByLabel('API 密钥', { exact: true }).getAttribute('placeholder'),
@@ -222,15 +239,11 @@ describe.skipIf(MODE === 'record')('web e2e: first-run DeepSeek credential setup
     await customModelId.waitFor({ state: 'detached', timeout: 15_000 })
 
     const document = await readFile(join(scaffold.harnessHome, 'settings.yaml'), 'utf8')
-    expect(document).toContain('id: deepseek-v4-pro')
-    expect(document).toContain('id: deepseek-v4-flash-vision-exp')
-    expect(document).toContain('inputModalities:')
-    expect(document).toContain('- image')
     expect(document).toContain('id: private-preview')
     expect(document).toContain('name: Private Preview')
     expect(document).toContain('contextWindow: 131072')
     expect(document).toContain('maxTokens: 64000')
-    expect(document).not.toMatch(/^\s*- id: deepseek-v4-flash$/m)
+    expect(document).not.toContain('id: deepseek-flash')
 
     await page.keyboard.press('Escape')
     // A connected Workspace is what puts a live composer — and its model
@@ -241,8 +254,7 @@ describe.skipIf(MODE === 'record')('web e2e: first-run DeepSeek credential setup
     await modelTrigger.waitFor({ timeout: 10_000 })
     await modelTrigger.click()
     await page.getByRole('menuitem', { name: /模型/ }).click()
-    expect(await page.getByText('deepseek-v4-flash', { exact: true }).count()).toBe(0)
-    await page.getByRole('menuitemradio', { name: 'DeepSeek-V4-Flash-Vision-Exp' }).waitFor({ timeout: 10_000 })
+    expect(await page.getByText('Configured Flash', { exact: true }).count()).toBe(0)
     await page.getByRole('menuitemradio', { name: 'Private Preview' }).waitFor({ timeout: 10_000 })
     expect(tripwire.warnings).toEqual([])
     expect(tripwire.pageErrors).toEqual([])

+ 2 - 2
docs/config-catalog.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write docs/config-catalog.md
-config-catalog.md: 9adf44b995601196b602e9219e53c52208039051
-config-catalog.zh.md: e2f6c6babc18bfa689f6830c81231012b4d9de18
+config-catalog.md: 8f6907a878c878b7d6f5dc5d51fc51576af426f2
+config-catalog.zh.md: e3fc5ce54aad731a510bcfd51f04cfb5ed30bb2b

+ 2 - 2
docs/config-catalog.md

@@ -1037,7 +1037,7 @@ export interface Config {
   maxTokens?: number
   /** Positive context capacity used when the selected model has no exact value (default 1,000,000). */
   defaultContextWindow?: number
-  /** Advisory models shown by discovery consumers; defaults to V4 Flash, V4 Pro, and V4 Flash Vision Exp. */
+  /** Advisory models shown by discovery consumers; defaults to DeepSeek-V41-Flash. */
   models?: DeepSeekCatalogModel[]
   /** Maximum provider idle time while one stream read is outstanding (default five minutes). */
   streamIdleTimeoutMs?: number
@@ -1094,7 +1094,7 @@ export interface DeepSeekCatalogModel {
 
 Depends on: [`ModelModality`](../packages/llm/llm/src/index.ts) · [`RetryPolicyConfig`](../packages/llm/llm/src/index.ts) · [`SystemPromptUpdate`](../packages/llm/llm/src/index.ts)
 
-Source: [`packages/llm/llm-deepseek/src/index.ts:125`](../packages/llm/llm-deepseek/src/index.ts)
+Source: [`packages/llm/llm-deepseek/src/index.ts:114`](../packages/llm/llm-deepseek/src/index.ts)
 
 <a id="deepseek-aidsh-llm-pi-ai"></a>
 

+ 2 - 2
docs/config-catalog.zh.md

@@ -1039,7 +1039,7 @@ export interface Config {
   maxTokens?: number
   /** Positive context capacity used when the selected model has no exact value (default 1,000,000). */
   defaultContextWindow?: number
-  /** Advisory models shown by discovery consumers; defaults to V4 Flash, V4 Pro, and V4 Flash Vision Exp. */
+  /** Advisory models shown by discovery consumers; defaults to DeepSeek-V41-Flash. */
   models?: DeepSeekCatalogModel[]
   /** Maximum provider idle time while one stream read is outstanding (default five minutes). */
   streamIdleTimeoutMs?: number
@@ -1096,7 +1096,7 @@ export interface DeepSeekCatalogModel {
 
 依赖:[`ModelModality`](../packages/llm/llm/src/index.ts) · [`RetryPolicyConfig`](../packages/llm/llm/src/index.ts) · [`SystemPromptUpdate`](../packages/llm/llm/src/index.ts)
 
-来源:[`packages/llm/llm-deepseek/src/index.ts:125`](../packages/llm/llm-deepseek/src/index.ts)
+来源:[`packages/llm/llm-deepseek/src/index.ts:114`](../packages/llm/llm-deepseek/src/index.ts)
 
 <a id="deepseek-aidsh-llm-pi-ai"></a>
 

+ 2 - 2
docs/user/guide/providers.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write docs/user/guide/providers.md
-providers.md: 0db3280760c487c8469399bc9668b0685213e375
-providers.zh.md: 3af9096f5a77ee40afe2ee788aa22a0758dd0e86
+providers.md: 90d3bb2c96a80185fbc5e3e70f1ff577f6923acc
+providers.zh.md: 2937d676253ee45dad48655398736d36fc37dff2

+ 1 - 1
docs/user/guide/providers.md

@@ -186,5 +186,5 @@ Every switch, its accepted values, and the protocols that take it are listed und
 - **The Effort menu does not appear for a model you entered by hand** — It declares no levels. Add `reasoningEfforts` to the model in `settings.yaml`.
 - **`off` does not stop a DeepSeek model from thinking** — An empty `off` sends no reasoning field at all, and an endpoint that thinks by default keeps thinking. Set `compat.thinkingFormat: deepseek` on the model or the route.
 - **A compat switch is refused as having no value** — A key written with nothing after the colon. Give it a value, or remove the key to keep the installed catalog's.
-- **An image is refused before sending** — The model declares no image modality. Give a custom provider's model `input: [text, image]`; on DeepSeek's own route, select `deepseek-v4-flash-vision-exp`, the model that declares images.
+- **An image is refused before sending** — The model declares no image modality. Give a custom provider's model `input: [text, image]`; on DeepSeek's own route, select an image-capable entry from the configured catalog (`deepseek-flash` by default) and confirm that your gateway serves that model with image input.
 - **The provider rejects a request carrying an image** — The model declares images its endpoint does not actually serve. Remove `image` from whichever list granted it — the model's `input`, or the route's `defaultInput` — then start a new session: the attached image stays in the session log, so the same request repeats until the session moves off it.

+ 1 - 1
docs/user/guide/providers.zh.md

@@ -186,5 +186,5 @@ llm-pi-ai:
 - **手动录入的模型没有推理等级菜单**:该模型没有声明任何等级。在 `settings.yaml` 中给该模型加上 `reasoningEfforts`。
 - **`off` 无法让 DeepSeek 模型停止思考**:留空的 `off` 不发送任何推理字段,默认思考的端点就继续思考。请在模型或路由上设置 `compat.thinkingFormat: deepseek`。
 - **某个 compat 开关因没有值而被拒绝**:冒号后什么都没写。给它一个值,或删掉该键以沿用已安装 catalog 的值。
-- **图片在发送前被拒绝**:该模型未声明图片模态。请给自定义提供方的模型加上 `input: [text, image]`;在 DeepSeek 自身的路由上,请选择声明了图片能力的模型 `deepseek-v4-flash-vision-exp`
+- **图片在发送前被拒绝**:该模型未声明图片模态。请给自定义提供方的模型加上 `input: [text, image]`;在 DeepSeek 自身的路由上,请从配置的目录中选择支持图片的条目(默认为 `deepseek-flash`),并确认网关提供该模型且支持图片输入
 - **提供方拒绝了带图片的请求**:该模型声明了其端点实际并不提供的图片能力。请从授予它图片能力的那个列表中移除 `image`——可能是模型的 `input`,也可能是路由的 `defaultInput`——然后开启新会话:附加的图片会留在会话日志里,因此在会话离开它之前,同一个请求会不断重复。

+ 1 - 1
packages/bundle/base/cordis.patch.yml

@@ -76,7 +76,7 @@
       name: '@deepseek-ai/dsh-agent-default-model'
       config:
         provider: deepseek-official
-        model: deepseek-v4-flash
+        model: deepseek-flash
 
     - id: jobs
       name: '@deepseek-ai/dsh-jobs-local'

+ 2 - 2
packages/llm/llm-deepseek/README.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write packages/llm/llm-deepseek/README.md
-README.md: 64d09214b189605ad4f72f6dedde72f0099aef0a
-README.zh.md: 3b8a2dc93fc0106e166c96ce49eab8fb59ba6203
+README.md: 19025b801943ecccd9a17a28870fc364fede7d69
+README.zh.md: 89fa42491aaa2f13ef09b2d0a70c5297cfb5c345

+ 3 - 2
packages/llm/llm-deepseek/README.md

@@ -46,7 +46,7 @@ Choose this adapter when the deployment targets DeepSeek's official API, optiona
     filesApiTimeoutMs: 60000
 ```
 
-A request selects the route with `provider: deepseek-official`; the model id passes through to the wire, so new DeepSeek models need no re-registration. Omitted `models` advertises `deepseek-v4-flash` as the fast, economical choice for focused work, `deepseek-v4-pro` as the stronger, higher-cost choice for complex or quality-critical work, and the image-capable `deepseek-v4-flash-vision-exp`; each has a 1,000,000-token context window. An explicit list replaces those defaults, and unlisted model ids still pass through as text-only routes. Clients, including model discovery tools, can read the advisory entries through `ctx.llm.listModels('deepseek-official')`. Image-capable entries may set `imagePixelBudget` to a positive integer or `low`, and may set `imageMaxBytes`. An entry may declare `systemPromptUpdate: in-history` when its endpoint reads the latest `system` message at any position of `messages` as the complete effective system prompt; the adapter reports the mode on the resolved model and the prepared call, and the agent loop then appends a changed prompt after the cached history instead of rewriting the leading system message ([decision rule](../../core/agent-loop/README.md#understand-the-implementation)). No default entry declares the mode: a deployment opts in per model through `models`, and any value other than `in-history` fails at load with `llm-deepseek: catalog model "<id>" systemPromptUpdate must be "in-history" when present`.
+A request selects the route with `provider: deepseek-official`; the model id passes through to the wire, so new DeepSeek models need no re-registration. Omitted `models` advertises only the text- and image-capable `deepseek-flash`, with a 1,000,000-token context window. An explicit list replaces those defaults, and unlisted model ids still pass through as text-only routes. Clients, including model discovery tools, can read the advisory entries through `ctx.llm.listModels('deepseek-official')`. Image-capable entries may set `imagePixelBudget` to a positive integer or `low`, and may set `imageMaxBytes`. An entry may declare `systemPromptUpdate: in-history` when its endpoint reads the latest `system` message at any position of `messages` as the complete effective system prompt; the adapter reports the mode on the resolved model and the prepared call, and the agent loop then appends a changed prompt after the cached history instead of rewriting the leading system message ([decision rule](../../core/agent-loop/README.md#understand-the-implementation)). The default `deepseek-flash` entry declares this mode; other models require an explicit `models` declaration, and any value other than `in-history` fails at load with `llm-deepseek: catalog model "<id>" systemPromptUpdate must be "in-history" when present`.
 
 | Field | Default | Meaning |
 |---|---|---|
@@ -56,7 +56,7 @@ A request selects the route with `provider: deepseek-official`; the model id pas
 | `reasoningEffort` | `high` | Default effort: `off`, `low`, `high`, or `max` |
 | `maxTokens` | `256,000` | Per-request output cap; a model's own cap and explicit request values win |
 | `defaultContextWindow` | `1,000,000` | Capacity fallback for models without an exact value |
-| `models` | V4 Flash + V4 Pro + V4 Flash Vision Exp | Advisory catalog shown by discovery consumers |
+| `models` | DeepSeek-V41-Flash | Advisory catalog shown by discovery consumers |
 | `streamIdleTimeoutMs` | `300,000` | Maximum provider idle time per outstanding stream read |
 | `maxRequestFilesBytes` | `128 MiB` | High watermark for retained request-image bytes before oldest-first offload |
 | `maxInlineRequestImageBytes` | `20 MiB` | Independent base64 fallback high watermark |
@@ -188,6 +188,7 @@ These limits define where the adapter stops and future work begins. They are cur
 - **Requests use raw `fetch`, not `@cordisjs/plugin-http`** — no shared proxy or interception configuration.
 - **Plugin-added content block types are skipped** — core text and supported image blocks are serialized, and empty tool output crosses the wire as the literal `(no output)`.
 - **Images are input-only durable attachments** — direct external URLs and assistant image output are not supported; DeepSeek input normally uses the Files API and uses inline base64 only for per-request recovery.
+- The default catalog pre-registers `deepseek-flash` and its text/image and in-history capabilities without probing gateway availability. Requests can fail with `INVALID_REQUEST` until the gateway enables the id. With `DEEPSEEK_API_KEY` and a supporting gateway configured, `DEEPSEEK_FLASH_E2E=1` enables the Chat Completions check in [this package's e2e suite](tests/adapter.e2e.ts).
 
 <a id="dev-note"></a>
 ### Dev Note

+ 3 - 2
packages/llm/llm-deepseek/README.zh.md

@@ -46,7 +46,7 @@ kind: "package-reference"
     filesApiTimeoutMs: 60000
 ```
 
-请求用 `provider: deepseek-official` 选择路由;模型 id 原样传到协议,因此新增 DeepSeek 模型无需重新注册。省略 `models` 时会公布适合专注任务、快速且经济的 `deepseek-v4-flash`,适合复杂或质量关键任务、能力更强且成本更高的 `deepseek-v4-pro`,以及支持图像的 `deepseek-v4-flash-vision-exp`;每个模型都有 1,000,000 token 上下文窗口。显式列表会替换这些默认值,未列出的模型 id 仍作为纯文本路由原样通过。包括模型发现工具在内的客户端可通过 `ctx.llm.listModels('deepseek-official')` 读取这些建议性条目。支持图片的条目可把 `imagePixelBudget` 设置为正整数或 `low`,也可以设置 `imageMaxBytes`。当端点把 `messages` 中任意位置最新的 `system` 消息读作完整的有效系统提示词时,条目可以声明 `systemPromptUpdate: in-history`;适配器会在已解析模型与已准备调用上报告该模式,agent loop(智能体循环)随后把变化后的提示词追加到已缓存历史之后,而不是改写开头的 system 消息([决策规则](../../core/agent-loop/README.zh.md#understand-the-implementation))。没有任何默认条目声明该模式:部署通过 `models` 逐模型启用,`in-history` 以外的任何值都会在加载时以 `llm-deepseek: catalog model "<id>" systemPromptUpdate must be "in-history" when present` 失败。
+请求用 `provider: deepseek-official` 选择路由;模型 id 原样传到协议,因此新增 DeepSeek 模型无需重新注册。省略 `models` 时仅公布支持文本和图像的 `deepseek-flash`,上下文窗口为 1,000,000 token。显式列表会替换这些默认值,未列出的模型 id 仍作为纯文本路由原样通过。包括模型发现工具在内的客户端可通过 `ctx.llm.listModels('deepseek-official')` 读取这些建议性条目。支持图片的条目可把 `imagePixelBudget` 设置为正整数或 `low`,也可以设置 `imageMaxBytes`。当端点把 `messages` 中任意位置最新的 `system` 消息读作完整的有效系统提示词时,条目可以声明 `systemPromptUpdate: in-history`;适配器会在已解析模型与已准备调用上报告该模式,agent loop(智能体循环)随后把变化后的提示词追加到已缓存历史之后,而不是改写开头的 system 消息([决策规则](../../core/agent-loop/README.zh.md#understand-the-implementation))。默认的 `deepseek-flash` 条目声明该模式;其他模型需通过 `models` 显式声明,`in-history` 以外的任何值都会在加载时以 `llm-deepseek: catalog model "<id>" systemPromptUpdate must be "in-history" when present` 失败。
 
 | 字段 | 默认值 | 含义 |
 |---|---|---|
@@ -56,7 +56,7 @@ kind: "package-reference"
 | `reasoningEffort` | `high` | 默认强度:`off`、`low`、`high` 或 `max` |
 | `maxTokens` | `256,000` | 单次请求输出上限;模型自身上限与显式请求值优先 |
 | `defaultContextWindow` | `1,000,000` | 无精确值模型的容量回退 |
-| `models` | V4 Flash + V4 Pro + V4 Flash Vision Exp | 供发现消费方查看的建议性目录 |
+| `models` | DeepSeek-V41-Flash | 供发现消费方查看的建议性目录 |
 | `streamIdleTimeoutMs` | `300,000` | 单次流读取未完成的最大提供方空闲时间 |
 | `maxRequestFilesBytes` | `128 MiB` | 按最旧优先卸载前保留的请求图片字节高水位 |
 | `maxInlineRequestImageBytes` | `20 MiB` | 独立的 base64 回退高水位 |
@@ -188,6 +188,7 @@ loop 保留的响应块会追加到下一个请求,并保留其更早的可复
 - **请求使用原始 `fetch`,而非 `@cordisjs/plugin-http`**——没有共享代理或拦截配置。
 - **跳过插件新增的内容块类型**——核心文本与受支持图片块会被序列化,空工具输出以字面量 `(no output)` 过线。
 - **图片是仅输入的持久附件**——不支持直接外部 URL 与 assistant 图片输出;DeepSeek 输入通常使用 Files API,仅在单次请求恢复时使用内联 base64。
+- 默认目录预注册 `deepseek-flash` 及其文本、图片和历史内更新能力,不探测网关可用性。网关开放该 ID 前,请求可能以 `INVALID_REQUEST` 失败。配置 `DEEPSEEK_API_KEY` 和支持该 ID 的网关后,设置 `DEEPSEEK_FLASH_E2E=1` 可启用[本包 e2e 测试文件](tests/adapter.e2e.ts)中的 Chat Completions 协议验证。
 
 <a id="dev-note"></a>
 ### 开发备注

+ 1 - 0
packages/llm/llm-deepseek/package.json

@@ -52,6 +52,7 @@
     "@deepseek-ai/dsh-anonymous-user-id": "workspace:^",
     "@deepseek-ai/dsh-atomic-write": "workspace:^",
     "@deepseek-ai/dsh-attachment": "workspace:^",
+    "@deepseek-ai/dsh-attachment-local": "workspace:^",
     "@deepseek-ai/dsh-credentials": "workspace:^",
     "@deepseek-ai/dsh-deepseek-llm-api-extensions": "workspace:^",
     "@deepseek-ai/dsh-fs": "workspace:^",

+ 4 - 15
packages/llm/llm-deepseek/src/index.ts

@@ -91,24 +91,13 @@ const PROVIDER = 'deepseek-official'
 
 const DEFAULT_MODELS: DeepSeekCatalogModel[] = [
   {
-    id: 'deepseek-v4-flash',
-    name: 'DeepSeek-V4-Flash',
-    description: 'Fast, efficient, and economical; suited to focused, routine, or parallel tasks.',
-    contextWindow: DEFAULT_CONTEXT_WINDOW,
-  },
-  {
-    id: 'deepseek-v4-pro',
-    name: 'DeepSeek-V4-Pro',
-    description: 'Stronger agentic coding, knowledge, and difficult reasoning; suited to complex or quality-critical tasks at higher cost.',
-    contextWindow: DEFAULT_CONTEXT_WINDOW,
-  },
-  {
-    id: 'deepseek-v4-flash-vision-exp',
-    name: 'DeepSeek-V4-Flash-Vision-Exp',
+    id: 'deepseek-flash',
+    name: 'DeepSeek-V41-Flash',
     contextWindow: DEFAULT_CONTEXT_WINDOW,
     inputModalities: ['text', 'image'],
     imagePixelBudget: DEFAULT_REQUEST_IMAGE_PIXEL_BUDGET,
     imageMaxBytes: DEFAULT_REQUEST_IMAGE_MAX_BYTES,
+    systemPromptUpdate: 'in-history',
   },
 ]
 
@@ -135,7 +124,7 @@ export interface Config {
   maxTokens?: number
   /** Positive context capacity used when the selected model has no exact value (default 1,000,000). */
   defaultContextWindow?: number
-  /** Advisory models shown by discovery consumers; defaults to V4 Flash, V4 Pro, and V4 Flash Vision Exp. */
+  /** Advisory models shown by discovery consumers; defaults to DeepSeek-V41-Flash. */
   models?: DeepSeekCatalogModel[]
   /** Maximum provider idle time while one stream read is outstanding (default five minutes). */
   streamIdleTimeoutMs?: number

+ 37 - 3
packages/llm/llm-deepseek/tests/adapter.e2e.ts

@@ -10,6 +10,7 @@ import AgentRegistry from '@deepseek-ai/dsh-agent'
 import LlmRuntime, { createUserMessage, ToolCallId, ReasoningEffortId, createMessage, createSystemMessage } from '@deepseek-ai/dsh-llm'
 import type { Message, ToolSchema } from '@deepseek-ai/dsh-llm'
 import AttachmentStore, { AttachmentId, ImageVariantId } from '@deepseek-ai/dsh-attachment'
+import LocalAttachments from '@deepseek-ai/dsh-attachment-local'
 import type {
   ImageAttachmentLimits,
   ImageAttachmentRef,
@@ -32,7 +33,8 @@ import { assemble, type AssembledResult } from './assemble.ts'
  * Real-API e2e for the direct-fetch adapter: V4 Flash across thinking modes
  * and a max-effort tool round trip with reasoning passback. The suite skips
  * entirely without $DEEPSEEK_API_KEY; the pre-release vision smoke additionally
- * requires $DEEPSEEK_VISION_E2E=1 (see vitest.e2e.config.ts).
+ * requires $DEEPSEEK_VISION_E2E=1, and the Flash image/system-update smoke
+ * requires $DEEPSEEK_FLASH_E2E=1 (see vitest.e2e.config.ts).
  */
 
 const FLASH = 'deepseek-v4-flash'
@@ -102,12 +104,15 @@ beforeEach(async () => {
   vi.stubEnv('DSH_HOME', identityHome)
 })
 
-async function harness(_model: string, config: Partial<Config> = {}) {
+async function harness(model: string, config: Partial<Config> = {}) {
   const ctx = new Context()
   contexts.push(ctx)
   await ctx.plugin(LlmRuntime)
   await ctx.plugin(E2eAttachmentStore)
-  await ctx.plugin(LlmDeepSeek, config)
+  await ctx.plugin(LlmDeepSeek, {
+    ...model === VISION ? { models: [{ id: VISION, inputModalities: ['text', 'image'] }] } : {},
+    ...config,
+  })
   return ctx
 }
 
@@ -143,6 +148,35 @@ const weatherTool: ToolSchema = {
 }
 
 describe.skipIf(!process.env.DEEPSEEK_API_KEY)('llm-deepseek e2e (real API)', () => {
+  it.skipIf(process.env.DEEPSEEK_FLASH_E2E !== '1')('deepseek-flash accepts images and retains system updates', async () => {
+    const ctx = new Context()
+    contexts.push(ctx)
+    await ctx.plugin(LlmRuntime)
+    await ctx.plugin(LocalAttachments)
+    await ctx.plugin(LlmDeepSeek, { maxTokens: 4096 })
+    const model = 'deepseek-flash'
+    await expect(ctx.llm.resolveModelInfo('deepseek-official', model)).resolves.toMatchObject({
+      inputModalities: ['text', 'image'], systemPromptUpdate: 'in-history',
+    })
+    const attachment = await ctx.attachments.saveImage({ data: readFileSync(new URL('fixtures/red.png', import.meta.url)), mediaType: 'image/png' })
+    const message = ask('What is the dominant color of this image?')[0]!
+    const history: Message[] = [
+      createSystemMessage('Answer with one English color word.', 'test'),
+      { ...message, content: [...message.content, { type: 'image', attachment }] },
+    ]
+    const reply = async () => {
+      const response = await assemble(ctx, { model, messages: history, reasoningEffort: ReasoningEffortId('high') })
+      expect(response.finish.kind, JSON.stringify(response.finish)).toBe('stop')
+      history.push(response.message)
+      return textOf(response).trim().toLowerCase()
+    }
+    expect(await reply()).toMatch(/^red[.!]?$/)
+    history.push(createSystemMessage('Reply to every user message with exactly banana.', 'test'), ...ask('Answer now.'))
+    expect(await reply()).toBe('banana')
+    history.push(...ask('Answer again.'))
+    expect(await reply()).toBe('banana')
+  })
+
   it.skipIf(!VISION_E2E_ENABLED)('uses the built-in official route to upload, reference, and delete one image', async () => {
     const key = process.env.DEEPSEEK_API_KEY
     if (key === undefined) throw new Error('e2e ran without DEEPSEEK_API_KEY')

+ 11 - 46
packages/llm/llm-deepseek/tests/adapter.spec.ts

@@ -379,7 +379,7 @@ describe('DeepSeekAdapter against a mock server', () => {
 
     await drain(adapter.stream({
       provider: 'deepseek-official',
-      model: 'deepseek-v4-flash-vision-exp',
+      model: 'deepseek-flash',
       messages: [createUserMessage({
         content: [
           { type: 'text', text: 'describe ' },
@@ -390,7 +390,7 @@ describe('DeepSeekAdapter against a mock server', () => {
     }))
 
     expect(server.requests[0]).toMatchObject({
-      model: 'deepseek-v4-flash-vision-exp',
+      model: 'deepseek-flash',
       messages: [{
         role: 'user',
         content: [
@@ -1697,27 +1697,15 @@ describe('plugin registration and config', () => {
     await ctx.plugin(LlmDeepSeek, { baseURL: 'http://127.0.0.1:1' })
     expect(ctx.llm.listProviders()).toEqual([{ id: 'deepseek-official', name: 'DeepSeek' }])
     await expect(ctx.llm.listModels('deepseek-official')).resolves.toEqual([
-      {
-        provider: 'deepseek-official',
-        id: 'deepseek-v4-flash',
-        name: 'DeepSeek-V4-Flash',
-        description: 'Fast, efficient, and economical; suited to focused, routine, or parallel tasks.',
-        inputModalities: ['text'],
-      },
-      {
-        provider: 'deepseek-official',
-        id: 'deepseek-v4-pro',
-        name: 'DeepSeek-V4-Pro',
-        description: 'Stronger agentic coding, knowledge, and difficult reasoning; suited to complex or quality-critical tasks at higher cost.',
-        inputModalities: ['text'],
-      },
-      { provider: 'deepseek-official', id: 'deepseek-v4-flash-vision-exp', name: 'DeepSeek-V4-Flash-Vision-Exp', inputModalities: ['text', 'image'] },
+      { provider: 'deepseek-official', id: 'deepseek-flash', name: 'DeepSeek-V41-Flash', inputModalities: ['text', 'image'] },
     ])
-    await expect(ctx.llm.resolveModelInfo('deepseek-official', 'deepseek-v4-flash'))
+    await expect(ctx.llm.resolveModelInfo('deepseek-official', 'deepseek-flash'))
       .resolves.toMatchObject({
         provider: 'deepseek-official',
-        id: 'deepseek-v4-flash',
-        name: 'DeepSeek-V4-Flash',
+        id: 'deepseek-flash',
+        name: 'DeepSeek-V41-Flash',
+        inputModalities: ['text', 'image'],
+        systemPromptUpdate: 'in-history',
         context: { contextWindow: 1_000_000 },
         defaultMaxTokens: 256_000,
         reasoning: {
@@ -1730,15 +1718,6 @@ describe('plugin registration and config', () => {
           defaultEffort: ReasoningEffortId('high'),
         },
       })
-    await expect(ctx.llm.resolveModelInfo('deepseek-official', 'deepseek-v4-flash-vision-exp'))
-      .resolves.toMatchObject({
-        provider: 'deepseek-official',
-        id: 'deepseek-v4-flash-vision-exp',
-        name: 'DeepSeek-V4-Flash-Vision-Exp',
-        inputModalities: ['text', 'image'],
-        context: { contextWindow: 1_000_000 },
-        defaultMaxTokens: 256_000,
-      })
   })
 
   it.each(['off', 'low', 'max'] as const)('uses the configured %s reasoning default', async (effort) => {
@@ -1824,21 +1803,7 @@ describe('plugin registration and config', () => {
     await ctx.plugin(LlmRuntime)
     LlmDeepSeek.apply(ctx, { baseURL: 'http://127.0.0.1:1' })
     await expect(ctx.llm.listModels('deepseek-official')).resolves.toEqual([
-      {
-        provider: 'deepseek-official',
-        id: 'deepseek-v4-flash',
-        name: 'DeepSeek-V4-Flash',
-        description: 'Fast, efficient, and economical; suited to focused, routine, or parallel tasks.',
-        inputModalities: ['text'],
-      },
-      {
-        provider: 'deepseek-official',
-        id: 'deepseek-v4-pro',
-        name: 'DeepSeek-V4-Pro',
-        description: 'Stronger agentic coding, knowledge, and difficult reasoning; suited to complex or quality-critical tasks at higher cost.',
-        inputModalities: ['text'],
-      },
-      { provider: 'deepseek-official', id: 'deepseek-v4-flash-vision-exp', name: 'DeepSeek-V4-Flash-Vision-Exp', inputModalities: ['text', 'image'] },
+      { provider: 'deepseek-official', id: 'deepseek-flash', name: 'DeepSeek-V41-Flash', inputModalities: ['text', 'image'] },
     ])
   })
 
@@ -2154,7 +2119,7 @@ describe('plugin registration and config', () => {
     // First-boot onboarding: the route registers so models stay discoverable;
     // only the request itself needs a key.
     expect(ctx.llm.listProviders()).toEqual([{ id: 'deepseek-official', name: 'DeepSeek' }])
-    await expect(ctx.llm.listModels('deepseek-official')).resolves.toHaveLength(3)
+    await expect(ctx.llm.listModels('deepseek-official')).resolves.toHaveLength(1)
     const first = await assemble(ctx, { model: 'deepseek-v4-flash', messages: [] })
     expect(first.finish).toMatchObject({ kind: 'error', failure: { code: 'MISSING_CREDENTIAL' } })
     // The guidance leads with the managed credential store.
@@ -2242,7 +2207,7 @@ describe('plugin registration and config', () => {
     expect(adapter).toBeInstanceOf(DeepSeekAdapter)
     // Direct embedding shares the plugin's one resolve step, so it advertises
     // the same default catalog instead of a divergent empty one.
-    await expect(adapter.listModels('deepseek-official')).resolves.toHaveLength(3)
+    await expect(adapter.listModels('deepseek-official')).resolves.toHaveLength(1)
   })
 
   it('resolves connection facts and the credential exactly once per stream call', async () => {

+ 4 - 4
packages/llm/llm-deepseek/tests/dynamic-config.spec.ts

@@ -191,7 +191,7 @@ describe('request-level dynamic configuration', () => {
     const dir = await home()
     const { ctx } = await boot(dir, { baseURL: 'http://127.0.0.1:1' })
 
-    await expect(ctx.llm.listModels('deepseek-official')).resolves.toHaveLength(3)
+    await expect(ctx.llm.listModels('deepseek-official')).resolves.toHaveLength(1)
     await ctx.settings.update(NS, {
       models: [{ id: 'settings-model', name: 'From Settings', inputModalities: ['text', 'image'] }],
     })
@@ -217,9 +217,9 @@ describe('request-level dynamic configuration', () => {
       source: { kind: 'plugin', plugin: 'test' },
     })]
 
-    await assemble(ctx, { model: 'deepseek-v4-flash-vision-exp', messages })
+    await assemble(ctx, { model: 'deepseek-flash', messages })
     await ctx.settings.update(NS, { maxRequestFilesBytes: 4, imageOffloadByteQuantum: 2 })
-    await assemble(ctx, { model: 'deepseek-v4-flash-vision-exp', messages })
+    await assemble(ctx, { model: 'deepseek-flash', messages })
 
     const first = (server.requests[0] as { messages: Array<{ content: unknown }> }).messages[0]?.content
     const second = (server.requests[1] as { messages: Array<{ content: unknown }> }).messages[0]?.content
@@ -261,7 +261,7 @@ describe('request-level dynamic configuration', () => {
     // Schema-valid but resolver-invalid: duplicate catalog ids pass the array
     // schema and fail the explicit resolve step.
     await ctx.settings.update(NS, { models: [{ id: 'dup' }, { id: 'dup' }] })
-    await expect(ctx.llm.listModels('deepseek-official')).resolves.toHaveLength(3)
+    await expect(ctx.llm.listModels('deepseek-official')).resolves.toHaveLength(1)
     await ctx.settings.update(NS, { models: [{ id: 'recovered' }] })
     await expect(ctx.llm.listModels('deepseek-official')).resolves.toEqual([
       { provider: 'deepseek-official', id: 'recovered', name: 'recovered', inputModalities: ['text'] },

二進制
packages/llm/llm-deepseek/tests/fixtures/red.png


+ 3 - 0
pnpm-lock.yaml

@@ -7034,6 +7034,9 @@ importers:
       '@deepseek-ai/dsh-attachment':
         specifier: workspace:^
         version: link:../../attachment/attachment
+      '@deepseek-ai/dsh-attachment-local':
+        specifier: workspace:^
+        version: link:../../attachment/attachment-local
       '@deepseek-ai/dsh-credentials':
         specifier: workspace:^
         version: link:../../credentials/credentials

+ 4 - 4
snapshots/sdk/system-prompt-in-history/notifications.expected.jsonl

@@ -6,16 +6,16 @@
 {"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"system/message","seq":7,"time":0,"data":{"turn":1,"step":1,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"plugin","plugin":"@deepseek-ai/dsh-system-prompt"},"id":"{{sessionId}}"}},"surfaceOp":"append"}}}
 {"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"user/message","seq":8,"time":0,"data":{"content":[{"type":"text","text":"Read task.txt with the read tool, then reply DONE."}],"source":{"kind":"user"},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"}}}
 {"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"user/message","seq":9,"time":0,"data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent DSH file policy: danger-full-access. The DSH file sandbox does not restrict file modifications by available operations.\n\nApproval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`)."}],"source":{"kind":"plugin","plugin":"@deepseek-ai/dsh-system-prompt","form":"snapshot","sections":[{"name":"sandbox:policy","text":"Current DSH file policy: danger-full-access. The DSH file sandbox does not restrict file modifications by available operations."},{"name":"approval:policy","text":"Approval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`)."}]},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"}}}
-{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"request/header","seq":10,"time":0,"data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"tools":"{{tools}}"},"reason":"initial"}}}}
-{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"request/context","seq":11,"time":0,"data":{"provider":"deepseek-official","model":"deepseek-v4-flash","systemPromptUpdate":"in-history"}}}}
+{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"request/header","seq":10,"time":0,"data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-flash"},"tools":"{{tools}}"},"reason":"initial"}}}}
+{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"request/context","seq":11,"time":0,"data":{"provider":"deepseek-official","model":"deepseek-flash","systemPromptUpdate":"in-history"}}}}
 {"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"session/title","seq":12,"time":0,"data":{"title":"Read task.txt with the read","messageSeqs":[8],"source":{"kind":"fallback"}}}}}
-{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/message","seq":13,"time":0,"data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"tool-call","id":"call_task_read","name":"read","arguments":"{\"file_path\":\"task.txt\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{sessionId}}"},"usage":{"inputTokens":10,"outputTokens":5},"stream":[{"type":"chunk","time":0,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":0,"index":0,"dt":[],"id":"call_task_read","name":"read","args":["{\"file_path\":\"task.txt\"}"]},{"type":"chunk","time":0,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"call_task_read","name":"read","arguments":"{\"file_path\":\"task.txt\"}"}}},{"type":"chunk","time":0,"chunk":{"type":"usage","usage":{"inputTokens":10,"outputTokens":5}}},{"type":"chunk","time":0,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}}}
+{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/message","seq":13,"time":0,"data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"tool-call","id":"call_task_read","name":"read","arguments":"{\"file_path\":\"task.txt\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-flash"},"id":"{{sessionId}}"},"usage":{"inputTokens":10,"outputTokens":5},"stream":[{"type":"chunk","time":0,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":0,"index":0,"dt":[],"id":"call_task_read","name":"read","args":["{\"file_path\":\"task.txt\"}"]},{"type":"chunk","time":0,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"call_task_read","name":"read","arguments":"{\"file_path\":\"task.txt\"}"}}},{"type":"chunk","time":0,"chunk":{"type":"usage","usage":{"inputTokens":10,"outputTokens":5}}},{"type":"chunk","time":0,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}}}
 {"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"tool/call","seq":14,"time":0,"data":{"turn":1,"step":1,"callId":"call_task_read","name":"read","arguments":"{\"file_path\":\"task.txt\"}"}}}}
 {"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"tool/result","seq":15,"time":0,"data":{"turn":1,"step":1,"message":{"source":{"kind":"tool","callId":"call_task_read"},"content":[{"type":"tool-result","toolCallId":"call_task_read","content":[{"type":"text","text":"<path>{{cwd}}/task.txt</path>\n<type>file</type>\n<content>\n1: Reply with the single word DONE after reading this file.\n\n(End of file - total 1 lines)\n</content>"}],"isError":false}],"role":"user","id":"{{sessionId}}"},"meta":{"path":"{{cwd}}/task.txt","offset":1,"lines":[{"number":1,"text":"Reply with the single word DONE after reading this file."}],"totalLines":1}},"sourceEventSeqs":[14],"surfaceOp":"append"}}}
 {"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"step/end","seq":16,"time":0,"data":{"turn":1,"step":1}}}}
 {"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"step/start","seq":17,"time":0,"data":{"turn":1,"step":2}}}}
 {"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"system/message","seq":18,"time":0,"data":{"turn":1,"step":2,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"plugin","plugin":"@deepseek-ai/dsh-system-prompt"},"id":"{{sessionId}}"}},"surfaceOp":"append"}}}
-{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/message","seq":19,"time":0,"data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"text","text":"DONE"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{sessionId}}"},"usage":{"inputTokens":12,"outputTokens":2,"cacheReadTokens":10},"stream":[{"type":"chunk","time":0,"chunk":{"type":"block-start","index":0,"blockType":"text"}},{"type":"text-chunks","time0":0,"index":0,"dt":[],"texts":["DONE"]},{"type":"chunk","time":0,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"DONE"}}},{"type":"chunk","time":0,"chunk":{"type":"usage","usage":{"inputTokens":12,"outputTokens":2,"cacheReadTokens":10}}},{"type":"chunk","time":0,"chunk":{"type":"finish","reason":{"kind":"stop"}}}]},"surfaceOp":"append"}}}
+{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/message","seq":19,"time":0,"data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"text","text":"DONE"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-flash"},"id":"{{sessionId}}"},"usage":{"inputTokens":12,"outputTokens":2,"cacheReadTokens":10},"stream":[{"type":"chunk","time":0,"chunk":{"type":"block-start","index":0,"blockType":"text"}},{"type":"text-chunks","time0":0,"index":0,"dt":[],"texts":["DONE"]},{"type":"chunk","time":0,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"DONE"}}},{"type":"chunk","time":0,"chunk":{"type":"usage","usage":{"inputTokens":12,"outputTokens":2,"cacheReadTokens":10}}},{"type":"chunk","time":0,"chunk":{"type":"finish","reason":{"kind":"stop"}}}]},"surfaceOp":"append"}}}
 {"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"step/end","seq":20,"time":0,"data":{"turn":1,"step":2}}}}
 {"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"turn/end","seq":21,"time":0,"data":{"turn":1,"reason":{"kind":"completed"}}}}}
 {"method":"session.status","params":{"sessionId":"{{sessionId}}","status":"idle"}}

+ 4 - 4
snapshots/sdk/system-prompt-in-history/session.v3.jsonl

@@ -9,15 +9,15 @@
 {"type":"system/message","data":{"turn":1,"step":1,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"plugin","plugin":"@deepseek-ai/dsh-system-prompt"},"id":"{{message:2}}"}},"surfaceOp":"append"}
 {"type":"user/message","data":{"content":[{"type":"text","text":"Read task.txt with the read tool, then reply DONE."}],"source":{"kind":"user"},"role":"user","id":"{{message:1}}"},"surfaceOp":"append"}
 {"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent DSH file policy: danger-full-access. The DSH file sandbox does not restrict file modifications by available operations.\n\nApproval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`)."}],"source":{"kind":"plugin","plugin":"@deepseek-ai/dsh-system-prompt","form":"snapshot","sections":[{"name":"sandbox:policy","text":"Current DSH file policy: danger-full-access. The DSH file sandbox does not restrict file modifications by available operations."},{"name":"approval:policy","text":"Approval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`)."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}
-{"type":"request/header","data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"tools":"{{tools}}"},"reason":"initial"}}
-{"type":"request/context","data":{"provider":"deepseek-official","model":"deepseek-v4-flash","systemPromptUpdate":"in-history"}}
+{"type":"request/header","data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-flash"},"tools":"{{tools}}"},"reason":"initial"}}
+{"type":"request/context","data":{"provider":"deepseek-official","model":"deepseek-flash","systemPromptUpdate":"in-history"}}
 {"type":"session/title","data":{"title":"Read task.txt with the read","messageSeqs":[8],"source":{"kind":"fallback"}}}
-{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"tool-call","id":"call_task_read","name":"read","arguments":"{\"file_path\":\"task.txt\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:4}}"},"usage":{"inputTokens":10,"outputTokens":5},"stream":[{"type":"chunk","time":0,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":0,"index":0,"dt":[],"id":"call_task_read","name":"read","args":["{\"file_path\":\"task.txt\"}"]},{"type":"chunk","time":0,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"call_task_read","name":"read","arguments":"{\"file_path\":\"task.txt\"}"}}},{"type":"chunk","time":0,"chunk":{"type":"usage","usage":{"inputTokens":10,"outputTokens":5}}},{"type":"chunk","time":0,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}
+{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"tool-call","id":"call_task_read","name":"read","arguments":"{\"file_path\":\"task.txt\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-flash"},"id":"{{message:4}}"},"usage":{"inputTokens":10,"outputTokens":5},"stream":[{"type":"chunk","time":0,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":0,"index":0,"dt":[],"id":"call_task_read","name":"read","args":["{\"file_path\":\"task.txt\"}"]},{"type":"chunk","time":0,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"call_task_read","name":"read","arguments":"{\"file_path\":\"task.txt\"}"}}},{"type":"chunk","time":0,"chunk":{"type":"usage","usage":{"inputTokens":10,"outputTokens":5}}},{"type":"chunk","time":0,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}
 {"type":"tool/call","data":{"turn":1,"step":1,"callId":"call_task_read","name":"read","arguments":"{\"file_path\":\"task.txt\"}"}}
 {"type":"tool/result","data":{"turn":1,"step":1,"message":{"source":{"kind":"tool","callId":"call_task_read"},"content":[{"type":"tool-result","toolCallId":"call_task_read","content":[{"type":"text","text":"<path>{{cwd}}/task.txt</path>\n<type>file</type>\n<content>\n1: Reply with the single word DONE after reading this file.\n\n(End of file - total 1 lines)\n</content>"}],"isError":false}],"role":"user","id":"{{message:5}}"},"meta":{"path":"{{cwd}}/task.txt","offset":1,"lines":[{"number":1,"text":"Reply with the single word DONE after reading this file."}],"totalLines":1}},"sourceEventSeqs":[14],"surfaceOp":"append"}
 {"type":"step/end","data":{"turn":1,"step":1}}
 {"type":"step/start","data":{"turn":1,"step":2}}
 {"type":"system/message","data":{"turn":1,"step":2,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"plugin","plugin":"@deepseek-ai/dsh-system-prompt"},"id":"{{message:6}}"}},"surfaceOp":"append"}
-{"type":"assistant/message","data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"text","text":"DONE"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:7}}"},"usage":{"inputTokens":12,"outputTokens":2,"cacheReadTokens":10},"stream":[{"type":"chunk","time":0,"chunk":{"type":"block-start","index":0,"blockType":"text"}},{"type":"text-chunks","time0":0,"index":0,"dt":[],"texts":["DONE"]},{"type":"chunk","time":0,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"DONE"}}},{"type":"chunk","time":0,"chunk":{"type":"usage","usage":{"inputTokens":12,"outputTokens":2,"cacheReadTokens":10}}},{"type":"chunk","time":0,"chunk":{"type":"finish","reason":{"kind":"stop"}}}]},"surfaceOp":"append"}
+{"type":"assistant/message","data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"text","text":"DONE"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-flash"},"id":"{{message:7}}"},"usage":{"inputTokens":12,"outputTokens":2,"cacheReadTokens":10},"stream":[{"type":"chunk","time":0,"chunk":{"type":"block-start","index":0,"blockType":"text"}},{"type":"text-chunks","time0":0,"index":0,"dt":[],"texts":["DONE"]},{"type":"chunk","time":0,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"DONE"}}},{"type":"chunk","time":0,"chunk":{"type":"usage","usage":{"inputTokens":12,"outputTokens":2,"cacheReadTokens":10}}},{"type":"chunk","time":0,"chunk":{"type":"finish","reason":{"kind":"stop"}}}]},"surfaceOp":"append"}
 {"type":"step/end","data":{"turn":1,"step":2}}
 {"type":"turn/end","data":{"turn":1,"reason":{"kind":"completed"}}}

+ 3 - 3
snapshots/session/system-prompt-in-history/cordis.snapshot.yml

@@ -8,7 +8,7 @@
   name: '@deepseek-ai/dsh-agent-default-model'
   config:
     provider: deepseek-official
-    model: deepseek-v4-flash
+    model: deepseek-flash
 
 - id: session-persistence-jsonl
   name: '@deepseek-ai/dsh-session-persistence-jsonl'
@@ -24,8 +24,8 @@
           - id: deepseek-official
             name: DeepSeek
             models:
-              - id: deepseek-v4-flash
+              - id: deepseek-flash
                 systemPromptUpdate: in-history
-              - id: deepseek-v4-pro
+                inputModalities: [text, image]
     - id: in-history-prompt-update
       name: '../../../packages/test-support/session-snapshot/tests/fixtures/in-history-prompt-update.ts'

+ 1 - 5
snapshots/session/system-prompt-in-history/cordis.yml

@@ -7,16 +7,12 @@
   config:
     thinking: enabled
     reasoningEffort: max
-    models:
-      - id: deepseek-v4-flash
-        systemPromptUpdate: in-history
-      - id: deepseek-v4-pro
 
 - id: agent-default-model
   name: '@deepseek-ai/dsh-agent-default-model'
   config:
     provider: deepseek-official
-    model: deepseek-v4-flash
+    model: deepseek-flash
 
 - id: session-persistence-jsonl
   name: '@deepseek-ai/dsh-session-persistence-jsonl'

+ 4 - 4
snapshots/session/system-prompt-in-history/session.v3.jsonl

@@ -9,15 +9,15 @@
 {"type":"system/message","data":{"turn":1,"step":1,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"plugin","plugin":"@deepseek-ai/dsh-system-prompt"},"id":"{{message:2}}"}},"surfaceOp":"append"}
 {"type":"user/message","data":{"content":[{"type":"text","text":"Read task.txt with the read tool, then reply DONE."}],"source":{"kind":"user"},"role":"user","id":"{{message:1}}"},"surfaceOp":"append"}
 {"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent DSH file policy: danger-full-access. The DSH file sandbox does not restrict file modifications by available operations.\n\nApproval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`)."}],"source":{"kind":"plugin","plugin":"@deepseek-ai/dsh-system-prompt","form":"snapshot","sections":[{"name":"sandbox:policy","text":"Current DSH file policy: danger-full-access. The DSH file sandbox does not restrict file modifications by available operations."},{"name":"approval:policy","text":"Approval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`)."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}
-{"type":"request/header","data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"tools":"{{tools}}"},"reason":"initial"}}
-{"type":"request/context","data":{"provider":"deepseek-official","model":"deepseek-v4-flash","systemPromptUpdate":"in-history"}}
+{"type":"request/header","data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-flash"},"tools":"{{tools}}"},"reason":"initial"}}
+{"type":"request/context","data":{"provider":"deepseek-official","model":"deepseek-flash","systemPromptUpdate":"in-history"}}
 {"type":"session/title","data":{"title":"Read task.txt with the read","messageSeqs":[8],"source":{"kind":"fallback"}}}
-{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"tool-call","id":"call_task_read","name":"read","arguments":"{\"file_path\":\"task.txt\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:4}}"},"usage":{"inputTokens":10,"outputTokens":5},"stream":[{"type":"chunk","time":0,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":0,"index":0,"dt":[],"id":"call_task_read","name":"read","args":["{\"file_path\":\"task.txt\"}"]},{"type":"chunk","time":0,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"call_task_read","name":"read","arguments":"{\"file_path\":\"task.txt\"}"}}},{"type":"chunk","time":0,"chunk":{"type":"usage","usage":{"inputTokens":10,"outputTokens":5}}},{"type":"chunk","time":0,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}
+{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"tool-call","id":"call_task_read","name":"read","arguments":"{\"file_path\":\"task.txt\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-flash"},"id":"{{message:4}}"},"usage":{"inputTokens":10,"outputTokens":5},"stream":[{"type":"chunk","time":0,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":0,"index":0,"dt":[],"id":"call_task_read","name":"read","args":["{\"file_path\":\"task.txt\"}"]},{"type":"chunk","time":0,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"call_task_read","name":"read","arguments":"{\"file_path\":\"task.txt\"}"}}},{"type":"chunk","time":0,"chunk":{"type":"usage","usage":{"inputTokens":10,"outputTokens":5}}},{"type":"chunk","time":0,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}
 {"type":"tool/call","data":{"turn":1,"step":1,"callId":"call_task_read","name":"read","arguments":"{\"file_path\":\"task.txt\"}"}}
 {"type":"tool/result","data":{"turn":1,"step":1,"message":{"source":{"kind":"tool","callId":"call_task_read"},"content":[{"type":"tool-result","toolCallId":"call_task_read","content":[{"type":"text","text":"<path>{{cwd}}/task.txt</path>\n<type>file</type>\n<content>\n1: Reply with the single word DONE after reading this file.\n\n(End of file - total 1 lines)\n</content>"}],"isError":false}],"role":"user","id":"{{message:5}}"},"meta":{"path":"{{cwd}}/task.txt","offset":1,"lines":[{"number":1,"text":"Reply with the single word DONE after reading this file."}],"totalLines":1}},"sourceEventSeqs":[14],"surfaceOp":"append"}
 {"type":"step/end","data":{"turn":1,"step":1}}
 {"type":"step/start","data":{"turn":1,"step":2}}
 {"type":"system/message","data":{"turn":1,"step":2,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"plugin","plugin":"@deepseek-ai/dsh-system-prompt"},"id":"{{message:6}}"}},"surfaceOp":"append"}
-{"type":"assistant/message","data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"text","text":"DONE"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:7}}"},"usage":{"inputTokens":12,"outputTokens":2,"cacheReadTokens":10},"stream":[{"type":"chunk","time":0,"chunk":{"type":"block-start","index":0,"blockType":"text"}},{"type":"text-chunks","time0":0,"index":0,"dt":[],"texts":["DONE"]},{"type":"chunk","time":0,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"DONE"}}},{"type":"chunk","time":0,"chunk":{"type":"usage","usage":{"inputTokens":12,"outputTokens":2,"cacheReadTokens":10}}},{"type":"chunk","time":0,"chunk":{"type":"finish","reason":{"kind":"stop"}}}]},"surfaceOp":"append"}
+{"type":"assistant/message","data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"text","text":"DONE"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-flash"},"id":"{{message:7}}"},"usage":{"inputTokens":12,"outputTokens":2,"cacheReadTokens":10},"stream":[{"type":"chunk","time":0,"chunk":{"type":"block-start","index":0,"blockType":"text"}},{"type":"text-chunks","time0":0,"index":0,"dt":[],"texts":["DONE"]},{"type":"chunk","time":0,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"DONE"}}},{"type":"chunk","time":0,"chunk":{"type":"usage","usage":{"inputTokens":12,"outputTokens":2,"cacheReadTokens":10}}},{"type":"chunk","time":0,"chunk":{"type":"finish","reason":{"kind":"stop"}}}]},"surfaceOp":"append"}
 {"type":"step/end","data":{"turn":1,"step":2}}
 {"type":"turn/end","data":{"turn":1,"reason":{"kind":"completed"}}}

+ 2 - 2
snapshots/session/system-prompt-in-history/system-prompt.expected.md

@@ -1,6 +1,6 @@
 You are an AI agent powered by DeepSeek Harness.
 
-You are a coding assistant powered by the deepseek-v4-flash model. Your working directory is {{cwd}}. Your bash tool runs under a file sandbox — a `[sandbox: file access denied …]` result is policy, not a command bug.
+You are a coding assistant powered by the deepseek-flash model. Your working directory is {{cwd}}. Your bash tool runs under a file sandbox — a `[sandbox: file access denied …]` result is policy, not a command bug.
 
 Verify your work by running the code or tests. Keep answers brief and factual.
 
@@ -35,7 +35,7 @@ Use subagent in the background by default. Start independent delegations togethe
 
 You are an AI agent powered by DeepSeek Harness.
 
-You are a coding assistant powered by the deepseek-v4-flash model. Your working directory is {{cwd}}. Your bash tool runs under a file sandbox — a `[sandbox: file access denied …]` result is policy, not a command bug.
+You are a coding assistant powered by the deepseek-flash model. Your working directory is {{cwd}}. Your bash tool runs under a file sandbox — a `[sandbox: file access denied …]` result is policy, not a command bug.
 
 Verify your work by running the code or tests. Keep answers brief and factual.