Forráskód Böngészése

refactor(llm): inherit Messages defaults and verify live parity

Tianyi Cui 1 hete
szülő
commit
accafa5fb2
47 módosított fájl, 128 hozzáadás és 68 törlés
  1. 2 2
      .agents/notes/implemented/feature/2026-09-07-deepseek-messages-adapter.i18n.yaml
  2. 1 1
      .agents/notes/implemented/feature/2026-09-07-deepseek-messages-adapter.md
  3. 1 1
      .agents/notes/implemented/feature/2026-09-07-deepseek-messages-adapter.zh.md
  4. 0 1
      apps/cli/tests/profiles/headless/credentials-snapshot.patch.yml
  5. 0 1
      apps/cli/tests/profiles/headless/real-model.patch.yml
  6. 0 1
      apps/cli/tests/profiles/headless/tests/fixtures/deepseek-defaults.patch.yml
  7. 0 1
      apps/cli/tests/profiles/headless/tests/harness.ts
  8. 2 2
      apps/cli/tests/profiles/headless/tests/ptc.e2e.ts
  9. 1 1
      apps/web/tests/scaffold.ts
  10. 0 2
      packages/bundle/base/cordis.patch.yml
  11. 0 1
      packages/bundle/sdk-minimal/cordis.patch.yml
  12. 0 1
      packages/bundle/sdk-minimal/tests/sdk-minimal.spec.ts
  13. 2 2
      packages/bundle/web-app/README.i18n.yaml
  14. 1 1
      packages/bundle/web-app/README.md
  15. 1 1
      packages/bundle/web-app/README.zh.md
  16. 1 1
      packages/context/agent-instructions/tests/agent-instructions.e2e.ts
  17. 1 1
      packages/core/agent-loop/tests/request-cache.e2e.ts
  18. 1 1
      packages/experimental/auto-review/tests/auto-review.e2e.ts
  19. 1 1
      packages/fs/tool-fs/tests/harness.ts
  20. 2 2
      packages/llm/llm-deepseek/README.i18n.yaml
  21. 1 2
      packages/llm/llm-deepseek/README.md
  22. 1 2
      packages/llm/llm-deepseek/README.zh.md
  23. 88 4
      packages/llm/llm-deepseek/tests/messages/adapter.e2e.ts
  24. 5 5
      packages/llm/llm-deepseek/tests/messages/adapter.spec.ts
  25. 1 1
      packages/llm/llm-deepseek/tests/messages/extensions.spec.ts
  26. 1 1
      packages/llm/llm-deepseek/tests/messages/files.spec.ts
  27. 0 1
      packages/llm/llm-deepseek/tests/messages/fixtures/cordis.yml
  28. 1 1
      packages/llm/llm-deepseek/tests/messages/helpers.ts
  29. 8 8
      packages/llm/llm-deepseek/tests/messages/serialize.spec.ts
  30. 1 1
      packages/llm/llm-pi-ai/tests/adapter.e2e.ts
  31. 1 1
      packages/sdk/server/src/server.ts
  32. 1 1
      packages/session/session-title-first-prompt-llm/tests/provider.e2e.ts
  33. 1 1
      packages/subagent/subagent-spawn-in-process/tests/harness.ts
  34. 1 1
      packages/workflow/workflow-ptc/tests/workflow-ptc.e2e.ts
  35. 0 1
      snapshots/acp/escalation-approved/cordis.yml
  36. 0 1
      snapshots/sdk/bash-tool/cordis.yml
  37. 0 1
      snapshots/sdk/persistent-tools/cordis.yml
  38. 0 1
      snapshots/sdk/subagent-dsh-sdk-diagnostic/cordis.yml
  39. 0 1
      snapshots/sdk/text-turn/cordis.yml
  40. 0 1
      snapshots/session/deepseek-messages-system-prompt/cordis.yml
  41. 0 1
      snapshots/session/deepseek-protocol-system-prompt/cordis.yml
  42. 0 1
      snapshots/session/empty-response-retry/cordis.yml
  43. 0 1
      snapshots/session/fs-glob-sampling/cordis.yml
  44. 0 1
      snapshots/session/persistent-pwsh-tool-turn/cordis.yml
  45. 0 1
      snapshots/session/pwsh-tool-turn/cordis.yml
  46. 0 1
      snapshots/session/system-prompt-in-history/cordis.yml
  47. 0 1
      snapshots/session/text-turn/cordis.yml

+ 2 - 2
.agents/notes/implemented/feature/2026-09-07-deepseek-messages-adapter.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-07-deepseek-messages-adapter.md
-2026-09-07-deepseek-messages-adapter.md: 80da9229d043246da097d9ce9458816d2b8506d9
-2026-09-07-deepseek-messages-adapter.zh.md: cc30bd6eb197d4de7d740e772e7661dc15eecb34
+2026-09-07-deepseek-messages-adapter.md: 9a92f9e95931bebfa9fa6e7e64fbd7d27ec8306c
+2026-09-07-deepseek-messages-adapter.zh.md: 67f6c97c0a416cbddb7ec9d0de01e47e658036b7

+ 1 - 1
.agents/notes/implemented/feature/2026-09-07-deepseek-messages-adapter.md

@@ -10,7 +10,7 @@ Deployments expose DeepSeek through Anthropic Messages gateways as well as chat-
 
 ## Decision
 
-The [DeepSeek adapter](../../../../packages/llm/llm-deepseek/README.md) serves multiple protocols under one `deepseek-official` route and `llm-deepseek` settings namespace. `common/` shares configuration, the model catalog, capability resolution, and Files lifecycle; `protocols/chat-completions/` and `protocols/messages/` own serialization, stream conversion, and transport. Cordis YAML selects the implementation through `protocol`, defaulting to `messages`; shipped first-party compositions also declare this choice explicitly. The existing `PreparedAdapterCall` freezes protocol, endpoint, credential reference, and model capabilities; retries retain that generation while subsequent calls read new configuration.
+The [DeepSeek adapter](../../../../packages/llm/llm-deepseek/README.md) serves multiple protocols under one `deepseek-official` route and `llm-deepseek` settings namespace. `common/` shares configuration, the model catalog, capability resolution, and Files lifecycle; `protocols/chat-completions/` and `protocols/messages/` own serialization, stream conversion, and transport. Cordis YAML selects the implementation through `protocol`, defaulting to `messages`; shipped first-party compositions inherit that default. The existing `PreparedAdapterCall` freezes protocol, endpoint, credential reference, and model capabilities; retries retain that generation while subsequent calls read new configuration.
 
 The adapter follows the [DeepSeek compatibility documentation](https://api-docs.deepseek.com/zh-cn/guides/anthropic_api) and [Anthropic streaming protocol](https://platform.claude.com/docs/en/build-with-claude/streaming). The pi-ai Anthropic implementation informed the handling of adjacent user messages, cumulative usage, fragmented tool arguments, and optional thinking signatures. DeepSeek effort uses `output_config.effort`; an Anthropic thinking token budget does not control DeepSeek effort. Both protocols forward explicit `temperature` values; DeepSeek accepts that parameter with thinking enabled and ignores its value, so callers retain their existing thinking configuration.
 

+ 1 - 1
.agents/notes/implemented/feature/2026-09-07-deepseek-messages-adapter.zh.md

@@ -10,7 +10,7 @@ Status: implemented
 
 ## 决策
 
-[DeepSeek 适配器](../../../../packages/llm/llm-deepseek/README.zh.md)通过一个 `deepseek-official` 路由和 `llm-deepseek` 设置命名空间支持多个协议。`common/` 共享配置、模型目录、能力解析和 Files 生命周期;`protocols/chat-completions/` 与 `protocols/messages/` 分别负责协议序列化、流转换和传输。`protocol` 配置在 Cordis YAML 中选择实现,默认 `messages`;随产品交付的官方组合也显式声明该选择。已有 `PreparedAdapterCall` 冻结协议、端点、凭据引用与模型能力,重试保持同一代配置,后续调用读取新配置。
+[DeepSeek 适配器](../../../../packages/llm/llm-deepseek/README.zh.md)通过一个 `deepseek-official` 路由和 `llm-deepseek` 设置命名空间支持多个协议。`common/` 共享配置、模型目录、能力解析和 Files 生命周期;`protocols/chat-completions/` 与 `protocols/messages/` 分别负责协议序列化、流转换和传输。`protocol` 配置在 Cordis YAML 中选择实现,默认 `messages`;随产品交付的官方组合继承该默认值。已有 `PreparedAdapterCall` 冻结协议、端点、凭据引用与模型能力,重试保持同一代配置,后续调用读取新配置。
 
 适配器遵循 [DeepSeek 兼容文档](https://api-docs.deepseek.com/zh-cn/guides/anthropic_api) 和 [Anthropic 流协议](https://platform.claude.com/docs/en/build-with-claude/streaming)。pi-ai 的 Anthropic 实现为相邻用户消息、累计用量、工具参数分片和可选思考签名的处理提供参考。DeepSeek 通过 `output_config.effort` 设置思考强度;Anthropic 思考 token 预算不控制 DeepSeek 思考强度。两种协议都转发显式 `temperature` 值;DeepSeek 在启用思考时接受该参数但忽略其值,因此调用方可以保留已有思考配置。
 

+ 0 - 1
apps/cli/tests/profiles/headless/credentials-snapshot.patch.yml

@@ -26,5 +26,4 @@
     - id: llm-deepseek-keyless
       name: '@deepseek-ai/dsh-llm-deepseek'
       config:
-        protocol: messages
         baseURL: 'http://127.0.0.1:9'

+ 0 - 1
apps/cli/tests/profiles/headless/real-model.patch.yml

@@ -8,7 +8,6 @@
 
 - id: llm-deepseek
   config:
-    protocol: messages
     thinking: enabled
     reasoningEffort: max
     models:

+ 0 - 1
apps/cli/tests/profiles/headless/tests/fixtures/deepseek-defaults.patch.yml

@@ -7,7 +7,6 @@
 
 - id: llm-deepseek
   config:
-    protocol: messages
     baseURL: !!js process.env.DSH_SNAPSHOT_BASE_URL
     thinking: enabled
     reasoningEffort: low

+ 0 - 1
apps/cli/tests/profiles/headless/tests/harness.ts

@@ -60,7 +60,6 @@ export async function codingHarness(workdir: string, options: CodingHarnessOptio
   })
   await ctx.plugin(AgentLoop, { agents: [] })
   await ctx.plugin(LlmDeepSeek, {
-    protocol: 'messages',
     ...options.modelContextWindow === undefined ? {} : {
       models: [{ id: 'deepseek-v4-flash', contextWindow: options.modelContextWindow }],
     },

+ 2 - 2
apps/cli/tests/profiles/headless/tests/ptc.e2e.ts

@@ -61,7 +61,7 @@ async function ptcModeHarness(cwd: string): Promise<Context> {
   await harness.plugin(ToolRuntime, { mode: 'ptc' })
   await harness.plugin(AgentRegistry)
   await harness.plugin(AgentLoop, { agents: [] })
-  await harness.plugin(LlmDeepSeek, { protocol: 'messages' })
+  await harness.plugin(LlmDeepSeek)
   if (harness.get('subprocess') === undefined) await harness.plugin(LocalSubprocessRuntime)
   await harness.plugin(BashEnvPlugin)
   await harness.plugin(LocalBashExecutor, { cwd, timeoutMs: 30_000 })
@@ -82,7 +82,7 @@ async function workspacePtcModeHarness(): Promise<Context> {
   await harness.plugin(ToolFs)
   await harness.plugin(AgentInstructions, { maxBytes: 65536 })
   await harness.plugin(AgentLoop, { agents: [] })
-  await harness.plugin(LlmDeepSeek, { protocol: 'messages', models: [{ id: 'deepseek-v4-flash' }] })
+  await harness.plugin(LlmDeepSeek, { models: [{ id: 'deepseek-v4-flash' }] })
   await mountRuntime(harness)
   return harness
 }

+ 1 - 1
apps/web/tests/scaffold.ts

@@ -644,7 +644,7 @@ export async function launchWebScaffold(options: LaunchOptions = {}): Promise<We
       }],
     ...maskDeepSeekCredential && !messages ? [] : [
       { id: 'llm-deepseek', disabled: mode !== 'record' && !maskDeepSeekCredential,
-        config: { protocol: messages ? 'messages' : 'chat-completions' } },
+        config: messages ? {} : { protocol: 'chat-completions' } },
     ],
   ]
 

+ 0 - 2
packages/bundle/base/cordis.patch.yml

@@ -498,5 +498,3 @@
     # choice.
     - id: llm-deepseek
       name: '@deepseek-ai/dsh-llm-deepseek'
-      config:
-        protocol: messages

+ 0 - 1
packages/bundle/sdk-minimal/cordis.patch.yml

@@ -26,7 +26,6 @@
     - id: llm-deepseek
       name: '@deepseek-ai/dsh-llm-deepseek'
       config:
-        protocol: messages
         apiKeyEnv: DEEPSEEK_API_KEY
         defaultContextWindow: !!js Number(process.env.DSH_CONTEXT_WINDOW ?? 1000000)
         streamIdleTimeoutMs: 172800000

+ 0 - 1
packages/bundle/sdk-minimal/tests/sdk-minimal.spec.ts

@@ -65,7 +65,6 @@ describe('dsh-sdk-minimal bundle', () => {
       config: { maxTokensAsSuccess: false },
     })
     expect(rows.find(row => row.id === 'llm-deepseek')?.config).toEqual({
-      protocol: 'messages',
       apiKeyEnv: 'DEEPSEEK_API_KEY',
       defaultContextWindow: { __jsExpr: 'Number(process.env.DSH_CONTEXT_WINDOW ?? 1000000)' },
       streamIdleTimeoutMs: 172800000,

+ 2 - 2
packages/bundle/web-app/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/bundle/web-app/README.md
-README.md: 84630f1d9142b1e2300baa0cde0513acdb602da9
-README.zh.md: 0488c74f433b0ef1d7e6b1677b7f50fd21a198a2
+README.md: 23ff6e925eb097eb99e5fa7ce8678c87c61aeb9c
+README.zh.md: 79b6335798e2bf522ce2bc1e201be745bb91e085

+ 1 - 1
packages/bundle/web-app/README.md

@@ -36,7 +36,7 @@ dsh --profile web --no-open --port 8080
 
 After startup you see a `dsh web:` line whose root URL carries a fresh process token. Unless `--no-open` or an SSH session suppresses it, the default browser opens that URL, receives a signed cookie, and redirects to the clean root page. You know it worked when the page loads and you can chat with the agent. Two failures to expect: if the frontend is not built, startup stops with a build hint (`pnpm run build` in a checkout); if the browser cannot be opened, a credential-free diagnostic prints to stderr while the server keeps running — open the printed startup URL yourself.
 
-**Settings → Models** displays **DeepSeek**, using `DEEPSEEK_API_KEY`. The default is `deepseek-official` / `deepseek-flash` (DeepSeek-V41-Flash). The [DeepSeek plugin](../../llm/llm-deepseek/README.md#choose-a-protocol) defaults to Chat Completions; set `protocol: messages` in Cordis YAML to switch. Web has no protocol selector.
+**Settings → Models** displays **DeepSeek**, using `DEEPSEEK_API_KEY`. The default is `deepseek-official` / `deepseek-flash` (DeepSeek-V41-Flash). The [DeepSeek plugin](../../llm/llm-deepseek/README.md#choose-a-protocol) defaults to Messages; set `protocol: chat-completions` in Cordis YAML to select Chat Completions. Web has no protocol selector.
 
 Saved model selections override the composition default. Both protocols share `deepseek-official` and `llm-deepseek` settings, so switching preserves model selections and credential references. Endpoint overrides retain their values; the settings card lets users supply a compatible API address.
 

+ 1 - 1
packages/bundle/web-app/README.zh.md

@@ -36,7 +36,7 @@ dsh --profile web --no-open --port 8080
 
 启动后你会看到 `dsh web:` 行,其根 URL 携带新的进程 token。除非 `--no-open` 或 SSH 会话抑制,否则默认浏览器会打开该 URL、取得签名 cookie,再重定向到不含认证参数的根页面。页面加载且你可以与 agent 对话,就说明成功了。两种可预期的失败:前端未构建时,启动会以构建提示停止(checkout 中运行 `pnpm run build`);浏览器无法打开时,stderr 会打印不含凭据的诊断,但服务器会继续运行——请自行打开已打印的启动 URL。
 
-**设置 → 模型**显示 **DeepSeek**,使用 `DEEPSEEK_API_KEY`。默认模型为 `deepseek-official` / `deepseek-flash`(DeepSeek-V41-Flash)。[DeepSeek 插件](../../llm/llm-deepseek/README.zh.md#choose-a-protocol)默认使用 Chat Completions;在 Cordis YAML 中设置 `protocol: messages` 可切换协议。Web 不提供协议选择器。
+**设置 → 模型**显示 **DeepSeek**,使用 `DEEPSEEK_API_KEY`。默认模型为 `deepseek-official` / `deepseek-flash`(DeepSeek-V41-Flash)。[DeepSeek 插件](../../llm/llm-deepseek/README.zh.md#choose-a-protocol)默认使用 Messages;在 Cordis YAML 中设置 `protocol: chat-completions` 可选择 Chat Completions。Web 不提供协议选择器。
 
 已保存的模型选择优先于组合默认值。两种协议共用 `deepseek-official` 与 `llm-deepseek` 设置,因此切换协议不改变模型选择或复制凭据。端点覆盖保持原值;设置卡片允许用户填写兼容的 API 地址。
 

+ 1 - 1
packages/context/agent-instructions/tests/agent-instructions.e2e.ts

@@ -41,7 +41,7 @@ async function harness(): Promise<{ ctx: Context; agent: Agent }> {
   await ctx.plugin(ToolFs)
   await ctx.plugin(AgentInstructions, { maxBytes: 65536 })
   await ctx.plugin(AgentLoop, { agents: [] })
-  await ctx.plugin(LlmDeepSeek, { protocol: 'messages', models: [{ id: 'deepseek-v4-flash' }] })
+  await ctx.plugin(LlmDeepSeek, { models: [{ id: 'deepseek-v4-flash' }] })
   const handle = await ctx.agents.create({
     sessionId: SessionId('workspace-context-e2e-session'),
     meta: { cwd: workdir },

+ 1 - 1
packages/core/agent-loop/tests/request-cache.e2e.ts

@@ -47,7 +47,7 @@ async function loopHarness(): Promise<Context> {
   await created.plugin(ToolRuntime)
   await created.plugin(AgentRegistry)
   await created.plugin(AgentLoop, { agents: [] })
-  await created.plugin(LlmDeepSeek, { protocol: 'messages' })
+  await created.plugin(LlmDeepSeek)
   created.tools.register(defineContentToolFixture({
     name: 'lookup',
     description: 'Look up the stored value for a key.',

+ 1 - 1
packages/experimental/auto-review/tests/auto-review.e2e.ts

@@ -133,7 +133,7 @@ async function mount(ctx: Context, workspace: string, dshHome: string): Promise<
     systemPrompt: {}, tools: { mode: 'both' },
   })
   // No reasoning or output-budget override: use each shipped model's defaults.
-  await ctx.plugin(LlmDeepSeek, { protocol: 'messages', retryPolicy: { mode: 'normal', maxRetries: 0 } })
+  await ctx.plugin(LlmDeepSeek, { retryPolicy: { mode: 'normal', maxRetries: 0 } })
   await ctx.plugin(SandboxPolicyService, { mode: 'workspace-write', workspaceRoot: workspace })
   await ctx.plugin(UnusedSandbox)
   await ctx.plugin(SandboxedFileSystem, { cwd: workspace })

+ 1 - 1
packages/fs/tool-fs/tests/harness.ts

@@ -16,7 +16,7 @@ export async function fsHarness(fsCwd: string, persona = ''): Promise<Context> {
   const ctx = new Context()
   await mountAgentLoopTestDependencies(ctx, { systemPrompt: { personaPrefix: persona } })
   await ctx.plugin(AgentLoop, { agents: [] })
-  await ctx.plugin(LlmDeepSeek, { protocol: 'messages' })
+  await ctx.plugin(LlmDeepSeek)
   await ctx.plugin(LocalFileSystem, { cwd: fsCwd })
   await ctx.plugin(FsPolicy)
   await ctx.plugin(ToolFs)

+ 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: 8c7d2d083a8a315ea2d7bf5190b77222901488b1
-README.zh.md: e7904b221f7573e997abba126892b83f73a5b4f6
+README.md: c03fa8ab740f9ffd315ee84b029e132bc40882b4
+README.zh.md: 80630f363aa4a065f0a2bbedc033fbca03fc641f

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

@@ -36,7 +36,6 @@ Choose this adapter for DeepSeek's official API or a gateway that supports the s
 ```yaml
 - name: '@deepseek-ai/dsh-llm-deepseek'
   config:
-    protocol: messages          # messages | chat-completions
     apiKeyEnv: DEEPSEEK_API_KEY  # credential reference, resolved per request
     reasoningEffort: high        # optional; off | low | high | max
     maxTokens: 256000            # optional per-request output cap
@@ -84,7 +83,7 @@ To select Chat Completions explicitly, patch the existing plugin:
     protocol: chat-completions
 ```
 
-`protocol` defaults to `messages`, with official root `https://api.deepseek.com/anthropic`; `chat-completions` uses `https://api.deepseek.com`. Shipped first-party compositions explicitly select Messages. Neither protocol requires `baseURL`: its official default applies when both `baseURL` and `$DEEPSEEK_BASE_URL` are absent. Switching protocols retains endpoint overrides, so users must supply an address compatible with the selected protocol. An explicit `https://api.deepseek.com` override selects the Chat root: remove that override to use the official Messages default, or set it to `https://api.deepseek.com/anthropic`. Chat appends `/chat/completions`; Messages appends `/v1/messages`. Apart from trailing slashes, neither infers or removes custom path suffixes such as `/v1`. Both share the `llm-deepseek` settings section, `apiKeyEnv`, and `deepseek-official`, so saved model selections remain valid.
+`protocol` defaults to `messages`, with official root `https://api.deepseek.com/anthropic`; `chat-completions` uses `https://api.deepseek.com`. Shipped first-party compositions inherit this default. Neither protocol requires `baseURL`: its official default applies when both `baseURL` and `$DEEPSEEK_BASE_URL` are absent. Switching protocols retains endpoint overrides, so users must supply an address compatible with the selected protocol. An explicit `https://api.deepseek.com` override selects the Chat root: remove that override to use the official Messages default, or set it to `https://api.deepseek.com/anthropic`. Chat appends `/chat/completions`; Messages appends `/v1/messages`. Apart from trailing slashes, neither infers or removes custom path suffixes such as `/v1`. Both share the `llm-deepseek` settings section, `apiKeyEnv`, and `deepseek-official`, so saved model selections remain valid.
 
 Messages sends text, thinking, tool calls, and tool results as content blocks, reasoning effort as `output_config.effort`, and images as Files references or inline base64. Models declaring `systemPromptUpdate: in-history` retain the initial top-level system and send new system snapshots after their corresponding user/tool-result turn; undeclared models use the latest snapshot as the top-level system. Replay metadata identifies the Messages format, model, and signatures. Chat requests serialize durable content without those signatures. Invalid Messages replay metadata emits a warning and omits signatures while retaining text and tool history.
 

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

@@ -36,7 +36,6 @@ kind: "package-reference"
 ```yaml
 - name: '@deepseek-ai/dsh-llm-deepseek'
   config:
-    protocol: messages          # messages | chat-completions
     apiKeyEnv: DEEPSEEK_API_KEY  # credential reference, resolved per request
     reasoningEffort: high        # optional; off | low | high | max
     maxTokens: 256000            # optional per-request output cap
@@ -84,7 +83,7 @@ kind: "package-reference"
     protocol: chat-completions
 ```
 
-`protocol` 默认为 `messages`,官方根地址为 `https://api.deepseek.com/anthropic`;`chat-completions` 使用 `https://api.deepseek.com`。随产品交付的官方组合显式选择 Messages。两种协议都不要求填写 `baseURL`:当 `baseURL` 与 `$DEEPSEEK_BASE_URL` 均未设置时使用当前协议的官方默认值。切换协议保留已有端点覆盖,用户需要填写与选定协议兼容的地址。显式填写的 `https://api.deepseek.com` 是 Chat 根地址:删除该覆盖即可使用官方 Messages 默认值,也可以改填 `https://api.deepseek.com/anthropic`。Chat 追加 `/chat/completions`,Messages 追加 `/v1/messages`;除去末尾斜线之外,不推测或删除自定义路径中的 `/v1` 等后缀。两种协议共用 `llm-deepseek` 设置、`apiKeyEnv` 与 `deepseek-official`,因此已保存的模型选择仍然有效。
+`protocol` 默认为 `messages`,官方根地址为 `https://api.deepseek.com/anthropic`;`chat-completions` 使用 `https://api.deepseek.com`。随产品交付的官方组合继承该默认值。两种协议都不要求填写 `baseURL`:当 `baseURL` 与 `$DEEPSEEK_BASE_URL` 均未设置时使用当前协议的官方默认值。切换协议保留已有端点覆盖,用户需要填写与选定协议兼容的地址。显式填写的 `https://api.deepseek.com` 是 Chat 根地址:删除该覆盖即可使用官方 Messages 默认值,也可以改填 `https://api.deepseek.com/anthropic`。Chat 追加 `/chat/completions`,Messages 追加 `/v1/messages`;除去末尾斜线之外,不推测或删除自定义路径中的 `/v1` 等后缀。两种协议共用 `llm-deepseek` 设置、`apiKeyEnv` 与 `deepseek-official`,因此已保存的模型选择仍然有效。
 
 Messages 以内容块发送文本、思考、工具调用和工具结果,以 `output_config.effort` 发送推理强度,并以 Files 引用或内联 base64 发送图片。声明 `systemPromptUpdate: in-history` 的模型保留初始顶层 system,在对应 user/tool-result 轮次之后发送新的 system 快照;未声明能力时,使用最新快照作为顶层 system。回放元数据记录 Messages 格式、模型和签名;Chat 请求只序列化持久化内容,不发送这些签名。无效的 Messages 回放元数据产生警告并省略签名,不丢弃文本或工具历史。
 

+ 88 - 4
packages/llm/llm-deepseek/tests/messages/adapter.e2e.ts

@@ -2,14 +2,21 @@
  * Real Messages round trips use the official root and require credentials.
  * System-update checks additionally require DEEPSEEK_IN_HISTORY_MODEL.
  */
+import { randomUUID } from 'node:crypto'
 import { mkdtemp, readFile, rm } from 'node:fs/promises'
 import { tmpdir } from 'node:os'
 import { join } from 'node:path'
 import { afterEach, describe, expect, it, vi } from 'vitest'
 import { Context, LoggerLevel } from '@deepseek-ai/cordis'
+import Loader from '@deepseek-ai/cordis-plugin-loader'
+import AgentRegistry from '@deepseek-ai/dsh-agent'
 import LocalAttachments from '@deepseek-ai/dsh-attachment-local'
-import LlmRuntime, { createSystemMessage, createToolResultMessage, ReasoningEffortId } from '@deepseek-ai/dsh-llm'
+import DeepSeekLlmApiExtensionRegistry from '@deepseek-ai/dsh-deepseek-llm-api-extensions'
+import LlmRuntime, { BlockAssembler, createSystemMessage, createToolResultMessage, ReasoningEffortId } from '@deepseek-ai/dsh-llm'
 import type { Message } from '@deepseek-ai/dsh-llm'
+import * as PluginPackageInventoryDeepSeek from '@deepseek-ai/dsh-plugin-package-inventory-deepseek'
+import SessionStore, { SessionId } from '@deepseek-ai/dsh-session'
+import * as SessionLogDeepSeek from '@deepseek-ai/dsh-session-log-deepseek'
 import * as Messages from '../../src/index.ts'
 import { DeepSeekFilesClient, MESSAGES_FILES_BETA } from '../../src/common/files-api.ts'
 import { assemble, options, user } from './helpers.ts'
@@ -29,7 +36,6 @@ async function boot(models?: Messages.Config['models']) {
   cleanups.push(() => ctx.fiber.dispose())
   await ctx.plugin(LlmRuntime)
   await ctx.plugin(Messages, {
-    protocol: 'messages',
     baseURL: Messages.MESSAGES_BASE_URL,
     maxTokens: 4096,
     ...models === undefined ? {} : { models },
@@ -60,20 +66,24 @@ describe.skipIf(!process.env.DEEPSEEK_API_KEY)('DeepSeek Messages real API', ()
     await reply('PROMPT_CLEARED')
   })
 
-  it('uploads, lists, retrieves, and reuses a Files image across Messages requests', async () => {
+  it('uploads, lists, retrieves, reuses, and replaces a deleted Files image across Messages requests', async () => {
     const ctx = await boot()
     await ctx.plugin(LocalAttachments)
     const fetchImpl = globalThis.fetch
     const uploads: string[] = []
     const bodies: string[] = []
     const files = new DeepSeekFilesClient({ baseURL: Messages.MESSAGES_BASE_URL, protocol: 'messages', apiKey: process.env.DEEPSEEK_API_KEY as string, fetch: fetchImpl })
+    const ownedFiles = new Set<ReturnType<typeof Messages.DeepSeekFileId>>()
+    cleanups.push(async () => {
+      for (const id of ownedFiles) await files.delete(id)
+    })
     vi.stubGlobal('fetch', async (input: string | URL | Request, init?: RequestInit) => {
       const url = typeof input === 'string' ? input : input instanceof URL ? input.href : input.url
       const response = await fetchImpl(input, init)
       if (url === `${Messages.MESSAGES_BASE_URL}/v1/files` && init?.method === 'POST' && response.ok) {
         const file = await response.clone().json() as { id: string }
         uploads.push(file.id)
-        cleanups.push(() => files.delete(Messages.DeepSeekFileId(file.id)))
+        ownedFiles.add(Messages.DeepSeekFileId(file.id))
       }
       if (url === `${Messages.MESSAGES_BASE_URL}/v1/messages`) {
         expect(new Headers(init?.headers).get('anthropic-beta')).toBe(MESSAGES_FILES_BETA)
@@ -110,6 +120,80 @@ describe.skipIf(!process.env.DEEPSEEK_API_KEY)('DeepSeek Messages real API', ()
       page = await files.list({ after, limit: 1_000 })
     }
     expect(page.data).toContainEqual(retrieved)
+    await files.delete(fileId)
+    ownedFiles.delete(fileId)
+    const recovered = await assemble(ctx.llm.stream(request), request.model)
+    expect(recovered.assembler.finish.kind).toBe('stop')
+    expect(recovered.message.content.filter(block => block.type === 'text').map(block => block.text).join('').toLowerCase()).toContain('red')
+    expect(uploads).toHaveLength(2)
+    expect(uploads[1]).not.toBe(fileId)
+    expect(bodies).toHaveLength(4)
+    expect(bodies[2]).toContain(`"file_id":"${fileId}"`)
+    expect(bodies[3]).toContain(`"file_id":"${uploads[1]}"`)
+    expect(bodies[3]).not.toContain('"type":"base64"')
+  })
+
+  it.each([false, true])('submits Loader package inventory and records HTTP acceptance with session-log enabled=%s', async (enabled) => {
+    const ctx = await boot()
+    await ctx.plugin(Loader)
+    await ctx.plugin(AgentRegistry)
+    await ctx.plugin(SessionStore)
+    await ctx.plugin(DeepSeekLlmApiExtensionRegistry)
+    await ctx.plugin(SessionLogDeepSeek, enabled ? { enabled: true } : {})
+    ctx.baseUrl = import.meta.url
+    // Select the source module while Loader owns its active package entry.
+    ctx.loader.internal = {
+      version: 'v2',
+      async import(specifier: string) {
+        if (specifier !== '@deepseek-ai/dsh-plugin-package-inventory-deepseek') throw new Error(`unexpected Loader import: ${specifier}`)
+        return PluginPackageInventoryDeepSeek
+      },
+    } as unknown as NonNullable<typeof ctx.loader.internal>
+    await ctx.loader.create({ name: '@deepseek-ai/dsh-plugin-package-inventory-deepseek' })
+    await ctx.loader.await()
+    const packageIdentity = JSON.parse(await readFile(new URL('../../../plugin-package-inventory-deepseek/package.json', import.meta.url), 'utf8')) as { name: string; version: string }
+    const session = ctx.sessions.create(SessionId(`real-messages-extensions-${randomUUID()}`))
+    session.append('turn/start', { turn: 1 })
+    const fetchImpl = globalThis.fetch
+    let afterSeq: number = -1
+    let throughSeq = 0
+    let requests = 0
+    vi.stubGlobal('fetch', async (input: string | URL | Request, init?: RequestInit) => {
+      const url = typeof input === 'string' ? input : input instanceof URL ? input.href : input.url
+      if (url !== `${Messages.MESSAGES_BASE_URL}/v1/messages`) return fetchImpl(input, init)
+      if (typeof init?.body !== 'string') throw new Error('expected a JSON Messages request')
+      const body = JSON.parse(init.body) as Record<string, unknown>
+      expect(body).toMatchObject({ dsh_plugin_packages: {
+        version: 1, packages: [{ name: packageIdentity.name, version: packageIdentity.version }],
+      } })
+      if (enabled) {
+        expect(body).toMatchObject({ dsh_session_log: {
+          version: 1, sessionFormatVersion: session.header.version, session: { id: session.id },
+          afterSeq, throughSeq,
+          events: Array.from({ length: throughSeq - afterSeq }, (_, index) => ({ seq: afterSeq + index + 1 })),
+        } })
+      } else expect(body).not.toHaveProperty('dsh_session_log')
+      expect(SessionLogDeepSeek.acceptedThrough(session)).toBe(afterSeq)
+      const response = await fetchImpl(input, init)
+      expect(response.ok).toBe(true)
+      expect(SessionLogDeepSeek.acceptedThrough(session)).toBe(afterSeq)
+      requests += 1
+      return response
+    })
+    for (let run = 0; run < 2; run++) {
+      afterSeq = SessionLogDeepSeek.acceptedThrough(session)
+      throughSeq = Number(session.seq) - 1
+      const assembler = new BlockAssembler()
+      for await (const chunk of ctx.llm.stream(options({ sessionId: session.id, reasoningEffort: ReasoningEffortId('off'), messages: [user('Reply with exactly PONG.')] }))) {
+        expect(SessionLogDeepSeek.acceptedThrough(session)).toBe(enabled ? throughSeq : -1)
+        assembler.push(chunk)
+      }
+      expect(assembler.finish.kind).toBe('stop')
+      expect(assembler.blocks().filter(block => block.type === 'text').map(block => block.text).join('')).toContain('PONG')
+      expect(requests).toBe(run + 1)
+      if (run === 0) session.append('step/start', { turn: 1, step: 1 })
+    }
+    expect(session.snapshotEvents().filter(event => event.type === 'session-log-deepseek/delivery-accepted')).toHaveLength(enabled ? 2 : 0)
   })
 
   it.each(['off', 'low', 'high', 'max'])('streams text with %s effort', async (effort) => {

+ 5 - 5
packages/llm/llm-deepseek/tests/messages/adapter.spec.ts

@@ -106,7 +106,7 @@ describe('direct Messages HTTP', () => {
     const prepare = vi.fn(async () => ({ fields: {}, accept }))
     const files = new DeepSeekFileStore()
     const llm = new DeepSeekMessagesAdapter({
-      connection: () => Messages.resolveAdapterOptions({ protocol: 'messages', baseURL: source.url }),
+      connection: () => Messages.resolveAdapterOptions({ baseURL: source.url }),
       apiKey: () => Promise.resolve('test-key'), userId: () => 'test-user',
       attachments: () => undefined, imageAccess: () => undefined, files: () => files,
       prepareExtensions: prepare,
@@ -122,11 +122,11 @@ describe('direct Messages HTTP', () => {
 
   it('freezes endpoint and defaults for a prepared call while the next call sees new settings', async () => {
     const first = await endpoint(), second = await endpoint()
-    let config = Messages.resolveAdapterOptions({ protocol: 'messages', baseURL: first.url, maxTokens: 10, models: [{ id: MODEL, systemPromptUpdate: 'in-history' }] })
+    let config = Messages.resolveAdapterOptions({ baseURL: first.url, maxTokens: 10, models: [{ id: MODEL, systemPromptUpdate: 'in-history' }] })
     const files = new DeepSeekFileStore()
     const llm = new DeepSeekMessagesAdapter({ connection: () => config, apiKey: snapshot => Promise.resolve(snapshot.maxTokens === 10 ? 'first' : 'second'), userId: () => 'user', attachments: () => undefined, imageAccess: () => undefined, files: () => files, prepareExtensions })
     const prepared = await llm.prepareCall('deepseek-official', MODEL)
-    config = Messages.resolveAdapterOptions({ protocol: 'messages', baseURL: second.url, maxTokens: 20 })
+    config = Messages.resolveAdapterOptions({ baseURL: second.url, maxTokens: 20 })
     expect(prepared.model.systemPromptUpdate).toBe('in-history')
     expect((await llm.resolveModel('deepseek-official', MODEL)).systemPromptUpdate).toBeUndefined()
     await chunks(prepared.stream(options()))
@@ -169,7 +169,7 @@ describe('Cordis provider composition', () => {
     const { ctx, home } = await context()
     vi.stubEnv('DEEPSEEK_API_KEY', 'test-key')
     await ctx.plugin(LlmRuntime)
-    await ctx.plugin(Messages, { protocol: 'messages', baseURL: http.url })
+    await ctx.plugin(Messages, { baseURL: http.url })
     const model = 'deepseek-v4-flash-vision-exp'
     const price = () => ctx.llm.imageRequestPricing('deepseek-official', model)!
     const dummy = { attachmentId: AttachmentId(`sha256:${'a'.repeat(64)}`), width: 1, height: 1, bytes: 3, mediaType: 'image/png' as const }
@@ -369,7 +369,7 @@ describe('Cordis provider composition', () => {
     vi.stubEnv('DEEPSEEK_BASE_URL', http.url)
     vi.stubEnv('DEEPSEEK_API_KEY', 'env-key')
     await ctx.plugin(LlmRuntime)
-    const fiber = ctx.plugin(Messages, { protocol: 'messages' })
+    const fiber = ctx.plugin(Messages)
     await fiber
     await chunks(ctx.llm.stream(options()))
     expect(http.requests[0]?.headers['x-api-key']).toBe('env-key')

+ 1 - 1
packages/llm/llm-deepseek/tests/messages/extensions.spec.ts

@@ -33,7 +33,7 @@ async function boot() {
   cleanup.push(() => ctx.fiber.dispose())
   await ctx.plugin(LlmRuntime)
   await ctx.plugin(DeepSeekLlmApiExtensionRegistry)
-  await ctx.plugin(DeepSeek, { protocol: 'messages', baseURL: 'https://messages.example.test/root' })
+  await ctx.plugin(DeepSeek, { baseURL: 'https://messages.example.test/root' })
   return ctx
 }
 

+ 1 - 1
packages/llm/llm-deepseek/tests/messages/files.spec.ts

@@ -36,7 +36,7 @@ function harness(config: Config = {}) {
   const attachments = { readImageRequest } as unknown as AttachmentStore
   const prepare = vi.fn(prepareExtensions)
   const adapter = new DeepSeekMessagesAdapter({
-    connection: () => resolveAdapterOptions(Object.assign({ protocol: 'messages' as const, baseURL: 'https://gateway.example/custom' }, config)),
+    connection: () => resolveAdapterOptions(Object.assign({ baseURL: 'https://gateway.example/custom' }, config)),
     apiKey: async () => 'test-key', userId: () => 'test-user', attachments: () => attachments,
     imageAccess: () => ({ readonlyPath: '/workspace/image.png' }), files: () => files, prepareExtensions: prepare,
   })

+ 0 - 1
packages/llm/llm-deepseek/tests/messages/fixtures/cordis.yml

@@ -17,7 +17,6 @@
     watch: false
 - name: '@deepseek-ai/dsh-llm-deepseek'
   config:
-    protocol: messages
     apiKeyEnv: DEEPSEEK_API_KEY
     baseURL: {{endpoint}}
     thinking: enabled

+ 1 - 1
packages/llm/llm-deepseek/tests/messages/helpers.ts

@@ -40,7 +40,7 @@ export async function assemble(stream: AsyncIterable<StreamChunk>, model = MODEL
 }
 export function adapter(config: Config = {}) {
   const files = new DeepSeekFileStore()
-  return new DeepSeekMessagesAdapter({ connection: () => resolveAdapterOptions(Object.assign({}, config, { protocol: 'messages' as const })), apiKey: () => Promise.resolve('test-key'), userId: () => 'test-user', attachments: () => undefined, imageAccess: () => undefined, files: () => files, prepareExtensions })
+  return new DeepSeekMessagesAdapter({ connection: () => resolveAdapterOptions(config), apiKey: () => Promise.resolve('test-key'), userId: () => 'test-user', attachments: () => undefined, imageAccess: () => undefined, files: () => files, prepareExtensions })
 }
 export async function server(reply: (response: ServerResponse, count: number) => void = response => response.end(sse(textEvents))) {
   const requests: { path: string; headers: IncomingHttpHeaders; body: Record<string, unknown> }[] = []

+ 8 - 8
packages/llm/llm-deepseek/tests/messages/serialize.spec.ts

@@ -12,14 +12,14 @@ import { readReplay, replayState } from '../../src/protocols/messages/replay.ts'
 import { serialize } from '../../src/protocols/messages/serialize.ts'
 import { MODEL, options, user } from './helpers.ts'
 
-const connection = resolveAdapterOptions({ protocol: 'messages' })
+const connection = resolveAdapterOptions({})
 const call = (id = 'a'): ContentBlock => ({ type: 'tool-call', id: ToolCallId(id), name: 'read', arguments: '{"path":"a"}' })
 const assistant = (content: ContentBlock[]) => createAssistantMessage({ content, source: { provider: 'deepseek-official', model: MODEL } })
 const result = (id = 'a', content: ContentBlock[] = [{ type: 'text', text: 'result' }]) => createToolResultMessage({ callId: ToolCallId(id), content, isError: false })
 const body = (messages: Message[] = [user()], overrides: Partial<GenerateOptions> = {}) => serialize(
   options({ messages, ...overrides }), connection, messages, new Map(), () => undefined,
 )
-const capable = resolveAdapterOptions({ protocol: 'messages', models: [{ id: MODEL, systemPromptUpdate: 'in-history' }] })
+const capable = resolveAdapterOptions({ models: [{ id: MODEL, systemPromptUpdate: 'in-history' }] })
 const nativeBody = (messages: Message[]) => serialize(options({ messages }), capable, messages, new Map(), () => undefined)
 
 describe('Messages request conversion', () => {
@@ -134,10 +134,10 @@ describe('Messages request conversion', () => {
     expect(body([user()], { purpose: 'session-title', temperature: 0 })).toMatchObject({ thinking: { type: 'disabled' }, temperature: 0 })
     expect(body([user()], { temperature: 0 })).toMatchObject({ thinking: { type: 'enabled' }, temperature: 0 })
     expect(() => body([user()], { reasoningEffort: ReasoningEffortId('medium') })).toThrow(/effort/)
-    const disabled = resolveAdapterOptions({ protocol: 'messages', thinking: 'disabled' })
+    const disabled = resolveAdapterOptions({ thinking: 'disabled' })
     expect(serialize(options(), disabled, [user()], new Map(), () => undefined).thinking).toEqual({ type: 'disabled' })
     expect(() => serialize(options({ reasoningEffort: ReasoningEffortId('high') }), disabled, [user()], new Map(), () => undefined)).toThrow(/effort/)
-    const capped = resolveAdapterOptions({ protocol: 'messages', models: [{ id: MODEL, maxTokens: 321 }] })
+    const capped = resolveAdapterOptions({ models: [{ id: MODEL, maxTokens: 321 }] })
     expect(serialize(options(), capped, [user()], new Map(), () => undefined).max_tokens).toBe(321)
   })
 
@@ -230,8 +230,8 @@ describe('validated configuration', () => {
     expect(modelInfo(connection, 'deepseek-official', 'custom').systemPromptUpdate).toBeUndefined()
     expect(modelInfo(capable, 'deepseek-official', MODEL).systemPromptUpdate).toBe('in-history')
     expect(modelInfo(capable, 'deepseek-official', 'custom').systemPromptUpdate).toBeUndefined()
-    expect(modelInfo(resolveAdapterOptions({ protocol: 'messages', thinking: 'disabled' }), 'deepseek-official', MODEL).reasoning?.efforts).toMatchObject([{ id: 'off', name: 'Off' }])
-    expect(resolveAdapterOptions({ protocol: 'messages', baseURL: 'https://example.com/anthropic///' }).baseURL).toBe('https://example.com/anthropic///')
+    expect(modelInfo(resolveAdapterOptions({ thinking: 'disabled' }), 'deepseek-official', MODEL).reasoning?.efforts).toMatchObject([{ id: 'off', name: 'Off' }])
+    expect(resolveAdapterOptions({ baseURL: 'https://example.com/anthropic///' }).baseURL).toBe('https://example.com/anthropic///')
   })
   it.each([
     { thinking: 'disabled', reasoningEffort: 'high' }, { models: [{ id: '' }] },
@@ -242,7 +242,7 @@ describe('validated configuration', () => {
     { maxTokens: 0 }, { streamIdleTimeoutMs: 0 },
     { models: [{ id: MODEL, systemPromptUpdate: 'unsupported' }] },
   ])('rejects invalid composition input %#', (value) => {
-    expect(() => resolveAdapterOptions({ ...value, protocol: 'messages' } as Config)).toThrow()
+    expect(() => resolveAdapterOptions(value as Config)).toThrow()
   })
 })
 
@@ -268,7 +268,7 @@ describe('Messages images', () => {
     expect(imagePricing(connection, MODEL, access).priceImages([ref])[0]?.visualTokens).toBe(0)
   })
   it('offloads an oldest prefix using exact encoded bytes and preserves durable references', async () => {
-    const config = resolveAdapterOptions({ protocol: 'messages',
+    const config = resolveAdapterOptions({
       maxInlineRequestImageBytes: 4, inlineImageOffloadByteQuantum: 1, maxImagesPerRequest: 2, imageOffloadCountQuantum: 1,
     })
     const history = [result('a', [image, image])]

+ 1 - 1
packages/llm/llm-pi-ai/tests/adapter.e2e.ts

@@ -153,7 +153,7 @@ describe.skipIf(!process.env.DEEPSEEK_API_KEY)('llm-pi-ai e2e (real API)', () =>
     const deepseekCtx = new Context()
     contexts.push(deepseekCtx)
     await deepseekCtx.plugin(LlmRuntime)
-    await deepseekCtx.plugin(LlmDeepSeek, { protocol: 'messages', thinking: 'disabled' })
+    await deepseekCtx.plugin(LlmDeepSeek, { thinking: 'disabled' })
 
     const piCtx = await harness(FLASH)
 

+ 1 - 1
packages/sdk/server/src/server.ts

@@ -149,7 +149,7 @@ export class HarnessSdkJsonRpcServer {
       : ReasoningEffortId(params.reasoningEffort)
     if (!this.hasAdapterFor(provider)) {
       if (provider !== 'deepseek-official') throw new Error(`no adapter registered for provider "${provider}"`)
-      this.llmFiber = await this.ctx.plugin(LlmDeepSeek, { protocol: 'messages' })
+      this.llmFiber = await this.ctx.plugin(LlmDeepSeek)
     }
     // Adapter presence was read from this service above; a successful fallback mount also requires it.
     const llm = this.ctx.get('llm') as LlmRuntime

+ 1 - 1
packages/session/session-title-first-prompt-llm/tests/provider.e2e.ts

@@ -19,7 +19,7 @@ describe.skipIf(!process.env.DEEPSEEK_API_KEY)('first-prompt title provider with
     const ctx = new Context()
     contexts.push(ctx)
     await ctx.plugin(LlmRuntime)
-    await ctx.plugin(LlmDeepSeek, { protocol: 'messages', thinking: 'disabled' })
+    await ctx.plugin(LlmDeepSeek, { thinking: 'disabled' })
     await ctx.plugin(SessionStore)
     await ctx.plugin(SessionProjectionRegistry)
     await ctx.plugin(SessionTitleService, {

+ 1 - 1
packages/subagent/subagent-spawn-in-process/tests/harness.ts

@@ -28,7 +28,7 @@ export async function spawnHarness(workdir: string): Promise<Context> {
     systemPrompt: { personaPrefix: 'You are a coding agent. Report only when the requested work is done.' },
   })
   await ctx.plugin(AgentLoop, { agents: [] })
-  await ctx.plugin(LlmDeepSeek, { protocol: 'messages' })
+  await ctx.plugin(LlmDeepSeek)
   await ctx.plugin(LocalSubprocessRuntime)
   await ctx.plugin(BashEnvPlugin)
   await ctx.plugin(LocalBashExecutor, { cwd: workdir, timeoutMs: 30_000 })

+ 1 - 1
packages/workflow/workflow-ptc/tests/workflow-ptc.e2e.ts

@@ -22,7 +22,7 @@ async function harness(): Promise<Context> {
   await mountAgentLoopTestDependencies(built)
   await mountPtcRuntime(built)
   await built.plugin(AgentLoop, { agents: [] })
-  await built.plugin(LlmDeepSeek, { protocol: 'messages' })
+  await built.plugin(LlmDeepSeek)
   await built.plugin(SubagentRuntime)
   await built.plugin(Spawn, { providerName: 'spawn' })
   await built.plugin(PtcWorkflowEngine, { provider: 'spawn' })

+ 0 - 1
snapshots/acp/escalation-approved/cordis.yml

@@ -5,7 +5,6 @@
 - id: llm-deepseek
   name: '@deepseek-ai/dsh-llm-deepseek'
   config:
-    protocol: messages
     thinking: enabled
     reasoningEffort: max
     models:

+ 0 - 1
snapshots/sdk/bash-tool/cordis.yml

@@ -8,7 +8,6 @@
 - id: llm-deepseek
   name: '@deepseek-ai/dsh-llm-deepseek'
   config:
-    protocol: messages
     thinking: enabled
     reasoningEffort: max
 

+ 0 - 1
snapshots/sdk/persistent-tools/cordis.yml

@@ -11,7 +11,6 @@
 - id: llm-deepseek
   name: '@deepseek-ai/dsh-llm-deepseek'
   config:
-    protocol: messages
     apiKeyEnv: DEEPSEEK_API_KEY
     streamIdleTimeoutMs: 172800000
     models:

+ 0 - 1
snapshots/sdk/subagent-dsh-sdk-diagnostic/cordis.yml

@@ -8,7 +8,6 @@
 - id: llm-deepseek
   name: '@deepseek-ai/dsh-llm-deepseek'
   config:
-    protocol: messages
     thinking: enabled
     reasoningEffort: max
 

+ 0 - 1
snapshots/sdk/text-turn/cordis.yml

@@ -10,7 +10,6 @@
 - id: llm-deepseek
   name: '@deepseek-ai/dsh-llm-deepseek'
   config:
-    protocol: messages
     thinking: enabled
     reasoningEffort: max
 

+ 0 - 1
snapshots/session/deepseek-messages-system-prompt/cordis.yml

@@ -1,6 +1,5 @@
 - id: llm-deepseek
   config:
-    protocol: messages
     models:
       - id: deepseek-v4-flash
         systemPromptUpdate: in-history

+ 0 - 1
snapshots/session/deepseek-protocol-system-prompt/cordis.yml

@@ -1,6 +1,5 @@
 - id: llm-deepseek
   config:
-    protocol: messages
     models:
       - id: deepseek-v4-flash
         systemPromptUpdate: in-history

+ 0 - 1
snapshots/session/empty-response-retry/cordis.yml

@@ -8,7 +8,6 @@
 - id: llm-deepseek
   name: '@deepseek-ai/dsh-llm-deepseek'
   config:
-    protocol: messages
     thinking: enabled
     reasoningEffort: max
     retryPolicy:

+ 0 - 1
snapshots/session/fs-glob-sampling/cordis.yml

@@ -2,7 +2,6 @@
 - id: llm-deepseek
   name: '@deepseek-ai/dsh-llm-deepseek'
   config:
-    protocol: messages
     models:
       - id: deepseek-v4-pro
 

+ 0 - 1
snapshots/session/persistent-pwsh-tool-turn/cordis.yml

@@ -2,7 +2,6 @@
 - id: llm-deepseek
   name: '@deepseek-ai/dsh-llm-deepseek'
   config:
-    protocol: messages
     models:
       - id: deepseek-v4-pro
 

+ 0 - 1
snapshots/session/pwsh-tool-turn/cordis.yml

@@ -2,7 +2,6 @@
 - id: llm-deepseek
   name: '@deepseek-ai/dsh-llm-deepseek'
   config:
-    protocol: messages
     models:
       - id: deepseek-v4-pro
 

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

@@ -5,7 +5,6 @@
 - id: llm-deepseek
   name: '@deepseek-ai/dsh-llm-deepseek'
   config:
-    protocol: messages
     thinking: enabled
     reasoningEffort: max
 

+ 0 - 1
snapshots/session/text-turn/cordis.yml

@@ -5,7 +5,6 @@
 - id: llm-deepseek
   name: '@deepseek-ai/dsh-llm-deepseek'
   config:
-    protocol: messages
     thinking: enabled
     reasoningEffort: max
     models: