diff --git a/AGENTS.md b/AGENTS.md index 659614c..0796215 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -102,9 +102,14 @@ These are load-bearing. Breaking one causes silent failures in opencode. 7. **`supportedUrls` is `{}` and stays empty.** CommandsCode URLs are not fetched by the SDK. 8. **Redact before surfacing errors.** Any upstream error text passed to the client must go through `redact()`. -9. **`file://` npm specs bypass install.** opencode imports `dist/index.js` directly, so the repo +9. **Replay assistant reasoning as a `{type:"reasoning"}` content part.** DeepSeek thinking mode + rejects any request carrying `tools` whose prior assistant turns omit their reasoning. In + `transform.ts`, forward each assistant `reasoning` part as `{type:"reasoning", text, signature?}`; + do not drop it and do not fabricate empty reasoning. `server.py` predates this requirement and is + not the guide here. +10. **`file://` npm specs bypass install.** opencode imports `dist/index.js` directly, so the repo must be rebuilt for opencode to see source changes. -10. **`/models` is config-driven, not provider-driven.** opencode builds the model list from +11. **`/models` is config-driven, not provider-driven.** opencode builds the model list from `provider.commandcode.models` in `opencode.json` and never asks a custom `file://` provider to discover models (only internal providers can register `discoverModels`). Refresh the map with `npm run sync-models`; do not expect a discovery hook in `src/` to populate it. diff --git a/README.md b/README.md index 8d3d70c..76d3b28 100644 --- a/README.md +++ b/README.md @@ -171,6 +171,7 @@ useful for diagnosing model selection, retry, tool-call, and finish-reason issue | Non-streaming (`doGenerate`) | Yes — buffers the stream internally | | Text deltas | Yes | | Reasoning deltas | Yes — emitted as `reasoning-start` / `reasoning-delta` / `reasoning-end` | +| Reasoning replay | Yes — assistant `reasoning` parts are sent back as `{type:"reasoning"}` content parts (required by DeepSeek thinking mode when tools are present); `signature` is forwarded when present | | Tool calls | Yes — `tool-input-start` / `tool-input-delta` / `tool-input-end` / `tool-call` | | Tool results | Yes — paired results are replayed; unpaired ids are dropped | | Multiple images (vision) | Yes — `data:` URIs, raw base64, `Uint8Array`, and remote URLs | diff --git a/src/transform.ts b/src/transform.ts index cc3a5a5..1747b16 100644 --- a/src/transform.ts +++ b/src/transform.ts @@ -193,6 +193,15 @@ export function transform(options: LanguageModelV3CallOptions, modelId: string): for (const part of message.content) { if (part.type === "text" && part.text) { parts.push({ type: "text", text: part.text }); + } else if (part.type === "reasoning") { + // DeepSeek thinking mode requires prior assistant reasoning to be passed + // back when `tools` are present; CommandCode reads it as a content part. + const reasoning = part as { text?: string; signature?: string }; + if (reasoning.text) { + const item: ContentPart = { type: "reasoning", text: reasoning.text }; + if (reasoning.signature) item["signature"] = reasoning.signature; + parts.push(item); + } } else if (part.type === "tool-call") { if (!paired.has(part.toolCallId)) continue; parts.push({ @@ -202,7 +211,6 @@ export function transform(options: LanguageModelV3CallOptions, modelId: string): input: (part as { input?: unknown }).input ?? {}, }); } - // reasoning parts are not replayed upstream } if (parts.length) messages.push({ role: "assistant", content: parts }); continue;