fix(kosong): preserve unsigned thinking in anthropic history serialization - #222
Conversation
…ation When converting assistant history to the Anthropic wire format, convertMessage() dropped any thinking block that had no signature. That was meant to satisfy api.anthropic.com (which requires a valid signature on thinking blocks), but it broke Anthropic-compatible backends. Kimi's Anthropic-protocol endpoint streams thinking without a signature_delta, yet requires the thinking to be present on a tool-call turn — once it was dropped, the next request failed with "thinking is enabled but reasoning_content is missing in assistant tool call message", making multi-step tool use unusable on those backends. Preserve unsigned thinking instead, emitting it without a `signature` field. The two backends are partitioned by signature presence: api.anthropic.com always supplies a signature (its history takes the signed branch unchanged), while Kimi never does (its thinking is now kept). Empty-and-unsigned parts carry nothing and are still skipped.
🦋 Changeset detectedLatest commit: f50169b The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
commit: |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f50169b367
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| } else if (part.think !== '') { | ||
| blocks.push({ type: 'thinking', thinking: part.think } as unknown as ThinkingBlockParam); |
There was a problem hiding this comment.
Gate unsigned thinking to compatible backends
When this provider targets api.anthropic.com and the caller supplies history containing an unsigned ThinkPart (for example after restoring or switching from an Anthropic-compatible backend that does not emit signature_delta), this now serializes a thinking block with no signature. Anthropic's extended-thinking flow returns a signature/signature_delta and requires the complete original thinking block to be passed back unchanged (https://platform.claude.com/docs/en/build-with-claude/extended-thinking), so these requests can be rejected where the old code skipped the unsigned block. Please keep the unsigned preservation behind a backend/capability check or otherwise avoid sending unsigned thinking to the official Anthropic API.
Useful? React with 👍 / 👎.
Problem
Multi-step tool use was broken against Anthropic-compatible backends (e.g. Kimi's
Anthropic-protocol endpoint). After the first tool call, the follow-up request failed with:
The Anthropic provider's
convertMessage()dropped any assistant thinking block that hadno signature. That guard was meant for api.anthropic.com (which rejects thinking blocks with
an invalid/empty signature), but it also discarded the thinking streamed by backends that
don't emit a
signature_delta. Those backends require the thinking to remain present on atool-call turn, so dropping it made every multi-step turn fail.
What changed
convertMessage()now preserves unsigned thinking instead of dropping it, emitting itwithout a
signaturefield. The two backends are cleanly partitioned by signature presence,so no backend switch is needed:
fixing the "reasoning_content is missing" failure.
Empty-and-unsigned parts carry no information and are still skipped. The streaming merge
logic (
mergeInPlace) was checked and is correct, so it is left untouched -- this is purelya history-serialization fix.
Verified against both real endpoints: the Kimi Anthropic endpoint now accepts the multi-step
request, while api.anthropic.com behaves exactly as before (it requires a valid signature,
which it always provides). Added regression tests covering unsigned thinking both standalone
and immediately before a
tool_useblock.