Skip to content

fix(kit): keep reasoning_content so thinking providers accept the history - #16

Closed
Hannd15 wants to merge 1 commit into
Coneja-Chibi:Mainstagefrom
Hannd15:fix/kit-reasoning-echo
Closed

Hannd15 wants to merge 1 commit into
Coneja-Chibi:Mainstagefrom
Hannd15:fix/kit-reasoning-echo

Conversation

@Hannd15

@Hannd15 Hannd15 commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

Fixes an error from OpenAI-compatible thinking providers (seen on Console Go, a DeepSeek-style gateway):

Error from provider (Console Go): Upstream request failed: [invalid_request_error] The reasoning_content in the thinking mode must be passed back to the API.

Root cause. A reasoning model answers with reasoning_content beside its content. DeepSeek-style backends REQUIRE that thinking to come back verbatim in the assistant turns of the next request; drop it and the API rejects the history. Kit streamed the thinking live (reasoning deltas) and then threw it away, and even where it was kept, the session store's message whitelist dropped it again on resume.

Fix. The thought now rides the wire end to end, gated by a per-spoke declaration:

  • ModelReply and the stored ModelMessage keep the reply's reasoning text; loop-core records it on the assistant message, and the session store's parseMessage carries it across resume.
  • chat.ts maps it onto the SDK message as a reasoning part (the openai-compatible adapter serializes that as reasoning_content) and reads it back from the response.
  • Only spokes whose adapter carries the part back declare reasoningEcho: custom, deepseek, local, nanogpt, openrouter, groq. Anthropic and Google need signature metadata a replica cannot reconstruct, openai chat-completions drops the part, and mistral would fold the thought into the visible text, so those stay silent by default (absent means no, matching the images capability pattern).

Proof. Red-first: a reply's reasoning records into history (say and use), the wire mapper attaches the part only when the spoke declared the echo, and the declared set is pinned by a capability test mirroring images.test.ts. verify:ci is green; the only failures across many runs are the pre-existing Claude plan usage live tests hitting Anthropic 429s. (One under-load run also showed a single not.toContain failure that never reproduced across ten subsequent full-suite runs; the changed code is deterministic and was re-run 10x clean.)

AI model: DeepSeek V4 Flash (via pi, a coding agent).

…tory [skip release]

Console Go (a DeepSeek-style gateway) rejected the next request with "The
reasoning_content in the thinking mode must be passed back to the API":
the assistant turns in the conversation carry the thinking a reasoning model
produced, and the API demands it comes back verbatim. Kit streamed the
reasoning live and then threw it away, and a resumed session lost it again
at the store's message whitelist.

The thought now rides the wire end to end, gated by a per-spoke declaration:
- ModelReply and the stored ModelMessage keep the reply's reasoning text.
- loop-core records it on the assistant message it introduces, and the
  session store's parseMessage carries it across resume.
- chat.ts maps it onto the SDK message as a reasoning part (the openai-
  compatible adapter serializes that as reasoning_content) and reads it
  back from the response; toReply and toAiMessage are exported and unit
  tested.
- Only the spokes whose adapters carry the part back declare it
  (reasoningEcho: custom, deepseek, local, nanogpt, openrouter, groq).
  Anthropic and Google need signature metadata they cannot reconstruct,
  openai chat-completions drops the part, and mistral would fold it into
  the visible text, so those stay silent by default.

Tests: loop records reasoning for say and use replies, wire mapping for
echoing and non-echoing spokes, and the declared set (mirrors the images
capability test). verify:ci green; only the pre-existing Claude plan usage
live tests hit their known Anthropic 429s.

AI model: DeepSeek V4 Flash (via pi, a coding agent).

@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: 2fafaaea84

ℹ️ 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/kit/loop/loop-core.ts
role: "assistant",
content: reply.text,
toolCalls: reply.calls,
reasoning: reply.reasoning,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve reasoning through the agent-window history

When using the Studio agent window with a reasoningEcho provider, recording reply.reasoning here is not enough: the browser posts follow-up history as only {role, content} in src/ui/agent/use-agent-chat.ts:190-198, and the server immediately rebuilds history the same way in src/ui/agent/turn-stream.ts:194-195. That means the next turn from the window still drops every prior assistant reasoning field before toAiMessage can emit reasoning_content, so DeepSeek-style backends continue to reject the follow-up/tool-loop history in the exact path this change is meant to fix.

Useful? React with 👍 / 👎.

@Hannd15 Hannd15 closed this Aug 24, 2026
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