fix(agent): add Thinking field to synthetic post-summary assistant message#1101
fix(agent): add Thinking field to synthetic post-summary assistant message#1101hoakhongmau98 wants to merge 2 commits intonextlevelbuilder:devfrom
Conversation
…ssage — fixes DeepSeek 400 reasoning_content error
When a session has been compacted, buildMessages injects a synthetic
[user summary, assistant ack] pair. The assistant ack had Content but no
Thinking field, so for thinking-mode providers (DeepSeek v4-pro, Kimi)
the wired request was missing reasoning_content on that turn — DeepSeek
rejects the entire request with HTTP 400 ("The `reasoning_content` in
the thinking mode must be passed back to the API.").
Fix: set a non-empty Thinking value on the synthetic assistant message.
The existing wire path in internal/providers/openai_request.go is
already model-gated via openAIWireAssistantReasoningContent (DeepSeek /
Kimi / OpenAI reasoning families); other providers ignore the field.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…epSeek reasoning_content Without this, finalizeRun reads rs.finalThinking which is always "", producing assistant messages with empty Thinking. DeepSeek/Kimi then reject iteration 2 with "reasoning_content in the thinking mode must be passed back to the API." Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Update: second fix appendedTrace Root cause of the residual failureThe synthetic post-summary fix only covers history rebuilt after compaction. For sub-agents on a fresh session, the failure is on iteration 2 itself: the assistant's real (LLM-generated) thinking from iteration 1 never reaches the next request because Flow:
Change in commit 2138a32One-line bridge in result := convertRunResult(pResult)
bridgeRS.finalThinking = result.Thinking
return result, nil
Verification
Manual reproduction on the cron-triggered sub-agent path is still pending. |
Summary
After session compaction, the agent loop injects a synthetic assistant ack ("I understand the context from our previous conversation. How can I help you?") into history but leaves
Thinkingempty. For thinking-mode providers (DeepSeek v4-pro, Kimi), the OpenAI-compat request builder requiresreasoning_contenton every assistant turn — the emptyThinkingcauses HTTP 400reasoning_content in the thinking mode must be passed back to the APIon the very next turn after compaction.This PR populates
Thinkingon the synthetic message so the existing wire logic ininternal/providers/openai_request.goemits a non-emptyreasoning_contentfor DeepSeek/Kimi families.Fixes #1047.
Root cause
internal/agent/loop_history.go:249-259appends a synthetic assistant turn after the user's post-summary "Got it." message. Before this change, onlyRoleandContentwere set.internal/providers/openai_request.go:59-61writesreasoning_content = m.Thinkingfor assistant turns whenopenAIWireAssistantReasoningContent(model)returns true (DeepSeek, Kimi, OpenAI reasoning families). WithThinking == "", the field is omitted and DeepSeek rejects the request.Change
Single 4-line diff in
internal/agent/loop_history.go— add aThinkingvalue to the synthetic message. No provider-layer changes, no new helpers, no new tests required (existinginternal/providers/openai_test.go:336already asserts that DeepSeek wiresreasoning_contentfromThinking).Relation to #1063
PR #1063 (
nagaame:dev) addresses a related-but-distinct symptom: it ensuresreasoning_contentis preserved across tool-call turns and injects an empty string for DeepSeek whenThinkingis absent in the provider layer. The two fixes are complementary:Either PR alone leaves the other failure mode open. They can be merged independently; this one is the minimal, scoped fix for the post-summary case.
Test plan
go build ./...passesgo build -tags sqliteonly ./...passesgo vet ./...passesgo test -race ./internal/providers/...passes (existing DeepSeek wire test covers the path)