Skip to content

fix(desktop): keep the thinking signature when continuing a paused turn - #10616

Merged
undivisible merged 1 commit into
mainfrom
watchdog/pause-turn-thinking-signature
Jul 26, 2026
Merged

fix(desktop): keep the thinking signature when continuing a paused turn#10616
undivisible merged 1 commit into
mainfrom
watchdog/pause-turn-thinking-signature

Conversation

@kodjima33

@kodjima33 kodjima33 commented Jul 26, 2026

Copy link
Copy Markdown
Collaborator

What changed and why

pause_turn continuations rebuild the paused assistant turn from the streamed
content blocks and resend it to Anthropic. The rebuilder (StreamedContentBlocks,
added with the web-search streaming change #10537) handled text_delta,
thinking_delta and input_json_delta but ignored signature_delta, so a
thinking block was replayed unsigned — and Anthropic rejects an unsigned
thinking block.

Typed chat is exactly where both halves meet:

  • adaptive thinking is enabled for the first model call of a user turn
    (use_adaptive_thinking, request_translation.rs), and
  • the server-side web_search tool 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_turn returned Err, and the user saw the
partial answer end in "Upstream provider error" instead of the searched
answer — 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_delta into the rebuilt block,
via the same append_string_field helper 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.json is
still 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 test378 passed, 0 failed.
  • The new regression test
    test_pause_turn_stream_accumulator_preserves_thinking_signature fails on
    the pre-fix accumulator
    (git stash of streaming.rs only) with the
    signature absent from the rebuilt block, and passes with the fix:
    left:  [{"thinking":"Check the web","type":"thinking"}, ...]
    right: [{"signature":"ErrUhd8=","thinking":"Check the web","type":"thinking"}, ...]
    
  • cargo fmt --check clean; cargo clippy --all-targets -- -D warnings clean.
  • make preflight green.

Not reproduced end to end against live Anthropic: pause_turn is upstream's
choice and cannot be forced, and ANTHROPIC_API_URL is a const with no test
seam, 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

Review in cubic

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 undivisible left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Focused fix — one match arm + regression test. CI green. Approving and merging.

@undivisible
undivisible merged commit d16b7b4 into main Jul 26, 2026
32 of 35 checks passed
@undivisible
undivisible deleted the watchdog/pause-turn-thinking-signature branch July 26, 2026 01:31
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. -->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants