feat(anthropic): detect actual model from thinking signature in stream#1223
Conversation
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.
There was a problem hiding this comment.
Code Review
This pull request introduces Anthropic thinking signature model detection, which extracts the actual response model from the protobuf payload inside the signature_delta stream event. It includes fallback mechanisms when the signature is missing or thinking is disabled, adds a 'No thinking signature' warning badge in the dashboard UI, and provides comprehensive unit tests and localization updates. The reviewer feedback suggests relaxing the strict Base64 length validation in thinking-signature-model.ts to support unpadded Base64 strings, preventing potential decoding failures if padding is stripped by proxies.
| // base64 长度必须是 4 的倍数(去 padding 后允许 2/3 位余数,标准 base64 一般要 padding) | ||
| if (trimmed.length % 4 !== 0) throw new Error("invalid base64 length"); |
There was a problem hiding this comment.
强制要求 trimmed.length % 4 === 0 过于严格。在实际应用中,Base64 字符串有时会丢失末尾的填充字符 =(例如被某些中转代理或客户端剥离)。强制要求 4 的倍数会导致这些合法但未填充的 Base64 签名解码失败。
我们可以通过检查去除填充字符后的实际长度来同时支持有填充和无填充的 Base64(在 Base64 编码中,有效字符长度除以 4 的余数不能为 1)。
| // base64 长度必须是 4 的倍数(去 padding 后允许 2/3 位余数,标准 base64 一般要 padding) | |
| if (trimmed.length % 4 !== 0) throw new Error("invalid base64 length"); | |
| // 支持有填充和无填充的 base64(有效长度除以 4 的余数不能为 1) | |
| const unpaddedLength = trimmed.replace(/=+$/, ""); | |
| if (unpaddedLength.length % 4 === 1) throw new Error("invalid base64 length"); |
📝 WalkthroughWalkthrough新增 Anthropic 流式 thinking signature 的严格 protobuf 解码与流中提取,按签名/启用思考/未启用思考三态确定实际响应模型,将检测结果写入特殊设置,并在错误详情 UI 显示“无签名”徽标;同时补充类型、i18n 文案与测试覆盖。 变更详情思考签名检测与展示完整流程
代码评审工作量估算🎯 3 (中等) | ⏱️ ~25 分钟 可能相关的 PR
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: af56afc808
ℹ️ 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".
| const anthropicModelDetection = resolveAnthropicStreamActualResponseModel({ | ||
| providerType: provider.providerType, | ||
| requestedModel: currentRequestedModel, | ||
| thinkingEnabled: isThinkingEnabled(session.request.message), |
There was a problem hiding this comment.
Use the forwarded body for thinking detection
When a final-phase body request filter changes thinking (for example an advanced remove or set op on the thinking path), this reads the stale session.request.message instead of the body that was actually serialized to the upstream; request-filter-engine.applyFinal explicitly mutates the cloned body passed from the forwarder, not the session. In that configuration an Anthropic request can be sent with thinking disabled/removed, but this branch still records fallback_no_signature_with_thinking and shows the “No thinking signature” badge even though the upstream correctly omitted a signature.
Useful? React with 👍 / 👎.
| // base64 长度必须是 4 的倍数(去 padding 后允许 2/3 位余数,标准 base64 一般要 padding) | ||
| if (trimmed.length % 4 !== 0) throw new Error("invalid base64 length"); |
There was a problem hiding this comment.
The
% 4 !== 0 check is too strict — it rejects valid unpadded base64 strings (lengths ≡ 2 or 3 mod 4). Standard base64 without = padding is common in API responses; e.g., a 6-char signature like "dGVzdA" (encoding 4 bytes) is well-formed but would be silently rejected here and silently fall back to the message_start model. Only a length ≡ 1 mod 4 is always invalid (6 bits cannot encode a full byte).
| // base64 长度必须是 4 的倍数(去 padding 后允许 2/3 位余数,标准 base64 一般要 padding) | |
| if (trimmed.length % 4 !== 0) throw new Error("invalid base64 length"); | |
| // length ≡ 1 (mod 4) is always invalid (6 bits < 8 bits needed for one byte); | |
| // lengths ≡ 0/2/3 are all valid padded or unpadded base64. | |
| if (trimmed.length % 4 === 1) throw new Error("invalid base64 length"); |
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/app/v1/_lib/proxy/thinking-signature-model.ts
Line: 95-96
Comment:
The `% 4 !== 0` check is too strict — it rejects valid *unpadded* base64 strings (lengths ≡ 2 or 3 mod 4). Standard base64 without `=` padding is common in API responses; e.g., a 6-char signature like `"dGVzdA"` (encoding 4 bytes) is well-formed but would be silently rejected here and silently fall back to the `message_start` model. Only a length ≡ 1 mod 4 is always invalid (6 bits cannot encode a full byte).
```suggestion
// length ≡ 1 (mod 4) is always invalid (6 bits < 8 bits needed for one byte);
// lengths ≡ 0/2/3 are all valid padded or unpadded base64.
if (trimmed.length % 4 === 1) throw new Error("invalid base64 length");
```
How can I resolve this? If you propose a fix, please make it concise.Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
| source: anthropicModelDetection.source, | ||
| extractedModel: anthropicModelDetection.actualResponseModel, | ||
| signatureFound: anthropicModelDetection.source === "signature", | ||
| thinkingEnabled: anthropicModelDetection.source !== "fallback_no_thinking", |
There was a problem hiding this comment.
thinkingEnabled stored in the special setting is derived from source !== "fallback_no_thinking" rather than from the actual request state. This diverges when isThinkingEnabled(session.request.message) = false but a signature_delta is present in the stream (source becomes "signature"), causing the stored thinkingEnabled to be true while the request never enabled thinking — the field's doc comment says it should be "由 isThinkingEnabled(session.request.message) 派生". Future consumers reading this audit field to determine whether thinking was requested would see incorrect data in that edge case. Consider using isThinkingEnabled(session.request.message) directly here.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/app/v1/_lib/proxy/response-handler.ts
Line: 2559
Comment:
`thinkingEnabled` stored in the special setting is derived from `source !== "fallback_no_thinking"` rather than from the actual request state. This diverges when `isThinkingEnabled(session.request.message) = false` but a `signature_delta` is present in the stream (source becomes `"signature"`), causing the stored `thinkingEnabled` to be `true` while the request never enabled thinking — the field's doc comment says it should be "由 `isThinkingEnabled(session.request.message)` 派生". Future consumers reading this audit field to determine whether thinking was *requested* would see incorrect data in that edge case. Consider using `isThinkingEnabled(session.request.message)` directly here.
How can I resolve this? If you propose a fix, please make it concise.
🧪 测试结果
总体结果: ✅ 所有测试通过 |
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 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/anthropic-actual-response-model.ts`:
- Around line 22-23: Update the two imports in
anthropic-actual-response-model.ts to use the project path alias instead of
relative paths: replace the relative imports for extractActualResponseModel and
extractThinkingSignatureModelFromStream with alias imports (e.g., starting with
`@/`) so they import from the proxy module via the '`@/app/v1/_lib/proxy/`...'
alias; ensure the symbol names extractActualResponseModel and
extractThinkingSignatureModelFromStream remain unchanged and that import paths
match the project's tsconfig/jsconfig alias mapping.
In `@src/app/v1/_lib/proxy/response-handler.ts`:
- Around line 45-48: The import in response-handler.ts uses a relative path for
the module providing isThinkingEnabled and
resolveAnthropicStreamActualResponseModel; update the import to use the project
path alias so it reads from
"`@/app/v1/_lib/proxy/anthropic-actual-response-model`" instead of the relative
"./anthropic-actual-response-model" thereby following the repository import
guideline and keeping references to isThinkingEnabled and
resolveAnthropicStreamActualResponseModel intact.
- Around line 2551-2560: The persisted thinkingEnabled value is computed
incorrectly from anthropicModelDetection.source (source !==
"fallback_no_thinking"), which can mark thinking as enabled when the source is
"signature" even if the request did not enable thinking; update the
session.addSpecialSetting call in response-handler.ts to use the original
runtime boolean that indicates whether thinking was enabled (the same flag
passed to or used when invoking the detection logic) instead of deriving it from
anthropicModelDetection.source (i.e., replace the computed expression for
thinkingEnabled with the actual isThinking/enabledThinking variable used at the
call site).
In `@src/app/v1/_lib/proxy/thinking-signature-model.ts`:
- Line 14: Replace the relative import of extractJsonChunks from
"./actual-response-model" with the repository path-alias import
"`@/app/v1/_lib/proxy/actual-response-model`"; update the import statement that
references extractJsonChunks so it uses the `@/` alias (keeping the same exported
symbol name) to comply with the TS import convention.
🪄 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: 31c896c6-c67e-4037-aabb-2a30a899daa0
📒 Files selected for processing (14)
messages/en/dashboard.jsonmessages/ja/dashboard.jsonmessages/ru/dashboard.jsonmessages/zh-CN/dashboard.jsonmessages/zh-TW/dashboard.jsonsrc/app/[locale]/dashboard/logs/_components/error-details-dialog/components/SummaryTab.tsxsrc/app/v1/_lib/proxy/actual-response-model.tssrc/app/v1/_lib/proxy/anthropic-actual-response-model.tssrc/app/v1/_lib/proxy/response-handler.tssrc/app/v1/_lib/proxy/thinking-signature-model.tssrc/lib/utils/special-settings.tssrc/types/special-settings.tstests/unit/proxy/anthropic-actual-response-model.test.tstests/unit/proxy/thinking-signature-model.test.ts
| throw new Error("invalid base64 alphabet"); | ||
| } | ||
| // base64 长度必须是 4 的倍数(去 padding 后允许 2/3 位余数,标准 base64 一般要 padding) | ||
| if (trimmed.length % 4 !== 0) throw new Error("invalid base64 length"); |
There was a problem hiding this comment.
[High] [LOGIC-BUG] base64 length check rejects valid unpadded base64
Why this is a problem: The check trimmed.length % 4 \!== 0 rejects valid unpadded base64 strings (lengths that are 2 or 3 mod 4). Standard base64 without trailing = padding is common in API responses - for example, a 6-char signature like "dGVzdA" (encoding 4 bytes of "test") is well-formed but would be silently rejected here, causing the decoder to fall back to the message_start model name instead of using the signature.
Only a length congruent to 1 mod 4 is always invalid (6 bits cannot encode a full byte).
The inline comment also contradicts the code: it says the decoder allows 2/3 remainder after removing padding, but the code does NOT allow those remainders.
Suggested fix:
// Only length === 1 (mod 4) is always invalid; lengths 0/2/3 are valid padded or unpadded base64.
if (trimmed.length % 4 === 1) throw new Error("invalid base64 length");| source: anthropicModelDetection.source, | ||
| extractedModel: anthropicModelDetection.actualResponseModel, | ||
| signatureFound: anthropicModelDetection.source === "signature", | ||
| thinkingEnabled: anthropicModelDetection.source !== "fallback_no_thinking", |
There was a problem hiding this comment.
[High] [LOGIC-BUG] thinkingEnabled audit field derived from source instead of request state
Why this is a problem: The thinkingEnabled field stored in the special setting is derived from anthropicModelDetection.source \!== "fallback_no_thinking" (i.e., it is true whenever source is "signature" or "fallback_no_signature_with_thinking"). However, the type doc comment on ThinkingSignatureModelDetectionSpecialSetting says this field should record whether thinking was requested: "由 isThinkingEnabled(session.request.message) 派生". This diverges in the edge case where a signature_deltais present in the stream but the request never enabled thinking (source becomes"signature", so thinkingEnabledis recorded astrue`). Future consumers reading this audit field to determine whether thinking was requested would see incorrect data.
The correct value is already computed 11 lines above as isThinkingEnabled(session.request.message) (passed into resolveAnthropicStreamActualResponseModel).
Suggested fix:
thinkingEnabled: isThinkingEnabled(session.request.message),There was a problem hiding this comment.
Code Review Summary
This PR adds a three-state model detection pipeline for Anthropic streaming responses via protobuf parsing of signature_delta events. The implementation is well-structured with clean separation of concerns, thorough null-safety, and good test coverage (37 new tests). Two logic issues were identified that should be addressed before merge.
PR Size: L
- Lines changed: 895 (881 additions, 14 deletions)
- Files changed: 14
Issues Found
| Category | Critical | High | Medium | Low |
|---|---|---|---|---|
| Logic/Bugs | 0 | 2 | 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 | 0 | 0 |
| Simplification | 0 | 0 | 0 | 0 |
High Priority Issues (Should Fix)
-
base64 length validation too strict (
thinking-signature-model.ts:96)
trimmed.length % 4 \!== 0rejects valid unpadded base64 (lengths 2 or 3 mod 4). Should betrimmed.length % 4 === 1to only reject truly invalid lengths. The adjacent comment correctly states "2/3 remainder allowed" but the code contradicts this. -
thinkingEnabledaudit field derived fromsourceinstead of request (response-handler.ts:2559)
The field recordssource \!== "fallback_no_thinking"instead of the actualisThinkingEnabled(session.request.message)value. When a signature is present but thinking was not requested, the audit field incorrectly reportsthinkingEnabled: true. The correct value is already computed on line 2548 and should be reused directly.
Review Coverage
- Logic and correctness
- Security (OWASP Top 10)
- Error handling
- Type safety
- Documentation accuracy
- Test coverage
- Code clarity
Automated review by Claude AI
…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.
There was a problem hiding this comment.
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 `@tests/unit/proxy/anthropic-actual-response-model.test.ts`:
- Around line 37-43: The helper function buildSignatureBase64ForModel constructs
length bytes using single-byte values which breaks protobuf varint encoding for
lengths >=128; change it to encode utf8.length, terminal.length, and
middle.length using proper protobuf varint encoding (e.g., a small
encodeVarint(n) that emits one or more bytes with 7-bit payload and MSB
continuation) and use those varint buffers when building terminal/middle/outer
before base64-encoding; update references in buildSignatureBase64ForModel to
call the varint encoder for each length field so decoded protobuf parsing
succeeds for large payloads.
🪄 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: a463a8ea-4386-4bd1-8d48-f46949c24023
📒 Files selected for processing (7)
messages/zh-CN/dashboard.jsonmessages/zh-TW/dashboard.jsonsrc/app/[locale]/dashboard/logs/_components/error-details-dialog/components/SummaryTab.tsxsrc/app/v1/_lib/proxy/anthropic-actual-response-model.tssrc/app/v1/_lib/proxy/response-handler.tssrc/app/v1/_lib/proxy/thinking-signature-model.tstests/unit/proxy/anthropic-actual-response-model.test.ts
✅ Files skipped from review due to trivial changes (1)
- messages/zh-TW/dashboard.json
🚧 Files skipped from review as they are similar to previous changes (4)
- src/app/[locale]/dashboard/logs/_components/error-details-dialog/components/SummaryTab.tsx
- messages/zh-CN/dashboard.json
- src/app/v1/_lib/proxy/response-handler.ts
- src/app/v1/_lib/proxy/thinking-signature-model.ts
🧪 测试结果
总体结果: ✅ 所有测试通过 |
- 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.
|
Actionable comments posted: 0 |
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.
|
Actionable comments posted: 0 |
🧪 测试结果
总体结果: ✅ 所有测试通过 |
There was a problem hiding this comment.
Code Review Summary
This PR adds Anthropic streaming actual-response-model detection by parsing the protobuf payload embedded in events, recording audit special settings and showing a dashboard warning badge when thinking was enabled but no signature was present.
PR Size: XL
- Lines changed: 1028 (1013 additions, 15 deletions)
- Files changed: 14
- Split suggestions: Consider splitting into (1) core proxy detection + types, (2) dashboard i18n/UI badge, (3) unit tests, to ease review and rollback.
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 | 0 | 0 |
| Simplification | 0 | 0 | 0 | 0 |
Review Coverage
- Logic and correctness
- Security (OWASP Top 10)
- Error handling
- Type safety
- Documentation accuracy
- Test coverage
- Code clarity
Automated review by Codex AI
There was a problem hiding this comment.
Code Review Summary
No significant issues identified in this PR.
PR Size: XL
- Lines changed: 1028 (1013 additions, 15 deletions)
- Files changed: 14
- Split suggestions: Consider splitting into (1) core proxy detection + types, (2) dashboard i18n/UI badge, (3) unit tests, to ease review and rollback.
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
There was a problem hiding this comment.
- Synced PR
#1223to current head1e9c4451fc3592bfe1886ee64a445035e4ffc3b9and re-validated the previously-reported issues; both are now fixed in the diff:src/app/v1/_lib/proxy/thinking-signature-model.tsnow supports unpadded/base64url and rejects onlylen % 4 === 1.src/app/v1/_lib/proxy/response-handler.tsnow recordsthinkingEnabledfromisThinkingEnabled(session.request.message)viathinkingActuallyEnabled.
- Updated size label to match current PR stats:
1013 additions + 15 deletions = 1028 lines⇒ appliedsize/XL(removedsize/L). - Submitted the required PR review summary via
gh pr review 1223 --comment(no >=80-confidence issues remaining in changed lines).
Note: bun isn’t available in this runner (bun: command not found), so I couldn’t execute the repo’s bun run test/lint/typecheck locally here.
* 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>
Summary
signature_delta内嵌的 protobuf payload(字段路径[2, 1, 6]),把实际用于思考的模型名作为actualResponseModel优先存储 —— 比message_start明文model更准确。{claude, claude-auth}且requestedModel以claude-开头。三态 source:signature(签名命中)/fallback_no_signature_with_thinking(开了思考却没拿到签名 → 详情页面亮"无思考签名"badge)/fallback_no_thinking(未开思考,保持原行为)。ThinkingSignatureModelDetectionSpecialSetting类型用于审计留痕;UI 仅在详情面板 SummaryTab 的 modelAudit 区块加 badge(列表不动以保持简洁);5 个 dashboard.json(zh-CN/zh-TW/en/ja/ru)各加noSignatureBadge/noSignatureTooltip翻译。Related PRs
actualResponseModelinfrastructure by adding more accurate model detection via thinking signature protobuf parsing for Anthropic streams, superseding the plainmessage_startmodel field for thinking-enabled requests关键设计
null,绝不抛。actual-response-model.ts的extractJsonChunks改为export,新模块共用同一份处理多data:行合并、[DONE]、event:边界的能力。response-handler.ts:2553流式分支前插入决策代码,非流式分支(line 1450)与错误兜底路径不动(signature_delta 仅存在于流式)。signature_delta事件 / 有事件但 base64 损坏 / protobuf 路径解不出,三种情况在thinkingEnabled时统一归到fallback_no_signature_with_thinking,共用同一个 badge 文案。Test plan
bun run typecheck全绿bun run lint全绿bun run test全绿(6119 passed / 13 skipped / 0 failed)bun run test:v1全绿(覆盖率 88%+)bun run build生产构建成功(含 standalone copy)claude-opus-4-7、损坏 base64、字段路径不存在、wire-type 错位、SSE 多事件解析、三态决策树边界signature_delta的 mock SSE,检查 DBmessage_requests.actual_response_model和special_settingsDescription enhanced by Claude AI
Greptile Summary
This PR extends the
actualResponseModelinfrastructure for Anthropic streaming responses by parsing the protobuf payload embedded insignature_deltaSSE events (field path[2, 1, 6]) to extract the model name used during thinking — a more reliable source than the plainmodelfield inmessage_start. A three-statesourcevalue (signature/fallback_no_signature_with_thinking/fallback_no_thinking) drives both DB audit persistence and a new "No thinking signature" badge in the SummaryTab detail panel.thinking-signature-model.ts(zero-dependency, all-null-safe hand-rolled protobuf + base64 decoder) andanthropic-actual-response-model.ts(three-way decision logic, reuses existingextractJsonChunks).response-handler.ts(streaming branch only) populatesThinkingSignatureModelDetectionSpecialSettingand overridesfinalActualResponseModel; non-streaming and error paths are untouched.noSignatureBadge/noSignatureTooltipadded to all five locale files; badge is rendered inSummaryTabonly onfallback_no_signature_with_thinking.Confidence Score: 5/5
Safe to merge: the change is additive, all new paths are guarded by provider-type and model-prefix checks, and any protobuf/base64 decoding failure silently falls back to the existing message_start model field.
The integration point is minimal (a single insertion before the existing streaming persistence call), all error paths return null and never throw, the old non-Anthropic and non-streaming code paths are untouched, and 37 new tests including a real Anthropic signature fixture confirm the decoder behaves correctly. The two flagged items are design trade-offs explicitly documented in code comments and do not affect correctness of the happy path.
No files require special attention beyond the noted design trade-offs in thinking-signature-model.ts and anthropic-actual-response-model.ts.
Important Files Changed
Flowchart
%%{init: {'theme': 'neutral'}}%% flowchart TD A[Streaming response complete] --> B{providerType in claude/claude-auth AND requestedModel starts with claude- or anthropic/} B -- No --> C[Old path: extractActualResponseModelForProvider] B -- Yes --> D[extractThinkingSignatureModelFromStream scan SSE for signature_delta events] D --> E{signature found and decoded?} E -- Yes --> F[source = signature, actualResponseModel = decoded model] E -- No --> G[extractActualResponseModelForProvider fallback to message_start model] G --> H{fallbackModel == null? stream truncated} H -- Yes --> I[source = fallback_no_thinking, actualResponseModel = null] H -- No --> J{thinkingEnabled?} J -- Yes --> K[source = fallback_no_signature_with_thinking, UI badge shown] J -- No --> L[source = fallback_no_thinking, no badge] F & I & K & L --> M[addSpecialSetting: ThinkingSignatureModelDetection] M --> N[updateMessageRequestDetails, actualResponseModel persisted] C --> NPrompt To Fix All With AI
Reviews (4): Last reviewed commit: "fix(proxy): skip thinking-signature dete..." | Re-trigger Greptile