Skip to content

fix: 补全 streaming message_delta 事件缺失的 input_tokens 和 cache 相关字段 - #2887

Merged
Calcium-Ion merged 2 commits into
QuantumNous:mainfrom
seefs001:fix/claude
Feb 7, 2026
Merged

fix: 补全 streaming message_delta 事件缺失的 input_tokens 和 cache 相关字段#2887
Calcium-Ion merged 2 commits into
QuantumNous:mainfrom
seefs001:fix/claude

Conversation

@seefs001

@seefs001 seefs001 commented Feb 7, 2026

Copy link
Copy Markdown
Collaborator
image

Summary by CodeRabbit

Release Notes

  • New Features

    • Improved Claude message delta streaming to automatically populate missing usage fields (input tokens, cache read tokens, cache creation tokens) from upstream data when only partial usage information is provided.
  • Tests

    • Added test suite covering message delta usage patching functionality, response format validation, and configuration-based patching behavior.

naaive and others added 2 commits February 7, 2026 18:17
当上游为 AWS Bedrock 时,message_delta 的 usage 可能缺少 input_tokens、
cache_creation_input_tokens、cache_read_input_tokens 等字段,导致与原生
Anthropic 格式不一致。从 message_start 积累的 claudeInfo 中补全这些字段后
重新序列化,确保客户端收到一致的 usage 格式。
@coderabbitai

coderabbitai Bot commented Feb 7, 2026

Copy link
Copy Markdown
Contributor

Walkthrough

This pull request adds support for patching incomplete Claude message_delta usage data in relay responses. It introduces utility functions to derive missing usage fields from cached information and conditionally apply them to JSON streams, with comprehensive test coverage for the new patching logic.

Changes

Cohort / File(s) Summary
Message Delta Usage Patching
relay/channel/claude/relay-claude.go
Introduces buildMessageDeltaPatchUsage, shouldSkipClaudeMessageDeltaUsagePatch, patchClaudeMessageDeltaUsageData, and setMessageDeltaUsageInt functions to derive and inject missing usage fields (input_tokens, cache_read_input_tokens, cache_creation_input_tokens) into message_delta responses. Integrates patching into stream handling with conditional bypass logic based on pass-through settings. Adds gjson and sjson imports for JSON manipulation.
Test Coverage
relay/channel/claude/message_delta_usage_patch_test.go, relay/channel/claude/relay_claude_test.go
New test files covering patch logic for unknown field preservation, zero-value handling, pass-through flag conditions, and usage field derivation. Additional tests validate FormatClaudeResponseInfo function behavior across message_start, message_delta, and content_block_delta event types.

Sequence Diagram

sequenceDiagram
    participant Stream as Upstream Stream
    participant Handler as Message Delta Handler
    participant Skipper as shouldSkipClaudeMessageDeltaUsagePatch
    participant Builder as buildMessageDeltaPatchUsage
    participant Patcher as patchClaudeMessageDeltaUsageData
    participant Output as Downstream

    Stream->>Handler: message_delta event
    Handler->>Skipper: Check PassThroughBodyEnabled
    Skipper-->>Handler: skip patching?
    
    alt Skip Patching
        Handler->>Output: Send original data
    else Apply Patching
        Handler->>Builder: Derive missing fields from claudeInfo
        Builder-->>Handler: Complete usage data
        Handler->>Patcher: Patch JSON with derived usage
        Patcher->>Patcher: setMessageDeltaUsageInt for each field
        Patcher-->>Handler: Mutated JSON
        Handler->>Output: Send patched data
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 A delta arrives quite incomplete,
With tokens missing—oh, what a treat!
But lo! The patching does deploy,
Deriving usage with bunny joy!
Cache creation tokens shine so bright,
Making message_deltas just right! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 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 (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: adding missing input_tokens and cache-related fields to streaming message_delta events in Claude relay handling.

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

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@relay/channel/claude/relay-claude.go`:
- Around line 577-585: The function shouldSkipClaudeMessageDeltaUsagePatch
dereferences info.ChannelSetting without checking the embedded pointer
info.ChannelMeta; update the guard to also verify info.ChannelMeta is non-nil
before accessing ChannelSetting (or read ChannelSetting via a nil-safe
accessor), i.e. add a nil-check for info.ChannelMeta in
shouldSkipClaudeMessageDeltaUsagePatch so it returns false (or the appropriate
default) when ChannelMeta is nil rather than panicking.
🧹 Nitpick comments (2)
relay/channel/claude/relay_claude_test.go (1)

1-175: Good test coverage for FormatClaudeResponseInfo across key scenarios.

The tests are well-structured and cover the important cases (message_start propagation, full vs. partial message_delta usage, nil guard, and text accumulation). A couple of observations:

  1. Inconsistent assertion style: This file uses raw t.Errorf/t.Fatal while the sibling test file (message_delta_usage_patch_test.go) uses testify/require/assert. Consider aligning for consistency.
  2. Missing coverage for oaiResponse path: All tests pass nil for the oaiResponse parameter. The branch at lines 683–687 of the implementation (oaiResponse.Id, .Created, .Model propagation) is untested.
relay/channel/claude/message_delta_usage_patch_test.go (1)

48-64: Consider adding a test case for nil ChannelMeta when global pass-through is false.

Currently, &relaycommon.RelayInfo{} (with nil ChannelMeta) is only tested when PassThroughRequestEnabled is true, which hits the early return. If the implementation is updated to guard against nil ChannelMeta (as suggested in the relay-claude.go review), a test confirming the safe fallback would be valuable.

Comment on lines +577 to +585
func shouldSkipClaudeMessageDeltaUsagePatch(info *relaycommon.RelayInfo) bool {
if model_setting.GetGlobalSettings().PassThroughRequestEnabled {
return true
}
if info == nil {
return false
}
return info.ChannelSetting.PassThroughBodyEnabled
}

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.

⚠️ Potential issue | 🟠 Major

Nil ChannelMeta will cause a panic.

If info is non-nil but info.ChannelMeta is nil (embedded pointer), accessing info.ChannelSetting on line 584 dereferences a nil pointer. The nil-check on line 581 only guards info itself.

🐛 Proposed fix
 func shouldSkipClaudeMessageDeltaUsagePatch(info *relaycommon.RelayInfo) bool {
 	if model_setting.GetGlobalSettings().PassThroughRequestEnabled {
 		return true
 	}
-	if info == nil {
+	if info == nil || info.ChannelMeta == nil {
 		return false
 	}
 	return info.ChannelSetting.PassThroughBodyEnabled
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func shouldSkipClaudeMessageDeltaUsagePatch(info *relaycommon.RelayInfo) bool {
if model_setting.GetGlobalSettings().PassThroughRequestEnabled {
return true
}
if info == nil {
return false
}
return info.ChannelSetting.PassThroughBodyEnabled
}
func shouldSkipClaudeMessageDeltaUsagePatch(info *relaycommon.RelayInfo) bool {
if model_setting.GetGlobalSettings().PassThroughRequestEnabled {
return true
}
if info == nil || info.ChannelMeta == nil {
return false
}
return info.ChannelSetting.PassThroughBodyEnabled
}
🤖 Prompt for AI Agents
In `@relay/channel/claude/relay-claude.go` around lines 577 - 585, The function
shouldSkipClaudeMessageDeltaUsagePatch dereferences info.ChannelSetting without
checking the embedded pointer info.ChannelMeta; update the guard to also verify
info.ChannelMeta is non-nil before accessing ChannelSetting (or read
ChannelSetting via a nil-safe accessor), i.e. add a nil-check for
info.ChannelMeta in shouldSkipClaudeMessageDeltaUsagePatch so it returns false
(or the appropriate default) when ChannelMeta is nil rather than panicking.

@seefs001
seefs001 marked this pull request as ready for review February 7, 2026 11:20
@Calcium-Ion
Calcium-Ion merged commit bc6810c into QuantumNous:main Feb 7, 2026
1 check passed
ennnnny pushed a commit to ennnnny/new-api that referenced this pull request Mar 17, 2026
fix: 补全 streaming message_delta 事件缺失的 input_tokens 和 cache 相关字段
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.

Streaming 的 message_delta 事件缺少 input_tokens 和 cache 相关字段

3 participants