From 524b9d98254ce43e63cabbb79e077bae7adedb43 Mon Sep 17 00:00:00 2001 From: zhaoyuan <46778435+zymouse@users.noreply.github.com> Date: Thu, 25 Jun 2026 16:05:23 +0800 Subject: [PATCH] =?UTF-8?q?fix(proxy):=20=E4=BF=AE=E5=A4=8D=20Copilot/Code?= =?UTF-8?q?x=20OAuth=20=E6=A8=A1=E5=9D=97=E7=BB=95=E8=BF=87=E5=85=A8?= =?UTF-8?q?=E5=B1=80=E4=BB=A3=E7=90=86=E5=AF=BC=E8=87=B4=20Claude=20?= =?UTF-8?q?=E6=A8=A1=E5=9E=8B=20400=20=E9=94=99=E8=AF=AF=20(#4583)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(copilot): 修复 GitHub Copilot 出站请求不走全局代理导致 model not supported * fix(proxy): 修复全局代理客户端配置未被应用到 Copilot/Codex OAuth 模块 两个关键问题: 1. CopilotAuthManager 在构造时写死 Client::new(),使得拉取 /models 列表、 换取 token 等认证流程无视全局代理配置(global_proxy_url),直连目标服务。 结果:直连时 /models 返回 0 个 Claude 模型 → live resolution 失效 → 模型 ID 无法正确归一化/匹配 → Copilot 上游返回 400 model_not_supported。 2. CodexOAuthManager 也有完全相同的问题,导致 Codex OAuth 认证请求绕过代理。 改动:删除两个模块的自持 http_client 字段,改为每次请求时从全局客户端现取 (crate::proxy::http_client::get())。这样: - 遵循全局代理 URL 配置 - 支持运行时热更新代理设置 - 符合代码库设计意图(http_client.rs 注释明确说"所有 HTTP 请求应使用此模块") 修复覆盖范围: - copilot_auth.rs: 7 处调用(token 获取、/models 拉取、model vendor 判断) - codex_oauth_auth.rs: 4 处调用(device code、OAuth token 刷新) Fixes #2016 #2931 --- .../src/proxy/providers/codex_oauth_auth.rs | 16 ++++-------- src-tauri/src/proxy/providers/copilot_auth.rs | 25 ++++++------------- 2 files changed, 12 insertions(+), 29 deletions(-) diff --git a/src-tauri/src/proxy/providers/codex_oauth_auth.rs b/src-tauri/src/proxy/providers/codex_oauth_auth.rs index 945473f01..b5d926f67 100644 --- a/src-tauri/src/proxy/providers/codex_oauth_auth.rs +++ b/src-tauri/src/proxy/providers/codex_oauth_auth.rs @@ -16,7 +16,7 @@ //! - 通过 JWT id_token 提取 chatgpt_account_id 作为账号唯一标识 use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine as _}; -use reqwest::Client; + use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::fs; @@ -230,7 +230,6 @@ pub struct CodexOAuthManager { /// 进行中的 Device Code 流程:device_auth_id -> {user_code, expires_at_ms} /// 过期条目会在 start_device_flow 时被清理,防止放弃的登录流程导致无界增长 pending_device_codes: Arc>>, - http_client: Client, storage_path: PathBuf, } @@ -244,7 +243,6 @@ impl CodexOAuthManager { access_tokens: Arc::new(RwLock::new(HashMap::new())), refresh_locks: Arc::new(RwLock::new(HashMap::new())), pending_device_codes: Arc::new(RwLock::new(HashMap::new())), - http_client: Client::new(), storage_path, }; @@ -266,8 +264,7 @@ impl CodexOAuthManager { pub async fn start_device_flow(&self) -> Result { log::info!("[CodexOAuth] 启动 Device Code 流程"); - let response = self - .http_client + let response = crate::proxy::http_client::get() .post(DEVICE_AUTH_USERCODE_URL) .header("Content-Type", "application/json") .header("User-Agent", CODEX_USER_AGENT) @@ -349,8 +346,7 @@ impl CodexOAuthManager { log::debug!("[CodexOAuth] 轮询 Device Code"); - let poll_response = self - .http_client + let poll_response = crate::proxy::http_client::get() .post(DEVICE_AUTH_TOKEN_URL) .header("Content-Type", "application/json") .header("User-Agent", CODEX_USER_AGENT) @@ -431,8 +427,7 @@ impl CodexOAuthManager { code: &str, code_verifier: &str, ) -> Result { - let response = self - .http_client + let response = crate::proxy::http_client::get() .post(OAUTH_TOKEN_URL) .header("Content-Type", "application/x-www-form-urlencoded") .header("User-Agent", CODEX_USER_AGENT) @@ -465,8 +460,7 @@ impl CodexOAuthManager { &self, refresh_token: &str, ) -> Result { - let response = self - .http_client + let response = crate::proxy::http_client::get() .post(OAUTH_TOKEN_URL) .header("Content-Type", "application/x-www-form-urlencoded") .header("User-Agent", CODEX_USER_AGENT) diff --git a/src-tauri/src/proxy/providers/copilot_auth.rs b/src-tauri/src/proxy/providers/copilot_auth.rs index c82011fc6..a0fd5c7a0 100644 --- a/src-tauri/src/proxy/providers/copilot_auth.rs +++ b/src-tauri/src/proxy/providers/copilot_auth.rs @@ -15,7 +15,6 @@ //! - Provider 通过 meta.authBinding 关联账号 //! - 自动迁移 v1 单账号格式到 v3 多账号 + 默认账号格式 -use reqwest::Client; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::fs; @@ -424,8 +423,6 @@ pub struct CopilotAuthManager { api_endpoints: Arc>>, /// 每个账号的端点拉取锁,避免并发拉取重复打 GitHub API endpoint_locks: Arc>>>>, - /// HTTP 客户端 - http_client: Client, /// 存储路径 storage_path: PathBuf, /// 待迁移的旧格式 token @@ -447,7 +444,6 @@ impl CopilotAuthManager { copilot_models: Arc::new(RwLock::new(HashMap::new())), api_endpoints: Arc::new(RwLock::new(HashMap::new())), endpoint_locks: Arc::new(RwLock::new(HashMap::new())), - http_client: Client::new(), storage_path, pending_migration: Arc::new(RwLock::new(None)), migration_error: Arc::new(RwLock::new(None)), @@ -602,8 +598,7 @@ impl CopilotAuthManager { }; log::info!("[CopilotAuth] 启动设备码流程 (domain: {domain})"); - let response = self - .http_client + let response = crate::proxy::http_client::get() .post(github_device_code_url(&domain)) .header("Accept", "application/json") .header("User-Agent", COPILOT_USER_AGENT) @@ -647,8 +642,7 @@ impl CopilotAuthManager { }; log::debug!("[CopilotAuth] 轮询 OAuth Token (domain: {domain})"); - let response = self - .http_client + let response = crate::proxy::http_client::get() .post(github_oauth_token_url(&domain)) .header("Accept", "application/json") .header("User-Agent", COPILOT_USER_AGENT) @@ -831,8 +825,7 @@ impl CopilotAuthManager { log::info!("[CopilotAuth] 获取账号 {account_id} 的 Copilot 可用模型"); - let response = self - .http_client + let response = crate::proxy::http_client::get() .get(&models_url) .header("Authorization", format!("Bearer {copilot_token}")) .header("Content-Type", "application/json") @@ -919,8 +912,7 @@ impl CopilotAuthManager { log::info!("[CopilotAuth] 获取账号 {account_id} 的 Copilot 使用量"); - let response = self - .http_client + let response = crate::proxy::http_client::get() .get(copilot_usage_url(&domain)) .header("Authorization", format!("token {github_token}")) .header("Content-Type", "application/json") @@ -1034,8 +1026,7 @@ impl CopilotAuthManager { log::debug!("[CopilotAuth] 为账号 {account_id} 惰性拉取动态 API 端点"); - let response = self - .http_client + let response = crate::proxy::http_client::get() .get(copilot_usage_url(&domain)) .header("Authorization", format!("token {github_token}")) .header("Content-Type", "application/json") @@ -1312,8 +1303,7 @@ impl CopilotAuthManager { github_token: &str, domain: &str, ) -> Result { - let response = self - .http_client + let response = crate::proxy::http_client::get() .get(github_user_url(domain)) .header("Authorization", format!("token {github_token}")) .header("User-Agent", COPILOT_USER_AGENT) @@ -1345,8 +1335,7 @@ impl CopilotAuthManager { ) -> Result<(), CopilotAuthError> { log::debug!("[CopilotAuth] 获取账号 {account_id} 的 Copilot Token (domain: {domain})"); - let response = self - .http_client + let response = crate::proxy::http_client::get() .get(copilot_token_url(domain)) .header("Authorization", format!("token {github_token}")) .header("User-Agent", COPILOT_USER_AGENT)