Skip to content

fix(proxy): recognize terminal Responses compaction streams (#1410) - #1411

Merged
ding113 merged 2 commits into
devfrom
fix/1410-stream-gate-compaction
Aug 10, 2026
Merged

fix(proxy): recognize terminal Responses compaction streams (#1410)#1411
ding113 merged 2 commits into
devfrom
fix/1410-stream-gate-compaction

Conversation

@ding113

@ding113 ding113 commented Aug 10, 2026

Copy link
Copy Markdown
Owner

问题

OpenAI Responses API 的合法流不一定包含 response.output_text.delta。当上游只在 terminal response.completedresponse.output[] 中返回 type: "compaction" 和非空 encrypted_content 时, stream gate 会先看到 terminal 而没有识别到有效内容, 最终以 empty_stream 返回 502, 并错误触发 provider failover 与 circuit breaker failure recording.

response.custom_tool_call_input.delta / .done 在当前 dev 已有非空内容识别规则, 但此前缺少覆盖 #1410 实际 Replay owner 与 forwarder 路径的直接回归测试.

Fixes #1410

修改

  • 将 classifier 的 protocol family 传入 parsed-frame 判定, 仅为 openai-responses 启用 terminal compaction 识别.
  • response.completedresponse.output[] 逐项检查, 只在同一个 item 同时满足 type === "compaction"encrypted_content 非空时判为 content.
  • response.output_item.doneresponse.completed 共享同一个 exact predicate, encrypted_content 只接受官方 schema 定义的非空 string; boolean、number、array/object 不再误提交.
  • 保持 error rule 优先级、其他 protocol family、空密文、非 compaction item 和普通 terminal-only 流的既有行为.
  • 补充 classifier、content gate 和 ProxyForwarder 集成回归, 覆盖 compaction-only terminal stream 与 custom tool-call input stream.

Replay owner 语义

本 PR 不移除 Replay owner 的 precommit gate。stream_gate_mode=off 仍只关闭普通请求 gate; Replay owner 继续执行 pre-content safety check, 防止错误或畸形前缀进入共享 Replay spool 并污染 attach follower.

修复后的行为是: 即使 stream_gate_mode=off 且请求为 Replay owner, 合法 terminal compaction 与 custom tool-call input stream 也会被识别为有效内容, 原样返回 HTTP 200, 不发生 502、failover 或 circuit breaker failure recording.

验收

  • 聚焦回归: 3 files, 92 tests passed.
  • bun run lint:fix: passed.
  • bun run lint: passed.
  • bun run typecheck: passed.
  • bun run build: passed.
  • git diff --check: passed.
  • 完整 Vitest: 首次并发运行在无关 keys-edit-key-expires-at-clear.test.ts 出现 2 个 flaky failures; 该文件单独重跑 6/6 passed; 修复前完整复验为 860 files / 8453 tests passed; review 修复后完整复验为 860 files / 8454 tests passed, 0 failures, exit 0.
  • 独立规格、正确性与 review-fix 复审: No findings.
  • CodeRabbit 的 encrypted_content 类型契约 finding 已按官方 OpenAI API schema 修复并补 TDD 负例.

结构化验收报告:

风险边界

本次没有单独新增 hedge-path 的 compaction fixture、同帧 response.error + compaction 组合或无 SSE event: 行组合。实现仍通过共享 content gate、embedded data.type 推导和 error-first 顺序覆盖这些路径; 独立审查未发现缺陷.

Greptile Summary

The PR updates the OpenAI Responses stream classifier to recognize non-empty compaction payloads in both output-item and terminal completion frames.

  • Passes the protocol family into parsed-frame classification so the new behavior remains isolated to OpenAI Responses.
  • Preserves error-first classification and requires compaction type and encrypted content to coexist in the same output item.
  • Adds classifier, content-gate, sequential-forwarder, and Replay-owner regression coverage for compaction and custom tool-call input streams.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
src/app/v1/_lib/proxy/stream-gate/frame-classifier.ts Adds family-scoped recognition of non-empty Responses compaction payloads while retaining error-rule precedence and existing classifications.
tests/unit/proxy/stream-gate-content-gate.test.ts Adds direct gate regressions for terminal compaction and custom tool-call input content.
tests/unit/proxy/stream-gate-forwarder-integration.test.ts Verifies sequential and Replay-owner forwarding succeeds without failover or circuit-breaker failure recording.
tests/unit/proxy/stream-gate-frame-classifier.test.ts Covers accepted compaction shapes and rejects empty, mismatched, and non-string encrypted content.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
  A[OpenAI Responses SSE frame] --> B[Parse event name and data]
  B --> C{Error rule matches?}
  C -->|Yes| D[Classify as error]
  C -->|No| E{Compaction item with non-empty encrypted content?}
  E -->|Yes| F[Classify as content and commit stream]
  E -->|No| G{Existing content rule matches?}
  G -->|Yes| F
  G -->|No| H{Terminal event?}
  H -->|Yes| I[Classify as terminal]
  H -->|No| J[Classify as neutral]
Loading

Reviews (2): Last reviewed commit: "fix(stream-gate): require non-empty stri..." | Re-trigger Greptile

Context used:

Some Responses upstreams return compaction output only in
response.completed without first emitting response.output_item.done,
so the content gate treated the stream as empty and triggered false
failover with circuit-breaking.

classifyParsedFrame now accepts the protocol family and, for
openai-responses, detects a compaction output item with non-empty
encrypted_content inside response.completed, classifying the frame
as content so the gate commits the stream.

Regression tests cover compaction-only terminal frames and custom
tool-call input deltas across the classifier, content gate, and
forwarder integration paths.

Fixes #1410
@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

本次修改扩展 OpenAI Responses 流内容分类,识别 compaction 完成帧和 custom tool-call 输入增量,并补充内容门、转发及 Replay owner 模式测试。

Changes

OpenAI Responses 流网关

Layer / File(s) Summary
Responses 内容分类逻辑
src/app/v1/_lib/proxy/stream-gate/frame-classifier.ts
分类调用链传递协议族参数。response.output_item.doneresponse.completed 中存在非空 compaction.encrypted_content 时,分类结果为 content
分类器行为验证
tests/unit/proxy/stream-gate-frame-classifier.test.ts
测试覆盖非空和空 custom tool-call 输入,以及 compaction、推理项、空 compaction 和非字符串压缩内容的分类结果。
内容门和转发集成验证
tests/unit/proxy/stream-gate-content-gate.test.ts, tests/unit/proxy/stream-gate-forwarder-integration.test.ts
测试验证有效 Responses 流触发提交并保留后续完成帧。enforce 和 Replay owner 测试验证流保持成功转发,且不触发 failover 或熔断记录。

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Linked Issues check ✅ Passed 实现覆盖了 #1410 的 compaction、custom tool-call、Replay owner 及错误优先级要求,并补充了对应回归测试。
Out of Scope Changes check ✅ Passed 代码和测试变更均围绕 #1410 的 Responses 流内容识别及 Replay owner 行为,没有发现无关修改。
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Title check ✅ Passed 标题准确概括了修复内容,即识别终止的 Responses compaction 流,且表述简洁明确。
Description check ✅ Passed 描述详细说明了问题、实现、测试、Replay owner 语义和风险边界,与变更内容直接相关。
✨ 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 fix/1410-stream-gate-compaction

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.

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/app/v1/_lib/proxy/stream-gate/frame-classifier.ts`:
- Around line 418-422: 将 frame-classifier.ts 中 response.completed 的 compaction
encrypted_content 校验改为仅接受非空字符串,并在 response.output_item.done 使用相同约束,替换
isNonEmptyValue 的宽泛判断;在 tests/unit/proxy/stream-gate-frame-classifier.test.ts
的相关测试中补充 true、数字、对象等非字符串值,断言其分类为 terminal 或 neutral。
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: ec92618c-a1f6-4346-a891-1ad2bc0cdc45

📥 Commits

Reviewing files that changed from the base of the PR and between 1ac2b8f and 40dbb56.

📒 Files selected for processing (4)
  • src/app/v1/_lib/proxy/stream-gate/frame-classifier.ts
  • tests/unit/proxy/stream-gate-content-gate.test.ts
  • tests/unit/proxy/stream-gate-forwarder-integration.test.ts
  • tests/unit/proxy/stream-gate-frame-classifier.test.ts

Comment thread src/app/v1/_lib/proxy/stream-gate/frame-classifier.ts Outdated
@github-actions

Copy link
Copy Markdown
Contributor

🧪 测试结果

测试类型 状态
代码质量
单元测试
集成测试
API 测试

总体结果: ✅ 所有测试通过

@github-actions github-actions Bot added the size/M Medium PR (< 500 lines) label Aug 10, 2026

@github-actions github-actions Bot 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.

Code Review Summary

No significant issues identified in this PR.

PR Size: M

  • Lines changed: 209
  • Files changed: 4

Review Coverage

  • Logic and correctness - Clean
  • Security (OWASP Top 10) - Clean
  • Error handling - Clean
  • Type safety - Clean
  • Documentation accuracy - Clean
  • Test coverage - Adequate
  • Code clarity - Good

Automated review by Codex AI

@github-actions github-actions Bot 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.

  • Reviewed PR #1411 across the modified lines in src/app/v1/_lib/proxy/stream-gate/frame-classifier.ts:359, tests/unit/proxy/stream-gate-content-gate.test.ts:257, tests/unit/proxy/stream-gate-forwarder-integration.test.ts:227, and tests/unit/proxy/stream-gate-frame-classifier.test.ts:273; I did not find any issues that cleared the reporting threshold.
  • Applied the size/M label and posted the required PR review summary with no inline defect comments.
  • I also attempted focused Vitest validation, but this runner does not have bun/bunx or node_modules available, so the review outcome is based on full diff/context analysis rather than local execution.

…ontent

The compaction signal rule on response.output_item.done accepted any
truthy encrypted_content value, allowing non-string types (booleans,
numbers, objects) to be misclassified as content. Consolidate the
per-item type guard into isNonEmptyCompactionItem and apply it to both
response.output_item.done and response.completed paths so the opaque
state must be a non-empty string before a frame is committed as
content.

Add regression tests covering malformed encrypted_content types across
event variants.
@github-actions

Copy link
Copy Markdown
Contributor

🧪 测试结果

测试类型 状态
代码质量
单元测试
集成测试
API 测试

总体结果: ✅ 所有测试通过

@ding113
ding113 merged commit 3fe3225 into dev Aug 10, 2026
11 checks passed
@github-project-automation github-project-automation Bot moved this from Backlog to Done in Claude Code Hub Roadmap Aug 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/M Medium PR (< 500 lines)

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

1 participant