Skip to content

fix: surface and render OpenAI and Claude reasoning summaries - #2645

Open
hqhq1025 wants to merge 3 commits into
apache:mainfrom
hqhq1025:codex/reasoning-summary-display
Open

fix: surface and render OpenAI and Claude reasoning summaries#2645
hqhq1025 wants to merge 3 commits into
apache:mainfrom
hqhq1025:codex/reasoning-summary-display

Conversation

@hqhq1025

@hqhq1025 hqhq1025 commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Summary

Reasoning-capable GPT and Claude models now request user-visible thinking by default, and the existing reasoning disclosure renders that content through the shared safe Markdown and KaTeX pipeline.

The implementation follows the resolved wire and recognized model family rather than the provider label. It preserves explicit-off behavior, encrypted reasoning replay, provider-native namespaces, code and currency literals, and display redaction.

The latest revision also:

  • supports multiline $$...$$ and \[...\] display math outside code fences;
  • preserves model-authored single newlines in plain reasoning while retaining Markdown and KaTeX rendering;
  • documents the load-bearing Anthropic internal capability lookup and its upgrade path.

Closes #2644

Request policy

OpenAI / GPT

  • Responses routes request reasoning: { effort: "medium", summary: "auto" } for recognized GPT reasoning models.
  • This 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 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.
  • This 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.

Shared display path

All provider-visible reasoning uses the existing common pipeline:

  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. Desktop reasoning disclosure

The disclosure supports safe Markdown and KaTeX for \(...\), \[...\], $...$, and $$...$$, including multiline display forms. Inline and fenced code remain literal, ordinary currency is preserved, collapsed previews are redacted, and plain reasoning keeps intentional single-line breaks.

Visual comparison

Same fixture content, viewport, state, and zoom level.

Before

Reasoning before

After

Reasoning after

Verification

Current exact-head local verification:

  • UI tests: 234 passed, 0 failed.
  • Focused runtime thinking tests: 39 passed, 0 failed.
  • UI and runtime typecheck: passed.
  • Desktop typecheck: passed.
  • Desktop renderer production build and third-party notice check: passed.
  • KaTeX desktop artifact audit: 59 emitted KaTeX font files and 59 matching CSS font references, including KaTeX_Main-Regular in WOFF2, WOFF, and TTF formats.
  • Biome and git diff --check: passed.
  • Astryx surface inventory: 208 files covered, 0 blockers, 1 intentional polish item.

Existing end-to-end evidence retained from the prior revision:

  • Real Electron fixture: 4 KaTeX nodes, 1 strong node, and no raw Markdown/LaTeX delimiters.
  • Real OpenAI Responses smoke: request contained reasoningSummary: "auto" and persisted non-empty thinking text.

GitHub CI is rerunning on the rebased exact head.

AI use

Select exactly one:

  • No generative tool made a substantive contribution
  • Generative tooling made a substantive contribution

Tool(s) and scope: Codex contributed implementation, tests, review remediation, and verification. Every commit in this pull request carries a Generated-by: Codex trailer.

Checklist

  • Tests cover the change and fail without it
  • Lint, format, typecheck and the affected suites pass locally

Does this PR entail a change in behavior?

  • Yes — described under Summary above
  • No

@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 状态。

Comment thread packages/runtime/src/model-factory.ts
@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 thread packages/runtime/src/model-factory.ts Outdated

@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.

@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.

Thanks for pushing this through several rounds — the OpenAI Responses side looks right to me, and the earlier points from @jackwener and the automated pass have all been addressed on this head. The provider-options → SDK → thinking-display chain is the correct place to fix this, and I like that you didn't invent a second transcript path.

Four independent reviewers looked at this. Three found nothing blocking. The fourth, reviewing blind, found something the other three missed, and I've since verified it myself against the pinned SDK and the model catalog. Two P1s, both on the Anthropic side. Details inline.

The root cause is one confusion, not two bugs: thinkingOptions.efforts is a UI fact — which effort tiers to show in a picker. supportsAdaptiveThinking is a wire fact — whether the model accepts thinking: {type: 'adaptive'}. This PR uses the first as a proxy for the second. They diverge in both directions, which is why there are two failure modes rather than one.

What I'd suggest: derive one normalized Claude thinking-mode decision — adaptive / legacy enabled+budget / unsupported — from the model id, and add request-body tests for Sonnet 4.5, Opus 4.5, a current adaptive model, and a dotted/slashed relay alias. The existing tests cover Haiku 4.5 and Opus 4.8, which happen to sit on the two ends that already work.

Not approving on this head, but that's a mechanical note as much as a judgement: reviewDecision is still CHANGES_REQUESTED from the earlier review, so merge is gated on that being lifted regardless of what I say here.

Comment thread packages/runtime/src/model-factory.ts Outdated
Comment thread packages/runtime/src/model-factory.ts Outdated
@hqhq1025
hqhq1025 force-pushed the codex/reasoning-summary-display branch 2 times, most recently from 3549efa to 0bfc703 Compare August 23, 2026 15:22
@hqhq1025 hqhq1025 changed the title fix(runtime): surface OpenAI and Claude reasoning summaries fix: surface and render OpenAI and Claude reasoning summaries Aug 23, 2026
@hqhq1025

hqhq1025 commented Aug 23, 2026

Copy link
Copy Markdown
Contributor Author

Pushed revision 0bfc703af with the root-cause fix completed across both transport and presentation.

What changed since the prior revision:

  • OpenAI Responses now requests summary: "auto" for recognized reasoning models, with a medium default effort only when the user has not selected a level.
  • Claude thinking mode is selected from the actual model capability: legacy budget-based thinking for Claude 4.5 and adaptive summarized thinking for newer Claude models.
  • The existing shared reasoning disclosure now renders safe Markdown and KaTeX instead of showing raw **...** and \\(...\\) delimiters.
  • The collapsed preview strips presentation delimiters, and the expanded content uses the selected compact technical-note treatment.
  • Added the required Astryx surface inventory entry for the new renderer module.

Validation on this revision:

  • UI: 223 passed
  • focused runtime: 72 passed
  • desktop typecheck: passed
  • renderer production build: passed
  • real Electron fixture confirmed rendered Markdown/KaTeX and no raw delimiters
  • real OpenAI Responses smoke confirmed reasoningSummary: "auto" and persisted non-empty thinking

The PR description now includes matched before/after screenshots. Please re-review this revision; merge readiness still depends on green CI and a fresh human approval.

@hqhq1025
hqhq1025 force-pushed the codex/reasoning-summary-display branch 2 times, most recently from aa09557 to 68b45d9 Compare August 23, 2026 15:25
@hqhq1025

Copy link
Copy Markdown
Contributor Author

CI update for 68b45d94f: the complete Linux CI job, dependency audit, and Windows packaging/upgrade/rollback workflow are all green. The revision is technically validated and mergeable; it is awaiting the requested fresh human approval.

@ARE404 ARE404 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.

Exact-head review of 68b45d94f1fa0c3fe4bbb1aae3eca277ff9ba425 against merge-base cd0163a1effc27aec61cc331f799959e30fd7446.

Result: NO-GO (COMMENT). The provider request-shape matrix is substantially improved and all expected exact-head checks are terminal green (test, dependency audit, Windows package), but the shared display change introduces one privacy-boundary regression and one ordinary-text rendering regression. The PR also remains DIRTY/conflicting, and the repository's existing CHANGES_REQUESTED decision remains in force.

Standards axis

  • The collapsed reasoning preview bypasses the display redaction boundary (P1, inline).
  • The PR description omits the mandatory yes/no generative-tooling declaration required by CONTRIBUTING.md; the sole commit also has no Generated-by: trailer, which is additionally required if material AI-authored content is present.
  • Nonblocking smell: default OpenAI effort resolution is repeated across direct Codex, direct OpenAI, native Responses, and compatible Chat branches. A single resolver would reduce future off/default drift.

Spec axis

  • The shared KaTeX scanner does not preserve paired currency, despite the PR's explicit ordinary-currency guarantee (P2, inline).
  • The resolved-wire/provider coverage and the Claude family-capability split otherwise match the stated request policy on this head.

No merge action was taken.

Comment thread packages/ui/src/chat-turn.tsx
Comment thread packages/ui/src/markdown-math.tsx
@hqhq1025
hqhq1025 force-pushed the codex/reasoning-summary-display branch from 68b45d9 to c4b7d44 Compare August 24, 2026 07:46

@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.

I re-checked every open point on c4b7d443 rather than on the heads they were raised against. Five are closed; two still apply, and I have replied in their existing threads instead of opening new ones.

Closed on this head

  • The Claude thinking-options gap is no longer there — the catalog now covers the effort models in generated metadata plus the Sonnet 4.5 / Opus 4.1 / Haiku 4.5 overrides, and the factory normalizes the namespace before falling back to it (model-factory.ts:407-412).
  • adaptive is no longer sent to models the pinned SDK says cannot accept it; model-factory.ts:414-416 picks adaptive or enabled with a 1024 budget by capability, covered at model-factory-thinking.test.ts:100-120 and :459-477.
  • An explicit unsupported relay level is no longer silently replaced with medium — the default only applies when the level is omitted (model-factory.ts:701-703, :721-724), asserted at model-factory-thinking.test.ts:502-507.
  • Both automated findings on model-factory.ts were real on the older commit and are fixed here: Copilot Responses now goes through the shared wire branch at :679-714, and namespaced Claude ids normalize via claudeFamilyId at :407-409 before the metadata lookup.

Still open — collapsed reasoning preview bypasses secret redaction (P1), and paired dollar signs are parsed as inline math (P2). Both in the threads above.

test, package and audit are all terminal green on this head.

中文

所有未决点我都对着 c4b7d443 重新核了一遍,不是沿用它们各自提出时的 head。五条已闭:Claude thinking-options 缺失(catalog 已覆盖 + model-factory.ts:407-412 先归一化命名空间再回退)、往不支持的模型发 adaptive:414-416 按 capability 选择)、显式非法 relay level 被替成 medium:701-703/:721-724 仅在省略时套默认)、两条自动化发现(旧 commit 上是真问题,当前 head 已修)。

两条仍成立:折叠态 reasoning 预览绕过脱敏(P1)、成对美元号被当作行内公式(P2)。都已回在原有线程里,没有另开新线。

此 head 上 testpackageaudit 均已终态绿。

@hqhq1025
hqhq1025 force-pushed the codex/reasoning-summary-display branch from c4b7d44 to e14eb41 Compare August 24, 2026 08:06

@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.

Rendering reasoning through the same Markdown pipeline as answers is the right direction, and routing the preview text separately so the collapsed summary stays plain is a nice touch. The redaction path still holds — Markdown redacts text and settledText itself, so moving the call out of DeepThinking does not widen anything.

Three things, two of which I would like fixed before this merges.

[P2] Display math written across several lines is left on screen as raw LaTeX

protectMathOutsideCode splits the source on newlines and hands each line to protectMathInLine, and every reader — readDelimitedMath and findClosingDollar — searches only within that one line. A delimiter pair that opens on one line and closes on another is never recognised.

I ran the functions from this branch directly against a few inputs:

inline \(..\)      TOKENIZED
single-line $$     TOKENIZED
single-line \[     TOKENIZED
multi-line $$      LEFT LITERAL
multi-line \[      LEFT LITERAL

The two that fail are the ordinary way a model emits display math:

$$
E = mc^2
$$

That reaches the reader as the literal characters, and worse than untouched — the body between the delimiters is still Markdown, so underscores become emphasis and backslashes get eaten. The three new tests all keep their display math on one line, which is why this passes. Please handle the multi-line form and add a case for it; the single-line paths look right and I have no concerns there.

[P2] Dropping white-space: pre-wrap from the reasoning body re-opens the problem #1748 caused

The rule this replaces carried its own history: it said the Astryx atoms own no white-space, so the inherited white-space: normal collapses every newline in thinking text, and that pre-wrap was restored after the #1748 migration dropped it. The new rule sets white-space: normal again and relies on Markdown to supply the structure.

That holds only for reasoning that is actually Markdown. The Markdown parser treats a single newline as a soft break and joins the lines unless the line ends in two spaces or a blank line separates the paragraphs. Plenty of reasoning text is plain prose with hard single newlines and neither of those, and all of it will now run together into one block. Before, pre-wrap guaranteed the layout regardless of what the model emitted.

Please check this against real reasoning output that is not Markdown-structured. If it reads badly, keeping pre-wrap on the container while still rendering Markdown inside it would preserve both.

[P3] @ai-sdk/anthropic/internal is the first internal-subpath import in the tree

getModelCapabilities comes from a subpath the package does export, so this is legitimate, and @ai-sdk/anthropic is pinned to an exact version, so nothing moves under you today. But it is the first place we reach into an internal entrypoint, and that name is the usual signal that its contents are not covered by the package's compatibility promise. Worth a comment at the import saying what it is for and what to do if it disappears, so whoever next bumps that version knows this is load-bearing.

I have not raised anything about the KaTeX dependency itself — pinned exactly, the stylesheet is imported alongside it, and the math container styles look sound. One thing I could not check from the diff: please confirm the KaTeX web fonts are actually bundled into the desktop renderer. When they are missing, KaTeX silently falls back to a serif face with the wrong metrics, so it looks merely ugly rather than broken. A screenshot of a rendered formula would settle it and would also cover the before/after evidence this change needs.

简体中文

把 reasoning 走与答案相同的 Markdown 管线是对的方向,把预览文本单独抽出来、让折叠态摘要保持纯文本也处理得很好。脱敏路径没有被削弱——Markdown 组件自身会对 textsettledText 调用脱敏,所以把这个调用从 DeepThinking 里移走并没有扩大暴露面。

三点,其中两点希望在合并前修掉。

[P2] 跨行书写的展示式公式会以 LaTeX 原文留在界面上

protectMathOutsideCode 按换行切分源文本、逐行交给 protectMathInLine,而所有读取函数(readDelimitedMathfindClosingDollar)都只在当前这一行内查找。一对在某行开启、在另一行闭合的定界符永远不会被识别。

我直接用这个分支上的函数跑了几组输入:

inline \(..\)      TOKENIZED
single-line $$     TOKENIZED
single-line \[     TOKENIZED
multi-line $$      LEFT LITERAL
multi-line \[      LEFT LITERAL

失败的这两种,恰恰是模型输出展示式公式最常见的写法:

$$
E = mc^2
$$

它会以字面字符呈现给读者,而且比"原样不动"更糟:定界符之间的正文仍会走 Markdown,于是下划线变成强调、反斜杠被吞掉。新增的三个测试的展示式公式都写在一行内,所以这个问题没有被暴露。请支持跨行形式并补一条对应测试;单行的几条路径看下来是对的,我没有疑虑。

[P2] 去掉 reasoning 正文的 white-space: pre-wrap,会重新打开 #1748 造成的那个问题

被替换掉的那条规则自带历史说明:Astryx 的原子组件不负责 white-space,因此继承来的 white-space: normal 会把 thinking 文本里的每一个换行都折叠掉;而 pre-wrap 正是在 #1748 迁移把它弄丢之后被恢复回来的。新规则重新设成了 white-space: normal,改为依赖 Markdown 来提供结构。

但这只有在 reasoning 本身确实是 Markdown 时才成立。 Markdown 解析器把单个换行当作软换行,除非行尾有两个空格、或者段落之间隔了空行,否则会把相邻行拼成一行。而相当多的 reasoning 是带硬换行的纯散文,两个条件都不满足,这些内容现在会全部挤成一整块。在此之前,pre-wrap 不管模型输出什么都能保证排版。

请拿真实的、非 Markdown 结构的 reasoning 输出验证一下。如果阅读效果变差,可以在容器上保留 pre-wrap、同时在其内部渲染 Markdown,两者可以兼得。

[P3] @ai-sdk/anthropic/internal 是本仓库第一处 internal 子路径导入

getModelCapabilities 来自该包确实导出的子路径,所以这个用法是合法的;而且 @ai-sdk/anthropic 锁的是精确版本,当下不会有东西在脚下移动。但这是我们第一次伸进 internal 入口,而这个命名通常意味着其内容不在该包的兼容性承诺范围内。建议在 import 处加一句注释,写明它的用途、以及万一它消失了该怎么办,好让下一个升级这个版本的人知道这里是承重的。

关于 KaTeX 依赖本身我没有提出问题——版本锁死、样式表随之引入、公式容器的样式也看着合理。有一点我从 diff 里看不出来:请确认 KaTeX 的 web 字体确实被打进了桌面端渲染进程。 字体缺失时 KaTeX 会静默回退到衬线字体并带着错误的度量,表现为"只是有点丑"而不是明显坏掉。一张公式渲染后的截图就能说明问题,同时也正好满足这个改动需要补的前后对比证据。

@hqhq1025
hqhq1025 force-pushed the codex/reasoning-summary-display branch from e14eb41 to e4b3549 Compare August 24, 2026 12:35
@hqhq1025

Copy link
Copy Markdown
Contributor Author

Addressed all three points from the latest review on exact head e4b3549b3.

  1. Multiline display math

    • protectMathOutsideCode now partitions fenced code from prose and scans each prose region as a whole.
    • $$...$$ and \[...\] may span lines.
    • \(...\) and $...$ remain line-bounded, preserving the existing inline-math and paired-currency safeguards.
    • A display delimiter cannot cross a fenced code block.
    • Regression coverage includes both multiline delimiter forms and the fence boundary.
  2. Plain reasoning line breaks

    • .maka-chat-reasoning-content restores white-space: pre-wrap.
    • Markdown and KaTeX still render inside the disclosure, while model-authored single newlines remain visible.
    • The regression test checks both the rendered newline and the CSS contract.
  3. Anthropic internal capability import

    • The import now carries a concise maintenance note explaining that it selects adaptive versus legacy thinking and should be replaced with the public capability API if the internal export disappears during an SDK upgrade.

Exact-head local verification:

  • UI: 234 passed, 0 failed.
  • Focused runtime thinking tests: 39 passed, 0 failed.
  • UI, runtime, and Desktop typechecks: passed.
  • Desktop renderer production build and third-party notice check: passed.
  • KaTeX packaging: 59 emitted font files and 59 matching CSS references. KaTeX_Main-Regular is present as WOFF2, WOFF, and TTF.
  • Biome and git diff --check: passed.
  • Astryx inventory: 208 files, 0 blockers, 1 intentional polish item.

The branch is rebased onto main@f37f5c3f2. All three PR commits carry Generated-by: Codex, and the PR body now follows the current AI-use template while retaining the before/after screenshots and earlier end-to-end evidence.

GitHub CI is running on this head. The PR is not being merged; @jackwener still needs to re-review and clear the existing CHANGES_REQUESTED decision.

@hqhq1025
hqhq1025 requested review from Astro-Han August 24, 2026 12:36

@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.

Re-review of exact head e4b3549b3c411cdbf669e12efd86b706fdf2ca4d: the earlier Anthropic blockers are fixed. Sonnet 4.5 now uses legacy enabled thinking with the required 1,024-token budget, while Opus 4.5 no longer receives unsupported adaptive thinking and Opus 4.8 retains summarized adaptive thinking.

This revision is not ready to merge because three current behavior gaps remain, detailed inline. Exact-head test, package, and audit are green. I also verified the root build, 234 UI tests, 94 focused Runtime/UI tests, the active Claude request-shape sweep, and production Markdown rendering probes.

const familyModelId = claudeFamilyId(modelId);
if (!familyModelId.startsWith('claude-')) return undefined;
const effectiveOptions = thinkingOptions ?? thinkingOptionsForModel('anthropic', familyModelId);
const supportsThinking =

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.

[P2] Include the shipped OpenCode Sonnet 4 route in the thinking-support decision. Selecting the active opencode/claude-sonnet-4 model with no explicit level reaches this function without thinkingOptions, so supportsThinking is false and the SDK capability lookup below never runs. A real getAIModel() + generateText() capture sent /zen/v1/messages with no thinking; positive controls on the same connection sent legacy enabled/1,024 for Sonnet 4.5 and summarized adaptive for Opus 4.8. A sweep of all 13 active shipped reasoning-capable Claude models on the Anthropic wire found exactly this active gap. Please make one classifier own both whether a shipped Claude family supports thinking and which wire mode it uses, or add Sonnet 4 to the canonical metadata seam, and cover every active Anthropic-wire Claude model plus an unknown non-Claude negative control.

return undefined;
}
const lineEnd = line.indexOf('\n', index + 1);
const close = findClosingDollar(line, index + 1, lineEnd < 0 ? line.length : lineEnd);

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.

[P2] Avoid enabling ambiguous single-dollar math on the shared technical Markdown surface. Ordinary shell text such as Use $HOME/$USER for paths. reaches this parser, pairs the two dollar signs, turns HOME/ into KaTeX, and leaves USER as bare text; a production MarkdownBody render reproduced the corruption and removed both $ characters. The numeric currency exception does not cover shell variables, and local heuristics cannot make $...$ unambiguous in general technical prose. Please retain explicit \(...\), \[...\], and $$...$$ delimiters while removing global single-dollar parsing, or scope it behind a narrow opt-in. Add rendered controls for $HOME/$USER, $PATH:$HOME, paired currency, inline code, and explicit TeX.

pattern: TOKEN_PATTERN,
render: (match, key) => {
const displayMode = match[1] === 'D';
const formula = decodeFormula(match[2] ?? '');

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.

[P2] Do not let source text impersonate the internal math-token protocol. Model or imported transcript text containing the private-use token shape \uE000MAKAMATHIFFFFFFEND\uE001 matches this plugin, then decodeFormula() calls String.fromCodePoint(0xFFFFFF) and throws RangeError: Invalid code point 16777215. A production MarkdownBody render reproduces the crash; trust: false and throwOnError: false cannot help because it occurs before KaTeX. The stored content is retried on reload, so the top-level renderer fallback repeats for the affected session. Please keep a per-render token-to-formula Map in a namespace proven absent from the source, render only keys created during that invocation, and leave unknown token-shaped source literal. Add malformed/raw-sentinel negative controls alongside valid inline and multiline display math.

@jackwener
jackwener dismissed their stale review August 24, 2026 13:41

The routing and rebase blocker recorded on c87bef5 is closed on e4b3549. Current-head findings are tracked separately in review 5008434071; this dismissal removes only the stale review state and does not approve the PR.

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

4 participants