fix(desktop): keep the thinking signature when continuing a paused turn - #10616
Merged
Conversation
A pause_turn continuation resends the paused assistant turn to Anthropic, so the gateway rebuilds that turn from the streamed content blocks. The rebuilder handled text_delta, thinking_delta and input_json_delta but ignored signature_delta, so a thinking block came back unsigned and Anthropic rejects an unsigned thinking block. Typed chat is exactly where both halves meet: adaptive thinking is on for the first model call of a user turn, and web search is exposed whenever the client sends tools. A turn that paused mid web search therefore failed its own continuation and the user saw the partial answer end in "Upstream provider error" instead of the searched answer. Verified: cargo test 378 passed; the new regression test fails on the pre-fix accumulator (signature absent) and passes on the fix; cargo fmt --check and cargo clippy --all-targets -D warnings clean. Failure-Class: none
undivisible
approved these changes
Jul 26, 2026
undivisible
left a comment
Collaborator
There was a problem hiding this comment.
LGTM. Focused fix — one match arm + regression test. CI green. Approving and merging.
kodjima33
added a commit
that referenced
this pull request
Jul 26, 2026
…on (#10633) ## What changed and why A `pause_turn` continuation is completed **non-streaming** at the gateway (`complete_anthropic_server_tool_turn`) and its result is spliced back into the open SSE body. That splice forwarded only `response_text_content()`, which deliberately drops `tool_use` blocks — so a continuation that decided to call a **client-side** tool reached the desktop agent as `finish_reason: "tool_calls"` with no tool call attached. Nothing to run, no error: the action the answer promised silently never happened. This is the main path, not an edge. Web search is injected only when the client already sends its own tools: ```rust let inject_web_search = web_search_supported && !client_tools.is_empty(); ``` so **every turn that can pause is a turn where the model can also call a client tool**. "Search the web, then add it to my notes / create a task" is the ordinary shape of a paused turn. The non-streaming lane already does this correctly (`translate_response` maps `ToolUse` → `tool_calls`); only the streaming continuation splice was missing it. This is the second defect in the same splice after #10616 (dropped `signature_delta`), both introduced with #10537. ## The fix Extract `continuation_delta_chunks()` and emit the continuation's text **and** its `tool_use` blocks as OpenAI chunks. Tool ordinals continue from `next_tool_ordinal`, so a call streamed before the pause is never overwritten by a client accumulating tool calls by `index`. Anthropic's own `server_tool_use` / `web_search_tool_result` blocks stay gateway-side, unchanged. ## Product invariants affected - INV-CHAT-1 ## How it was verified - `cargo test` — **380 passed**, 0 failed. - Two new regression tests (`pause_turn_continuation_forwards_client_tool_calls`, `pause_turn_continuation_tool_ordinals_continue_from_the_streamed_prefix`) **fail on the pre-fix behaviour** (continuation forwards text only: `expected one text chunk and one tool call, left: 1 right: 2`) and pass on the fix. - `cargo fmt --check` clean; `cargo clippy --all-targets -- -D warnings` clean. - `make preflight` green. **Not verified against the live provider:** an Anthropic `pause_turn` cannot be forced on demand, so the fix is proven through the production translation seam rather than a real round trip. ## Follow-up noticed, not fixed here The same continuation branch bills only the continuation's usage (`merge_stream_usage(initial_usage, &anthropic_resp.usage)`), so the paused segment's `output_tokens` from its `message_delta` are dropped from `record_llm_usage`. Cost accounting only, no user impact — left out to keep this fix surgical. Failure-Class: none 🤖 automated by hourly watchdog — tested and merged <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/BasedHardware/omi/pull/10633?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. -->
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What changed and why
pause_turncontinuations rebuild the paused assistant turn from the streamedcontent blocks and resend it to Anthropic. The rebuilder (
StreamedContentBlocks,added with the web-search streaming change #10537) handled
text_delta,thinking_deltaandinput_json_deltabut ignoredsignature_delta, so athinking block was replayed unsigned — and Anthropic rejects an unsigned
thinking block.
Typed chat is exactly where both halves meet:
(
use_adaptive_thinking,request_translation.rs), andweb_searchtool is exposed whenever the client sends tools(
inject_web_search).So a typed-chat turn that paused mid web search failed its own continuation:
complete_anthropic_server_tool_turnreturnedErr, and the user saw thepartial answer end in
"Upstream provider error"instead of the searchedanswer — the failure mode #10537 exists to remove. The same file already
documents this class one function up: "a thinking-enabled request whose
assistant tool_use turn lacks its thinking block is rejected upstream."
The fix is the missing arm: capture
signature_deltainto the rebuilt block,via the same
append_string_fieldhelper the other text-bearing deltas use(signatures may arrive across several deltas, so it appends).
No changelog fragment: #10537 has not shipped in a release yet
(
desktop/macos/changelog/unreleased/20260725-web-search-streaming.jsonisstill unreleased), so this repairs an unreleased change rather than adding a
user-visible line of its own.
Product invariants affected
none
Failure class (fixes)
Failure-Class: none
How it was verified
cargo test— 378 passed, 0 failed.test_pause_turn_stream_accumulator_preserves_thinking_signaturefails onthe pre-fix accumulator (
git stashofstreaming.rsonly) with thesignature absent from the rebuilt block, and passes with the fix:
cargo fmt --checkclean;cargo clippy --all-targets -- -D warningsclean.make preflightgreen.Not reproduced end to end against live Anthropic:
pause_turnis upstream'schoice and cannot be forced, and
ANTHROPIC_API_URLis aconstwith no testseam, so the continuation request body was verified through the production
accumulator that builds it, not over the wire.
🤖 automated by hourly watchdog — tested and merged