Skip to content

fix: thinking-block continuity on non-Anthropic backends - #24

Open
BenSheridanEdwards wants to merge 2 commits into
aattaran:mainfrom
BenSheridanEdwards:fix/thinking-blocks-continuity
Open

fix: thinking-block continuity on non-Anthropic backends#24
BenSheridanEdwards wants to merge 2 commits into
aattaran:mainfrom
BenSheridanEdwards:fix/thinking-blocks-continuity

Conversation

@BenSheridanEdwards

Copy link
Copy Markdown

DeepSeek's anthropic-compat endpoint returns 400 mid-conversation:

API Error: 400 The `content[].thinking` in the thinking mode must be passed back to the API.

…once Claude Code emits a thinking block in any assistant turn. The proxy was both stripping all thinking blocks from messages AND leaving the top-level thinking: { type: "enabled", ... } field in the request body, creating a contradictory state DeepSeek rejects.

Two fixes in this PR:

1. Drop top-level thinking and context_management on non-Anthropic routes. Backends like DeepSeek don't honor Anthropic's extended-thinking spec consistently, and a stale config field is a noisier error than no config at all.

2. Stop stripping thinking blocks on isModelCall. The original strip from commit 70518b6 was added to clean up foreign-backend blocks on backend switches — but it's too broad for pure-DeepSeek sessions, where DeepSeek's own prior thinking blocks are valid and required for continuity. Removing the strip unblocks the conversation.

Backend-switch case (e.g. DeepSeek session → Anthropic) is still handled by the Anthropic-side strip (hadNonAnthropicSession ? stripAllThinkingBlocks : stripUnsignedThinkingBlocks), which doesn't change in this PR.

Test plan

  • Plain conversation with deepclaude (default mode, DeepSeek): multiple turns including tool use, no 400s.
  • Same conversation that previously hit the 400 reliably (after Claude Code emitted a thinking block) now goes through.
  • Proxy log shows status 200 across the board.

Notes

This bug also affects feat/cost-statusline (#23) and any branch that descends from main while keeping the original strip. Both fixes are also present on #23 by virtue of being committed there first; when one PR merges, the other's rebase will recognise the duplicate patches and drop them.

BenSheridanEdwards and others added 2 commits May 7, 2026 15:07
DeepSeek's anthropic-compat endpoint 400s with:

  The \`content[].thinking\` in the thinking mode must be passed back to
  the API.

…when the request body has \`thinking: { type: \"enabled\", ... }\` at
the top level but the messages don't carry thinking content blocks.

Background: foreign-backend thinking blocks are invalid against
Anthropic's signing key, so the proxy strips them from messages on
isModelCall. But it left the top-level \`thinking\` config in place,
creating the contradictory state DeepSeek rejects.

Fix: drop both \`thinking\` and \`context_management\` for isModelCall
routes (mirrors what the image-fallback path on PR aattaran#21 already does on
forceAnthropicForImage). Backends like DeepSeek don't honor Anthropic's
extended-thinking config anyway, so dropping it costs nothing and
fixes the 400.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…inuity

Previous attempt dropped only the top-level \`thinking\` config; the
400 still fires because DeepSeek's check is on \`content[].thinking\`
inside messages — it expects its own prior thinking blocks to be
passed back verbatim for conversation continuity.

The original strip was added to clean up foreign-backend blocks on
backend switches (commit 70518b6), but it also removes DeepSeek's own
blocks in pure-DeepSeek sessions, breaking continuity. For now: leave
thinking blocks in place on isModelCall so DeepSeek can see its own
history. We continue to drop the top-level thinking config since
non-Anthropic backends don't honor Anthropic's extended-thinking spec
consistently.

Backend-switch case (DeepSeek session → Anthropic) is still handled
by the Anthropic-side strip (\`hadNonAnthropicSession ?
stripAllThinkingBlocks : stripUnsignedThinkingBlocks\`), which
shouldn't regress.

If a future user reports a foreign-block 400 going INTO DeepSeek
(e.g. switching mid-session from openrouter to deepseek), we'll need a
finer-grained strip that distinguishes block origin.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
g-roliveira added a commit to g-roliveira/deepclaude that referenced this pull request Aug 6, 2026
…parity

Summary of fixes and features applied from aattaran/deepclaude
issues and PRs, plus original improvements:

Model coverage (Issue aattaran#39):
- Add claude-fable-5, claude-opus-5, claude-sonnet-5 to MODEL_REMAP
- Tier-based _default fallback (fable/opus/sonnet/haiku) so new
  Claude models degrade predictably instead of silently routing
  to the wrong backend
- Warning log when model is forwarded unmapped

Proxy resilience (PR aattaran#18):
- proxyRes error handler — upstream TCP reset mid-response no
  longer crashes the entire proxy process
- proxyReq error after headers-sent: destroy response instead of
  injecting JSON into the SSE stream (which corrupted the parser)

Thinking-block continuity (PR aattaran#24):
- Drop top-level thinking/context_management on non-Anthropic
  routes instead of stripping all thinking blocks from history
- Fixes DeepSeek 400: "content[].thinking must be passed back"

Proxy in normal mode (PR aattaran#9):
- Default launch now starts the proxy; cost tracking and live
  /switch work in all sessions, not just --remote
- Use ANTHROPIC_API_KEY instead of ANTHROPIC_AUTH_TOKEN so
  subscription OAuth tokens are handled correctly

Body size limit (PR aattaran#7):
- 50 MB cap on /v1/messages request body, returns 413

Forward unknown args to claude (Issue aattaran#25):
- Bash: -- separator support for explicit passthrough
- PowerShell: ValueFromRemainingArguments captures unknown flags

PowerShell parity (PR aattaran#5, aattaran#8):
- --switch/-s parameter with backend name normalization
- try/finally ensures proxy cleanup on Ctrl+C/crash

Model [1m] suffix:
- All model names include [1m] token window hint so Claude Code
  doesn't assume 200k context and auto-compact prematurely

Project documentation:
- CLAUDE.md with architecture overview, proxy routing, SSE
  normalization, model remapping, and development conventions
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.

1 participant