Skip to content

fix(runtime): surface OpenAI and Claude reasoning summaries - #2645

Open
hqhq1025 wants to merge 1 commit into
apache:mainfrom
hqhq1025:codex/reasoning-summary-display
Open

fix(runtime): surface OpenAI and Claude reasoning summaries#2645
hqhq1025 wants to merge 1 commit into
apache:mainfrom
hqhq1025:codex/reasoning-summary-display

Conversation

@hqhq1025

@hqhq1025 hqhq1025 commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Closes #2644

Default policy

Reasoning-capable GPT and Claude models request user-visible thinking by default. Thinking is disabled only when the user explicitly selects off. Routing follows the resolved wire and recognized model family, not the provider label.

OpenAI / GPT

  • Responses routes request reasoning: { effort: "medium", summary: "auto" } for recognized GPT reasoning models
  • covers direct OpenAI, Codex subscription, custom Responses relays, and third-party native Responses overrides
  • OpenAI Chat-compatible relays request the documented default reasoning_effort: "medium" and keep using the existing reasoning_content response transport
  • explicit off sends effort: "none" and omits summary

Claude / Anthropic

  • adaptive Claude models request thinking: { type: "adaptive", display: "summarized" }
  • legacy budget-based Claude models enable thinking with the minimum 1,024-token budget
  • covers direct Anthropic, Claude subscription, custom Anthropic relays, OpenCode Claude, and Copilot Anthropic routes
  • explicit off keeps using thinking: { type: "disabled" } where supported
  • MiniMax, Kimi, and unknown non-Claude Anthropic-compatible models do not receive Claude-specific display options

Thinking display integration

No new Desktop component is required. Returned provider reasoning follows the existing chain:

  1. provider summary/thinking delta
  2. AI SDK reasoning-delta
  3. ModelAdapter Maka thinking event
  4. backend thinking_delta / thinking_complete
  5. AssistantMessage.thinking
  6. existing Desktop Thinking disclosure

The PR includes a real OpenAI Responses SSE contract test proving summary deltas become non-empty Maka thinking text. Existing signed-thinking tests cover the Anthropic delta/signature path, while request-shape tests cover direct and third-party GPT/Claude routes.

Validation

  • focused runtime tests: 77 passed
  • full runtime serial suite: 3325 passed, 0 failed, 9 skipped
  • SDK request-body tests confirm default visible thinking for Responses, Chat-compatible GPT, adaptive Claude, and legacy budget-based Claude

@hqhq1025 hqhq1025 changed the title fix(runtime): surface OpenAI reasoning summaries fix(runtime): surface OpenAI and Claude reasoning summaries Aug 10, 2026
@hqhq1025
hqhq1025 marked this pull request as ready for review August 10, 2026 12:46
@hqhq1025
hqhq1025 force-pushed the codex/reasoning-summary-display branch from 0b82b9c to c87bef5 Compare August 10, 2026 12:53
@hqhq1025

Copy link
Copy Markdown
Contributor Author

Self-review completed after marking ready.

Fixed two issues before merge:

  1. Scoped explicit OpenAI summary: auto to recognized GPT models so DeepSeek/Volcengine keep their provider-specific reasoning behavior.
  2. Guarded Claude visible-thinking defaults by the claude-* model family so unrelated Anthropic-protocol models cannot receive Claude budget/display options.

Rebased onto the latest main. Post-rebase validation: 77 focused tests passed; full runtime serial suite passed with 3325 passed, 0 failed, 9 skipped.

@Astro-Han

Copy link
Copy Markdown
Contributor

Thanks — the gap is real (on main, buildProviderOptions sends no reasoning parameters for openai/anthropic/claude-subscription when the user hasn't selected a thinking level, so reasoning content never appears in the transcript), and the fix completes the request-side default rather than adding a parallel path — the display chain already existed (SDK reasoning_summary_text.deltareasoning-delta → adapter thinking events → existing thinking_delta/AssistantMessage.thinking). I verified the wire behavior against the pinned SDK sources (@ai-sdk/openai@4.0.42, @ai-sdk/anthropic@4.0.39): reasoningSummary: 'auto' sends reasoning: {effort, summary: 'auto'} and still includes reasoning.encrypted_content when store:false (compatible with the #2328/#2518 encrypted chain); thinking: {type:'adaptive', display:'summarized'} passes through verbatim. No schema/protocol changes, old data/clients unaffected, and the change is not obsolete on main (nothing replaced it).

Conclusion: PASS — all P3, including one worth handling.

P3-1 (worth handling): explicit off is silently upgraded to visible adaptive thinking on Claude effort models, contradicting the PR's own policy. The branch order in the PR's model-factory.ts (visibleClaudeThinking + the off branch gated on offBehavior==='anthropic-thinking-disabled') means buildProviderOptions(conn('anthropic'), 'claude-opus-4-8', 'off') yields {thinking:{type:'adaptive',display:'summarized'}} — the PR's own test asserts it. Old behavior was to send an empty config (provider default). The repo's convention (e.g. the kimi-coding-plan branch "must fail loudly, never silently upgraded") is to refuse, not silently upgrade. Currently unreachable via the UI (the off option is normalized away for effort models), but reachable by direct runtime callers. Suggest either rejecting off for effort models explicitly (mirroring the kimi branch) or documenting the deviation honestly.

P3-2: the Anthropic response path for display:'summarized' has no dedicated test (only request-shape assertions; response shape relies on the existing signed-thinking tests' delta shape); P3-3: the default-on wire parameters are negotiated with no third-party relay capability check — relays proxying older APIs may 400 or silently drop the fields; worth one real relay test before merge; P3-4: the branch is ~530 commits behind main (mergeable=CONFLICTING) with the 3325-passed verification done against an old tree — rebase and rerun the full runtime suite before merge.


AI-assisted review disclosure: this review was produced with AI assistance (pi review subagent on opencode-go/deepseek-v4-flash). The subagent verified wire behavior against the pinned SDK node_modules sources and traced the display chain; P3-1 is from reading the PR's own tests, P3-3 is a prediction (relay negotiation untested), not an observed failure. Please weigh these findings with your own judgment.

中文摘要(AI 辅助审查)

结论:PASS,全 P3(其中 P3-1 建议处理)。问题真实(main 上 buildProviderOptions 在用户未选 thinking 级别时对 openai/anthropic/claude-subscription 一律不发 reasoning 参数 → 模型按默认运行、reasoning 不出现在 transcript;display 链路本就存在——SDK reasoning_summary_text.delta → reasoning-delta → adapter thinking 事件 → 既有 thinking_delta/AssistantMessage.thinking),改动补齐请求端默认,非并行实现。已按固定版本 SDK 源码验证 wire 行为:reasoningSummary:'auto' 发 reasoning:{effort,summary:'auto'} 且 store:false 时仍带 reasoning.encrypted_content(与 #2328/#2518 加密链共存);thinking:{type:'adaptive',display:'summarized'} 由 SDK 原样透传。无 schema/协议改动,旧数据/旧客户端不受影响,未被 main 后续提交取代。P3-1(建议处理):显式 off 在 Claude effort 模型上被静默升级为开启可见 adaptive thinking,与 PR 自身政策矛盾(PR 自带测试断言 off→{thinking:{type:'adaptive',display:'summarized'}})——旧行为是发空配置;仓库惯例(kimi-coding-plan 分支"必须大声失败、不得静默升级")是拒绝而非升级。当前仅直接 runtime 调用方可达(UI 对 effort 模型不提供 off 选项),建议显式拒绝或如实文档化。P3-2:Anthropic 响应路径无 dedicated 测试(只有请求体断言);P3-3:默认开启的 wire 参数对第三方 relay 无能力协商(代理旧版 API 可能 400 或静默丢字段),合并前建议对真实 relay 实测;P3-4:分支落后 main ~530 commits(CONFLICTING),"3325 passed" 是对旧树的验证,rebase 后重跑全套。

@Astro-Han Astro-Han left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The main design is sound: this completes the existing provider-options → SDK → thinking-display chain rather than creating another transcript path, and the provider/wire split has broad focused coverage.

This head is now conflicting with current main in two reasoning-contract test files, so its old-base green checks are not integration evidence; please rebase and rerun the focused Runtime tests, typecheck, and CI. One additional low-severity edge remains for custom Chat relays: an explicit unsupported setting can be silently replaced by the GPT-family default. From first principles, an invalid explicit choice must not become a different valid choice; defaulting should apply only when no choice was supplied.

Reviewed with Codex using two independent reviewer agents and an external DeepSeek review; I verified the latest head, current-main conflicts, provider-option paths, existing discussion, and CI state.

中文

主方案是合理的:它补齐现有 provider-options → SDK → thinking-display 链路,没有建立第二条 transcript 路径;provider/wire 分流也有较完整的聚焦测试。

但当前 head 已与最新 main 在两个 reasoning contract 测试文件中冲突,旧 base 上的全绿检查不能证明可集成;请先 rebase,再重跑 Runtime 聚焦测试、typecheck 与 CI。另有一个低严重度边界:自定义 Chat relay 的显式不支持选项可能被静默替换成 GPT family 默认值。按第一性原理,无效的显式选择不应变成另一个有效选择;只有调用方没有提供选择时才应使用默认值。

本次由 Codex 配合两个独立 reviewer agent 和外部 DeepSeek 审查;我核验了最新 head、current-main 冲突、provider option 路径、已有讨论和 CI 状态。

}
if (wire === 'openai-chat' && adapter.kind === 'openai-compatible') {
const reasoningEffort = explicitReasoningEffort ?? defaultOpenAiReasoningEffort(modelId);
if (reasoningEffort) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P3 — Do not replace an invalid explicit relay choice with medium. For an undeclared custom OpenAI Chat relay, resolveThinkingLevel can discard an explicit off/high, after which this fallback recognizes a GPT-family model id and emits reasoningEffort: 'medium'. Direct Runtime callers can therefore ask to disable thinking and instead enable it, or ask for high and get downgraded. Apply the family default only when the original thinkingLevel was absent; if an explicit level resolves unsupported, omit options or reject it. Add custom-relay off/high coverage.

@Astro-Han

Copy link
Copy Markdown
Contributor

/agentic_review

@qodo-code-review

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (2) 📘 Rule violations (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Copilot omits default effort 🐞 Bug ≡ Correctness
Description
Fix-now: a GitHub Copilot model declared with apiProtocol: 'openai-responses' retains adapter kind
github-copilot, so an unset thinking level produces summary: 'auto' without the default `effort:
'medium'`, violating the resolved Responses-wire request contract. The adapter-kind guard
incorrectly restricts default effort selection to native OpenAI adapters even though Copilot
resolves to the same wire.
Code

packages/runtime/src/model-factory.ts[R596-598]

+    const reasoningEffort =
+      explicitReasoningEffort ??
+      (adapter.kind === 'openai' ? defaultOpenAiReasoningEffort(modelId) : undefined);
Relevance

●●● Strong

Directly contradicts the stated resolved-wire policy; accepted precedent favors testing and fixing
Responses request-shape correctness.

PR-#2518

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The Responses branch derives the default reasoning effort only when adapter.kind === 'openai',
while Copilot’s registry adapter remains github-copilot even when an account declaration routes
its GPT-5.5 model through openai-responses. Because defaultOpenAiReasoningEffort(modelId)
already recognizes supported GPT IDs from OpenAI metadata, the Copilot model could safely receive
the same default; however, the current guard skips the medium effort, and the added test covers only
an explicit high level rather than the unset-level case.

packages/runtime/src/model-factory.ts[351-364]
packages/runtime/src/model-factory.ts[593-605]
packages/runtime/src/model-runtime.ts[130-135]
packages/runtime/src/tests/model-factory-thinking.test.ts[327-340]
packages/core/src/provider-registry.ts[1762-1772]
packages/core/src/model-metadata.generated.ts[8384-8397]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description

`buildFamilyWire` restricts default GPT reasoning-effort derivation to adapters whose kind is `openai`. GitHub Copilot models can resolve to the OpenAI Responses wire while retaining adapter kind `github-copilot`, causing an unset thinking level to emit `summary: 'auto'` without the required default `effort: 'medium'`.

## Issue Context

Treat the resolved wire as the routing authority and keep using the native OpenAI provider-options namespace for the Responses wire. Reuse `defaultOpenAiReasoningEffort(modelId)`, which already returns a value only for recognized OpenAI reasoning model IDs, rather than adding a Copilot-specific branch, new state, configuration, public API, or test seam. Extend coverage beyond the existing explicit `high`-level Copilot case to verify the unset-level default.

## Fix Focus Areas

- packages/runtime/src/model-factory.ts[593-605]
- packages/runtime/src/__tests__/model-factory-thinking.test.ts[327-340]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Namespaced Claude loses thinking 🐞 Bug ≡ Correctness
Description
Fix-now: visibleClaudeThinking recognizes the final segment of a namespaced Claude ID but looks up
fallback metadata using the unnormalized ID, so an Anthropic-compatible route such as
anthropic/claude-opus-4-8 emits no thinking options. This prevents the default summarized thinking
promised for recognized Claude models.
Code

packages/runtime/src/model-factory.ts[R372-376]

+  const familyModelId = modelId.includes('/')
+    ? modelId.slice(modelId.lastIndexOf('/') + 1)
+    : modelId;
+  if (!familyModelId.startsWith('claude-')) return undefined;
+  const effectiveOptions = thinkingOptions ?? thinkingOptionsForModel('anthropic', modelId);
Relevance

●●● Strong

Matches the PR’s explicit namespace-routing intent and is a deterministic normalization bug;
accepted runtime correctness fixes are common.

PR-#2163
PR-#2518

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The helper strips the namespace for its Claude-family check, but thinkingOptionsForModel delegates
to an exact model-ID metadata lookup and therefore cannot find anthropic/claude-opus-4-8 under the
Anthropic provider's unqualified claude-opus-4-8 key. Anthropic-compatible connections resolve to
the Anthropic adapter and invoke this helper from the shared anthropic-messages branch, making the
failure reachable.

packages/runtime/src/model-factory.ts[367-385]
packages/runtime/src/model-factory.ts[608-610]
packages/core/src/model-thinking.ts[277-282]
packages/core/src/model-metadata.ts[45-56]
packages/core/src/provider-registry.ts[1744-1754]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Namespaced Claude model IDs pass the family check but miss Anthropic metadata, disabling the new default visible-thinking request.

## Issue Context
`familyModelId` is already the normalized family identifier. Reuse it for the fallback metadata lookup; no new state, branch, configuration, or public surface is needed.

## Fix Focus Areas
- packages/runtime/src/model-factory.ts[367-385]
- packages/runtime/src/__tests__/model-factory-thinking.test.ts[343-354]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Context sources
Review mode: 🧠 Deep: This changes shared provider-option logic across multiple independent OpenAI, Anthropic, relay, subscription, and wire-resolution paths, creating a dense set of easy-to-miss contract and routing regressions that benefits from redundant review passes.

Grey Divider

Tip of the day
💡 Did you know, you can keep summaries lean with Finding overflow, which tucks the rest behind 'View more'

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread packages/runtime/src/model-factory.ts Outdated
Comment on lines +596 to +598
const reasoningEffort =
explicitReasoningEffort ??
(adapter.kind === 'openai' ? defaultOpenAiReasoningEffort(modelId) : undefined);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

1. Copilot omits default effort 🐞 Bug ≡ Correctness

Fix-now: a GitHub Copilot model declared with apiProtocol: 'openai-responses' retains adapter kind
github-copilot, so an unset thinking level produces summary: 'auto' without the default `effort:
'medium'`, violating the resolved Responses-wire request contract. The adapter-kind guard
incorrectly restricts default effort selection to native OpenAI adapters even though Copilot
resolves to the same wire.
Agent Prompt
## Issue description

`buildFamilyWire` restricts default GPT reasoning-effort derivation to adapters whose kind is `openai`. GitHub Copilot models can resolve to the OpenAI Responses wire while retaining adapter kind `github-copilot`, causing an unset thinking level to emit `summary: 'auto'` without the required default `effort: 'medium'`.

## Issue Context

Treat the resolved wire as the routing authority and keep using the native OpenAI provider-options namespace for the Responses wire. Reuse `defaultOpenAiReasoningEffort(modelId)`, which already returns a value only for recognized OpenAI reasoning model IDs, rather than adding a Copilot-specific branch, new state, configuration, public API, or test seam. Extend coverage beyond the existing explicit `high`-level Copilot case to verify the unset-level default.

## Fix Focus Areas

- packages/runtime/src/model-factory.ts[593-605]
- packages/runtime/src/__tests__/model-factory-thinking.test.ts[327-340]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment thread packages/runtime/src/model-factory.ts Outdated
Comment on lines +372 to +376
const familyModelId = modelId.includes('/')
? modelId.slice(modelId.lastIndexOf('/') + 1)
: modelId;
if (!familyModelId.startsWith('claude-')) return undefined;
const effectiveOptions = thinkingOptions ?? thinkingOptionsForModel('anthropic', modelId);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

2. Namespaced claude loses thinking 🐞 Bug ≡ Correctness

Fix-now: visibleClaudeThinking recognizes the final segment of a namespaced Claude ID but looks up
fallback metadata using the unnormalized ID, so an Anthropic-compatible route such as
anthropic/claude-opus-4-8 emits no thinking options. This prevents the default summarized thinking
promised for recognized Claude models.
Agent Prompt
## Issue description
Namespaced Claude model IDs pass the family check but miss Anthropic metadata, disabling the new default visible-thinking request.

## Issue Context
`familyModelId` is already the normalized family identifier. Reuse it for the fallback metadata lookup; no new state, branch, configuration, or public surface is needed.

## Fix Focus Areas
- packages/runtime/src/model-factory.ts[367-385]
- packages/runtime/src/__tests__/model-factory-thinking.test.ts[343-354]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

@jackwener jackwener left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Automated review of exact head c87bef5a51f8d74e70d763e8d96f7f7cfd93fd5f against current main@d62857a8357e9160926726a2a13096bc2dc2b91d.

The bug still exists on current main: recognized GPT Responses routes do not request reasoningSummary, and recognized Claude Anthropic routes do not request summarized display by default. The PR's requested default-medium/summarized policy matches issue #2644, and its request-shape plus real SSE boundary coverage is directionally strong.

Blocking rebase/authority conflict. This head is based before the current Responses routing and replay contract. Current buildFamilyWire distinguishes native OpenAI from open-responses, verifies reasoningReplay, keys provider options by the actual Open Responses provider name, preserves serviceTier, and enables forceReasoning for encrypted-content replay. The PR's version destructures only { adapter, wire } and returns a single openai namespace for every Responses wire. It is now text-conflicting, and resolving that mechanically would either drop current replay/service-tier behavior or silently send summary options to a namespace the Open Responses adapter does not read.

Rebase and implement visibility at the current resolved-wire seam: retain the existing reasoningReplay and open-responses branches, add OpenAI reasoningSummary: 'auto' only where the native OpenAI Responses schema consumes it, and preserve provider-native options/names for Open Responses relays. Re-run the real Responses SSE test and the provider contract sweep on the rebased exact head.

Required conclusions:

  1. Optimal for the actual problem: the policy is sound, but this revision is no longer an optimal/current implementation.
  2. Production code to delete: none identified beyond avoiding a parallel/legacy Responses branch during rebase.
  3. Tests to delete/replace: none identified; port the strong request-shape and SSE tests to the current routing matrix.
  4. Deeper refactor: no new abstraction; adapt the change to the existing current-main wire/replay authority.
  5. Ready to merge: no; it is conflicting and predates material routing contracts.
  6. Residual risks/gaps: provider-visible reasoning changes user-visible behavior and token/cost characteristics, so independent human review and exact-head CI remain required after rebase.

@hqhq1025
hqhq1025 force-pushed the codex/reasoning-summary-display branch from c87bef5 to ebfab4b Compare August 22, 2026 06:01
@hqhq1025

Copy link
Copy Markdown
Contributor Author

Rebased onto current main@279f0fb72 and force-pushed the rewritten head as ebfab4bc4.

This revision addresses the review findings while preserving the current runtime routing contracts:

  • keeps native OpenAI Responses and Open Responses continuation paths separate;
  • requests reasoningSummary: "auto" with the recognized GPT default effort only on SDK namespaces that consume it;
  • gives GitHub Copilot Responses routes the same default effort through the resolved wire;
  • normalizes namespaced Claude model IDs before Anthropic metadata lookup;
  • requests summarized Claude thinking for native and compatible Anthropic routes;
  • does not replace an explicit unsupported thinking level with a family default;
  • preserves encrypted replay, plaintext Open Responses replay, service tier, and provider-native option namespaces.

Validation on the exact head:

  • Runtime build and typecheck passed;
  • 81 focused provider/wire/SSE tests passed;
  • GitHub CI passed, including lint, formatting, build, typecheck, affected workspace tests, Runtime Host, Desktop E2E, Storybook, and CLI release-candidate validation.

Ready for re-review.

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.

Reasoning summaries are omitted for default GPT and Claude thinking

3 participants