Skip to content

fix(responses): keep DeepSeek reasoning_content on tool-call continuations (#950) - #971

Merged
Wibias merged 3 commits into
lidge-jun:devfrom
Wibias:codex/950-reasoning-replay
Aug 4, 2026
Merged

fix(responses): keep DeepSeek reasoning_content on tool-call continuations (#950)#971
Wibias merged 3 commits into
lidge-jun:devfrom
Wibias:codex/950-reasoning-replay

Conversation

@Wibias

@Wibias Wibias commented Aug 3, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Keeps DeepSeek thinking-mode reasoning_content on tool-call continuations so opencode-go/deepseek-v4-flash (and other preserveReasoningContentModels providers) no longer receives bare assistant tool_calls after history transformations — fixing the intermittent upstream HTTP 400 from issue [Bug] OpenCode Go DeepSeek V4 Flash intermittently drops reasoning_content on tool-call continuation #950.
  • The bridge now records raw reasoning per tool-call id in a small in-process, bounded cache (64 entries / 256 KiB / 1h TTL, never logged); the openai-chat adapter re-attaches it when a tool-call turn serializes without thinking parts (compacted history, lost assistant turn, orphan-repaired tool results).
  • Two more drop paths are closed: the parser attaches a reasoning item that arrives after its function_call to the owning assistant turn instead of discarding it, and the image bridge preserves reasoning_raw_delta in replayed assistant turns (mirroring the web-search loop from DeepSeek V4 web-search continuation drops reasoning_raw_delta and returns 502 #688).

Validation

  • bun run typecheck — pass
  • bun test tests/deepseek-reasoning-replay-gaps.test.ts tests/bridge-raw-reasoning-hidden.test.ts tests/images/loop-reasoning-replay.test.ts tests/images/loop.test.ts tests/web-search.test.ts tests/opencode-go-deepseek.test.ts tests/deepseek-reasoning-replay.test.ts — pass (93 tests)
  • bun run privacy:scan — pass
  • bun run test — not completed locally (Windows runner: suite exceeded ~50 min and the background worker was terminated; CI runs the full matrix on this head)

Review notes

  • The replay cache is scoped by conversation (_clientThreadId) plus call id so colliding provider ids (call_1) cannot leak reasoning across threads; entries are bounded (64 / 256 KiB / 1h TTL), swept on insert, and never logged or serialized. Parallel tool calls share one recorded reasoning block and are deduplicated at serialization. Adjacent but distinct: open PR fix(kiro): round-trip the redactedContent reasoning blob #948 covers Kiro's redacted reasoning blob, not this wire.
  • Regression tests were red against the pre-fix code for all four drop paths.

Limitations

  • The cache cannot recover reasoning for a call the proxy never saw in this process (e.g., after a proxy restart mid-round); the client-side reasoning item remains the primary replay source, and the cache is the fallback for history transformations.

Fixes #950

Summary by CodeRabbit

  • Bug Fixes
    • Preserved assistant reasoning when replaying tool calls across streaming and non-streaming responses.
    • Prevented reasoning from being incorrectly reused between separate conversations.
    • Improved recovery of reasoning for reordered, retried, orphaned, and parallel tool calls.
    • Preserved reasoning before image-generation tool calls.
    • Added safeguards for expired, oversized, or invalid cached reasoning.

@github-actions github-actions Bot added the bug Something isn't working label Aug 3, 2026
@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: b2c8f8f1-88d4-4174-8942-d095aea3685b

📥 Commits

Reviewing files that changed from the base of the PR and between f31b5fb and 0b73d5a.

📒 Files selected for processing (2)
  • src/bridge.ts
  • tests/bridge-raw-reasoning-hidden.test.ts

📝 Walkthrough

Walkthrough

Changes

Reasoning replay preservation

Layer / File(s) Summary
Replay cache contract
src/responses/reasoning-replay-cache.ts, tests/deepseek-reasoning-replay-gaps.test.ts
Adds scoped, bounded, TTL-based reasoning storage keyed by tool-call ID. Tests cover isolation, expiration, eviction, and invalid entries.
Reasoning capture and tool-call association
src/bridge.ts, src/responses/parser.ts
Streaming and batch paths retain raw reasoning until tool-call start. Parser paths attach pending reasoning to the owning assistant tool call.
Chat message reasoning replay
src/adapters/openai-chat.ts, tests/deepseek-reasoning-replay-gaps.test.ts
Restores cached reasoning for reasoning-preserving assistant tool calls and synthesized orphan tool-result messages. Parallel-call reasoning is deduplicated.
Bridge wiring and image replay
src/server/responses/core.ts, src/web-search/loop.ts, src/images/loop.ts, tests/images/loop-reasoning-replay.test.ts
Passes client-thread scopes to response bridges and preserves raw image-loop reasoning as thinking content before replayed tool calls.
Regression coverage
tests/bridge-raw-reasoning-hidden.test.ts, tests/deepseek-reasoning-replay-gaps.test.ts
Covers hidden reasoning capture, history transformations, retries, orphan repair, cache behavior, and opaque reasoning handling.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ModelStream
  participant ResponsesBridge
  participant ReplayCache
  participant OpenAIChatAdapter
  ModelStream->>ResponsesBridge: emit raw reasoning and tool call
  ResponsesBridge->>ReplayCache: rememberReasoningForCall(callId, reasoning, scope)
  OpenAIChatAdapter->>ReplayCache: peekReasoningForCall(callId, scope)
  ReplayCache-->>OpenAIChatAdapter: cached reasoning
  OpenAIChatAdapter-->>ModelStream: assistant tool call with reasoning_content
Loading

Possibly related PRs

Suggested reviewers: ingwannu, lidge-jun

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 12.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the DeepSeek reasoning replay fix for tool-call continuations, which is the pull request's primary change.
Linked Issues check ✅ Passed The changes preserve and replay provider reasoning across tool continuations, retries, compaction, synthetic paths, and repaired histories, with regression coverage for issue #950.
Out of Scope Changes check ✅ Passed The implementation and tests remain focused on reasoning replay, cache scoping, parser handling, bridge integration, and regression coverage for issue #950.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/adapters/openai-chat.ts`:
- Around line 405-412: Update the orphan repair block around
peekReasoningForCall to emit reasoning_content only when
modelInList(provider.preserveReasoningContentModels, parsed.modelId) is true,
matching the normal assistant path. Keep the cached reasoning lookup and
assistant tool-call replay unchanged otherwise.

In `@src/responses/reasoning-replay-cache.ts`:
- Around line 66-75: The reasoning cache currently expires entries only during
peek operations and uses an exclusive TTL boundary. Update the cache state
around rememberReasoningForCall to schedule cleanup for the next entry expiry,
have the timer delete every due entry and reschedule for the next expiry, and
clear that timer in clearReasoningReplayCacheForTests. Change the expiration
check in peekReasoningForCall to use >= TTL_MS while preserving totalBytes
accounting.

In `@tests/deepseek-reasoning-replay-gaps.test.ts`:
- Around line 124-133: Update the test covering orphan tool-result repair,
identified by “GAP C: orphan tool result (lost assistant turn) is repaired WITH
the recorded reasoning,” to call wireFor() twice without clearing the remembered
reasoning cache. Extract the synthesized assistant message from each result and
assert both contain REASONING in reasoning_content, preserving the existing call
ID and input setup.
- Around line 177-194: Strengthen the tests around rememberReasoningForCall:
verify the exact MAX_ENTRIES boundary by asserting call_5 is retained and call_6
is evicted after inserting 70 entries, and add several individually valid
reasoning entries whose combined size exceeds the 256 KiB aggregate limit,
asserting the oldest entries are evicted while newer entries remain available.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 221f3a65-7ca2-4c65-b4d3-85a864a2b65b

📥 Commits

Reviewing files that changed from the base of the PR and between a088e4b and e2d9421.

📒 Files selected for processing (8)
  • src/adapters/openai-chat.ts
  • src/bridge.ts
  • src/images/loop.ts
  • src/responses/parser.ts
  • src/responses/reasoning-replay-cache.ts
  • tests/bridge-raw-reasoning-hidden.test.ts
  • tests/deepseek-reasoning-replay-gaps.test.ts
  • tests/images/loop-reasoning-replay.test.ts

Comment thread src/adapters/openai-chat.ts
Comment thread src/responses/reasoning-replay-cache.ts Outdated
Comment thread tests/deepseek-reasoning-replay-gaps.test.ts
Comment thread tests/deepseek-reasoning-replay-gaps.test.ts

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e2d9421882

ℹ️ 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".

Comment thread src/responses/reasoning-replay-cache.ts
Comment thread src/adapters/openai-chat.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/bridge.ts (1)

854-854: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Clear pending raw reasoning in the hidden-thinking path.

When hideThinkingSummary is true, the branch at Lines 842-849 returns before Line 854 clears rawReasoningForNextToolCall. A prior reasoning_raw_delta can then be stored for a later tool call even though a thinking_delta occurred between them.

Flush the hidden raw item, then clear the pending cache candidate before appending hidden thinking. Add a regression case in tests/bridge-raw-reasoning-hidden.test.ts for reasoning_raw_delta → thinking_delta → tool_call_start.

Proposed fix
 case "thinking_delta": {
   if (options?.hideThinkingSummary) {
+    flushHiddenRawReasoning();
+    rawReasoningForNextToolCall = "";
     ({ value: hiddenThinkingText, bytes: hiddenThinkingBytes } = appendString(
       hiddenThinkingText,
       hiddenThinkingBytes,

As per path instructions, “A behavior change in src/ should come with a focused regression test near the existing tests for that subsystem.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/bridge.ts` at line 854, Update the hidden-thinking branch in the bridge
flow around rawReasoningForNextToolCall so it flushes any pending hidden raw
item and clears the pending raw-reasoning cache before appending hidden
thinking, including when the branch returns early. Add a focused regression case
in the existing hidden raw-reasoning tests covering reasoning_raw_delta →
thinking_delta → tool_call_start and verify the stale raw reasoning is not
attached to the later tool call.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/bridge.ts`:
- Line 854: Update the hidden-thinking branch in the bridge flow around
rawReasoningForNextToolCall so it flushes any pending hidden raw item and clears
the pending raw-reasoning cache before appending hidden thinking, including when
the branch returns early. Add a focused regression case in the existing hidden
raw-reasoning tests covering reasoning_raw_delta → thinking_delta →
tool_call_start and verify the stale raw reasoning is not attached to the later
tool call.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 8974f182-b8d3-48c5-8995-e5b7a681e52a

📥 Commits

Reviewing files that changed from the base of the PR and between e2d9421 and f31b5fb.

📒 Files selected for processing (7)
  • src/adapters/openai-chat.ts
  • src/bridge.ts
  • src/images/loop.ts
  • src/responses/reasoning-replay-cache.ts
  • src/server/responses/core.ts
  • src/web-search/loop.ts
  • tests/deepseek-reasoning-replay-gaps.test.ts

@Wibias

Wibias commented Aug 3, 2026

Copy link
Copy Markdown
Collaborator Author

[GD] Addressed feedback

feedbacks:

  • review_comment:3708335892
  • review_comment:3708335900
  • review_comment:3708335905
  • review_comment:3708335909
  • review_comment:3708343304
  • review_comment:3708343307
  • coderabbitai review (outside-diff thinking_delta finding, cr-comment:v1:2870ade7ed7705fcb7211de9)

commit: 0b73d5a

@Wibias

Wibias commented Aug 3, 2026

Copy link
Copy Markdown
Collaborator Author

[GD] Merge ready

PR: #971 — fix(responses): keep DeepSeek reasoning_content on tool-call continuations (#950)
Head: 0b73d5a → dev (mergeStateStatus: CLEAN)
Linked issues: #950

Reviews

  • Humans (owners first): none open
  • Bots: 0 unresolved threads. CodeRabbit: 3 review rounds — every finding fixed in 31b5fb8 (orphan replay gate, TTL sweep + >= boundary, exact cache-cap/byte-cap tests, retry coverage) and �b73d5af (hidden hinking_delta clears pending raw reasoning); Codex connector P1 (conversation scope) and P2 (preserve-model gate) fixed and threads resolved
  • Own bug + security + spec/standards: bug-review complementary pass (silent failures / leaks / edge cases) + security-review matrix + spec/standards pass done on tip; blockers fixed: parallel-call reasoning dedupe, orphan
    easoning_content gating, conversation-scoped replay cache, TTL sweep

Tip freshness

  • Updated from dev: already current (�088e4b1c base)
  • Compiles/tests against tip: �un run typecheck — pass; focused suites (96 tests) — pass
  • Conflicts: none

Checks

  • Local/CLI: �un run typecheck, focused parser/bridge/adapter/image-loop/web-search suites, �un run privacy:scan — green
  • Required CI: green on 0b73d5a ( est 1/4-4/4, macos,
    pm-global windows-latest, gates, label, �nforce-target, changes,
    eact-doctor)
  • Policy: no enforced CODEOWNERS/approval/merge-queue blockers

Residual

  • Full local �un run test not completed on this machine (Windows runner too slow); CI's full test matrix covers it — green.

Ready to merge.

@Wibias

Wibias commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator Author

[GD] Merging

Why it helps: this restores DeepSeek thinking-mode reasoning_content on tool-call continuations, so opencode-go/deepseek-v4-flash sessions stop hitting the intermittent upstream 400 from issue #950. The parser, bridge, and image-loop fixes keep the replay invariant across compaction, resume, orphan repairs, and synthetic loops, and the replay cache is conversation-scoped, bounded, and never logged.

@Wibias
Wibias merged commit 55fdde9 into lidge-jun:dev Aug 4, 2026
22 checks passed
@Wibias
Wibias deleted the codex/950-reasoning-replay branch August 4, 2026 00:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant