Skip to content

feat: add native Claude Messages compatibility - #6652

Open
Citrus086 wants to merge 2 commits into
QuantumNous:mainfrom
Citrus086:feat/native-claude-compatibility
Open

feat: add native Claude Messages compatibility#6652
Citrus086 wants to merge 2 commits into
QuantumNous:mainfrom
Citrus086:feat/native-claude-compatibility

Conversation

@Citrus086

@Citrus086 Citrus086 commented Aug 4, 2026

Copy link
Copy Markdown

变更描述 / Description

补齐 Claude Messages 接口的几个原生行为:目前 /v1/messages 已经可以使用,但还有几个地方会影响 Claude Code 一类的客户端:

  • 缺少 /v1/messages/count_tokens
  • Claude 错误会经过 OpenAI 错误格式转换
  • 流式连接结束时,没有区分正常的 message_stop 和意外断开

本 PR 做了以下调整:

  • 增加原生 /v1/messages/count_tokens
  • Claude 上游错误按 Anthropic 格式返回,并保留 request_id
  • Claude 流式请求只有收到 message_stop 才记为正常完成
  • 连接提前断开时标记为 incomplete,避免误判成功
  • 抽出 Claude 请求的公共准备逻辑,Messages 和 count_tokens 共用
  • 不支持原生 count_tokens 的渠道会明确返回错误,不会偷偷改用本地估算或触发生成请求

这些不影响现有 OpenAI 接口

变更类型 / Type of change

  • 新功能 (New feature)
  • 性能优化 / 重构 (Refactor)

提交前检查项 / Checklist

  • 人工确认: 我已亲自整理并撰写此描述,没有直接粘贴未经处理的 AI 输出。
  • 非重复提交: 我已搜索现有的 Issues 与 PRs,确认不是重复提交。
  • Bug fix 说明: 本 PR 不作为 Bug fix 提交。
  • 变更理解: 我已理解这些更改的工作原理及可能影响。
  • 范围聚焦: 本 PR 未包含任何与当前任务无关的代码改动。
  • 本地验证: 已在本地运行并通过测试或手动验证,维护者可以据此复核结果。
  • 安全合规: 代码中无敏感凭据,且符合项目代码规范。

运行证明 / Proof of Work

改动在下述情况生效:

  • 普通 Messages 请求
  • SSE 流式事件顺序
  • tool use
  • thinking/signature
  • 图片输入
  • 长请求传输
  • count_tokens
  • Anthropic 原生错误
  • 流式中断处理

Summary by CodeRabbit

  • New Features
    • Added Claude token-counting support through POST /v1/messages/count_tokens.
    • Preserved native Claude error formats, request IDs, and status details.
  • Bug Fixes
    • Improved Claude request validation, error handling, and upstream response processing.
    • Improved streaming completion handling, including detection of incomplete streams and proper terminal-event processing.
    • Corrected relay performance reporting to reflect actual request outcomes.
  • Tests
    • Added coverage for token counting, Claude errors, streaming completion, and response validation.

@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 39896f83-957f-4c53-b4d2-19f0c798d94e

📥 Commits

Reviewing files that changed from the base of the PR and between 9d92975 and 6d85cad.

📒 Files selected for processing (2)
  • service/error.go
  • service/error_test.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • service/error.go
  • service/error_test.go

Walkthrough

This PR adds Claude count-tokens endpoint support with dedicated request and response handling, converts all middleware error responses to a relay-aware helper that formats Claude-native error envelopes, and adds stream-terminal tracking to detect incomplete streams and classify success correctly.

Changes

Claude Count-Tokens Feature

Layer / File(s) Summary
Relay mode, route, and DTOs
relay/constant/relay_mode*.go, router/relay-router.go, relaykit/dto/claude.go, relaykit/relayconvert/convmeta/format.go
Adds RelayModeClaudeCountTokens constant and path classification for /v1/messages/count_tokens. Registers an authenticated POST route. Adds ClaudeCountTokensRequest and ClaudeCountTokensResponse DTOs recognized by format detection.
Adaptor interface and Claude implementation
relay/channel/adapter.go, relay/channel/claude/adaptor.go, relay/channel/claude/count_tokens_test.go
Adds ClaudeCountTokensAdaptor interface. Implements ConvertClaudeCountTokensRequest to copy token-count-supported fields, GetRequestURL to select /v1/messages/count_tokens, and response dispatch. Sets StreamTerminalRequired for streaming responses. Tests verify URL construction, field filtering, and response validation.
Response validation handler
relay/channel/claude/relay-claude.go
Adds ClaudeCountTokensHandler that validates InputTokens presence and non-negativity, copies the native response body, and returns Claude-formatted errors on validation failure.
Request preparation and helper
relay/claude_handler.go
Extracts prepareClaudeRequest with token defaults, thinking-model adaptation, and system-prompt handling from ClaudeHelper. Adds ClaudeCountTokensHelper to validate and copy Claude requests, apply model mapping and preprocessing, build outbound payloads, execute upstream requests, and delegate response handling to the adaptor.
Controller routing and billing
controller/relay.go
Routes count-tokens mode to ClaudeCountTokensHelper. Skips token estimation, pricing, pre-consumption, refund handling, and tiered billing preparation for count-token requests. Routes other Claude requests to ClaudeHelper.

Relay Error Response Unification

Layer / File(s) Summary
Claude error types and conversion
relaykit/types/error.go, relaykit/types/error_test.go
Adds ClaudeErrorResponse struct with optional request ID. Adds ToClaudeErrorResponse method to convert NewAPIError to native Claude format. Adds NewClaudeError constructor and ErrOptionWithClaudeRequestID option. Implements error-type normalization from upstream error codes and HTTP status codes with fallback handling for empty types.
Native Claude error passthrough
service/error.go, service/error_test.go
Adds RelayClaudeErrorHandler to extract and preserve native Anthropic error envelopes, extracting request IDs from JSON body or HTTP header as fallback. Returns native Claude errors for valid envelopes and generic Claude errors for malformed responses. Tests verify envelope preservation, status mapping, header-based ID fallback, and body read failure handling.
abortWithRelayMessage helper
middleware/utils.go, middleware/utils_test.go, middleware/performance.go
Replaces abortWithOpenAiMessage with abortWithRelayMessage. The helper detects /v1/messages paths and returns Claude-formatted errors with request ID context. Other paths retain generic error responses. Preserves error codes and status codes. Tests verify Claude and OpenAI error formats, request ID propagation, and oversized request handling.
Middleware call-site migration
middleware/auth.go, middleware/distributor.go, middleware/jimeng_adapter.go, middleware/model-rate-limit.go
Replaces all abortWithOpenAiMessage calls with abortWithRelayMessage in token validation, IP validation, user status checks, token group access, channel permissions, token-model access, playground group access, and rate-limit checks. Preserves status codes and messages. Wraps distributor JSON parsing errors with %w for error-chain preservation. Oversized request errors return HTTP 413.

Stream Terminal Tracking

Layer / File(s) Summary
StreamStatus enum and IsSuccessful method
relay/common/stream_status.go, relay/common/stream_status_test.go, relay/common/relay_info.go
Adds StreamEndReasonIncomplete constant for streams ending without required terminal markers. Adds IsSuccessful() method that returns true when the stream ended normally and has no errors. Adds StreamTerminalRequired field to RelayInfo. Tests verify incomplete and normal end classifications and success detection.
Scanner terminal-wait and incomplete detection
relay/helper/stream_scanner.go, relay/helper/stream_scanner_test.go
Adds handler completion tracking and conditional channel closure. For streams requiring a terminal event, the scanner closes the data channel, waits for queued events to drain, then marks the stream incomplete if the terminal was never received. Prevents double-closing channels. Tests verify incomplete stream detection and queued-event draining when terminal is present.
Claude stream integration
relay/channel/claude/relay-claude.go, relay/channel/claude/relay_claude_test.go, relaykit/relayconvert/internal/claude_messages/to_oai_chat_resp.go
Adds MessageStopReceived field to ClaudeResponseInfo to track message completion markers. FormatClaudeResponseInfo now detects message_stop events, sets this flag, and calls sr.Done() for clean stream termination. Test verifies message_stop handling. Updates consumers log_info_generate.go and text_quota.go to use IsSuccessful() for success classification.

Estimated code review effort: 4 (Complex) | ~75 minutes

Possibly related PRs

  • QuantumNous/new-api#5825: Both PRs modify Claude relay handling in relay/claude_handler.go and relay/channel/claude/adaptor.go.
  • QuantumNous/new-api#6587: Both PRs modify relay/claude_handler.go and ClaudeHelper request preparation.
  • QuantumNous/new-api#6629: Both PRs modify Claude streaming response conversion and termination behavior in relaykit/relayconvert and stream-state handling.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant Router as relay-router.go
  participant Controller as controller.Relay
  participant Helper as ClaudeCountTokensHelper
  participant Adaptor as Claude Adaptor
  participant Upstream as Claude API

  Client->>Router: POST /v1/messages/count_tokens
  Router->>Controller: Relay(RelayModeClaudeCountTokens)
  Controller->>Controller: skip token estimation, billing prep
  Controller->>Helper: ClaudeCountTokensHelper(c, info)
  Helper->>Adaptor: ConvertClaudeCountTokensRequest
  Adaptor-->>Helper: ClaudeCountTokensRequest
  Helper->>Upstream: send request
  Upstream-->>Helper: response
  Helper->>Adaptor: DoClaudeCountTokensResponse
  Adaptor->>Adaptor: ClaudeCountTokensHandler validates InputTokens
  Adaptor-->>Client: copy native response or Claude error
Loading
sequenceDiagram
  participant Middleware
  participant AbortHelper as abortWithRelayMessage
  participant ErrorTypes as types.NewAPIError
  participant Client

  Middleware->>AbortHelper: abort(ctx, statusCode, message, errorCode)
  AbortHelper->>AbortHelper: check request path
  alt path is /v1/messages
    AbortHelper->>ErrorTypes: ToClaudeErrorResponse(requestID)
    ErrorTypes-->>AbortHelper: ClaudeErrorResponse
  else other path
    AbortHelper->>AbortHelper: build generic error response
  end
  AbortHelper-->>Client: JSON error response
Loading

Poem

A rabbit counts tokens, hop by hop,
Through Claude's new door, we never stop.
Errors now speak in Anthropic's tongue,
Streams wait for the terminal song. 🎵
Incomplete? No, we drain the queue —
Thump thump, this PR rings true! 🐇

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 7.02% 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the pull request's main change: adding native Claude Messages compatibility.
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 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.

@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

🤖 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 `@service/error.go`:
- Around line 151-154: When io.ReadAll fails in the error path after
CloseResponseBodyGracefully, the code returns NewClaudeError without preserving
the upstream request ID from the response headers. In the error handling branch
where io.ReadAll fails (inside the if err != nil block), extract the request-id
from resp.Header.Get("request-id") and include it in the NewClaudeError call
alongside the existing err, types.ErrorCodeReadResponseBodyFailed, and
statusCode parameters so that the upstream request ID is not lost when the
body-read fails.
🪄 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: 926b698e-7184-422d-a4a0-07aaa758dcf0

📥 Commits

Reviewing files that changed from the base of the PR and between 0ab0202 and 9d92975.

📒 Files selected for processing (31)
  • controller/relay.go
  • middleware/auth.go
  • middleware/distributor.go
  • middleware/jimeng_adapter.go
  • middleware/model-rate-limit.go
  • middleware/performance.go
  • middleware/utils.go
  • middleware/utils_test.go
  • relay/channel/adapter.go
  • relay/channel/claude/adaptor.go
  • relay/channel/claude/count_tokens_test.go
  • relay/channel/claude/relay-claude.go
  • relay/channel/claude/relay_claude_test.go
  • relay/claude_handler.go
  • relay/common/relay_info.go
  • relay/common/stream_status.go
  • relay/common/stream_status_test.go
  • relay/constant/relay_mode.go
  • relay/constant/relay_mode_test.go
  • relay/helper/stream_scanner.go
  • relay/helper/stream_scanner_test.go
  • relaykit/dto/claude.go
  • relaykit/relayconvert/convmeta/format.go
  • relaykit/relayconvert/internal/claude_messages/to_oai_chat_resp.go
  • relaykit/types/error.go
  • relaykit/types/error_test.go
  • router/relay-router.go
  • service/error.go
  • service/error_test.go
  • service/log_info_generate.go
  • service/text_quota.go

Comment thread service/error.go Outdated
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.

1 participant