Skip to content

fix(proxy): normalize nullable Responses output fields#1220

Merged
ding113 merged 2 commits into
ding113:devfrom
puyujian:codex/fix-responses-null-normalizer
May 28, 2026
Merged

fix(proxy): normalize nullable Responses output fields#1220
ding113 merged 2 commits into
ding113:devfrom
puyujian:codex/fix-responses-null-normalizer

Conversation

@puyujian

@puyujian puyujian commented May 27, 2026

Copy link
Copy Markdown
Contributor

Summary

Normalize nullable fields in non-streaming OpenAI Responses payloads before returning them to clients, ensuring compatibility with the official openai-python SDK parser.

Fixes #1219

Problem

When using CCH as an OpenAI-compatible proxy for /v1/responses, the raw HTTP response can be successful (HTTP 200, status: completed), but clients using the official openai-python Responses API parser fail with:

TypeError: 'NoneType' object is not iterable

This occurs because some upstream providers return null for fields where the OpenAI Responses schema expects arrays, strings, or objects. The proxy passes these null values through unchanged, causing the SDK parser to crash before the caller can read response.output_text.

Solution

Add a response output normalizer (response-output-normalizer.ts) that runs on non-SSE responses after the existing ResponseFixer pipeline, before the response is returned to the client.

Normalization rules applied:

Field Before After
output null []
message.content null []
text (output_text) null ""
annotations null []
logprobs null []
function_call.arguments null or object "{}" or JSON string
tools null []
summary (reasoning) null []

Design decisions:

  • Scoped to originalFormat === "response" only (OpenAI Responses API format)
  • Skipped when bypassResponseRectifier is enabled on the endpoint policy
  • Only processes 2xx JSON responses
  • Preserves usage: null (no normalization needed)
  • Strips transfer-encoding and content-length headers after re-serialization
  • Logs each applied fix for observability

This is the output-side counterpart to the existing request-input rectifier added in #888.

Changes

File Change Purpose
src/app/v1/_lib/proxy/response-output-normalizer.ts Added Core normalizer: payload normalization + response wrapper
src/app/v1/_lib/proxy/response-handler.ts Modified Integrate normalizer before handleNonStream()
tests/unit/proxy/response-output-normalizer.test.ts Added Unit tests for payload and response-level normalization

Testing

  • Unit tests added: normalizeResponseOutputPayload (3 tests) + normalizeResponseOutput (2 tests)
  • Type check passes (bun run typecheck)
  • Lint passes (bun run lint)
  • Build passes (bun run build)
  • Verified with openai==2.24.0 local parser smoke test

Related


Description enhanced by Claude AI

Greptile Summary

This PR adds a ResponseOutputNormalizer that converts null Responses API fields (output, content, text, annotations, logprobs, tools, summary, function.arguments) to their SDK-expected types (empty array, empty string, or serialized JSON), fixing TypeError: 'NoneType' object is not iterable crashes in the openai-python parser when upstream providers return nulls.

  • New file response-output-normalizer.ts: clones and parses the fixed response body, applies targeted null→default coercions, re-serializes, and strips content-encoding/content-length/transfer-encoding headers; gracefully falls back to the original response on parse failure.
  • Integration in ResponseFixer.processNonStream: normalizer runs as the final step after the encoding and JSON fixers, scoped to originalFormat === \"response\" non-SSE 2xx responses.
  • Tests: 9 unit tests covering all normalization paths, format guards, error fallbacks, and header stripping.

Confidence Score: 5/5

Safe to merge; the normalizer is additive, scoped to Responses-format non-streaming responses, and falls back to the original response on any error.

The logic is correct for all cases reviewed: null-to-default coercions, format/status/content-type guards, graceful fallback on JSON parse failure, and correct header cleanup. The one design note — that the normalizer is also skipped when enableResponseFixer is false — is reflected in an explicit test and is an intentional architectural choice, not a defect on the changed path.

No files require special attention, though the coupling between enableResponseFixer and the normalizer in response-fixer/index.ts is worth a second look if the normalizer is ever meant to operate independently.

Important Files Changed

Filename Overview
src/app/v1/_lib/proxy/response-output-normalizer.ts New normalizer: correctly guards on format/status/content-type, handles null→array/string/serialized-JSON conversions, and gracefully falls back on parse failure.
src/app/v1/_lib/proxy/response-fixer/index.ts Integrates normalizer at the tail of processNonStream; the normalizer is silently skipped when enableResponseFixer is false, coupling two independent concerns under one feature flag.
src/app/v1/_lib/proxy/response-handler.ts Adds a comment clarifying that bypassResponseRectifier skips both the fixer and the embedded normalizer; no logic changes.
src/app/v1/_lib/proxy/response-fixer/response-fixer.test.ts Updates disabled-fixer test to use a Responses-format payload and verifies null is preserved; adds a new test confirming normalization fires when the fixer is enabled.
tests/unit/proxy/response-output-normalizer.test.ts Comprehensive unit tests covering payload normalization (null output, nested content parts, function arguments) and response-level behaviour (header stripping, format guard, error fallbacks).

Sequence Diagram

sequenceDiagram
    participant Client
    participant Handler as ProxyResponseHandler
    participant Fixer as ResponseFixer
    participant Normalizer as ResponseOutputNormalizer

    Client->>Handler: "non-streaming request (originalFormat=response)"
    Handler->>Handler: bypassResponseRectifier?
    alt "bypass = true"
        Handler-->>Client: raw upstream response (null fields preserved)
    else "bypass = false"
        Handler->>Fixer: process(session, response)
        Fixer->>Fixer: enableResponseFixer?
        alt fixer disabled
            Fixer-->>Handler: raw response (normalizer also skipped)
        else fixer enabled
            Fixer->>Fixer: EncodingFixer + JsonFixer
            Fixer->>Normalizer: normalizeResponseOutput(session, fixedResponse)
            Normalizer->>Normalizer: "originalFormat === response?"
            Normalizer->>Normalizer: 2xx + JSON content-type?
            Normalizer->>Normalizer: clone + parse body
            Normalizer->>Normalizer: normalizeResponseOutputPayload()
            alt null fields found
                Normalizer-->>Fixer: "new Response (nulls to array/string/{})"
            else no fixes needed
                Normalizer-->>Fixer: original response (identity)
            end
            Fixer-->>Handler: normalizedResponse
        end
        Handler-->>Client: normalized response
    end
Loading
Prompt To Fix All With AI
Fix the following 1 code review issue. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 1
src/app/v1/_lib/proxy/response-fixer/index.ts:217-220
The normalizer is embedded inside `processNonStream`, which is only reached when `enableResponseFixer` is `true`. When the fixer is disabled (`enableResponseFixer: false`), `ResponseFixer.process` returns the raw response at line 219 before `processNonStream` is ever called, so `normalizeResponseOutput` is silently skipped. The two concerns — JSON repair and schema normalization — are independent: a deployment that opts out of the fixer to avoid its overhead will still expose the `TypeError: 'NoneType' object is not iterable` crash for Responses API clients. The test "禁用时应原样透传" now explicitly asserts `output: null` is preserved in this case, confirming the bypass is intentional, but the PR description only mentions `bypassResponseRectifier` as the skip condition, which may mislead operators who disable the fixer for unrelated reasons. Consider calling `normalizeResponseOutput` unconditionally from `ResponseFixer.process` (after the early-return guard) so the normalizer's lifecycle is independent of `enableResponseFixer`.

```suggestion
    const enabled = settings.enableResponseFixer ?? true;
    if (!enabled) {
      return await normalizeResponseOutput(session, response);
    }
```

Reviews (2): Last reviewed commit: "fix(proxy): 完善 Responses 输出归一化边界" | Re-trigger Greptile

@gemini-code-assist

Copy link
Copy Markdown
Contributor

Warning

Gemini encountered an error creating the review. You can try again by commenting /gemini review.

@github-actions github-actions Bot added bug Something isn't working area:provider area:OpenAI labels May 27, 2026
@coderabbitai

coderabbitai Bot commented May 27, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

在代理非流式 /v1/responses 路径新增响应输出归一化模块并在 ResponseFixer 非流式分支集成,修复包含 null 的字段为 SDK 兼容的空数组/空字符串/字符串化参数,同时添加单元测试与一处 dispatch 注释说明 bypass 行为。

变更详情

Responses API 输出字段归一化

Layer / File(s) Summary
响应输出规范化核心实现
src/app/v1/_lib/proxy/response-output-normalizer.ts
新增一组内部工具函数与核心函数,用于判断 JSON content-type、清理响应头、参数字符串化。normalizeResponseOutputPayload 针对 Responses API 格式(object === "response")执行就地修复:将 output: null[],递归规范化数组元素中的 content/text/annotations/logprobsfunction.arguments(非字符串化值序列化为 JSON 字符串),并返回 { payload, applied, fixes }normalizeResponseOutput 作为异步入口,限定触发条件(session.originalFormat === "response"、2xx 状态、JSON content-type),读取并解析响应体,调用规范化逻辑,若有修复则返回带清理后 headers 的新 Response,否则返回原始 Response
非流式 ResponseFixer 集成
src/app/v1/_lib/proxy/response-fixer/index.ts
在非流式修复分支中将原先直接返回的修复 Response 改为先构建 fixedResponse,再调用 normalizeResponseOutput(session, fixedResponse) 获取最终返回值,并新增相应导入。
dispatch 注释(bypass 说明)
src/app/v1/_lib/proxy/response-handler.ts
dispatchbypassResponseRectifier 相关逻辑附近新增注释,说明 raw passthrough 端点会跳过 ResponseFixer 与响应输出归一化。
单元测试覆盖
tests/unit/proxy/response-output-normalizer.test.ts, src/app/v1/_lib/proxy/response-fixer/response-fixer.test.ts
新增/更新测试:覆盖 normalizeResponseOutputPayload(null 字段归一化、顶层 output 为 null、非 response 类型跳过)与 normalizeResponseOutput(session.originalFormat 为 response 时应用、非 response 格式或非 JSON 内容/非 2xx/JSON 解析失败等情况跳过),并在 ResponseFixer 测试中添加非流式场景断言以验证 output 被归一化。

审查总览

本次变更为 /v1/responses 非流式响应添加了输出字段归一化处理,解决 OpenAI Python SDK 在解析包含 null 值的数组/字符串字段时失败的问题。核心改动包括新增规范化模块、集成到代理响应处理流程,以及完整的单元测试覆盖。

审查工作量评估

🎯 3 (中等复杂度) | ⏱️ ~25 分钟

可能相关的 PRs

  • ding113/claude-code-hub#1074: 与本次新增的 JSON 输出归一化在 header 清理/ResponseFixer 层存在交叉(cleanResponseHeaders 相关)。
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: normalizing nullable fields in Responses output to ensure SDK compatibility.
Linked Issues check ✅ Passed The PR fully addresses issue #1219 requirements: normalizes nullable fields (output, content, text, annotations, logprobs, tools, summary, function.arguments) to SDK-compatible defaults, scoped to non-SSE 2xx JSON responses, honors endpoint policy bypass flag, includes unit tests, and validates with openai-python SDK.
Out of Scope Changes check ✅ Passed All changes are directly related to #1219: new normalizer module, integration into response handler, tests, and a comment update. No unrelated modifications detected.
Description check ✅ Passed PR 描述详细阐述了问题、解决方案、规范化规则、设计决策和测试覆盖,与代码变更密切相关。

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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 and usage tips.

@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: 1993f4d52c

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +900 to +902
const normalizedResponse = session.getEndpointPolicy().bypassResponseRectifier
? fixedResponse
: await normalizeResponseOutput(session, fixedResponse);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Honor disabled response fixer for normalization

When the global enableResponseFixer setting is false, ResponseFixer.process() returns the original response, but this newly added call still rewrites successful /v1/responses JSON because it is gated only by bypassResponseRectifier. In deployments that disable the response fixer for compatibility or debugging, nullable fields will still be mutated before the client receives them; gate normalizeResponseOutput on the same cached fixer setting or run it from within ResponseFixer.process().

Useful? React with 👍 / 👎.

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

const result = normalizeResponseOutputPayload(parsed);
if (!result.applied) return response;

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.

[Medium] [TEST-MISSING-CRITICAL] Missing test for applied: false optimization path

Why this is a problem: This early-return path ensures compliant Responses API payloads bypass unnecessary JSON.stringify + new Response() overhead. Without a test, a future refactor could accidentally remove this optimization, impacting every non-streaming Responses API call.

Suggested fix: Add a test in tests/unit/proxy/response-output-normalizer.test.ts:

it("returns the original response when no normalization is needed", async () => {
  const response = new Response(
    JSON.stringify({
      id: "resp_test",
      object: "response",
      status: "completed",
      output: [
        { id: "msg_1", type: "message", role: "assistant", status: "completed", content: [] },
      ],
    }),
    { status: 200, headers: { "content-type": "application/json" } }
  );
  const normalized = await normalizeResponseOutput(createResponseSession(), response);
  expect(normalized).toBe(response);
});

@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

The PR correctly normalizes nullable fields in OpenAI Responses API non-streaming payloads. The core logic is sound and the main happy paths are well-tested. One edge-case test gap in normalizeResponseOutput prevents the new module from fully meeting the project's 80% coverage threshold for defensive early-return paths.

PR Size: M

  • Lines changed: 353
  • Files changed: 3

Issues Found

Category Critical High Medium Low
Logic/Bugs 0 0 0 0
Security 0 0 0 0
Error Handling 0 0 0 0
Types 0 0 0 0
Comments/Docs 0 0 0 0
Tests 0 0 1 0
Simplification 0 0 0 0

Medium Priority Issues (Should Fix)

[TEST-MISSING-CRITICAL] Missing test for applied: false optimization path
src/app/v1/_lib/proxy/response-output-normalizer.ts:174 -- normalizeResponseOutput returns the original response when normalizeResponseOutputPayload finds no fixes. Without a test, this early-return optimization could be accidentally removed, causing all compliant Responses API payloads to be unnecessarily re-stringified and re-wrapped in a new Response object.

Additionally, the following defensive early-return branches in normalizeResponseOutput are also untested and worth covering:

  • Non-2xx status codes (response.status < 200 || response.status >= 300)
  • Non-JSON content types (\!isJsonContentType(contentType))
  • JSON parse failure (JSON.parse throws)

Suggested tests:

it("returns the original response when no normalization is needed", async () => {
  const response = new Response(
    JSON.stringify({
      id: "resp_test",
      object: "response",
      status: "completed",
      output: [
        { id: "msg_1", type: "message", role: "assistant", status: "completed", content: [] },
      ],
    }),
    { status: 200, headers: { "content-type": "application/json" } }
  );
  const normalized = await normalizeResponseOutput(createResponseSession(), response);
  expect(normalized).toBe(response);
});

it("skips normalization for non-2xx responses", async () => {
  const response = new Response('{"object":"response"}', { status: 500, headers: { "content-type": "application/json" } });
  const normalized = await normalizeResponseOutput(createResponseSession(), response);
  expect(normalized).toBe(response);
});

it("skips normalization for non-JSON content types", async () => {
  const response = new Response("text", { status: 200, headers: { "content-type": "text/plain" } });
  const normalized = await normalizeResponseOutput(createResponseSession(), response);
  expect(normalized).toBe(response);
});

it("returns the original response when JSON parsing fails", async () => {
  const response = new Response("not json", { status: 200, headers: { "content-type": "application/json" } });
  const normalized = await normalizeResponseOutput(createResponseSession(), response);
  expect(normalized).toBe(response);
});

Review Coverage

  • Logic and correctness - Clean
  • Security (OWASP Top 10) - Clean
  • Error handling - Clean (silent JSON.parse falls follow existing codebase pattern)
  • Type safety - Clean
  • Documentation accuracy - Clean
  • Test coverage - Adequate for critical paths, edge case tests recommended
  • Code clarity - Good

Automated review by Claude AI

Comment on lines +20 to +25
function cleanResponseHeaders(headers: Headers): Headers {
const cleaned = new Headers(headers);
cleaned.delete("transfer-encoding");
cleaned.delete("content-length");
return cleaned;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 The re-serialized response body is always plain UTF-8 JSON, but cleanResponseHeaders only removes transfer-encoding and content-length. If any middleware in the proxy chain preserves a content-encoding: gzip (or br/deflate) header on a body that was already decompressed by the HTTP layer, the client would try to decompress plain JSON and fail. Adding a content-encoding delete here makes the header set consistent with the actual body.

Suggested change
function cleanResponseHeaders(headers: Headers): Headers {
const cleaned = new Headers(headers);
cleaned.delete("transfer-encoding");
cleaned.delete("content-length");
return cleaned;
}
function cleanResponseHeaders(headers: Headers): Headers {
const cleaned = new Headers(headers);
cleaned.delete("transfer-encoding");
cleaned.delete("content-length");
cleaned.delete("content-encoding");
return cleaned;
}
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/app/v1/_lib/proxy/response-output-normalizer.ts
Line: 20-25

Comment:
The re-serialized response body is always plain UTF-8 JSON, but `cleanResponseHeaders` only removes `transfer-encoding` and `content-length`. If any middleware in the proxy chain preserves a `content-encoding: gzip` (or `br`/`deflate`) header on a body that was already decompressed by the HTTP layer, the client would try to decompress plain JSON and fail. Adding a `content-encoding` delete here makes the header set consistent with the actual body.

```suggestion
function cleanResponseHeaders(headers: Headers): Headers {
  const cleaned = new Headers(headers);
  cleaned.delete("transfer-encoding");
  cleaned.delete("content-length");
  cleaned.delete("content-encoding");
  return cleaned;
}
```

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines 899 to 904
if (!isSSE) {
return await ProxyResponseHandler.handleNonStream(session, fixedResponse);
const normalizedResponse = session.getEndpointPolicy().bypassResponseRectifier
? fixedResponse
: await normalizeResponseOutput(session, fixedResponse);
return await ProxyResponseHandler.handleNonStream(session, normalizedResponse);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 bypassResponseRectifier now silently controls two independent transforms — the response fixer (a few lines above) and this new output normalizer. A future developer reading only this block may not realise the flag has dual responsibility and either sets it incorrectly or strips the normalizer call when refactoring rectifier logic. A short inline comment captures the intent.

Suggested change
if (!isSSE) {
return await ProxyResponseHandler.handleNonStream(session, fixedResponse);
const normalizedResponse = session.getEndpointPolicy().bypassResponseRectifier
? fixedResponse
: await normalizeResponseOutput(session, fixedResponse);
return await ProxyResponseHandler.handleNonStream(session, normalizedResponse);
}
if (!isSSE) {
// bypassResponseRectifier also gates output normalization so that
// raw-passthrough endpoints (e.g. /responses/compact) are untouched.
const normalizedResponse = session.getEndpointPolicy().bypassResponseRectifier
? fixedResponse
: await normalizeResponseOutput(session, fixedResponse);
return await ProxyResponseHandler.handleNonStream(session, normalizedResponse);
}
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/app/v1/_lib/proxy/response-handler.ts
Line: 899-904

Comment:
`bypassResponseRectifier` now silently controls two independent transforms — the response fixer (a few lines above) and this new output normalizer. A future developer reading only this block may not realise the flag has dual responsibility and either sets it incorrectly or strips the normalizer call when refactoring rectifier logic. A short inline comment captures the intent.

```suggestion
    if (!isSSE) {
      // bypassResponseRectifier also gates output normalization so that
      // raw-passthrough endpoints (e.g. /responses/compact) are untouched.
      const normalizedResponse = session.getEndpointPolicy().bypassResponseRectifier
        ? fixedResponse
        : await normalizeResponseOutput(session, fixedResponse);
      return await ProxyResponseHandler.handleNonStream(session, normalizedResponse);
    }
```

How can I resolve this? If you propose a fix, please make it concise.

@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/response-output-normalizer.ts`:
- Around line 16-18: The isJsonContentType function currently does
case-sensitive checks; normalize the input first (e.g., const ct = (contentType
|| "").toLowerCase()) and then perform includes("application/json") ||
includes("+json") to correctly detect JSON regardless of header casing; update
the isJsonContentType function to use this normalized ct variable so calls like
isJsonContentType("Application/JSON") return true.
🪄 Autofix (Beta)

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

Run ID: 1b2124c7-cdcb-4358-98a9-d622e13b3469

📥 Commits

Reviewing files that changed from the base of the PR and between e0d0978 and 1993f4d.

📒 Files selected for processing (3)
  • src/app/v1/_lib/proxy/response-handler.ts
  • src/app/v1/_lib/proxy/response-output-normalizer.ts
  • tests/unit/proxy/response-output-normalizer.test.ts

Comment thread src/app/v1/_lib/proxy/response-output-normalizer.ts

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

🧹 Nitpick comments (1)
src/app/v1/_lib/proxy/response-fixer/index.ts (1)

7-7: ⚡ Quick win

请改为使用 @/ 路径别名导入。

这里新增的是相对路径导入,建议改为 @/ 别名路径以符合仓库约定。

As per coding guidelines **/*.{ts,tsx,js,jsx}: Use path alias @/ to map to ./src/ for imports.

🤖 Prompt for 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.

In `@src/app/v1/_lib/proxy/response-fixer/index.ts` at line 7, 当前在
src/app/v1/_lib/proxy/response-fixer/index.ts 中用相对路径导入
normalizeResponseOutput;请将 import { normalizeResponseOutput } from
"../response-output-normalizer" 替换为使用仓库约定的别名导入,如 import {
normalizeResponseOutput } from
"`@/app/v1/_lib/proxy/response-output-normalizer`"(保持符号名 normalizeResponseOutput
不变)。
🤖 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.

Nitpick comments:
In `@src/app/v1/_lib/proxy/response-fixer/index.ts`:
- Line 7: 当前在 src/app/v1/_lib/proxy/response-fixer/index.ts 中用相对路径导入
normalizeResponseOutput;请将 import { normalizeResponseOutput } from
"../response-output-normalizer" 替换为使用仓库约定的别名导入,如 import {
normalizeResponseOutput } from
"`@/app/v1/_lib/proxy/response-output-normalizer`"(保持符号名 normalizeResponseOutput
不变)。

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 1e611435-a95c-4ebe-b015-91174b8128e4

📥 Commits

Reviewing files that changed from the base of the PR and between 1993f4d and 6640f4b.

📒 Files selected for processing (5)
  • src/app/v1/_lib/proxy/response-fixer/index.ts
  • src/app/v1/_lib/proxy/response-fixer/response-fixer.test.ts
  • src/app/v1/_lib/proxy/response-handler.ts
  • src/app/v1/_lib/proxy/response-output-normalizer.ts
  • tests/unit/proxy/response-output-normalizer.test.ts
✅ Files skipped from review due to trivial changes (1)
  • src/app/v1/_lib/proxy/response-handler.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/app/v1/_lib/proxy/response-output-normalizer.ts

@ding113
ding113 merged commit 833c5ec into ding113:dev May 28, 2026
2 checks passed
@github-project-automation github-project-automation Bot moved this from Backlog to Done in Claude Code Hub Roadmap May 28, 2026
@github-actions github-actions Bot mentioned this pull request May 28, 2026
ding113 added a commit that referenced this pull request May 28, 2026
* chore: 将 Codex 测试模型升级到 GPT-5.5 (#1221)

Co-authored-by: tesgth032 <tesgth032@users.noreply.github.com>

* feat(anthropic): detect actual model from thinking signature in stream (#1223)

* feat(anthropic): detect model from thinking signature in stream

Use the thinking signature protobuf embedded in Anthropic stream
content_block_delta events to extract the actual model name. This is
more accurate than the message_start plain text when thinking is on.

Three-state detection:
- signature found → use model from signature
- no signature with thinking on → fall back to plain model, warn via UI
- no signature without thinking → plain model (normal)

Add a UI badge and i18n labels for five locales when thinking was
enabled but no signature arrived.

* fix(proxy): drop claude- prefix gate and validate thinking signature model

Remove the requestedModel prefix check so the detection works with
model-redirect, Bedrock, and Vertex providers. Validate signature-decoded
model names for length (≤128 chars) and presence of "claude" to reject
spoofed payloads.

Also fix several related issues:
- Accept both standard and URL-safe base64 alphabets, and correct the
  length validation to only reject remainder 1.
- Show the no-signature badge in the UI even when no model field is
  present.
- Return fallback_no_thinking for empty streams to avoid false
  no-signature alerts.
- Record the truthful thinkingEnabled value in audit logs instead of a
  source-derived heuristic.
- Fix SSE chunk joining in tests to ensure proper event boundary blank
  lines.
- Correct full-width punctuation in zh-CN and zh-TW tooltip strings.

* fix(proxy): strip base64 padding and use @/ imports

- Strip trailing padding from base64 input before checking invalid
  length, so that padded strings like "xxxxx=" are correctly rejected.
- Convert relative imports to @/ path aliases for consistency.
- Update test fixture to encode protobuf length fields with varint.

* fix(proxy): skip thinking-signature detection for non-Anthropic models

Restores a guard on the requested model family (claude- or anthropic/
prefix) so that providers using the Anthropic-compatible API path but
serving non-Anthropic models (e.g. GLM) are not incorrectly classified.
Without this gate, those providers could produce a
fallback_no_signature_with_thinking source even though they never emit
thinking signatures.

Drops the requirement that the signature-decoded model name contain
"claude", leaving only the length check (<= 128 chars). This allows
future Anthropic model families that may drop the "claude-" prefix.

Fixes import order in response-handler.ts after lint:fix.

* fix(proxy): normalize nullable Responses output fields (#1220)

* fix(proxy): 规范化 Responses 输出空值

* fix(proxy): 完善 Responses 输出归一化边界

* fix: return full self user list shape (#1213)

* fix: return full self user list shape

* fix: target self user display loading

* chore: remove unreachable self user guard

* 优化公开状态页 Redis 聚合与轮询性能 (#1211)

* 优化公开状态页 Redis 聚合与轮询性能

* 修复公开状态页 rollup 边界问题

* 完善公开状态页 rollup 可靠性

* 完善公开状态页轮询测试清理

* 完善公开状态页轮询测试清理

---------

Co-authored-by: tesgth032 <tesgth032@users.noreply.github.com>

---------

Co-authored-by: tesgth032 <tesgth032@hotmail.com>
Co-authored-by: tesgth032 <tesgth032@users.noreply.github.com>
Co-authored-by: puyujian <46592377+puyujian@users.noreply.github.com>
Co-authored-by: Clouder <Clouder0@outlook.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:OpenAI area:provider bug Something isn't working size/M Medium PR (< 500 lines)

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

2 participants