mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-07-31 02:51:21 +08:00
Add Chat Completions routing for Codex providers
- Add a Codex API format selector and routing badge for Chat Completions providers. - Convert Codex Responses requests to upstream Chat Completions when routing is required. - Convert Chat Completions JSON and SSE responses back to Responses format. - Keep generated Codex wire_api values on Responses for Codex compatibility. - Add i18n labels, provider metadata handling, and focused conversion tests.
This commit is contained in:
@@ -1108,20 +1108,29 @@ impl RequestForwarder {
|
||||
Some(api_format) => super::providers::claude_api_format_needs_transform(api_format),
|
||||
None => adapter.needs_transform(provider),
|
||||
};
|
||||
let (effective_endpoint, passthrough_query) =
|
||||
if needs_transform && adapter.name() == "Claude" {
|
||||
let api_format = resolved_claude_api_format
|
||||
.as_deref()
|
||||
.unwrap_or_else(|| super::providers::get_claude_api_format(provider));
|
||||
rewrite_claude_transform_endpoint(endpoint, api_format, is_copilot, &mapped_body)
|
||||
} else {
|
||||
(
|
||||
endpoint.to_string(),
|
||||
split_endpoint_and_query(endpoint)
|
||||
.1
|
||||
.map(ToString::to_string),
|
||||
)
|
||||
};
|
||||
let codex_responses_to_chat = matches!(app_type, AppType::Codex)
|
||||
&& super::providers::should_convert_codex_responses_to_chat(provider, endpoint);
|
||||
let (effective_endpoint, passthrough_query) = if codex_responses_to_chat {
|
||||
rewrite_codex_responses_endpoint_to_chat(endpoint)
|
||||
} else if needs_transform && adapter.name() == "Claude" {
|
||||
let api_format = resolved_claude_api_format
|
||||
.as_deref()
|
||||
.unwrap_or_else(|| super::providers::get_claude_api_format(provider));
|
||||
rewrite_claude_transform_endpoint(endpoint, api_format, is_copilot, &mapped_body)
|
||||
} else {
|
||||
(
|
||||
endpoint.to_string(),
|
||||
split_endpoint_and_query(endpoint)
|
||||
.1
|
||||
.map(ToString::to_string),
|
||||
)
|
||||
};
|
||||
|
||||
let codex_chat_base_is_full_endpoint = codex_responses_to_chat
|
||||
&& base_url
|
||||
.trim_end_matches('/')
|
||||
.to_ascii_lowercase()
|
||||
.ends_with("/chat/completions");
|
||||
|
||||
let url = if matches!(resolved_claude_api_format.as_deref(), Some("gemini_native")) {
|
||||
super::gemini_url::resolve_gemini_native_url(
|
||||
@@ -1129,14 +1138,16 @@ impl RequestForwarder {
|
||||
&effective_endpoint,
|
||||
is_full_url,
|
||||
)
|
||||
} else if is_full_url {
|
||||
} else if is_full_url || codex_chat_base_is_full_endpoint {
|
||||
append_query_to_full_url(&base_url, passthrough_query.as_deref())
|
||||
} else {
|
||||
adapter.build_url(&base_url, &effective_endpoint)
|
||||
};
|
||||
|
||||
// 转换请求体(如果需要)
|
||||
let request_body = if needs_transform {
|
||||
let request_body = if codex_responses_to_chat {
|
||||
super::providers::transform_codex_chat::responses_to_chat_completions(mapped_body)?
|
||||
} else if needs_transform {
|
||||
if adapter.name() == "Claude" {
|
||||
let api_format = resolved_claude_api_format
|
||||
.as_deref()
|
||||
@@ -1169,7 +1180,8 @@ impl RequestForwarder {
|
||||
);
|
||||
let request_is_streaming =
|
||||
is_streaming_request(&effective_endpoint, &filtered_body, headers);
|
||||
let force_identity_encoding = needs_transform || request_is_streaming;
|
||||
let force_identity_encoding =
|
||||
needs_transform || codex_responses_to_chat || request_is_streaming;
|
||||
|
||||
// Codex OAuth 需要注入的 ChatGPT-Account-Id(在动态 token 获取期间填充)
|
||||
let mut codex_oauth_account_id: Option<String> = None;
|
||||
@@ -2037,6 +2049,18 @@ fn is_claude_messages_path(path: &str) -> bool {
|
||||
matches!(path, "/v1/messages" | "/claude/v1/messages")
|
||||
}
|
||||
|
||||
fn rewrite_codex_responses_endpoint_to_chat(endpoint: &str) -> (String, Option<String>) {
|
||||
let (_path, query) = split_endpoint_and_query(endpoint);
|
||||
let passthrough_query = query.map(ToString::to_string);
|
||||
let target_path = "/chat/completions";
|
||||
let rewritten = match passthrough_query.as_deref() {
|
||||
Some(query) if !query.is_empty() => format!("{target_path}?{query}"),
|
||||
_ => target_path.to_string(),
|
||||
};
|
||||
|
||||
(rewritten, passthrough_query)
|
||||
}
|
||||
|
||||
fn rewrite_claude_transform_endpoint(
|
||||
endpoint: &str,
|
||||
api_format: &str,
|
||||
@@ -2760,6 +2784,15 @@ mod tests {
|
||||
assert_eq!(passthrough_query.as_deref(), Some("x-id=1"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rewrite_codex_responses_endpoint_to_chat_preserves_query() {
|
||||
let (endpoint, passthrough_query) =
|
||||
rewrite_codex_responses_endpoint_to_chat("/v1/responses?foo=bar");
|
||||
|
||||
assert_eq!(endpoint, "/chat/completions?foo=bar");
|
||||
assert_eq!(passthrough_query.as_deref(), Some("foo=bar"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rewrite_claude_transform_endpoint_uses_copilot_path() {
|
||||
let (endpoint, passthrough_query) = rewrite_claude_transform_endpoint(
|
||||
|
||||
Reference in New Issue
Block a user