From 4b34a8f57fb88860ab6db0fdadc05d0756c2dea6 Mon Sep 17 00:00:00 2001 From: jiesen Date: Sun, 21 Dec 2025 15:22:15 +0700 Subject: [PATCH] =?UTF-8?q?feat(codex):=20=E6=94=AF=E6=8C=81=20Yunyi=20?= =?UTF-8?q?=E7=AD=89=E4=BB=A3=E7=90=86=E7=9A=84=20API=20Key=20=E8=AE=A4?= =?UTF-8?q?=E8=AF=81=E5=92=8C=20responses=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 主要改进 ### 1. 增强 API Key 字段兼容性 - 支持多种字段名:api_key, apiKey, OPENAI_API_KEY - 兼容 Codex CLI 和 Yunyi 等代理的凭证格式 ### 2. 修复健康检查逻辑 - 自定义 base_url 时使用 Codex responses API 格式 - 官方 OpenAI 时使用 chat/completions API - 正确处理 Yunyi 等代理的 500 错误响应 ### 3. 改进错误提示 - 提供更友好的配置错误提示信息 ## 技术细节 - codex.rs: 添加 OPENAI_API_KEY 别名支持 - provider_pool_service.rs: 根据 base_url 选择正确的 API 格式 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- src-tauri/src/providers/codex.rs | 8 ++- .../src/services/provider_pool_service.rs | 68 +++++++++++++++++-- 2 files changed, 68 insertions(+), 8 deletions(-) diff --git a/src-tauri/src/providers/codex.rs b/src-tauri/src/providers/codex.rs index 04cc2e4e2..64761b639 100644 --- a/src-tauri/src/providers/codex.rs +++ b/src-tauri/src/providers/codex.rs @@ -51,7 +51,13 @@ pub struct CodexCredentials { )] pub refresh_token: Option, /// API Key(Codex CLI 支持通过 API Key 登录) - #[serde(default, skip_serializing_if = "Option::is_none", alias = "apiKey")] + /// 支持字段名: api_key, apiKey, OPENAI_API_KEY + #[serde( + default, + skip_serializing_if = "Option::is_none", + alias = "apiKey", + alias = "OPENAI_API_KEY" + )] pub api_key: Option, /// API Base URL(可选) #[serde(default, skip_serializing_if = "Option::is_none", alias = "apiBaseUrl")] diff --git a/src-tauri/src/services/provider_pool_service.rs b/src-tauri/src/services/provider_pool_service.rs index b184ab321..7c916fd2b 100644 --- a/src-tauri/src/services/provider_pool_service.rs +++ b/src-tauri/src/services/provider_pool_service.rs @@ -837,6 +837,7 @@ impl ProviderPoolService { } // Codex 健康检查 + // 支持 Yunyi 等代理使用 responses API 格式 async fn check_codex_health(&self, creds_path: &str, model: &str) -> Result<(), String> { use crate::providers::codex::CodexProvider; @@ -846,13 +847,14 @@ impl ProviderPoolService { .await .map_err(|e| format!("加载 Codex 凭证失败: {}", e))?; - let token = provider - .ensure_valid_token() - .await - .map_err(|e| format!("获取 Codex Token 失败: {}", e))?; + let token = provider.ensure_valid_token().await.map_err(|e| { + format!( + "获取 Codex Token 失败: 配置错误,请检查凭证设置。详情:{}", + e + ) + })?; - // 使用 OpenAI 兼容 API 进行健康检查 - // 兼容 Codex CLI API Key 模式:如果 auth.json 提供 api_base_url,则优先使用 + // 获取 base_url,如果设置了则使用 responses API,否则使用 OpenAI chat/completions let base_url = provider .credentials .api_base_url @@ -860,7 +862,59 @@ impl ProviderPoolService { .map(|s| s.trim()) .filter(|s| !s.is_empty()); - self.check_openai_health(&token, base_url, model).await + match base_url { + Some(base) => { + // 使用自定义 base_url (如 Yunyi),使用 responses API 格式 + let base = base.trim_end_matches('/'); + let url = if base.ends_with("/v1") { + format!("{}/responses", base) + } else { + format!("{}/v1/responses", base) + }; + + // Codex/Yunyi 使用 responses API 格式 + let request_body = serde_json::json!({ + "model": model, + "input": "Say OK", + "max_output_tokens": 10 + }); + + tracing::debug!( + "[HEALTH_CHECK] Codex responses API URL: {}, model: {}", + url, + model + ); + + let response = self + .client + .post(&url) + .bearer_auth(&token) + .header("Content-Type", "application/json") + .header("Accept", "text/event-stream") + .header("Openai-Beta", "responses=experimental") + .json(&request_body) + .timeout(self.health_check_timeout) + .send() + .await + .map_err(|e| format!("请求失败: {}", e))?; + + if response.status().is_success() { + Ok(()) + } else { + let status = response.status(); + let body = response.text().await.unwrap_or_default(); + Err(format!( + "HTTP {} - {}", + status, + body.chars().take(200).collect::() + )) + } + } + None => { + // 没有自定义 base_url,使用 OpenAI 官方 chat/completions API + self.check_openai_health(&token, None, model).await + } + } } // Claude OAuth 健康检查