Skip to content

feat(responses): route /v1/responses through non-OpenAI adaptors via chat completions conversion - #2970

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

feat(responses): route /v1/responses through non-OpenAI adaptors via chat completions conversion#2970
0-don wants to merge 5406 commits into
QuantumNous:mainfrom
0-don:feat/responses-via-chat-completions

Conversation

@0-don

@0-don 0-don commented Feb 18, 2026

Copy link
Copy Markdown
Contributor

Summary

Enable channels without native Responses API support (starting with Claude) to serve /v1/responses requests by converting Responses to Chat Completions format, routing through the existing adaptor pipeline, and converting responses back. Any future adaptor (Gemini, Deepseek, etc.) gets support by adding the same 3-line pattern.

Related: #2941, #2043, #1527

Why not use existing PRs directly?

vs #2892 (494 lines, Claude-only)

  • Reimplements all logic already in RequestOpenAI2ClaudeMessage (max tokens, thinking, tools, instructions, tool choice)
  • Naive SSE passthrough piping raw Claude events without converting to Responses API format. Clients expecting Responses SSE events will not work
  • Only works for Claude with no reusable converter for other adaptors
  • Adopted from feat(claude): Support calling Claude channels via /v1/responses #2892: the 2-line bug fix for tool calls being dropped when text content exists

vs #2817 (1039 lines)

  • Similar adaptor-level approach (good), but includes its own stream state and converter
  • Our stream state machine handles additional cases: reasoning content, usage chunks, SummaryIndex field
  • Adopted from feat: enhance OpenAI responses handling and compatibility #2817: adaptor-level ConvertOpenAIResponsesRequest pattern, GetJsonType() for input detection, developer to system role mapping, PromptCacheKey/PromptCacheRetention passthrough, non-function tool passthrough via Custom field

Architecture (1006 lines, adaptor-agnostic)

Converters (reusable for any adaptor):

  • ResponsesRequestToChatCompletionsRequest: handles developer role mapping, function_call/function_call_output merging into tool messages, content type detection via GetJsonType(), response format conversion, PromptCacheKey/PromptCacheRetention passthrough
  • ChatCompletionsResponseToResponsesResponse: non-streaming response conversion
  • ChatToResponsesStreamState: streaming state machine emitting proper Responses SSE events (response.created, response.output_text.delta, response.function_call_arguments.delta, response.completed, usage)

Claude adaptor: 3 lines chaining ResponsesRequestToChatCompletionsRequest then ConvertOpenAIRequest (reuses all existing Claude conversion logic)

Files changed

File Change
relay/channel/claude/adaptor.go Implement ConvertOpenAIResponsesRequest (+5 lines)
relay/channel/claude/relay-claude.go Add RelayFormatOpenAIResponses to stream/non-stream handlers
service/openaicompat/responses_to_chat.go Add converters + bug fix from #2892
service/openaicompat/chat_stream_to_responses_stream.go New: reusable stream state machine
service/openai_chat_responses_compat.go Service wrapper functions
dto/openai_response.go Add fields to ResponsesStreamResponse

Test plan

  • Streaming /v1/responses through Claude channel (verify SSE events match Responses API format)
  • Non-streaming /v1/responses through Claude channel
  • Tool calls with text content (both appear in output)
  • Multi-turn with function_call + function_call_output in input

Calcium-Ion and others added 30 commits February 6, 2026 23:08
fix: /v1/chat/completions -> /v1/responses json_schema
将散落在多个文件中的预扣费/结算/退款逻辑抽象为统一的 BillingSession 生命周期管理:

- 新增 BillingSettler 接口 (relay/common/billing.go) 避免循环引用
- 新增 FundingSource 接口 + WalletFunding / SubscriptionFunding 实现 (service/funding_source.go)
- 新增 BillingSession 封装预扣/结算/退款原子操作 (service/billing_session.go)
- 新增 SettleBilling 统一结算辅助函数,替换各 handler 中的 quotaDelta 模式
- 重写 PreConsumeBilling 为 BillingSession 工厂入口
- controller/relay.go 退款守卫改用 BillingSession.Refund()

修复的 Bug:
- 令牌额度泄漏:PreConsumeTokenQuota 成功但 DecreaseUserQuota 失败时未回滚
- 订阅退款遗漏:FinalPreConsumedQuota=0 但 SubscriptionPreConsumed>0 时跳过退款
- 订阅多扣费:subConsume 强制为 1 但 FinalPreConsumedQuota 不同步
- 退款路径不统一:钱包/订阅退款逻辑现统一由 FundingSource.Refund 分派
- Settle 部分失败保护:新增 fundingSettled 标记,资金来源提交后
  令牌调整失败不再导致 Refund 误退已结算的资金
- 订阅多扣费修复:trySubscription 传 subConsume 而非 preConsumedQuota
  给 preConsume,保证三者(amount/preConsume/FinalPreConsumedQuota)一致
- 令牌回滚错误记录:preConsume 中 funding 失败时令牌回滚错误不再丢弃
- 移除钱包路径死代码:用户额度不足的 strings.Contains 匹配不可能命中
- WalletFunding.Refund 不重试:IncreaseUserQuota 非幂等,重试会多退
…e recharge card tabs

- Defaulting to subscriptions when available and avoiding initial flash when no plans exist.
- Adjust the wide-screen layout to place wallet and invite sections side by side, simplify the subscription header and controls, and add padding to prevent card borders from clipping.
- Update related i18n strings by adding the new tab label and removing the obsolete subscription blurb.
…-when-no-plans

✨ refactor(wallet): Top-up layout to embed subscription plans into the recharge card tabs
refactor: 抽象统一计费会话 BillingSession
Add a lightweight active-subscription check to skip subscription pre-consume when none exist, reducing unnecessary transactions and locks. In the subscription UI, disable subscription-first options when no active plan is available, show the effective fallback to wallet with a clear notice, and distinguish “invalidated” from “expired” states. Update i18n strings across supported locales to reflect the new messages and status labels.
Aligns the error variable types in the subscription-first path so that quota fallback checks use the correct NewAPIError.
This prevents build failures and preserves the intended wallet fallback when subscription pre-consume returns an insufficient quota error.
Routes quota alerts through a subscription-specific check when billing from subscriptions, preventing wallet-based thresholds from triggering false warnings.
Updates the notification settings description and localization keys to clarify that both wallet and subscription balances are monitored.
🔔 feat: Add subscription-aware quota notifications and update UI copy
…-fallback

✨ chore: Improve subscription billing fallback and UI states
当上游为 AWS Bedrock 时,message_delta 的 usage 可能缺少 input_tokens、
cache_creation_input_tokens、cache_read_input_tokens 等字段,导致与原生
Anthropic 格式不一致。从 message_start 积累的 claudeInfo 中补全这些字段后
重新序列化,确保客户端收到一致的 usage 格式。
Modified the formatUserLogs function to include a startIdx parameter, allowing for more flexible log ID assignment. Updated calls to this function in GetLogByTokenId and GetUserLogs to pass the appropriate starting index.
feat: add Codex channel disclaimer (i18n, OpenAI terms)
feat: Force beta=true parameter for Anthropic channel
feat(oauth): implement custom OAuth provider
fix: Claude stream block index/type transitions
fix: add paragraph breaks between reasoning summary chunks
# Conflicts:
#	service/openaicompat/chat_to_responses.go
fix: 使用openai兼容接口调用部分渠道在最终端点为claude原生端点下还是走了openai扣减input_token的逻辑
fix: 补全 streaming message_delta 事件缺失的 input_tokens 和 cache 相关字段
…rable

feat: make 5m cache-creation ratio configurable
seefs001 and others added 2 commits March 10, 2026 01:59
…eader-append

feat:support $keep_only_declared and deduped $append for header override
@0-don
0-don force-pushed the feat/responses-via-chat-completions branch from 1875596 to 00ae74b Compare March 10, 2026 18:47
@0-don
0-don force-pushed the feat/responses-via-chat-completions branch 3 times, most recently from 59a03f9 to ae1b48a Compare March 13, 2026 12:32
@0-don
0-don force-pushed the feat/responses-via-chat-completions branch from ae1b48a to 34d9d40 Compare March 14, 2026 15:59
seefs001 and others added 7 commits March 15, 2026 00:23
…cff3f3572f535919

feat: add logs content tooltip
…chat completions conversion

Enable channels without native Responses API support (starting with
Claude) to serve /v1/responses by converting Responses to Chat
Completions format, routing through the existing adaptor pipeline,
and converting responses back.

Converters (adaptor-agnostic, reusable for any future adaptor):
- ResponsesRequestToChatCompletionsRequest: handles developer role
  mapping, function_call/function_call_output merging, tool passthrough,
  response format conversion, PromptCacheKey/PromptCacheRetention
- ChatCompletionsResponseToResponsesResponse: non-streaming conversion
- ChatToResponsesStreamState: streaming state machine that emits proper
  Responses SSE events (response.created, output_text.delta,
  function_call_arguments.delta, response.completed, usage)

Claude adaptor: 3-line integration chaining ResponsesRequest to
ChatCompletions to native Claude format via existing conversion path.

Bug fix from #2892: tool calls no longer dropped when text content
exists in ResponsesResponseToChatCompletionsResponse.

Related: #2941, #2043, #1527
@0-don
0-don force-pushed the feat/responses-via-chat-completions branch from 34d9d40 to 55cf511 Compare March 16, 2026 23:29
@0-don

0-don commented Mar 17, 2026

Copy link
Copy Markdown
Contributor Author

Addressed remaining CodeRabbit feedback:

  • encoding/json import: Replaced all json.RawMessage with []byte in both responses_to_chat.go and chat_stream_to_responses_stream.go, removed direct encoding/json imports.
  • input_file key lookup: Changed partMap["file"] to partMap["file_data"] to match the Responses API spec.
  • Tool-call Content nil: Changed Content: "" to Content: nil in flushToolCalls so tool-call-only messages serialize as "content": null.
  • Reasoning output index: Allocated via NextOutputIndex instead of hardcoded 0 to avoid colliding with message/tool output indices.
  • Reasoning done event: Added response.reasoning_summary_text.done emission in FinalEvents so reasoning streams have a proper closing event.

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.