feat: add OpenAI Responses API format conversion (api_format = "openai_responses")

Support Anthropic ↔ OpenAI Responses API format conversion alongside existing
Chat Completions conversion. The Responses API uses a flat input/output structure
with lifted function_call/function_call_output items and named SSE lifecycle events.
This commit is contained in:
Jason
2026-03-05 09:30:37 +08:00
parent fb8996d19c
commit a30e2096bb
14 changed files with 1081 additions and 66 deletions
+27 -9
View File
@@ -13,7 +13,11 @@ use super::{
CLAUDE_PARSER_CONFIG, CODEX_PARSER_CONFIG, GEMINI_PARSER_CONFIG, OPENAI_PARSER_CONFIG,
},
handler_context::RequestContext,
providers::{get_adapter, streaming::create_anthropic_sse_stream, transform},
providers::{
get_adapter, get_claude_api_format, streaming::create_anthropic_sse_stream,
streaming_responses::create_anthropic_sse_stream_from_responses, transform,
transform_responses,
},
response_processor::{create_logged_passthrough_stream, process_response, SseUsageCollector},
server::ProxyState,
types::*,
@@ -22,6 +26,7 @@ use super::{
};
use crate::app_config::AppType;
use axum::{extract::State, http::StatusCode, response::IntoResponse, Json};
use bytes::Bytes;
use serde_json::{json, Value};
// ============================================================================
@@ -107,7 +112,7 @@ pub async fn handle_messages(
/// Claude 格式转换处理(独有逻辑)
///
/// 处理 OpenRouter 旧 OpenAI 兼容接口的回退方案(当前默认不启用)
/// 支持 OpenAI Chat Completions 和 Responses API 两种格式的转换
async fn handle_claude_transform(
response: reqwest::Response,
ctx: &RequestContext,
@@ -116,11 +121,18 @@ async fn handle_claude_transform(
is_stream: bool,
) -> Result<axum::response::Response, ProxyError> {
let status = response.status();
let api_format = get_claude_api_format(&ctx.provider);
if is_stream {
// 流式响应转换 (OpenAI SSE → Anthropic SSE)
// 根据 api_format 选择流式转换器
let stream = response.bytes_stream();
let sse_stream = create_anthropic_sse_stream(stream);
let sse_stream: Box<
dyn futures::Stream<Item = Result<Bytes, std::io::Error>> + Send + Unpin,
> = if api_format == "openai_responses" {
Box::new(Box::pin(create_anthropic_sse_stream_from_responses(stream)))
} else {
Box::new(Box::pin(create_anthropic_sse_stream(stream)))
};
// 创建使用量收集器
let usage_collector = {
@@ -186,7 +198,7 @@ async fn handle_claude_transform(
return Ok((headers, body).into_response());
}
// 非流式响应转换 (OpenAI → Anthropic)
// 非流式响应转换 (OpenAI/Responses → Anthropic)
let response_headers = response.headers().clone();
let body_bytes = response.bytes().await.map_err(|e| {
@@ -196,12 +208,18 @@ async fn handle_claude_transform(
let body_str = String::from_utf8_lossy(&body_bytes);
let openai_response: Value = serde_json::from_slice(&body_bytes).map_err(|e| {
log::error!("[Claude] 解析 OpenAI 响应失败: {e}, body: {body_str}");
ProxyError::TransformError(format!("Failed to parse OpenAI response: {e}"))
let upstream_response: Value = serde_json::from_slice(&body_bytes).map_err(|e| {
log::error!("[Claude] 解析上游响应失败: {e}, body: {body_str}");
ProxyError::TransformError(format!("Failed to parse upstream response: {e}"))
})?;
let anthropic_response = transform::openai_to_anthropic(openai_response).map_err(|e| {
// 根据 api_format 选择非流式转换器
let anthropic_response = if api_format == "openai_responses" {
transform_responses::responses_to_anthropic(upstream_response)
} else {
transform::openai_to_anthropic(upstream_response)
}
.map_err(|e| {
log::error!("[Claude] 转换响应失败: {e}");
e
})?;