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

This commit is contained in:
Yichen Jiang authored and imccyu committed 2026-09-10 03:39:56 +08:00
1 parent 4cbaeb8d69
commit bc5fd3b8dc
28 files changed
+130 -148

No files matched your search

@@ -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
@@ -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.
@@ -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`;先前快照不是准入输入。因此首次请求、恢复的会话、路由变更以及同一路由的能力变更,都使用将服务该调用的绑定适配器的能力。
@@ -36,40 +36,20 @@
- button "恢复默认模型"
- textbox "模型 ID 1":
- /placeholder: 模型 ID
- text: deepseek-v4-pro
- text: private-preview
- textbox "显示名称 1":
- /placeholder: 显示名称
- text: DeepSeek-V4-Pro
- button "容量 1":
- text: Private Preview
- button "容量 1" [expanded]:
- 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":
- /placeholder: 显示名称
- text: Private Preview
- button "容量 3" [expanded]:
- img
- button "删除模型 3":
- img
- text: 上下文窗口
- textbox "上下文窗口 3":
- textbox "上下文窗口 1":
- /placeholder: 1M
- text: "131072"
- text: 最大输出 token 数
- textbox "最大输出 token 数 3":
- textbox "最大输出 token 数 1":
- /placeholder: 256K
- text: 64K
- button "添加模型":
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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 () => {
@@ -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'] },
Binary file not shown.

After

Width:  |  Height:  |  Size: 69 B

+3
View File
@@ -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
@@ -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"}}
@@ -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"}}}
@@ -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'
@@ -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'
@@ -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"}}}
@@ -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.