Replay assistant reasoning as a content part for DeepSeek thinking mode
Forward each assistant reasoning part to /alpha/generate as
{type:"reasoning", text, signature?} instead of dropping it. DeepSeek
thinking mode rejects any request carrying tools whose prior assistant
turns omit their reasoning, so multi-turn tool-call conversations 400'd.
Only real (non-empty) reasoning is forwarded; no fabricated injection.
Update README and AGENTS.md accordingly.
This commit is contained in:
parent
0b1f933ead
commit
ab760fb876
@ -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.
|
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
|
8. **Redact before surfacing errors.** Any upstream error text passed to the client must go through
|
||||||
`redact()`.
|
`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.
|
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
|
`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
|
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.
|
`npm run sync-models`; do not expect a discovery hook in `src/` to populate it.
|
||||||
|
|||||||
@ -171,6 +171,7 @@ useful for diagnosing model selection, retry, tool-call, and finish-reason issue
|
|||||||
| Non-streaming (`doGenerate`) | Yes — buffers the stream internally |
|
| Non-streaming (`doGenerate`) | Yes — buffers the stream internally |
|
||||||
| Text deltas | Yes |
|
| Text deltas | Yes |
|
||||||
| Reasoning deltas | Yes — emitted as `reasoning-start` / `reasoning-delta` / `reasoning-end` |
|
| 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 calls | Yes — `tool-input-start` / `tool-input-delta` / `tool-input-end` / `tool-call` |
|
||||||
| Tool results | Yes — paired results are replayed; unpaired ids are dropped |
|
| Tool results | Yes — paired results are replayed; unpaired ids are dropped |
|
||||||
| Multiple images (vision) | Yes — `data:` URIs, raw base64, `Uint8Array`, and remote URLs |
|
| Multiple images (vision) | Yes — `data:` URIs, raw base64, `Uint8Array`, and remote URLs |
|
||||||
|
|||||||
@ -193,6 +193,15 @@ export function transform(options: LanguageModelV3CallOptions, modelId: string):
|
|||||||
for (const part of message.content) {
|
for (const part of message.content) {
|
||||||
if (part.type === "text" && part.text) {
|
if (part.type === "text" && part.text) {
|
||||||
parts.push({ type: "text", 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") {
|
} else if (part.type === "tool-call") {
|
||||||
if (!paired.has(part.toolCallId)) continue;
|
if (!paired.has(part.toolCallId)) continue;
|
||||||
parts.push({
|
parts.push({
|
||||||
@ -202,7 +211,6 @@ export function transform(options: LanguageModelV3CallOptions, modelId: string):
|
|||||||
input: (part as { input?: unknown }).input ?? {},
|
input: (part as { input?: unknown }).input ?? {},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// reasoning parts are not replayed upstream
|
|
||||||
}
|
}
|
||||||
if (parts.length) messages.push({ role: "assistant", content: parts });
|
if (parts.length) messages.push({ role: "assistant", content: parts });
|
||||||
continue;
|
continue;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user