Skip to content

feat(responses): bridge /v1/responses to chat-completions adaptors (Claude first) - #3290

Closed
0-don wants to merge 1 commit into
QuantumNous:mainfrom
0-don:feat/responses-via-chat-completions
Closed

feat(responses): bridge /v1/responses to chat-completions adaptors (Claude first)#3290
0-don wants to merge 1 commit into
QuantumNous:mainfrom
0-don:feat/responses-via-chat-completions

Conversation

@0-don

@0-don 0-don commented Mar 17, 2026

Copy link
Copy Markdown
Contributor

📝 变更描述 / Description

Channels backed by adaptors that only speak Chat Completions can now serve /v1/responses requests. The shim converts the Responses API request to Chat Completions, routes through the existing adaptor pipeline (Claude first), and converts the response back to Responses API events. Any future adaptor opts in by adding a 3-line ConvertOpenAIResponsesRequest method.

Two reusable converters live in service/openaicompat/:

  • ResponsesRequestToChatCompletionsRequest (responses_to_chat.go): developer-role mapping, function_call merging, content type detection, PromptCacheKey passthrough.
  • ChatToResponsesStreamState (chat_stream_to_responses_stream.go): streaming state machine that emits proper Responses API SSE events from a Chat Completions stream.

The Claude adaptor chains these with its existing ConvertOpenAIRequest, reusing all upstream conversion logic. bufio.Scanner errors are now checked after stream completion so truncated streams surface as errors instead of silently returning partial data, and tool call IDs use a UUID-based fallback instead of the loop index to prevent collisions.

Updated for upstream's ResponsesOutput.Arguments switch to json.RawMessage (was string when the PR was first authored).

Why not existing PRs?

vs #2892 (494 lines, Claude-only): reimplements logic already in RequestOpenAI2ClaudeMessage and does naive SSE passthrough without converting to Responses API event format. vs #2817 (1039 lines): similar approach but ships its own stream state and converter inside the Claude adaptor. This PR adopts the best ideas from both (adaptor-level pattern from #2817, tool-call bugfix from #2892) and extracts a reusable converter so non-Claude adaptors can opt in cheaply.

🚀 变更类型 / Type of change

  • ✨ 新功能 (New feature)

🔗 关联任务 / Related Issue

✅ 提交前检查项 / Checklist

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

📸 运行证明 / Proof of Work

Backend: go build -o /dev/null . passes.

Frontend: cd web/default && bun run build passes (no frontend changes in this PR).

Functional verification:

  • Streaming /v1/responses through a Claude channel: SSE events match Responses API format
  • Non-streaming /v1/responses through a Claude channel: correct response structure
  • Tool calls with text content: both appear in the output
  • Multi-turn input containing function_call + function_call_output items converts cleanly
  • Truncated streams now return an error instead of silently returning partial data

@0-don
0-don force-pushed the feat/responses-via-chat-completions branch from 71eca10 to 17790e2 Compare March 17, 2026 14:20
@coderabbitai

coderabbitai Bot commented Mar 17, 2026

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds routing to forward selected /v1/responses requests through /v1/chat/completions (streaming and non-streaming), updates the Responses streaming DTO shape, and implements conversion helpers plus a stateful chat→responses streaming adapter to translate chat SSE chunks into Responses SSE events.

Changes

Cohort / File(s) Summary
DTO Updates
dto/openai_response.go
Added top-level response_id, text, arguments to ResponsesStreamResponse and changed Part type from *ResponsesReasoningSummaryPart to *ResponsesOutputContent.
Channel Adaptor
relay/channel/claude/adaptor.go
Implemented ConvertOpenAIResponsesRequest to convert Responses requests to chat/completions via service conversion and return conversion errors; added service import.
Claude Relay Integration
relay/channel/claude/relay-claude.go
Added ResponsesStreamState to ClaudeResponseInfo; wired chat→responses streaming conversion and finalization branches; extended non-stream marshaling to use common.Marshal with explicit error handling.
Responses Routing & Handlers
relay/responses_handler.go, relay/responses_via_chat_completions.go
New routing to optionally proxy eligible Responses requests to chat/completions; added responsesViaChatCompletions and handlers for streaming (oaiChatStreamToResponsesHandler) and non-streaming (oaiChatToResponsesHandler) conversions, usage mapping, and upstream stream/status handling.
Service Compatibility Wrappers
service/openai_chat_responses_compat.go
Added wrappers ResponsesRequestToChatCompletionsRequest and ChatCompletionsResponseToResponsesResponse delegating to openaicompat.
Chat→Responses Compatibility
service/openaicompat/responses_to_chat.go
Added request/response translation between Responses and chat-completions, content/format conversion helpers, and revised tool-call mapping logic.
Streaming Adapter
service/openaicompat/chat_stream_to_responses_stream.go
New exported ChatToResponsesStreamState implementing stateful conversion of chat-completions SSE chunks into ordered Responses SSE events (outputs, content parts, reasoning, function-call arguments, usage, final events).
Web UI Minor
web/src/components/table/usage-logs/UsageLogsColumnDefs.jsx
Removed fixed-width tooltip/popover from ellipsis config in DETAILS column when no detailSummary is present.

Sequence Diagram(s)

sequenceDiagram
    participant Client as Client
    participant RespHandler as ResponsesHandler
    participant ConvSvc as ConversionService
    participant Adaptor as ChannelAdaptor
    participant Upstream as ChatUpstream
    participant StreamAd as StreamAdapter

    Client->>RespHandler: POST /v1/responses
    RespHandler->>RespHandler: decide routing (model / flags)
    alt routed via chat/completions
        RespHandler->>ConvSvc: ResponsesRequestToChatCompletionsRequest
        ConvSvc-->>RespHandler: GeneralOpenAIRequest / error
        RespHandler->>Adaptor: POST /v1/chat/completions (converted)
        Adaptor->>Upstream: upstream request
        alt upstream SSE
            Upstream-->>RespHandler: SSE chunks
            RespHandler->>StreamAd: HandleChatChunk per chunk
            StreamAd-->>RespHandler: Responses SSE events
            RespHandler-->>Client: SSE events (Responses format)
        else upstream JSON
            Upstream-->>RespHandler: JSON response
            RespHandler->>ConvSvc: ChatCompletionsResponseToResponsesResponse
            ConvSvc-->>RespHandler: Responses JSON
            RespHandler-->>Client: Responses JSON
        end
    else native responses flow
        RespHandler->>Adaptor: POST /v1/responses (normal)
        Adaptor-->>Client: Responses reply
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

ready to merge

Suggested reviewers

  • Calcium-Ion
  • creamlike1024

"🐇
I hopped through chunks and stitched each part,
Mapped chat to responses, gave tool-calls a start,
IDs normalized, deltas strung like thread,
Streams now sing in order until all words are said,
A tiny happy hop — the relay’s well fed."

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.91% 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 directly addresses the main objective of this PR: enabling /v1/responses to work with non-OpenAI adaptors (Claude first) through chat-completions conversion.

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

@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: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@relay/channel/claude/relay-claude.go`:
- Around line 858-868: The block that marshals responsesResp uses json.Marshal
directly; replace that call with common.Marshal (i.e., responseData, err =
common.Marshal(responsesResp)) and keep the existing error handling (return
types.NewError(err, types.ErrorCodeBadResponseBody)); also remove any direct
dependency on encoding/json from this file's imports if it's only used for this
marshal so the code follows the common/json.go wrapper requirement. Ensure the
change is applied in the same case branch that converts with
ResponseClaude2OpenAI and service.ChatCompletionsResponseToResponsesResponse so
behavior and error mapping remain identical.

In `@relay/responses_via_chat_completions.go`:
- Around line 160-207: oaiChatStreamToResponsesHandler currently only
accumulates content via contentBuilder and ignores streaming tool calls; add a
slice variable (e.g., toolCalls := []dto.ToolCall{}) near where usage/model are
declared, append any entries from choice.Delta.ToolCalls inside the chunk
processing loop (using the existing for _, choice := range chunk.Choices block),
and then include these accumulated toolCalls when constructing the final
synthetic response (the same place where contentBuilder.String(), model, usage,
and finishReason are used) so streamed tool calls are preserved and returned.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f4403ad5-9767-4cf8-a691-40ed598a3734

📥 Commits

Reviewing files that changed from the base of the PR and between 8ed2ea6 and 17790e2.

📒 Files selected for processing (9)
  • dto/openai_response.go
  • relay/channel/claude/adaptor.go
  • relay/channel/claude/relay-claude.go
  • relay/responses_handler.go
  • relay/responses_via_chat_completions.go
  • service/openai_chat_responses_compat.go
  • service/openaicompat/chat_stream_to_responses_stream.go
  • service/openaicompat/responses_to_chat.go
  • web/src/components/table/usage-logs/UsageLogsColumnDefs.jsx
💤 Files with no reviewable changes (1)
  • web/src/components/table/usage-logs/UsageLogsColumnDefs.jsx

Comment thread relay/channel/claude/relay-claude.go
Comment thread relay/responses_via_chat_completions.go
@0-don
0-don force-pushed the feat/responses-via-chat-completions branch 2 times, most recently from d02513a to 5397d8d Compare March 19, 2026 15:31

@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: 2

🧹 Nitpick comments (1)
relay/channel/claude/relay-claude.go (1)

851-857: Pre-existing: json.Marshal used instead of common.Marshal.

Line 854 uses json.Marshal(openaiResponse) directly, which violates the coding guideline. While this is pre-existing code not introduced by this PR, consider fixing it for consistency since you're modifying this function.

♻️ Proposed fix
 	case types.RelayFormatOpenAI:
 		openaiResponse := ResponseClaude2OpenAI(&claudeResponse)
 		openaiResponse.Usage = *claudeInfo.Usage
-		responseData, err = json.Marshal(openaiResponse)
+		responseData, err = common.Marshal(openaiResponse)
 		if err != nil {
 			return types.NewError(err, types.ErrorCodeBadResponseBody)
 		}

As per coding guidelines: "All JSON marshal/unmarshal operations MUST use wrapper functions in common/json.go."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@relay/channel/claude/relay-claude.go` around lines 851 - 857, Replace the
direct call to json.Marshal when encoding openaiResponse with the project's
wrapper common.Marshal; locate the block that creates openaiResponse via
ResponseClaude2OpenAI(&claudeResponse) and assigns openaiResponse.Usage =
*claudeInfo.Usage, then swap json.Marshal(openaiResponse) for
common.Marshal(openaiResponse) and preserve the existing error handling (return
types.NewError(err, types.ErrorCodeBadResponseBody)) so behavior is unchanged
aside from using the mandated wrapper.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@relay/responses_via_chat_completions.go`:
- Around line 174-225: After the bufio.NewScanner read loop, check scanner.Err()
and handle any non-nil error instead of silently ignoring it: immediately after
the for scanner.Scan() { ... } block, call if err := scanner.Err(); err != nil {
/* propagate / return the error, or set a failure on the surrounding handler and
log it with context (include resp.Body or request id) */ }. Update the
surrounding function that reads from resp.Body to propagate that error (or
return a non-nil error) so truncated or I/O-failed streams are reported instead
of returning partial content; place this check directly after the scanner loop
that populates contentBuilder, toolCalls, model, usage, and finishReason.

In `@service/openaicompat/chat_stream_to_responses_stream.go`:
- Around line 110-119: The fallback generation for missing call.ID (the block
that sets callID when call.Index is nil and s.ToolCallOrder is empty) can
produce collisions because fmt.Sprintf("call_%d", idx) resets per chunk; change
it to use a monotonically-increasing, concurrency-safe counter on the stream
struct (e.g., add s.callFallbackCounter uint64 and increment with
atomic.AddUint64) and format callID from that counter (or combine the counter
with idx/chunk identifier) instead of just idx; update references in the call-ID
assignment logic (where callID, call.Index, s.ToolCallOrder and idx are used) to
use the new counter for unique IDs.

---

Nitpick comments:
In `@relay/channel/claude/relay-claude.go`:
- Around line 851-857: Replace the direct call to json.Marshal when encoding
openaiResponse with the project's wrapper common.Marshal; locate the block that
creates openaiResponse via ResponseClaude2OpenAI(&claudeResponse) and assigns
openaiResponse.Usage = *claudeInfo.Usage, then swap json.Marshal(openaiResponse)
for common.Marshal(openaiResponse) and preserve the existing error handling
(return types.NewError(err, types.ErrorCodeBadResponseBody)) so behavior is
unchanged aside from using the mandated wrapper.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 2fa6af5b-707d-45ed-b9e8-ac24f991157b

📥 Commits

Reviewing files that changed from the base of the PR and between 17790e2 and 5397d8d.

📒 Files selected for processing (9)
  • dto/openai_response.go
  • relay/channel/claude/adaptor.go
  • relay/channel/claude/relay-claude.go
  • relay/responses_handler.go
  • relay/responses_via_chat_completions.go
  • service/openai_chat_responses_compat.go
  • service/openaicompat/chat_stream_to_responses_stream.go
  • service/openaicompat/responses_to_chat.go
  • web/src/components/table/usage-logs/UsageLogsColumnDefs.jsx
💤 Files with no reviewable changes (1)
  • web/src/components/table/usage-logs/UsageLogsColumnDefs.jsx
✅ Files skipped from review due to trivial changes (1)
  • service/openai_chat_responses_compat.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • relay/channel/claude/adaptor.go

Comment thread relay/responses_via_chat_completions.go
Comment thread service/openaicompat/chat_stream_to_responses_stream.go
@0-don
0-don force-pushed the feat/responses-via-chat-completions branch 2 times, most recently from fab4b53 to 4ba4a03 Compare March 21, 2026 00:42

@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: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@service/openaicompat/chat_stream_to_responses_stream.go`:
- Around line 88-102: The code is reserving an output slot for streaming
reasoning (setting ReasoningOutputIndex and incrementing NextOutputIndex when
emitting response.reasoning_summary_text.delta) but FinalEvents() and
buildFinalOutput() never produce the corresponding
response.output_item.added/done or a concrete output item, causing output_index
mismatches; fix by either (A) stop consuming an output slot for reasoning deltas
(do not set ReasoningOutputIndex/advance NextOutputIndex when emitting
response.reasoning_summary_text.delta) so subsequent items keep consistent
indices, or (B) ensure the reserved slot is materialized by emitting a matching
response.output_item.added (and response.output_item.done) and including the
reasoning item in buildFinalOutput() so FinalEvents() produces the same
output_index; update all places using ReasoningOutputIndex/NextOutputIndex
(including the emission site for response.reasoning_summary_text.delta and the
logic in FinalEvents() and buildFinalOutput()) to keep indices consistent.

In `@service/openaicompat/responses_to_chat.go`:
- Around line 261-289: Non-function tools appended into
dto.ToolCallRequest.Custom are never consumed by the Claude relay (which only
reads tool.Function.* and textRequest.WebSearchOptions), so built-in tools like
web_search_preview and file_search are dropped; update the Claude relay to
inspect dto.ToolCallRequest.Custom when Type != "function", unmarshal the Custom
JSON and map known tool types (e.g., "web_search_preview", "file_search") into
the fields the relay sends upstream (for example populate
textRequest.WebSearchOptions or the equivalent request fields it expects), or
add a clear unmarshalling/mapping layer in relay-claude to handle these custom
tool payloads so non-function tools are forwarded instead of ignored.
- Around line 148-157: flushToolCalls currently constructs dto.Message with
Content: nil which downstream relay rewrites to "..." and injects synthetic
text; instead create the assistant message with a non-nil empty content before
attaching tool calls so no synthetic text is added. In other words, inside
flushToolCalls (and where dto.Message is constructed for assistant tool_call
turns) ensure msg.Content is initialized to an explicit empty text content
(e.g., an empty string content object) prior to calling
msg.SetToolCalls(pendingToolCalls) and appending to messages so the assistant
tool-call turn has empty, not nil, content.
- Around line 455-472: The current code only appends an assistant "message"
output when choice.Message.IsStringContent() is true, which drops text when the
message is multipart; change the logic in the block handling text content
(around choice.Message.IsStringContent()) to call and flatten
choice.Message.ParseContent() instead of gating on IsStringContent(), iterate
parsed parts to collect any text parts, and append a single dto.ResponsesOutput
with Type "message" and Content entries for each text part (preserving existing
fields like ID, Status, Role and using output_text for text parts) so multipart
text is emitted the same as a plain string.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 810f0ca6-b901-414c-8281-019a9b5b16f8

📥 Commits

Reviewing files that changed from the base of the PR and between fab4b53 and 4ba4a03.

📒 Files selected for processing (9)
  • dto/openai_response.go
  • relay/channel/claude/adaptor.go
  • relay/channel/claude/relay-claude.go
  • relay/responses_handler.go
  • relay/responses_via_chat_completions.go
  • service/openai_chat_responses_compat.go
  • service/openaicompat/chat_stream_to_responses_stream.go
  • service/openaicompat/responses_to_chat.go
  • web/src/components/table/usage-logs/UsageLogsColumnDefs.jsx
💤 Files with no reviewable changes (1)
  • web/src/components/table/usage-logs/UsageLogsColumnDefs.jsx
✅ Files skipped from review due to trivial changes (1)
  • relay/responses_via_chat_completions.go
🚧 Files skipped from review as they are similar to previous changes (4)
  • relay/channel/claude/adaptor.go
  • relay/responses_handler.go
  • service/openai_chat_responses_compat.go
  • dto/openai_response.go

Comment thread service/openaicompat/chat_stream_to_responses_stream.go
Comment thread service/openaicompat/responses_to_chat.go Outdated
Comment thread service/openaicompat/responses_to_chat.go Outdated
Comment thread service/openaicompat/responses_to_chat.go Outdated
@0-don
0-don force-pushed the feat/responses-via-chat-completions branch from 4ba4a03 to b165f6e Compare March 21, 2026 19:39

@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

♻️ Duplicate comments (4)
service/openaicompat/chat_stream_to_responses_stream.go (1)

87-102: ⚠️ Potential issue | 🟠 Major

Reasoning deltas consume an output slot that never exists in response.completed.output.

HandleChatChunk() reserves NextOutputIndex for reasoning, but FinalEvents() / buildFinalOutput() never materialize a matching output item. If reasoning arrives before text or tool calls, later streamed output_index values no longer line up with the final response.completed.output array.

Also applies to: 181-191, 446-484

service/openaicompat/responses_to_chat.go (3)

148-157: ⚠️ Potential issue | 🟠 Major

Don't emit nil content for assistant tool-call turns.

This builds assistant tool-call messages with Content: nil. Downstream, relay/channel/claude/relay-claude.go rewrites nil assistant content to "...", so replayed function_call turns gain synthetic text and the prompt changes across tool iterations.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@service/openaicompat/responses_to_chat.go` around lines 148 - 157, The
flushToolCalls helper is creating assistant messages with Content: nil which
downstream code rewrites to "..." and alters prompts; update the flushToolCalls
implementation so the dto.Message created for tool-call turns uses a non-nil
empty content (e.g., Content: "" or an explicit empty content object) instead of
nil before calling msg.SetToolCalls(pendingToolCalls) and appending to messages,
ensuring the assistant tool-call turn has a stable, non-nil Content field.

455-473: ⚠️ Potential issue | 🟠 Major

Flatten multipart assistant content before emitting output_text.

This only emits a message output when choice.Message.IsStringContent() is true. If an adaptor returns text as multipart content, /v1/responses comes back without the assistant message output even though the text is available in ParseContent().

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@service/openaicompat/responses_to_chat.go` around lines 455 - 473, The
current block only emits an assistant message when
choice.Message.IsStringContent() is true, so multipart text parts are dropped;
update the logic in the responses construction (the block building
dto.ResponsesOutput for Type:"message") to flatten/parse multipart content via
choice.Message.ParseContent() (or equivalent API on choice.Message) and extract
any text parts, concatenating or emitting them as a single output_text entry
when present; keep using dto.ResponsesOutput and dto.ResponsesOutputContent,
preserve existing fields (ID, Status, Role), and ensure you still prefer
StringContent() when available but fall back to parsed/flattened parts to avoid
losing multipart text.

261-289: ⚠️ Potential issue | 🟠 Major

Claude still drops non-function tools from this conversion.

These tools are packed into dto.ToolCallRequest.Custom, but the Claude path passes the converted chat request straight into ConvertOpenAIRequest and only reads tool.Function.* / WebSearchOptions. web_search_preview, file_search, and similar built-ins never make it upstream.

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@relay/responses_via_chat_completions.go`:
- Around line 79-95: The current branch uses the upstream Content-Type
(isStream) to choose oaiChatStreamToResponsesHandler which fully buffers the
chat stream; change this so buffering is only used when the original caller did
NOT request streaming: detect the caller's stream flag (e.g. the request/Info
struct field that represents stream=true or the request query/header) and only
call oaiChatStreamToResponsesHandler when that flag is false; otherwise route
the upstream stream into the incremental Responses stream translator/state
machine (i.e., use the incremental chunk-to-Responses handler instead of
buffering). Update the isStream branch logic around isStream,
oaiChatStreamToResponsesHandler and oaiChatToResponsesHandler (and apply the
same gating for the other affected block at lines 155-268).

---

Duplicate comments:
In `@service/openaicompat/responses_to_chat.go`:
- Around line 148-157: The flushToolCalls helper is creating assistant messages
with Content: nil which downstream code rewrites to "..." and alters prompts;
update the flushToolCalls implementation so the dto.Message created for
tool-call turns uses a non-nil empty content (e.g., Content: "" or an explicit
empty content object) instead of nil before calling
msg.SetToolCalls(pendingToolCalls) and appending to messages, ensuring the
assistant tool-call turn has a stable, non-nil Content field.
- Around line 455-473: The current block only emits an assistant message when
choice.Message.IsStringContent() is true, so multipart text parts are dropped;
update the logic in the responses construction (the block building
dto.ResponsesOutput for Type:"message") to flatten/parse multipart content via
choice.Message.ParseContent() (or equivalent API on choice.Message) and extract
any text parts, concatenating or emitting them as a single output_text entry
when present; keep using dto.ResponsesOutput and dto.ResponsesOutputContent,
preserve existing fields (ID, Status, Role), and ensure you still prefer
StringContent() when available but fall back to parsed/flattened parts to avoid
losing multipart text.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 702db28f-e355-4b5f-904d-fc93fcb716c1

📥 Commits

Reviewing files that changed from the base of the PR and between 4ba4a03 and b165f6e.

📒 Files selected for processing (9)
  • dto/openai_response.go
  • relay/channel/claude/adaptor.go
  • relay/channel/claude/relay-claude.go
  • relay/responses_handler.go
  • relay/responses_via_chat_completions.go
  • service/openai_chat_responses_compat.go
  • service/openaicompat/chat_stream_to_responses_stream.go
  • service/openaicompat/responses_to_chat.go
  • web/src/components/table/usage-logs/UsageLogsColumnDefs.jsx
💤 Files with no reviewable changes (1)
  • web/src/components/table/usage-logs/UsageLogsColumnDefs.jsx
🚧 Files skipped from review as they are similar to previous changes (4)
  • relay/channel/claude/adaptor.go
  • relay/responses_handler.go
  • service/openai_chat_responses_compat.go
  • dto/openai_response.go

Comment thread relay/responses_via_chat_completions.go
@0-don
0-don force-pushed the feat/responses-via-chat-completions branch 6 times, most recently from c1138f1 to d20aa03 Compare March 27, 2026 15:09
@ghost

This comment was marked as spam.

@0-don
0-don force-pushed the feat/responses-via-chat-completions branch 5 times, most recently from 7783a5d to 311978b Compare April 4, 2026 14:09
@0-don
0-don force-pushed the feat/responses-via-chat-completions branch 5 times, most recently from 766d821 to 1ad7774 Compare April 15, 2026 20:02
@0-don
0-don force-pushed the feat/responses-via-chat-completions branch 2 times, most recently from 6c3c8a3 to 38c1634 Compare April 19, 2026 18:48
@0-don
0-don force-pushed the feat/responses-via-chat-completions branch 2 times, most recently from c8b6bb2 to 0baffcd Compare May 9, 2026 18:26
@0-don 0-don changed the title feat(responses): route /v1/responses through non-OpenAI adaptors via chat completions conversion feat(responses): bridge /v1/responses to chat-completions adaptors (Claude first) May 9, 2026
@0-don
0-don force-pushed the feat/responses-via-chat-completions branch 4 times, most recently from 4435141 to 653f3ab Compare May 19, 2026 14:45
@0-don
0-don force-pushed the feat/responses-via-chat-completions branch 5 times, most recently from 6982634 to 60e6f53 Compare May 27, 2026 20:25
@0-don
0-don force-pushed the feat/responses-via-chat-completions branch 5 times, most recently from 18724e1 to fa671a8 Compare June 7, 2026 14:51
@0-don
0-don force-pushed the feat/responses-via-chat-completions branch 2 times, most recently from 230d558 to b897aeb Compare June 14, 2026 16:39
@0-don
0-don force-pushed the feat/responses-via-chat-completions branch from b897aeb to b056f53 Compare June 18, 2026 20:56
@0-don
0-don force-pushed the feat/responses-via-chat-completions branch 3 times, most recently from db08aff to 328938b Compare July 3, 2026 23:39
@0-don
0-don force-pushed the feat/responses-via-chat-completions branch 2 times, most recently from 9888e46 to 91790b7 Compare July 9, 2026 20:35
…chat completions conversion

Adds a compatibility shim that lets clients using the OpenAI Responses API
hit channels backed by adaptors that only speak Chat Completions. The
shim converts the request, streams the chat-style response, and emits
Responses API events back to the client.

Updated for upstream's ResponsesOutput.Arguments switch to json.RawMessage
(was string when this PR was authored).
@0-don
0-don force-pushed the feat/responses-via-chat-completions branch from 91790b7 to ba86160 Compare July 22, 2026 17:14
@0-don

0-don commented Jul 22, 2026

Copy link
Copy Markdown
Contributor Author

Closing: no longer maintaining these against upstream.

@0-don 0-don closed this Jul 22, 2026
@0-don
0-don deleted the feat/responses-via-chat-completions branch July 22, 2026 18:30
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