From c1048ebcbef0f533a38feacd0249fd700baa08b2 Mon Sep 17 00:00:00 2001 From: YoVinchen Date: Thu, 12 Feb 2026 23:00:46 +0800 Subject: [PATCH] refactor(proxy): remove Codex Chat Completions API format support Codex CLI only uses OpenAI Responses API. Remove unused Chat Completions routes, handler, parser config, URL builder branches, frontend API format selector, and stale i18n keys. Claude openai_chat support is preserved. --- src-tauri/src/proxy/handler_config.rs | 33 ---------- src-tauri/src/proxy/handlers.rs | 45 +------------ src-tauri/src/proxy/providers/codex.rs | 27 +------- src-tauri/src/proxy/server.rs | 14 ----- src-tauri/src/proxy/url_builder.rs | 42 ++----------- src-tauri/src/services/proxy.rs | 2 +- .../providers/forms/CodexFormFields.tsx | 63 +------------------ .../providers/forms/ProviderForm.tsx | 38 +---------- src/i18n/locales/en.json | 4 -- src/i18n/locales/ja.json | 4 -- src/i18n/locales/zh.json | 4 -- src/types.ts | 3 +- src/utils/providerConfigUtils.ts | 21 +------ 13 files changed, 17 insertions(+), 283 deletions(-) diff --git a/src-tauri/src/proxy/handler_config.rs b/src-tauri/src/proxy/handler_config.rs index fcbeb1fa2..42179ac49 100644 --- a/src-tauri/src/proxy/handler_config.rs +++ b/src-tauri/src/proxy/handler_config.rs @@ -42,22 +42,6 @@ fn claude_model_extractor(events: &[Value], request_model: &str) -> String { request_model.to_string() } -/// OpenAI Chat Completions 流式响应模型提取(优先使用 usage.model) -fn openai_model_extractor(events: &[Value], request_model: &str) -> String { - // 首先尝试从解析的 usage 中获取模型 - if let Some(usage) = TokenUsage::from_openai_stream_events(events) { - if let Some(model) = usage.model { - return model; - } - } - // 回退:从事件中直接提取 - events - .iter() - .find_map(|e| e.get("model")?.as_str()) - .unwrap_or(request_model) - .to_string() -} - /// Codex 智能流式响应模型提取(自动检测格式) fn codex_auto_model_extractor(events: &[Value], request_model: &str) -> String { // 首先尝试从解析的 usage 中获取模型 @@ -107,14 +91,6 @@ pub const CLAUDE_PARSER_CONFIG: UsageParserConfig = UsageParserConfig { app_type_str: "claude", }; -/// OpenAI Chat Completions API 解析配置(用于 Codex /v1/chat/completions) -pub const OPENAI_PARSER_CONFIG: UsageParserConfig = UsageParserConfig { - stream_parser: TokenUsage::from_openai_stream_events, - response_parser: TokenUsage::from_openai_response, - model_extractor: openai_model_extractor, - app_type_str: "codex", -}; - /// Codex 智能解析配置(自动检测 OpenAI 或 Codex 格式) pub const CODEX_PARSER_CONFIG: UsageParserConfig = UsageParserConfig { stream_parser: TokenUsage::from_codex_stream_events_auto, @@ -160,15 +136,6 @@ pub const CLAUDE_HANDLER_CONFIG: HandlerConfig = HandlerConfig { parser_config: &CLAUDE_PARSER_CONFIG, }; -/// Codex Chat Completions Handler 配置 -#[allow(dead_code)] -pub const CODEX_CHAT_HANDLER_CONFIG: HandlerConfig = HandlerConfig { - app_type: AppType::Codex, - tag: "Codex", - app_type_str: "codex", - parser_config: &OPENAI_PARSER_CONFIG, -}; - /// Codex Responses Handler 配置 #[allow(dead_code)] pub const CODEX_RESPONSES_HANDLER_CONFIG: HandlerConfig = HandlerConfig { diff --git a/src-tauri/src/proxy/handlers.rs b/src-tauri/src/proxy/handlers.rs index a3e83e340..6b072e1cd 100644 --- a/src-tauri/src/proxy/handlers.rs +++ b/src-tauri/src/proxy/handlers.rs @@ -9,9 +9,7 @@ use super::{ error_mapper::{get_error_message, map_proxy_error_to_status}, - handler_config::{ - CLAUDE_PARSER_CONFIG, CODEX_PARSER_CONFIG, GEMINI_PARSER_CONFIG, OPENAI_PARSER_CONFIG, - }, + handler_config::{CLAUDE_PARSER_CONFIG, CODEX_PARSER_CONFIG, GEMINI_PARSER_CONFIG}, handler_context::RequestContext, providers::{get_adapter, streaming::create_anthropic_sse_stream, transform}, response_processor::{create_logged_passthrough_stream, process_response, SseUsageCollector}, @@ -273,47 +271,6 @@ async fn handle_claude_transform( // Codex API 处理器 // ============================================================================ -/// 处理 /v1/chat/completions 请求(OpenAI Chat Completions API - Codex CLI) -pub async fn handle_chat_completions( - State(state): State, - headers: axum::http::HeaderMap, - Json(body): Json, -) -> Result { - let mut ctx = - RequestContext::new(&state, &body, &headers, AppType::Codex, "Codex", "codex").await?; - - let is_stream = body - .get("stream") - .and_then(|v| v.as_bool()) - .unwrap_or(false); - - let forwarder = ctx.create_forwarder(&state); - let result = match forwarder - .forward_with_retry( - &AppType::Codex, - "/chat/completions", - body, - headers, - ctx.get_providers(), - ) - .await - { - Ok(result) => result, - Err(mut err) => { - if let Some(provider) = err.provider.take() { - ctx.provider = provider; - } - log_forward_error(&state, &ctx, is_stream, &err.error); - return Err(err.error); - } - }; - - ctx.provider = result.provider; - let response = result.response; - - process_response(response, &ctx, &state, &OPENAI_PARSER_CONFIG).await -} - /// 处理 /v1/responses 请求(OpenAI Responses API - Codex CLI 透传) pub async fn handle_responses( State(state): State, diff --git a/src-tauri/src/proxy/providers/codex.rs b/src-tauri/src/proxy/providers/codex.rs index 3d4351d14..833b6b89c 100644 --- a/src-tauri/src/proxy/providers/codex.rs +++ b/src-tauri/src/proxy/providers/codex.rs @@ -144,13 +144,8 @@ impl ProviderAdapter for CodexAdapter { let endpoint_trimmed = endpoint.trim_start_matches('/'); // 检测 base_url 是否已经以 API 路径结尾(用户填写了完整路径) - // 支持的 API 路径模式:/v1/responses, /responses, /v1/chat/completions, /chat/completions - let api_path_patterns = [ - "/v1/responses", - "/responses", - "/v1/chat/completions", - "/chat/completions", - ]; + // 仅支持 Responses API 路径模式:/v1/responses, /responses + let api_path_patterns = ["/v1/responses", "/responses"]; let base_ends_with_api_path = api_path_patterns .iter() @@ -293,17 +288,6 @@ mod tests { assert_eq!(url, "https://example.com/v1/responses"); } - #[test] - fn test_build_url_full_path_chat_completions() { - let adapter = CodexAdapter::new(); - // base_url 已包含完整路径 /v1/chat/completions,不再追加 - let url = adapter.build_url( - "https://example.com/v1/chat/completions", - "/chat/completions", - ); - assert_eq!(url, "https://example.com/v1/chat/completions"); - } - #[test] fn test_build_url_full_path_short_suffix() { let adapter = CodexAdapter::new(); @@ -319,13 +303,6 @@ mod tests { // 场景:https://integrate.api.nvidia.com/v1 + /v1/responses let url = adapter.build_url("https://integrate.api.nvidia.com/v1", "/v1/responses"); assert_eq!(url, "https://integrate.api.nvidia.com/v1/responses"); - - // 另一个场景:/v1 + /v1/chat/completions - let url2 = adapter.build_url( - "https://integrate.api.nvidia.com/v1", - "/v1/chat/completions", - ); - assert_eq!(url2, "https://integrate.api.nvidia.com/v1/chat/completions"); } // 官方客户端检测测试 diff --git a/src-tauri/src/proxy/server.rs b/src-tauri/src/proxy/server.rs index 9de6b7292..6e6691605 100644 --- a/src-tauri/src/proxy/server.rs +++ b/src-tauri/src/proxy/server.rs @@ -218,20 +218,6 @@ impl ProxyServer { // Claude API (支持带前缀和不带前缀两种格式) .route("/v1/messages", post(handlers::handle_messages)) .route("/claude/v1/messages", post(handlers::handle_messages)) - // OpenAI Chat Completions API (Codex CLI,支持带前缀和不带前缀) - .route("/chat/completions", post(handlers::handle_chat_completions)) - .route( - "/v1/chat/completions", - post(handlers::handle_chat_completions), - ) - .route( - "/v1/v1/chat/completions", - post(handlers::handle_chat_completions), - ) - .route( - "/codex/v1/chat/completions", - post(handlers::handle_chat_completions), - ) // OpenAI Responses API (Codex CLI,支持带前缀和不带前缀) .route("/responses", post(handlers::handle_responses)) .route("/v1/responses", post(handlers::handle_responses)) diff --git a/src-tauri/src/proxy/url_builder.rs b/src-tauri/src/proxy/url_builder.rs index 5bfa578df..c6dd127d8 100644 --- a/src-tauri/src/proxy/url_builder.rs +++ b/src-tauri/src/proxy/url_builder.rs @@ -59,32 +59,11 @@ impl ApiPathPatterns { } fn for_codex(api_format: Option<&str>) -> Self { - // Codex: - // - Direct mode (Codex CLI) hard-codes Responses API: `/responses` - // - Proxy mode might target different upstream formats (e.g. Chat Completions) - let is_chat = matches!(api_format, Some("chat")); - if is_chat { - Self { - direct_endpoint: "/responses", - proxy_endpoint: "/v1/chat/completions", - full_url_patterns: &[ - "/v1/responses", - "/responses", - "/v1/chat/completions", - "/chat/completions", - ], - } - } else { - Self { - direct_endpoint: "/responses", - proxy_endpoint: "/responses", - full_url_patterns: &[ - "/v1/responses", - "/responses", - "/v1/chat/completions", - "/chat/completions", - ], - } + let _ = api_format; + Self { + direct_endpoint: "/responses", + proxy_endpoint: "/responses", + full_url_patterns: &["/v1/responses", "/responses"], } } @@ -304,17 +283,6 @@ mod tests { assert!(!preview.is_full_url); } - #[test] - fn test_build_url_preview_codex_chat_proxy_endpoint() { - let preview = build_url_preview(&AppType::Codex, "https://api.openai.com", Some("chat")); - assert_eq!(preview.direct_url, "https://api.openai.com/v1/responses"); - assert_eq!( - preview.proxy_url, - "https://api.openai.com/v1/chat/completions" - ); - assert!(!preview.is_full_url); - } - #[test] fn test_build_url_preview_codex_full_url() { // 全链接时:直连/代理均保持原地址(运行时适配器规则) diff --git a/src-tauri/src/services/proxy.rs b/src-tauri/src/services/proxy.rs index 0f429b030..d19684df0 100644 --- a/src-tauri/src/services/proxy.rs +++ b/src-tauri/src/services/proxy.rs @@ -814,7 +814,7 @@ impl ProxyService { /// /// 代理服务器的路由已经根据 API 端点自动区分应用类型: /// - `/v1/messages` → Claude - /// - `/v1/chat/completions`, `/v1/responses` → Codex + /// - `/v1/responses` → Codex /// - `/v1beta/*` → Gemini /// /// 因此不需要在 URL 中添加应用前缀。 diff --git a/src/components/providers/forms/CodexFormFields.tsx b/src/components/providers/forms/CodexFormFields.tsx index 38abb4f05..62f87ad29 100644 --- a/src/components/providers/forms/CodexFormFields.tsx +++ b/src/components/providers/forms/CodexFormFields.tsx @@ -2,22 +2,11 @@ import { useTranslation } from "react-i18next"; import EndpointSpeedTest from "./EndpointSpeedTest"; import { ApiKeySection, EndpointField } from "./shared"; import type { ProviderCategory } from "@/types"; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "@/components/ui/select"; -import { FormLabel } from "@/components/ui/form"; interface EndpointCandidate { url: string; } -// Codex API 格式类型 -export type CodexApiFormat = "responses" | "chat"; - interface CodexFormFieldsProps { providerId?: string; // API Key @@ -46,11 +35,6 @@ interface CodexFormFieldsProps { // Speed Test Endpoints speedTestEndpoints: EndpointCandidate[]; - - // API Format (for Codex providers) - apiFormat?: CodexApiFormat; - onApiFormatChange?: (format: CodexApiFormat) => void; - shouldShowApiFormatSelector?: boolean; } export function CodexFormFields({ @@ -74,21 +58,9 @@ export function CodexFormFields({ modelName = "", onModelNameChange, speedTestEndpoints, - apiFormat = "responses", - onApiFormatChange, - shouldShowApiFormatSelector = false, }: CodexFormFieldsProps) { const { t } = useTranslation(); - // 根据 API 格式选择提示文本 - const apiHint = - apiFormat === "chat" - ? t("providerForm.codexApiHintChat", { - defaultValue: - "💡 填写兼容 OpenAI Chat Completions 格式的服务端点地址", - }) - : t("providerForm.codexApiHint"); - return ( <> {/* Codex API Key 输入框 */} @@ -112,37 +84,6 @@ export function CodexFormFields({ }} /> - {/* API 格式选择器 */} - {shouldShowApiFormatSelector && onApiFormatChange && ( -
- - {t("providerForm.apiFormat", { defaultValue: "API 格式" })} - - -

- {t("providerForm.codexApiFormatHint", { - defaultValue: "选择供应商支持的 API 格式", - })} -

-
- )} - {/* Codex Base URL 输入框 */} {shouldShowSpeedTest && ( onEndpointModalToggle(true)} appType="codex" - apiFormat={apiFormat} + apiFormat="responses" /> )} diff --git a/src/components/providers/forms/ProviderForm.tsx b/src/components/providers/forms/ProviderForm.tsx index e32ff30b4..8a3db5749 100644 --- a/src/components/providers/forms/ProviderForm.tsx +++ b/src/components/providers/forms/ProviderForm.tsx @@ -37,7 +37,6 @@ import { OpenCodeFormFields } from "./OpenCodeFormFields"; import type { UniversalProviderPreset } from "@/config/universalProviderPresets"; import { applyTemplateValues, - extractCodexWireApi, setCodexWireApi, } from "@/utils/providerConfigUtils"; import { mergeProviderMeta } from "@/utils/providerMetaUtils"; @@ -50,7 +49,7 @@ import { Label } from "@/components/ui/label"; import { ProviderPresetSelector } from "./ProviderPresetSelector"; import { BasicFormFields } from "./BasicFormFields"; import { ClaudeFormFields } from "./ClaudeFormFields"; -import { CodexFormFields, type CodexApiFormat } from "./CodexFormFields"; +import { CodexFormFields } from "./CodexFormFields"; import { GeminiFormFields } from "./GeminiFormFields"; import { OmoFormFields } from "./OmoFormFields"; import { type OmoGlobalConfigFieldsRef } from "./OmoGlobalConfigFields"; @@ -369,19 +368,6 @@ export function ProviderForm({ : "anthropic"; }); - const [localCodexApiFormat, setLocalCodexApiFormat] = - useState(() => { - if (appId !== "codex") return "responses"; - if (initialData?.meta?.apiFormat === "chat") return "chat"; - if (initialData?.meta?.apiFormat === "responses") return "responses"; - - const initialConfig = initialData?.settingsConfig?.config; - if (typeof initialConfig === "string") { - return extractCodexWireApi(initialConfig) ?? "responses"; - } - return "responses"; - }); - const { codexAuth, codexConfig, @@ -390,7 +376,6 @@ export function ProviderForm({ codexModelName, codexAuthError, setCodexAuth, - setCodexConfig, handleCodexApiKeyChange, handleCodexBaseUrlChange, handleCodexModelNameChange, @@ -413,14 +398,6 @@ export function ProviderForm({ setLocalClaudeApiFormat(format); }, []); - const handleCodexApiFormatChange = useCallback( - (format: CodexApiFormat) => { - setLocalCodexApiFormat(format); - setCodexConfig((prev) => setCodexWireApi(prev, format)); - }, - [setCodexConfig], - ); - useEffect(() => { if (appId === "codex" && !initialData && selectedPresetId === "custom") { const template = getCodexCustomTemplate(); @@ -1078,7 +1055,7 @@ export function ProviderForm({ const authJson = JSON.parse(codexAuth); const configObj = { auth: authJson, - config: codexConfig ?? "", + config: setCodexWireApi(codexConfig ?? "", "responses"), }; settingsConfig = JSON.stringify(configObj); } catch (err) { @@ -1198,9 +1175,7 @@ export function ProviderForm({ const providerApiFormat = appId === "claude" && category !== "official" ? localClaudeApiFormat - : appId === "codex" && category !== "official" - ? localCodexApiFormat - : undefined; + : undefined; const baseMeta: ProviderMeta | undefined = payload.meta ?? (initialData?.meta ? { ...initialData.meta } : undefined); @@ -1310,9 +1285,6 @@ export function ProviderForm({ if (appId === "codex") { const template = getCodexCustomTemplate(); - setLocalCodexApiFormat( - extractCodexWireApi(template.config) ?? "responses", - ); resetCodexConfig(template.auth, template.config); } if (appId === "gemini") { @@ -1347,7 +1319,6 @@ export function ProviderForm({ const auth = preset.auth ?? {}; const config = preset.config ?? ""; - setLocalCodexApiFormat(extractCodexWireApi(config) ?? "responses"); resetCodexConfig(auth, config); form.reset({ @@ -1584,9 +1555,6 @@ export function ProviderForm({ modelName={codexModelName} onModelNameChange={handleCodexModelNameChange} speedTestEndpoints={speedTestEndpoints} - apiFormat={localCodexApiFormat} - onApiFormatChange={handleCodexApiFormatChange} - shouldShowApiFormatSelector={category !== "official"} /> )} diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index 33031b187..2ac2f789f 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -515,10 +515,6 @@ "apiHint": "💡 Fill in Claude API compatible service endpoint, avoid trailing slash", "apiHintOAI": "💡 Fill in OpenAI Chat Completions compatible service endpoint, avoid trailing slash", "codexApiHint": "💡 Fill in service endpoint compatible with OpenAI Response format", - "codexApiHintChat": "💡 Fill in service endpoint compatible with OpenAI Chat Completions format", - "codexApiFormatResponses": "OpenAI Responses (Default)", - "codexApiFormatChat": "OpenAI Chat Completions", - "codexApiFormatHint": "Select the API format supported by the provider", "directRequestUrl": "CLI Direct Request URL:", "directRequestUrlDesc": "Actual request URL after CLI appends default suffix", "proxyRequestUrl": "CCS Proxy Request URL:", diff --git a/src/i18n/locales/ja.json b/src/i18n/locales/ja.json index 4a53571b2..d6895b24d 100644 --- a/src/i18n/locales/ja.json +++ b/src/i18n/locales/ja.json @@ -515,10 +515,6 @@ "apiHint": "💡 Claude API 互換サービスのエンドポイントを入力してください。末尾にスラッシュを付けないでください", "apiHintOAI": "💡 OpenAI Chat Completions 互換サービスのエンドポイントを入力してください。末尾にスラッシュを付けないでください", "codexApiHint": "💡 OpenAI Response 互換のサービスエンドポイントを入力してください", - "codexApiHintChat": "💡 OpenAI Chat Completions 互換のサービスエンドポイントを入力してください", - "codexApiFormatResponses": "OpenAI Responses(デフォルト)", - "codexApiFormatChat": "OpenAI Chat Completions", - "codexApiFormatHint": "プロバイダーがサポートする API フォーマットを選択", "directRequestUrl": "CLI 直接リクエスト URL:", "directRequestUrlDesc": "CLI がデフォルトサフィックスを追加した後の実際のリクエスト URL", "proxyRequestUrl": "CCS プロキシリクエスト URL:", diff --git a/src/i18n/locales/zh.json b/src/i18n/locales/zh.json index aa9c4bdfa..c5e41dd0f 100644 --- a/src/i18n/locales/zh.json +++ b/src/i18n/locales/zh.json @@ -515,10 +515,6 @@ "apiHint": "💡 填写兼容 Claude API 的服务端点地址,不要以斜杠结尾", "apiHintOAI": "💡 填写兼容 OpenAI Chat Completions 的服务端点地址,不要以斜杠结尾", "codexApiHint": "💡 填写兼容 OpenAI Response 格式的服务端点地址", - "codexApiHintChat": "💡 填写兼容 OpenAI Chat Completions 格式的服务端点地址", - "codexApiFormatResponses": "OpenAI Responses (默认)", - "codexApiFormatChat": "OpenAI Chat Completions", - "codexApiFormatHint": "选择供应商支持的 API 格式", "directRequestUrl": "CLI 直连请求地址:", "directRequestUrlDesc": "CLI 硬拼接默认后缀后的实际请求地址", "proxyRequestUrl": "CCS 代理请求地址:", diff --git a/src/types.ts b/src/types.ts index 2b3c477b5..bd2803b75 100644 --- a/src/types.ts +++ b/src/types.ts @@ -146,8 +146,7 @@ export interface ProviderMeta { // - "openai_chat": OpenAI Chat Completions 格式,需要格式转换 // Codex: // - "responses": OpenAI Responses API - // - "chat": OpenAI Chat Completions API - apiFormat?: "anthropic" | "openai_chat" | "responses" | "chat"; + apiFormat?: "anthropic" | "openai_chat" | "responses"; } // Skill 同步方式 diff --git a/src/utils/providerConfigUtils.ts b/src/utils/providerConfigUtils.ts index 1c1c7fb4d..705610f6d 100644 --- a/src/utils/providerConfigUtils.ts +++ b/src/utils/providerConfigUtils.ts @@ -468,27 +468,10 @@ export const setCodexBaseUrl = ( return `${prefix}${replacementLine}\n`; }; -// 从 Codex 的 TOML 配置文本中提取 wire_api 字段(responses/chat) -export const extractCodexWireApi = ( - configText: string | undefined | null, -): "responses" | "chat" | undefined => { - try { - const raw = typeof configText === "string" ? configText : ""; - const text = normalizeQuotes(raw); - if (!text) return undefined; - - const m = text.match(/^wire_api\s*=\s*(['"])(responses|chat)\1/m); - if (!m || !m[2]) return undefined; - return m[2] === "chat" ? "chat" : "responses"; - } catch { - return undefined; - } -}; - -// 在 Codex 的 TOML 配置文本中写入或更新 wire_api 字段 +// 在 Codex 的 TOML 配置文本中写入或更新 wire_api 字段(仅保留 responses) export const setCodexWireApi = ( configText: string, - wireApi: "responses" | "chat", + wireApi: "responses" = "responses", ): string => { const normalizedText = normalizeQuotes(configText); const replacementLine = `wire_api = "${wireApi}"`;