Uh oh!
There was an error while loading. Please reload this page.
fix(llm): accept reasoning field in OpenAI-compatible streams - #35284
fix(llm): accept reasoning field in OpenAI-compatible streams#35284mickaeldamatha wants to merge 1 commit into
reasoning field in OpenAI-compatible streams#35284Conversation
Some OpenAI-compatible providers (Scaleway, vLLM/SGLang, OpenRouter, ...) stream chain-of-thought under a `reasoning` delta field instead of the `reasoning_content` field emitted by DeepSeek et al. The OpenAI Chat protocol only declared and read `reasoning_content`, so `reasoning` was stripped by the delta schema and the model's thinking was silently dropped for those providers. Declare `reasoning` on the delta schema and fall back to it when `reasoning_content` is absent, preferring `reasoning_content` when both are present. Adds streaming tests for both cases.
Thanks for updating your PR! It now meets our contributing guidelines. 👍 |
Automated PR Cleanup Thank you for contributing to opencode. Due to the high volume of PRs from users and AI agents, we periodically close older PRs using automated criteria so maintainers can focus review time on the most active and community-supported contributions. This PR was closed because it matched the following cleanup criteria:
PRs created within the last month are not affected by this cleanup. If you believe this PR was closed incorrectly, or if you are still actively working on it, please leave a comment explaining why it should be reopened. A maintainer can review and reopen it if appropriate. Thanks again for taking the time to contribute. |
Issue for this PR
Closes#35283
Type of change
What does this PR do?
The OpenAI Chat streaming delta schema (
OpenAIChatDeltainpackages/llm/src/protocols/openai-chat.ts) only declaresreasoning_content. Each SSE chunk is decoded viaProtocol.jsonEvent→Schema.fromJsonString, which strips any key that isn't on the struct. So when a provider streams its thinking underreasoninginstead ofreasoning_content, that key is dropped during decoding andstep()never sees it — the reasoning is silently lost.I hit this with Scaleway; it also affects self-hosted vLLM / SGLang deployments and OpenRouter's
reasoningfield. The model was clearly reasoning but nothing surfaced in the stream. I had been working around it by rewritingreasoning→reasoning_contentin a proxy in front ofopencode serve; this makes that workaround unnecessary.Two changes:
reasoningtoOpenAIChatDeltaso the decoder keeps the fieldstep(), readdelta.reasoning_content ?? delta.reasoning, soreasoning_contentstill takes precedence when a provider sends bothIt's additive to the wire schema — providers that only send
reasoning_content(DeepSeek etc.) take the exact same path as before.How did you verify your code works?
Added two streaming tests in
packages/llm/test/provider/openai-chat.test.ts, next to the existingreasoning_contentone:reasoningemits the expected reasoning-start / reasoning-delta / reasoning-end eventsreasoning_contentwinsbun testinpackages/llm: 29 pass / 0 fail.bun typecheckclean (the pre-push hook also runs the full monorepo typecheck, 30/30).Screenshots / recordings
N/A — not a UI change.
Checklist