Skip to content

fix(responses): give every reasoning input item the summary the upstream requires - #4673

Merged
lidge-jun merged 2 commits into
devfrom
codex/reasoning-input-summary
Sep 15, 2026
Merged

lidge-jun merged 2 commits into
devfrom
codex/reasoning-input-summary

Conversation

@lidge-jun

@lidge-jun lidge-jun commented Sep 15, 2026

Copy link
Copy Markdown
Owner

Summary

A reasoning item that reaches a Responses upstream without a summary field is refused before
inference with Missing required parameter: 'input[N].summary', and the whole turn fails. The field
is optional in responsesRequestSchema, so such an item passed every local gate and broke only on
the wire — reported from an Aside/Pi client talking to the proxy over /v1/chat/completions.

Two layers produced or passed on that item, and both are fixed:

  • src/chat/inbound.ts minted the reasoning item for a replayed assistant turn (Audit F6) with
    content only. Every other item this proxy mints — src/bridge.ts, src/claude/inbound.ts
    already carries summary; this ingress was the one that did not. It now carries the replayed
    thinking as a summary_text part, matching the Claude ingress, which also matters because
    sanitizeReasoningInputContent blanks content for every destination except a
    preserveResponsesReasoningContent provider, so summary is the only channel that survives to a
    native backend.
  • sanitizeReasoningInputContent now supplies an empty summary for any reasoning input item that
    arrives without the key, so a third-party client or another translated ingress cannot reintroduce
    the same 400. This is not gated on the destination: an item missing the key is not a shape a
    canonical backend issued — Codex and this proxy always emit summary — so nothing a native
    backend sent is reshaped. An item that already has a summary is returned untouched.

Verification

  • Hosted CI at the exact head of this branch is the evidence for this change. The local suite,
    typecheck, and build were NOT RUN: the maintainer explicitly prohibited local suite runs for
    this task.
  • Static review of the blast radius instead: sanitizeReasoningInputContent has two direct test
    owners (tests/providers/deepseek-reasoning-replay.test.ts,
    tests/adapters/anthropic/anthropic-thinking-signature.test.ts), and the only whole-item equality
    assertion over a summary-less item is the first test in the DeepSeek file, whose expectation is
    updated here with the reason inline. Every other test that builds a reasoning item either supplies
    summary or does not compare the sanitized item by equality.
  • New regression coverage: tests/providers/deepseek-reasoning-replay.test.ts (summary supplied
    when absent, existing summary untouched, repair applies on the content-preserving path) and
    tests/responses/chat-inbound-reasoning-replay.test.ts (the minted item carries the summary, and
    the replayed thinking survives sanitization).

Checklist

  • Scope stays focused and avoids unrelated cleanup.
  • Docs or release notes were updated when needed.
  • Security-sensitive changes were reviewed for secrets, auth, and unsafe defaults.

Summary by CodeRabbit

  • Bug Fixes

    • Fixed Responses API requests containing reasoning items without a summary so they no longer fail validation.
    • Chat Completions reasoning translations now preserve both summary and reasoning text.
    • Improved reasoning replay to retain plaintext, required metadata, and correct message ordering.
  • Tests

    • Added regression coverage for missing, existing, and preserved reasoning summaries.
    • Added coverage for synthesized reasoning replay behavior and reliable recovery of prior conversation state.

…eam requires

A reasoning item forwarded without `summary` is refused with
`Missing required parameter: 'input[N].summary'` before inference, while
responsesRequestSchema marks the field optional so it passed every local gate.

The chat ingress minted such an item for a replayed assistant turn, and the
reasoning sanitizer passed any client-supplied one through untouched. The
ingress now carries the replayed thinking as a summary_text part, mirroring
src/claude/inbound.ts, and the sanitizer supplies an empty summary for any item
that arrives without the key.
@lidge-jun
lidge-jun requested a review from Ingwannu as a code owner September 15, 2026 01:38
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 15, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-15T01:43:16.814738Z 304ef17 PR opened
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@github-actions github-actions Bot added the bug Something isn't working label Sep 15, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Deterministic PR hygiene checks passed.

@coderabbitai

coderabbitai Bot commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

Review Change StackReview Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Advanced

Run ID: 9570112d-bf31-4852-8c26-98daea4a2e20

📥 Commits

Reviewing files that changed from the base of the PR and between 304ef17 and ffcc189.

📒 Files selected for processing (1)
  • tests/codex-integration/issue-702-expired-replay-state.test.ts

Included review availability: Your plan provides up to 10 included reviews per hour; 7 remain after this review.


📝 Walkthrough

Walkthrough

The change adds reasoning summaries during Chat Completions translation and repairs missing summaries before sending Responses input. Tests cover synthesized summaries, empty-summary injection, existing-summary preservation, encrypted content, raw-content preservation, and replayed requests.

Changes

Reasoning replay

Layer / File(s) Summary
Synthesize reasoning summaries
src/chat/inbound.ts, tests/responses/chat-inbound-reasoning-replay.test.ts
Translated assistant reasoning items now include plaintext in summary as well as content. Tests verify the summary and its preservation when sanitization clears content.
Repair missing Responses summaries
src/adapters/openai-responses/reasoning.ts, tests/providers/deepseek-reasoning-replay.test.ts, tests/codex-integration/issue-702-expired-replay-state.test.ts
sanitizeReasoningInputContent detects a missing summary key and adds summary: []. Existing summaries remain unchanged. Tests cover encrypted content, raw-content preservation, and forwarded replay input.

Priority: ➖ Normal

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Bug fix · Severity of issue fixed: Medium

Merge Risk: ⚪ Minimal · up to ffcc1

The PR supplies the required reasoning summaries for translated and replayed inputs without an identified merge-blocking regression.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: adding the required summary to reasoning input items sent to Responses upstreams.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 2 functions across 5 files.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/reasoning-input-summary

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@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: 304ef17a2d

ℹ️ 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/chat/inbound.ts
// Claude ingress (src/claude/inbound.ts), which has always minted both.
input.push({
type: "reasoning",
summary: [{ type: "summary_text", text: reasoningText }],

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Update the owned reasoning contract for the new summary shape

This changes translated Chat replay from a content-only reasoning item to one containing a non-empty summary, and the sanitizer now makes that summary the only text sent to canonical Responses targets, but none of the owning structure documents were updated. In particular, structure/providers/chat-compat.md:247-253 still says raw reasoning remains in content and explicitly records that routing raw chain-of-thought through the summary channel was reverted. Update structure/data-planes/inbound-compat.md and the reasoning-channel invariant to describe this new exception, or keep the raw text out of summary, so the repository's architecture contract no longer contradicts the wire behavior.

AGENTS.md reference: AGENTS.md:L33-L41

Useful? React with 👍 / 👎.

@lidge-jun

Copy link
Copy Markdown
Owner Author

리뷰 · 우선순위 75 / 80

이 PR은 Chat Completions → Responses 변환과 reasoning 입력 살균에서 summary 키가 빠져 업스트림이 Missing required parameter: 'input[N].summary' 로 거절하던 구멍을 막는다. 지금 dev(HEAD 9b711073a, 패키지 2.56.0, 방금 합쳐진 #4671 openai-responses 파사드 이후)의 src/chat/inbound.ts는 assistant 의 reasoning_content/reasoning_details 를 재생할 때 { type: "reasoning", content: [{ type: "reasoning_text", text }] } 만 넣고 summary 를 붙이지 않는다. 로컬 responsesRequestSchema 는 그 필드를 optional 로 두어서 게이트를 통과하고, 실제 Responses 업스트림에서만 400이 난다. Aside/Pi 클라이언트가 /v1/chat/completions 로 프록시에 붙을 때 보고된 증상과 맞다.

고침은 두 층이다. 첫째, chat 인그레스가 Claude 인그레스(src/claude/inbound.ts)처럼 재생된 thinking 을 summary: [{ type: "summary_text", text }]content 양쪽에 담는다. 이유 주석이 중요하다. sanitizeReasoningInputContentpreserveResponsesReasoningContent 가 아닌 목적지에서 content 를 비우기 때문에, summary 가 네이티브 백엔드까지 살아남는 유일한 채널이다. 둘째, 살균 함수 자체가 summary 키가 없는 reasoning 입력 항목에 빈 배열을 넣는다. 목적지와 무관하다. Codex/이 프록시가 내보내는 항목은 이미 summary 를 가지고 있으므로, 키가 없는 항목은 번역된 인그레스나 제3자 클라이언트에서 온 것이고, 빈 배열을 넣는 것은 “빠진 키만 채우는” 형태 교정이다. 이미 summary 가 있으면 그대로 둔다.

파일 위치도 현재 dev 와 맞다. #4671 이후 살균기는 모놀리스가 아니라 src/adapters/openai-responses/reasoning.ts 리프에 있고, 파사드 src/adapters/openai-responses.tssanitizeReasoningInputContent 를 재export 한다. 테스트 import 가 그 파사드를 쓰는 것도 맞다. 회귀는 DeepSeek 살균 스위트(없을 때 채움 / 있을 때 유지 / content 보존 경로에서도 수리)와 chat-inbound 재생 스위트(요약 보유 + 살균 후 content 비고 summary 유지)를 추가했다.

같은 구멍에 대한 열린 PR #4629(Ingwannu, #4628)와 선택이 다르다. #4629 는 wire 를 맞추려고 summary: [] 만 넣고 raw thinking 을 summary_text 로 올리지 않는다(privacy / “raw 를 summary 로 재분류하지 않는다”). 이 PR(#4673) 은 summary 에 텍스트를 넣어 살균 후에도 재생 thinking 이 남게 한다. 빈 summary 만 넣으면 살균 뒤 content·summary 가 둘 다 비어 네이티브 경로에서 재생 내용이 사라질 수 있다. 그래서 “400만 막는가” vs “재생 내용까지 살리는가”가 메인테이너 판단 축이다. types.ts/config.ts 스플릿과 무관하고, close-don't-rebase 대상이 아니다. draft 가 아니고 MERGEABLE, hygiene 계열은 SUCCESS 로 보인다.

라인 / 경로 수준:

라인 ~316 (src/chat/inbound.ts) - 기존은 content 만. 이번엔 summary_text + content. Claude inbound 와 같은 “thinking → summary” 매핑이다. #4629 의 빈 배열 선택과 의도적으로 갈라선다
라인 ~62–70 (src/adapters/openai-responses/reasoning.ts) - missingSummary 가 early-return 조건을 깨고 next.summary = [] 를 넣는다. content blank / status strip 과 독립적으로 동작한다
경로 tests/providers/deepseek-reasoning-replay.test.ts - 첫 equality 기대값에 summary: [] 를 넣었고, 세 개 회귀 테스트가 살균 계약을 핀다
경로 tests/responses/chat-inbound-reasoning-replay.test.ts - 파사드에서 sanitize 를 import 해 “살균 후 summary 생존”을 증명한다. #4671 이후 경로가 맞다
심볼 assistantReasoningText / F6 adjacency - reasoning 이 assistant message 바로 앞에 오는 순서는 그대로다

메인테이너의 판단이 필요한 지점

너의 추천
CI 초록 확인 후 #4673 을 우선 착륙 후보로 둔다. 살균기까지 고쳐서 제3자 omit 재발을 막고, content blanking 이후에도 재생 텍스트가 남는 쪽이 #4629 의 “빈 summary 만”보다 제품 회귀(#4628 계열 도구 연속 턴)에 더 가깝다. 머지하면 #4628 을 닫고 #4629Landed via #4673 / superseded 로 정리하는 것을 권한다. privacy 우려가 크면 머지 코멘트에 “Chat 재생 thinking 은 Claude inbound 와 같이 summary_text 로 실린다”를 한 줄 고정한다. types/config 스플릿 때문에 닫을 PR이 아니다.

이 댓글은 grok-bot이 작성했습니다

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant