Skip to content

feat(xai): 支持 Claude Messages 兼容转发#5626

Open
wangwingzero wants to merge 1 commit into
QuantumNous:mainfrom
wangwingzero:codex/claude-code-xai-compat
Open

feat(xai): 支持 Claude Messages 兼容转发#5626
wangwingzero wants to merge 1 commit into
QuantumNous:mainfrom
wangwingzero:codex/claude-code-xai-compat

Conversation

@wangwingzero

@wangwingzero wangwingzero commented Jun 20, 2026

Copy link
Copy Markdown

⚠️ 提交说明 / PR Notice

Important

  • 本说明已根据本次改动人工整理,没有直接粘贴未经处理的 AI 输出。
  • 代码为 AI 辅助生成,并已通过本地测试验证。

📝 变更描述 / Description

为 xAI 渠道补齐 Claude Messages 兼容转发,使 Claude Code / Anthropic Messages 格式请求可以落到 xAI 的 OpenAI-compatible chat completions 接口,并把响应转换回 Claude 格式。

具体改动:

  • 在 xAI adaptor 中实现 ConvertClaudeRequest,复用已有 ClaudeToOpenAIRequest 转换逻辑,并在流式请求中补充 stream_options.include_usage
  • RelayFormatClaude 走 xAI 渠道时,将上游 URL 改为 /v1/chat/completions,避免把 /v1/messages 直接发给 xAI。
  • xAI 非流式响应在 Claude 格式下转换为 Anthropic message 响应。
  • xAI 流式响应在 Claude 格式下转换为 Anthropic SSE 事件,避免 Claude Code 收到 OpenAI chunk 或 [DONE] 后判定为 malformed response。
  • 新增 xAI 单元测试覆盖 Claude 请求转换、URL 转换、非流式响应转换和流式 SSE 转换。

查重时发现 /v1/messages/count_tokens 已有独立打开的 PR(如 #5596#4441)。为避免重复提交和扩大影响范围,本 PR 不重复实现 count_tokens,仅聚焦 xAI 渠道的 Claude Messages relay 兼容。

🚀 变更类型 / Type of change

  • 🐛 Bug 修复 (Bug fix) - 请关联对应 Issue,避免将设计取舍、理解偏差或预期不一致直接归类为 bug
  • ✨ 新功能 (New feature) - 重大特性建议先通过 Issue 沟通
  • ⚡ 性能优化 / 重构 (Refactor)
  • 📝 文档更新 (Documentation)

🔗 关联任务 / Related Issue

✅ 提交前检查项 / Checklist

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

📸 运行证明 / Proof of Work

git diff --check
go test ./relay/channel/xai ./relay/... ./controller ./router

验证结果:

  • git diff --check 无输出。
  • go test ./relay/channel/xai ./relay/... ./controller ./router 通过。

Summary by CodeRabbit

  • New Features

    • Claude request conversion is now fully functional, enabling proper request validation and OpenAI-compatible handling.
    • Claude streaming responses now include usage information tracking.
    • Claude responses are properly converted to downstream format, supporting both streaming and non-streaming modes.
  • Tests

    • Added comprehensive unit tests for Claude request and response handling.

@coderabbitai

coderabbitai Bot commented Jun 20, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

The xAI channel adaptor gains functional Claude support: ConvertClaudeRequest now converts Claude requests to OpenAI-compatible format via service.ClaudeToOpenAIRequest and conditionally sets stream options. GetRequestURL adds a Claude-specific /v1/chat/completions path. Both streaming (xAIStreamHandler) and non-streaming (xAIHandler) response handlers now branch on RelayFormat to apply Claude-specific formatting. Four unit tests cover all new paths.

Changes

xAI Claude Format Integration

Layer / File(s) Summary
Claude request conversion and URL routing
relay/channel/xai/adaptor.go
Replaces the ConvertClaudeRequest stub with a real implementation calling service.ClaudeToOpenAIRequest, conditionally attaching StreamOptions.IncludeUsage, and delegating to ConvertOpenAIRequest. Adds a Claude-specific branch in GetRequestURL that constructs a /v1/chat/completions URL from ChannelBaseUrl when relay format is Claude and mode is not Responses.
Streaming and non-streaming Claude response handling
relay/channel/xai/text.go
xAIStreamHandler now tracks the last marshalled chunk in lastStreamData, routes chunks through openai.HandleStreamFormat (Claude) or helper.ObjectData (other), and finalizes with openai.HandleFinalResponse or helper.Done accordingly. xAIHandler builds a dto.OpenAITextResponse, optionally applies service.ResponseOpenAI2Claude, marshals and writes it, and returns &openAIResponse.Usage.
Unit tests for adaptor and handler behavior
relay/channel/xai/adaptor_test.go
Four tests covering ConvertClaudeRequest field mapping, GetRequestURL URL selection, xAIHandler non-streaming response conversion and usage counts, and xAIStreamHandler streaming event output and token extraction.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • QuantumNous/new-api#1522: Fixes service/convert.go Claude conversion behavior and enables ConvertClaudeRequest in the deepseek channel — directly related to the shared ClaudeToOpenAIRequest path now wired in the xAI adaptor.
  • QuantumNous/new-api#1531: Modifies ClaudeToOpenAIRequest tool result/name resolution, which directly impacts the conversion path now called by xAI.ConvertClaudeRequest.
  • QuantumNous/new-api#1832: Implements Claude support in another channel's adaptor.go by changing ConvertClaudeRequest and GetRequestURL routing based on RelayFormat — the same structural pattern applied here.

Suggested reviewers

  • Calcium-Ion

Poem

🐇 Hop hop, the xAI gate swings wide,
Claude requests now ride inside!
Stream or not, the format bends,
/v1/chat/completions — where the URL ends.
Usage counts and chunks aligned,
A fluffy coder's tidy mind. ✨

🚥 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: adding Claude Messages API compatibility to xAI channel relay functionality.
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.

✏️ 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.

@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

🧹 Nitpick comments (1)
relay/channel/xai/adaptor_test.go (1)

84-111: ⚡ Quick win

Add a regression case for responses without usage.

Please add a non-stream Claude-format test where upstream omits usage, and assert the handler preserves the intended downstream usage contract (no synthetic zero-token usage).

As per coding guidelines, backend tests must protect billing/accounting invariants and regression paths.

🤖 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 `@relay/channel/xai/adaptor_test.go` around lines 84 - 111, Add a new
regression test function (similar to TestXAIHandlerConvertsClaudeFormatResponse)
that tests the xAIHandler function with a Claude-format response that omits the
usage field entirely. In this test, create a responseBody without the usage
object, call xAIHandler with the same setup, and verify that the handler does
not create synthetic zero-token usage values. Assert that the returned usage is
nil or that zero values are not populated to protect the billing/accounting
invariants.

Source: Coding guidelines

🤖 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 `@relay/channel/xai/text.go`:
- Around line 126-128: The issue is that when xaiResponse.Usage is nil, the
condition at the if statement is not entered, but then at line 142 the code
returns a pointer to openAIResponse.Usage which creates a non-nil pointer to a
zero-valued struct instead of preserving the nil semantics. To fix this, modify
the code to only return the Usage pointer when it was actually populated from
the upstream response. Make the return at line 142 conditional so that it
returns nil for Usage when xaiResponse.Usage was nil (i.e., the if block was not
entered), thereby preserving the nullable semantics and preventing synthetic
zero-token usage from being created when usage data is absent from upstream.

---

Nitpick comments:
In `@relay/channel/xai/adaptor_test.go`:
- Around line 84-111: Add a new regression test function (similar to
TestXAIHandlerConvertsClaudeFormatResponse) that tests the xAIHandler function
with a Claude-format response that omits the usage field entirely. In this test,
create a responseBody without the usage object, call xAIHandler with the same
setup, and verify that the handler does not create synthetic zero-token usage
values. Assert that the returned usage is nil or that zero values are not
populated to protect the billing/accounting invariants.
🪄 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: 5e3119ea-54e0-437d-a1f8-d33d02b5e4f3

📥 Commits

Reviewing files that changed from the base of the PR and between a68041f and 30b6828.

📒 Files selected for processing (3)
  • relay/channel/xai/adaptor.go
  • relay/channel/xai/adaptor_test.go
  • relay/channel/xai/text.go

Comment thread relay/channel/xai/text.go
Comment on lines +126 to +128
if xaiResponse.Usage != nil {
openAIResponse.Usage = *xaiResponse.Usage
}

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 | ⚡ Quick win

Preserve nullable usage semantics when upstream omits usage.

At Line 142, returning &openAIResponse.Usage makes usage non-nil even when Line 126 is not entered (xaiResponse.Usage == nil). That turns “usage absent” into synthetic zero-token usage, which can break downstream usage/billing fallback logic.

Suggested fix
 	openAIResponse := dto.OpenAITextResponse{
 		Id:      xaiResponse.Id,
 		Object:  xaiResponse.Object,
 		Created: xaiResponse.Created,
 		Model:   xaiResponse.Model,
 		Choices: xaiResponse.Choices,
 	}
+	var usagePtr *dto.Usage
 	if xaiResponse.Usage != nil {
 		openAIResponse.Usage = *xaiResponse.Usage
+		usagePtr = &openAIResponse.Usage
 	}
@@
-	return &openAIResponse.Usage, nil
+	return usagePtr, nil
 }

Also applies to: 142-142

🤖 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 `@relay/channel/xai/text.go` around lines 126 - 128, The issue is that when
xaiResponse.Usage is nil, the condition at the if statement is not entered, but
then at line 142 the code returns a pointer to openAIResponse.Usage which
creates a non-nil pointer to a zero-valued struct instead of preserving the nil
semantics. To fix this, modify the code to only return the Usage pointer when it
was actually populated from the upstream response. Make the return at line 142
conditional so that it returns nil for Usage when xaiResponse.Usage was nil
(i.e., the if block was not entered), thereby preserving the nullable semantics
and preventing synthetic zero-token usage from being created when usage data is
absent from upstream.

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