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}"`;