Skip to content

feat(llm): capture Claude/OpenAI reasoning as a real subtype:reasoning channel (ENG-1109) - #280

Open
torrmal wants to merge 1 commit into
stagingfrom
feat/eng-1109-reasoning-subtype-channel
Open

feat(llm): capture Claude/OpenAI reasoning as a real subtype:reasoning channel (ENG-1109)#280
torrmal wants to merge 1 commit into
stagingfrom
feat/eng-1109-reasoning-subtype-channel

Conversation

@torrmal

@torrmal torrmal commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Summary

Anton has no way to distinguish the model's own reasoning/narration ("let me verify that first...", "confirmed — this changed, let me re-check...") from the final answer — both travel through StreamTextDelta, the only text-carrying event that exists. That means narration gets permanently baked into the persisted answer instead of showing as a live, disappearing "current thought." The frontend already supports this correctly (built for cowork's ENG-1108) via a thought.progress + subtype: reasoning|thinking event — the Hermes harness already emits it; Anton just never did.

Confirmed via direct code reading, not assumption:

  • StreamTextDelta is the only text event in provider.py's StreamEvent union.
  • Anthropic's extended-thinking content blocks were never parsed (misclassified as plain text, or thinking_delta/signature_delta silently dropped).
  • OpenAI's Responses-API reasoning-summary events were never requested (reasoning.summary was missing from the request kwargs) and never handled in the streaming loop.
  • The MindsHub-managed gateway path (mdb.ai/mindshub.ai) proxies even Claude calls over an OpenAI chat.completions-shaped envelope (FLAVOR_MINDS_PASSTHROUGH), not the Responses API — so a correct fix needs 3 code paths, not just "enable Claude thinking."

Changes

  • provider.py: new StreamReasoningDelta event, added to StreamEvent.
  • anthropic.py: output_config.effort (verified real — Claude 4.6/4.7's adaptive-thinking parameter, already sent whenever reasoning_effort is configured for a compatible model) already triggers server-side thinking; the gap was purely on the parsing side. Added content_block_start handling for thinking/redacted_thinking block types, and content_block_delta handling for thinking_deltaStreamReasoningDelta. signature_delta is intentionally a no-op (cryptographic verification signature, not user-facing text). No new request parameter — reuses the existing, safely-gated reasoning_effort config.
  • openai.py (Responses API / FLAVOR_OPENAI): added "summary": "auto" alongside the existing effort in the reasoning kwarg (only sent when reasoning_effort is already configured, same gating as today), plus a new response.reasoning_summary_text.delta branch.
  • openai.py (chat.completions / FLAVOR_MINDS_PASSTHROUGH + FLAVOR_OPENAI_COMPATIBLE_GENERIC): reads delta.reasoning_content defensively via getattr — the de facto convention several OpenAI-compatible reasoning gateways (DeepSeek, vLLM's reasoning parser) use. This one is best-effort — flagging clearly for review since I couldn't verify the actual mdb.ai gateway's wire format from this repo alone.
  • session.py: the synthetic "Thinking..." timer (reasoning_start/reasoning_done) now also closes on the first StreamReasoningDelta, matching its existing behavior for text/complete.

Companion PR in cowork-server (maps StreamReasoningDelta → the wire subtype: reasoning event, matching hermes_harness's existing shape exactly): mindsdb/cowork-server#241 (opening next).

No frontend changes needed — cowork's ENG-1108 already built the client-side handling for this exact subtype: reasoning|thinking shape.

Linear: https://linear.app/mindsdb/issue/ENG-1109/anton-capture-claudeopenai-reasoning-as-a-real-subtypereasoning

Test plan

  • New tests: TestAnthropicProviderReasoningStream (thinking_delta → StreamReasoningDelta, signature_delta ignored, effort passed via extra_body), TestResponsesAPIReasoningSummary (kwargs gating, reasoning_summary_text.delta mapping), TestChatCompletionsReasoningContent (reasoning_content read + graceful absence)
  • Full suite: 1377 passed, 17 skipped — 2 pre-existing failures in test_chat_scratchpad.py (subprocess sandbox execution issue), confirmed identical on unmodified staging via git stash, unrelated to this change
  • ast.parse sanity check on all 4 edited modules; ruff check clean (one pre-existing, unrelated E741 elsewhere in session.py)

🤖 Generated with Claude Code

…g channel (ENG-1109)

Anton had no way to distinguish the model's own reasoning/narration
("let me verify that first...") from the final answer -- both traveled
through StreamTextDelta, so narration got permanently baked into the
persisted answer instead of showing as a live, disappearing "current
thought" (which the frontend already supports via Hermes's existing
thought.progress + subtype channel -- Anton just never emitted it).

- provider.py: new StreamReasoningDelta event, added to StreamEvent.
- anthropic.py: output_config.effort already triggers server-side
  adaptive thinking when reasoning_effort is configured; the missing
  piece was parsing the resulting `thinking` content blocks instead of
  misclassifying them as text / silently dropping thinking_delta.
  signature_delta is intentionally ignored (a verification signature,
  not text).
- openai.py (Responses API / FLAVOR_OPENAI): request reasoning.summary
  alongside the existing effort, handle
  response.reasoning_summary_text.delta.
- openai.py (chat.completions / FLAVOR_MINDS_PASSTHROUGH +
  FLAVOR_OPENAI_COMPATIBLE_GENERIC -- the path the MindsHub-managed
  gateway actually uses even for Claude models): read delta.reasoning_content
  defensively, the de facto convention several OpenAI-compatible
  reasoning gateways use. Best-effort -- needs verification against the
  real mdb.ai gateway wire format.
- session.py: the synthetic "Thinking..." timer now also closes on the
  first StreamReasoningDelta, matching its existing text/complete
  behavior.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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