From 82c75de51c911dfc13ce365fbfd7941b6fc14d36 Mon Sep 17 00:00:00 2001 From: Jason Date: Thu, 19 Mar 2026 17:29:04 +0800 Subject: [PATCH 01/10] fix(copilot): unify request fingerprint across all Copilot API calls - Make header constants in copilot_auth.rs public, add COPILOT_INTEGRATION_ID - Unify User-Agent across 4 internal methods (eliminate "CC-Switch" leakage) and version string - claude.rs add_auth_headers now references shared constants, adds user-agent / api-version / openai-intent - forwarder.rs filters all 6 fixed fingerprint headers for Copilot requests to prevent reqwest append duplication - stream_check.rs health check pipeline aligned with new fingerprint --- src-tauri/src/proxy/forwarder.rs | 15 ++++++++++++- src-tauri/src/proxy/providers/claude.rs | 11 ++++++---- src-tauri/src/proxy/providers/copilot_auth.rs | 21 ++++++++++++------- src-tauri/src/services/stream_check.rs | 10 ++++++--- 4 files changed, 41 insertions(+), 16 deletions(-) diff --git a/src-tauri/src/proxy/forwarder.rs b/src-tauri/src/proxy/forwarder.rs index 864e5771d..109264c9f 100644 --- a/src-tauri/src/proxy/forwarder.rs +++ b/src-tauri/src/proxy/forwarder.rs @@ -856,9 +856,22 @@ impl RequestForwarder { // 过滤黑名单 Headers,保护隐私并避免冲突 for (key, value) in headers { + let key_str = key.as_str(); if HEADER_BLACKLIST .iter() - .any(|h| key.as_str().eq_ignore_ascii_case(h)) + .any(|h| key_str.eq_ignore_ascii_case(h)) + { + continue; + } + // Copilot 请求:过滤会由 add_auth_headers 注入的固定指纹头, + // 防止客户端原始头与注入头重复(reqwest header() 是追加语义) + if is_copilot + && (key_str.eq_ignore_ascii_case("user-agent") + || key_str.eq_ignore_ascii_case("editor-version") + || key_str.eq_ignore_ascii_case("editor-plugin-version") + || key_str.eq_ignore_ascii_case("copilot-integration-id") + || key_str.eq_ignore_ascii_case("x-github-api-version") + || key_str.eq_ignore_ascii_case("openai-intent")) { continue; } diff --git a/src-tauri/src/proxy/providers/claude.rs b/src-tauri/src/proxy/providers/claude.rs index 6c63a050d..e20449dd7 100644 --- a/src-tauri/src/proxy/providers/claude.rs +++ b/src-tauri/src/proxy/providers/claude.rs @@ -346,12 +346,15 @@ impl ProviderAdapter for ClaudeAdapter { AuthStrategy::Bearer => { request.header("Authorization", format!("Bearer {}", auth.api_key)) } - // GitHub Copilot: Bearer + 特定的 Editor headers + // GitHub Copilot: Bearer + 统一指纹头 AuthStrategy::GitHubCopilot => request .header("Authorization", format!("Bearer {}", auth.api_key)) - .header("Editor-Version", "vscode/1.85.0") - .header("Editor-Plugin-Version", "copilot/1.150.0") - .header("Copilot-Integration-Id", "vscode-chat"), + .header("editor-version", super::copilot_auth::COPILOT_EDITOR_VERSION) + .header("editor-plugin-version", super::copilot_auth::COPILOT_PLUGIN_VERSION) + .header("copilot-integration-id", super::copilot_auth::COPILOT_INTEGRATION_ID) + .header("user-agent", super::copilot_auth::COPILOT_USER_AGENT) + .header("x-github-api-version", super::copilot_auth::COPILOT_API_VERSION) + .header("openai-intent", "conversation-panel"), _ => request, } } diff --git a/src-tauri/src/proxy/providers/copilot_auth.rs b/src-tauri/src/proxy/providers/copilot_auth.rs index 7fcbf4287..589c57426 100644 --- a/src-tauri/src/proxy/providers/copilot_auth.rs +++ b/src-tauri/src/proxy/providers/copilot_auth.rs @@ -46,10 +46,11 @@ const TOKEN_REFRESH_BUFFER_SECONDS: i64 = 60; const COPILOT_MODELS_URL: &str = "https://api.githubcopilot.com/models"; /// Copilot API Header 常量 -const COPILOT_EDITOR_VERSION: &str = "vscode/1.96.0"; -const COPILOT_PLUGIN_VERSION: &str = "copilot-chat/0.26.7"; -const COPILOT_USER_AGENT: &str = "GitHubCopilotChat/0.26.7"; -const COPILOT_API_VERSION: &str = "2025-04-01"; +pub const COPILOT_EDITOR_VERSION: &str = "vscode/1.96.0"; +pub const COPILOT_PLUGIN_VERSION: &str = "copilot-chat/0.26.7"; +pub const COPILOT_USER_AGENT: &str = "GitHubCopilotChat/0.26.7"; +pub const COPILOT_API_VERSION: &str = "2025-04-01"; +pub const COPILOT_INTEGRATION_ID: &str = "vscode-chat"; /// Copilot 使用量 API URL const COPILOT_USAGE_URL: &str = "https://api.github.com/copilot_internal/user"; @@ -465,6 +466,7 @@ impl CopilotAuthManager { .http_client .post(GITHUB_DEVICE_CODE_URL) .header("Accept", "application/json") + .header("User-Agent", COPILOT_USER_AGENT) .form(&[("client_id", GITHUB_CLIENT_ID), ("scope", "read:user")]) .send() .await?; @@ -502,6 +504,7 @@ impl CopilotAuthManager { .http_client .post(GITHUB_OAUTH_TOKEN_URL) .header("Accept", "application/json") + .header("User-Agent", COPILOT_USER_AGENT) .form(&[ ("client_id", GITHUB_CLIENT_ID), ("device_code", device_code), @@ -948,7 +951,9 @@ impl CopilotAuthManager { .http_client .get(GITHUB_USER_URL) .header("Authorization", format!("token {}", github_token)) - .header("User-Agent", "CC-Switch") + .header("User-Agent", COPILOT_USER_AGENT) + .header("Editor-Version", COPILOT_EDITOR_VERSION) + .header("Editor-Plugin-Version", COPILOT_PLUGIN_VERSION) .send() .await?; @@ -978,9 +983,9 @@ impl CopilotAuthManager { .http_client .get(COPILOT_TOKEN_URL) .header("Authorization", format!("token {}", github_token)) - .header("User-Agent", "CC-Switch") - .header("Editor-Version", "vscode/1.85.0") - .header("Editor-Plugin-Version", "copilot/1.150.0") + .header("User-Agent", COPILOT_USER_AGENT) + .header("Editor-Version", COPILOT_EDITOR_VERSION) + .header("Editor-Plugin-Version", COPILOT_PLUGIN_VERSION) .send() .await?; diff --git a/src-tauri/src/services/stream_check.rs b/src-tauri/src/services/stream_check.rs index b889ad468..69f917233 100644 --- a/src-tauri/src/services/stream_check.rs +++ b/src-tauri/src/services/stream_check.rs @@ -13,6 +13,7 @@ use crate::app_config::AppType; use crate::error::AppError; use crate::provider::Provider; use crate::proxy::providers::transform::anthropic_to_openai; +use crate::proxy::providers::copilot_auth; use crate::proxy::providers::{get_adapter, AuthInfo, AuthStrategy}; /// 健康状态枚举 @@ -362,9 +363,12 @@ impl StreamCheckService { .header("content-type", "application/json") .header("accept", "text/event-stream") .header("accept-encoding", "identity") - .header("editor-version", "vscode/1.85.0") - .header("editor-plugin-version", "copilot/1.150.0") - .header("copilot-integration-id", "vscode-chat"); + .header("user-agent", copilot_auth::COPILOT_USER_AGENT) + .header("editor-version", copilot_auth::COPILOT_EDITOR_VERSION) + .header("editor-plugin-version", copilot_auth::COPILOT_PLUGIN_VERSION) + .header("copilot-integration-id", copilot_auth::COPILOT_INTEGRATION_ID) + .header("x-github-api-version", copilot_auth::COPILOT_API_VERSION) + .header("openai-intent", "conversation-panel"); } else if is_openai_chat { // OpenAI-compatible: Bearer auth + standard headers only request_builder = request_builder From fd2b232f1c7f8ce9b8fbf6695e19bf158854acc6 Mon Sep 17 00:00:00 2001 From: Jason Date: Fri, 20 Mar 2026 08:59:41 +0800 Subject: [PATCH 02/10] chore: update Xiaomi MiMo preset model from mimo-v2-flash to mimo-v2-pro --- src/config/claudeProviderPresets.ts | 8 ++++---- src/config/openclawProviderPresets.ts | 8 ++++---- src/config/opencodeProviderPresets.ts | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/config/claudeProviderPresets.ts b/src/config/claudeProviderPresets.ts index 7e9ff23ee..51e88371e 100644 --- a/src/config/claudeProviderPresets.ts +++ b/src/config/claudeProviderPresets.ts @@ -732,10 +732,10 @@ export const providerPresets: ProviderPreset[] = [ env: { ANTHROPIC_BASE_URL: "https://api.xiaomimimo.com/anthropic", ANTHROPIC_AUTH_TOKEN: "", - ANTHROPIC_MODEL: "mimo-v2-flash", - ANTHROPIC_DEFAULT_HAIKU_MODEL: "mimo-v2-flash", - ANTHROPIC_DEFAULT_SONNET_MODEL: "mimo-v2-flash", - ANTHROPIC_DEFAULT_OPUS_MODEL: "mimo-v2-flash", + ANTHROPIC_MODEL: "mimo-v2-pro", + ANTHROPIC_DEFAULT_HAIKU_MODEL: "mimo-v2-pro", + ANTHROPIC_DEFAULT_SONNET_MODEL: "mimo-v2-pro", + ANTHROPIC_DEFAULT_OPUS_MODEL: "mimo-v2-pro", }, }, category: "cn_official", diff --git a/src/config/openclawProviderPresets.ts b/src/config/openclawProviderPresets.ts index 5ec7c8b16..2d4f49bc7 100644 --- a/src/config/openclawProviderPresets.ts +++ b/src/config/openclawProviderPresets.ts @@ -563,8 +563,8 @@ export const openclawProviderPresets: OpenClawProviderPreset[] = [ api: "openai-completions", models: [ { - id: "mimo-v2-flash", - name: "MiMo V2 Flash", + id: "mimo-v2-pro", + name: "MiMo V2 Pro", contextWindow: 128000, cost: { input: 0.001, output: 0.004 }, }, @@ -581,8 +581,8 @@ export const openclawProviderPresets: OpenClawProviderPreset[] = [ }, }, suggestedDefaults: { - model: { primary: "xiaomimimo/mimo-v2-flash" }, - modelCatalog: { "xiaomimimo/mimo-v2-flash": { alias: "MiMo" } }, + model: { primary: "xiaomimimo/mimo-v2-pro" }, + modelCatalog: { "xiaomimimo/mimo-v2-pro": { alias: "MiMo" } }, }, }, diff --git a/src/config/opencodeProviderPresets.ts b/src/config/opencodeProviderPresets.ts index 18c993d1d..89832fba6 100644 --- a/src/config/opencodeProviderPresets.ts +++ b/src/config/opencodeProviderPresets.ts @@ -753,7 +753,7 @@ export const opencodeProviderPresets: OpenCodeProviderPreset[] = [ setCacheKey: true, }, models: { - "mimo-v2-flash": { name: "MiMo V2 Flash" }, + "mimo-v2-pro": { name: "MiMo V2 Pro" }, }, }, category: "cn_official", From 552f7abee40d3e50a28f7693bbaca171c2ae4f30 Mon Sep 17 00:00:00 2001 From: Jason Date: Fri, 20 Mar 2026 15:23:18 +0800 Subject: [PATCH 03/10] refactor(ui): remove duplicate OAuth tab from AddProviderDialog - Remove AuthCenterPanel import and OAuth TabsContent - Narrow activeTab type from three values to "app-specific" | "universal" - Simplify footer by removing oauth branch, reducing to two-way conditional - Change TabsList from grid-cols-3 to grid-cols-2 - OAuth authentication remains available in settings page and CopilotAuthSection --- .../providers/AddProviderDialog.tsx | 22 +++---------------- 1 file changed, 3 insertions(+), 19 deletions(-) diff --git a/src/components/providers/AddProviderDialog.tsx b/src/components/providers/AddProviderDialog.tsx index 34a722982..a4f223d7c 100644 --- a/src/components/providers/AddProviderDialog.tsx +++ b/src/components/providers/AddProviderDialog.tsx @@ -14,7 +14,6 @@ import { } from "@/components/providers/forms/ProviderForm"; import { UniversalProviderFormModal } from "@/components/universal/UniversalProviderFormModal"; import { UniversalProviderPanel } from "@/components/universal"; -import { AuthCenterPanel } from "@/components/settings/AuthCenterPanel"; import { providerPresets } from "@/config/claudeProviderPresets"; import { codexProviderPresets } from "@/config/codexProviderPresets"; import { geminiProviderPresets } from "@/config/geminiProviderPresets"; @@ -44,7 +43,7 @@ export function AddProviderDialog({ // OpenCode and OpenClaw don't support universal providers const showUniversalTab = appId !== "opencode" && appId !== "openclaw"; const [activeTab, setActiveTab] = useState< - "app-specific" | "universal" | "oauth" + "app-specific" | "universal" >("app-specific"); const [universalFormOpen, setUniversalFormOpen] = useState(false); const [selectedUniversalPreset, setSelectedUniversalPreset] = @@ -256,14 +255,6 @@ export function AddProviderDialog({ {t("common.add")} - ) : activeTab === "oauth" ? ( - ) : ( <>