feat: support OpenAI Responses API on Claude upstream channels - #6707
feat: support OpenAI Responses API on Claude upstream channels#6707margrop wants to merge 3 commits into
Conversation
A client calling POST /v1/responses that is routed to a Claude (type 14) channel previously failed with '500 not implemented (convert_request_failed)', because the Claude adaptor's ConvertOpenAIResponsesRequest is an unimplemented stub. This blocks Responses-only clients (e.g. Codex CLI >= 0.142, which no longer supports wire_api=chat) from using Claude-backed channels. The relaykit/relayconvert package already registers a complete bidirectional openai_responses <-> claude_messages converter; it was simply never invoked for this path. This change wires it up by reusing the existing Claude response pipeline: - responses_handler: when the selected channel is a Claude API type (and pass-through is off), convert the Responses request to Claude Messages via service.ConvertRequest and forward it, keeping info.RelayFormat = RelayFormatOpenAIResponses. - HandleClaudeResponseData: add a RelayFormatOpenAIResponses branch that converts the upstream Claude JSON back to a Responses response. - ClaudeResponsesStreamHandler (new): convert the upstream Claude SSE stream to Responses SSE, reusing the existing usage/billing/tool accounting. Existing /v1/messages (Claude inbound) and RelayFormatOpenAI behavior is unchanged; the new path activates only for Responses requests on Claude channels. Verified: unit tests for both non-streaming and streaming conversion, and an end-to-end run driving real streaming + non-streaming /v1/responses requests through a Claude channel against a live Claude Messages upstream.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
WalkthroughClaude relay support converts Anthropic requests and Claude responses for the OpenAI Responses format. It supports non-streaming payloads and streaming SSE events with usage accounting, response IDs, serialization, terminal events, and error handling. ChangesClaude OpenAI Responses support
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant OpenAIResponsesClient
participant ClaudeAdaptor
participant ClaudeResponsesStreamHandler
participant ClaudeUpstream
OpenAIResponsesClient->>ClaudeAdaptor: Send streaming Responses request
ClaudeAdaptor->>ClaudeResponsesStreamHandler: Select Responses stream handler
ClaudeResponsesStreamHandler->>ClaudeUpstream: Read Claude SSE data
ClaudeUpstream-->>ClaudeResponsesStreamHandler: Return Claude events and usage
ClaudeResponsesStreamHandler-->>OpenAIResponsesClient: Write serialized Responses events
Possibly related issues
Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 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.
Actionable comments posted: 2
🤖 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/claude/relay_responses_test.go`:
- Around line 48-50: Strengthen both response contract tests in
relay/channel/claude/relay_responses_test.go:48-50 and
relay/channel/claude/relay_responses_test.go:92-94. In the non-streaming test,
assert the response ID, converted output text/items, and usage fields; in the
SSE test, parse each payload and assert the response ID, text delta, and final
usage or completion data, using the expected converted values.
In `@relay/responses_handler.go`:
- Around line 89-107: Update ResponsesHelper’s pass-through eligibility check to
exclude constant.APITypeAnthropic whenever either pass-through flag is enabled.
Ensure Anthropic requests always reach the APITypeAnthropic branch, where
ConvertRequest produces a dto.ClaudeRequest before forwarding.
🪄 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: 515e50ac-938e-487a-84ed-269bac25324d
📒 Files selected for processing (4)
relay/channel/claude/adaptor.gorelay/channel/claude/relay-claude.gorelay/channel/claude/relay_responses_test.gorelay/responses_handler.go
- responses_handler: exclude APITypeAnthropic from the pass-through path so a raw /v1/responses body is never forwarded to the Claude Messages upstream when pass-through is enabled (CodeRabbit major). - ClaudeResponsesStreamHandler: call relayconvert.FinalizeStreamResponse after the stream ends so the terminal response.completed event (with final usage) is emitted; previously the stream never signalled completion to the client. - relay_responses_test: strengthen both conversion tests to assert the response ID, converted output text/items, and usage fields; parse SSE payloads and assert the response ID, joined text deltas, and terminal usage (CodeRabbit minor).
|
Both CodeRabbit review comments addressed in commit 615d279:
Strengthening the streaming test surfaced one more real issue in the same commit: Validation: both conversion tests pass, full |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 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/claude/relay_responses_test.go`:
- Around line 121-166: Update the SSE event parsing assertions in the test to
record each event’s position while iterating through blocks. Assert that
response.created occurs before response.output_text.delta and response.completed
occurs after the output events, while preserving the existing payload and
content assertions.
- Around line 167-169: Add an assertion in the terminal usage checks for
finalUsage.TotalTokens, verifying it equals 15 alongside the existing
PromptTokens and CompletionTokens assertions in the response completion test.
- Around line 135-154: In the response event parsing test, replace the visible
json.Unmarshal and json.Marshal calls with the centralized common.Unmarshal and
common.Marshal wrappers. Preserve explicit assertion failures for both marshal
and unmarshal errors, including the existing SSE data validation and usage
conversion checks.
🪄 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: 0688bfda-a724-461a-a63e-d44009b11d82
📒 Files selected for processing (3)
relay/channel/claude/relay-claude.gorelay/channel/claude/relay_responses_test.gorelay/responses_handler.go
🚧 Files skipped from review as they are similar to previous changes (2)
- relay/responses_handler.go
- relay/channel/claude/relay-claude.go
…json wrappers Address follow-up review: - record SSE event positions and assert response.created precedes the output deltas and response.completed follows them - assert the terminal response.completed carries 15 total tokens - replace json.Unmarshal/json.Marshal with the centralized common.Unmarshal/common.Marshal wrappers (with hard assertion failures)
Problem
A client calling
POST /v1/responsesthat gets routed to a Claude (type 14) channel fails with:Root cause:
relay/channel/claude/adaptor.goConvertOpenAIResponsesRequestis a// TODO implement mestub returningerrors.New("not implemented").This blocks Responses-only clients — notably Codex CLI ≥ 0.142, which removed
wire_api="chat"and hard-requires the Responses API — from using Claude-backed channels (e.g. as a fallback when OpenAI channels are down).Key observation
relaykit/relayconvertalready registers a complete, bidirectionalopenai_responses ↔ claude_messagesconverter (text_converter_registry.go, incl. streaming chunk converters). The conversion logic exists — it is simply never invoked for this path.Approach: reuse the existing Claude response pipeline
Rather than reimplementing the stub (which only handles the request direction and can't see the response context needed for streaming), this PR wires the existing converter into the Claude relay pipeline, mirroring the existing
RelayFormatOpenAIhandling:relay/responses_handler.go— when the selected channel is a Claude API type (constant.APITypeAnthropic) and pass-through is off, convert the Responses request to Claude Messages viaservice.ConvertRequest(..., types.RelayFormatClaude, ...)and forward it, keepinginfo.RelayFormat = RelayFormatOpenAIResponses.HandleClaudeResponseData— add aRelayFormatOpenAIResponsesbranch converting the upstream Claude JSON back to a Responses response viarelayconvert.ConvertResponse.ClaudeResponsesStreamHandler(new) — convert the upstream Claude SSE stream to Responses SSE viarelayconvert.ConvertStreamResponseChunk, reusing the existing usage/billing/tool-call accounting (FormatClaudeResponseInfo,countClaudeStreamBillableTools).Existing
/v1/messages(Claude inbound) andRelayFormatOpenAIbehavior is unchanged — the new path activates only when a Responses-format request is routed to a Claude channel. Thechannel.Adaptorinterface is untouched.Testing
relay/channel/claude/relay_responses_test.go): non-streaming Claude→Responses conversion and streaming Claude SSE→Responses SSE both pass; full./relay/...suite passes with no regressions./v1/responsesrequests:{"object":"response", ... "output_text":"ok"}, consume log showsrequest_conversion:["OpenAI Responses","Claude Messages"]response.created/response.output_item.added/...deltaSSE events,stream_status: okScope
Claude (type 14) channels only. OpenAI-Chat (type 1) and Gemini upstreams are out of scope (Gemini already has its own Responses bridge).
Summary by CodeRabbit
New Features
Bug Fixes