mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-08-02 18:41:35 +08:00
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:
@@ -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
|
||||
})?;
|
||||
|
||||
Reference in New Issue
Block a user