|
| 1 | +## 🔥 关键修复:第三方 provider 协议路由 + 反爬 UA 注入 |
| 2 | + |
| 3 | +**症状 (Kimi For Coding · Windows)**:`403 access_terminated_error`,而 macOS 同账号同 key 正常。 |
| 4 | + |
| 5 | +**症状 (MiMo Token Plan · 全平台)**:404,请求直接打到 `https://token-plan-cn.xiaomimimo.com/responses`,**dashboard 显示 0 请求**(我们的代理也零日志)。 |
| 6 | + |
| 7 | +**根因(三层叠加)**: |
| 8 | + |
| 9 | +(1) **`apiFormat == "responses"` 被误读为"上游原生 Responses 透传"**:历史代码据此把 `desktop_config_target_for_provider` 路由到 `direct_provider` 模式,直接把 Codex CLI 的 `openai_base_url` 写成第三方上游 → CLI 走 `/responses` 直连 → MiMo 等只懂 chat completions 的上游必 404,且**整条请求绕过代理零观测**。但 `ResponsesAdapter` 本质是 chat ↔ responses **协议转换器**,不是透传 —— 第三方原生 Responses 在 v2.x 不存在。 |
| 10 | + |
| 11 | +(2) **fallback 全栈不一致**:8 处后端 / 前端 fallback 写 "responses",与 schema serde default + `AdapterRegistry::lookup` 的 "未知 → openai_chat" 矛盾,造成 v1.x 老配置升级 / 表单未填字段 → 落 responses → 触发 (1)。 |
| 12 | + |
| 13 | +(3) **healing 识别条件覆盖不到真机配置**:旧 `heal_builtin_provider_fields` 仅对 `isBuiltin=true && id == preset.id` 生效,但实测真机配置全部 `isBuiltin=false`、id 是随机 hex(`b405e7b0` 等),完全跳过 healing → KimiCLI UA / authScheme / apiFormat 都没被注入 → Windows 端 Codex CLI 透传 `codex_cli_rs/...` UA → Kimi 反爬 403。 |
| 14 | + |
| 15 | +**修法(三段)**: |
| 16 | + |
| 17 | +(1) **删 direct_provider 分支,所有 provider 永远走 local_proxy**;Responses↔Chat 协议转换由代理在本地完成。 |
| 18 | + |
| 19 | +(2) **fallback 全栈统一为 `openai_chat`**:后端 (add+update+import) + 前端 (mapProvider/providerBody/getPresets/选预设/编辑) 共 8 处全改,与 schema default 对齐。 |
| 20 | + |
| 21 | +(3) **healing 按 baseUrl 命中 preset**:`normalize_base_url` 做宽松匹配(去 scheme / 末尾 `/` / `/v\d+` / 大小写),preset 的 `baseUrl` + `baseUrlOptions[*].value` 统一索引;命中即视作该 preset,**强制覆盖** `apiFormat / authScheme / extraHeaders` + 把 `isBuiltin=true`,**不动** id / name / baseUrl / apiKey / models。admin 路径 heal 后**写回磁盘**根治旧残留。 |
| 22 | + |
| 23 | +| Provider 场景 | v2.0.9 | v2.0.10 | |
| 24 | +|---|---|---| |
| 25 | +| MiMo Token Plan(任何集群) | ❌ 404 + 零观测 | ✓ 走代理 + 协议转换 | |
| 26 | +| Kimi For Coding · Windows | ❌ 403 反爬 | ✓ KimiCLI UA 强制注入 | |
| 27 | +| 老配置 / 用户手改坏 apiFormat | ❌ 散播错值 | ✓ 启动即根治 + 写盘 | |
| 28 | +| 用户自建反代(baseUrl 不命中) | ✓ 不动 | ✓ 不动 | |
| 29 | + |
| 30 | +--- |
| 31 | + |
| 32 | +## 主线改动 |
| 33 | + |
| 34 | +**(1) admin/handlers.rs 5229 行 → 13 模块拆分**(PR #54+#55):按职责切到 `common / desktop / providers/{crud,test,models,balance} / proxy / settings / feedback / update / mod`,_legacy 整体删除。无运行时行为变化,纯结构性 refactor,后续小改 / review 不再卡在巨型文件。 |
| 35 | + |
| 36 | +**(2) Codex CLI 身份头出站剔除 + 默认 UA neutral 化**(PR #57):反向适配 Kimi / 通用反爬上游对客户端身份的检测;`originator / x-codex-* / x-openai-*` 等强制 strip,无 provider extras 指定时默认 UA 不再透传 `codex_cli_rs/...`。配合 healing 的 baseUrl 命中,Kimi For Coding 的 KimiCLI UA 现在能稳定注入。 |
| 37 | + |
| 38 | +**(3) extra_headers 提交时校验 + 仓库 docs 结构整理**(PR #58+#56):前者把"运行时静默丢 header"(如 UA 字符串带换行 → resolver `HeaderValue::from_str` 失败)前移到保存时硬错;后者把 docs 按 setup / refactor / litellm / archive 分类。 |
| 39 | + |
| 40 | +--- |
| 41 | + |
| 42 | +## English |
| 43 | + |
| 44 | +## 🔥 Critical fixes: third-party provider protocol routing + anti-scraper UA injection |
| 45 | + |
| 46 | +**Symptom (Kimi For Coding · Windows)**: `403 access_terminated_error`; same account + key works on macOS. |
| 47 | + |
| 48 | +**Symptom (MiMo Token Plan · all platforms)**: 404, request hitting `https://token-plan-cn.xiaomimimo.com/responses` directly, **zero requests on the upstream dashboard** (and zero logs in our proxy). |
| 49 | + |
| 50 | +**Root cause (three layers)**: |
| 51 | + |
| 52 | +(1) **`apiFormat == "responses"` misread as "upstream-native Responses passthrough"**: legacy code routed `desktop_config_target_for_provider` to `direct_provider` mode, writing the third-party upstream straight into Codex CLI's `openai_base_url` → CLI calls `/responses` directly → MiMo and other chat-completions-only upstreams 404, with the **entire request bypassing our proxy and producing zero observability**. But `ResponsesAdapter` is really a chat ↔ responses **protocol converter** — third-party native Responses doesn't exist in v2.x. |
| 53 | + |
| 54 | +(2) **Inconsistent fallbacks across the stack**: 8 backend/frontend fallback sites wrote `"responses"`, contradicting the schema serde default + `AdapterRegistry::lookup` "unknown → openai_chat" rule. |
| 55 | + |
| 56 | +(3) **Healing didn't match real-world configs**: old `heal_builtin_provider_fields` only matched `isBuiltin=true && id == preset.id`, but real machine configs all had `isBuiltin=false` and random hex ids — healing was completely skipped → KimiCLI UA / authScheme / apiFormat were never injected → Codex CLI on Windows leaked its native `codex_cli_rs/...` UA → Kimi 403. |
| 57 | + |
| 58 | +**Fix (three parts)**: |
| 59 | + |
| 60 | +(1) **Drop the `direct_provider` branch — every provider always goes through `local_proxy`**; Responses ↔ Chat conversion happens locally in the proxy. |
| 61 | + |
| 62 | +(2) **Stack-wide fallback unified to `openai_chat`**: backend (add+update+import) + frontend (mapProvider / providerBody / getPresets / preset-pick / edit), 8 sites total. |
| 63 | + |
| 64 | +(3) **Healing matches presets via baseUrl**: `normalize_base_url` does loose matching (strip scheme / trailing `/` / trailing `/v\d+` / lowercase); `baseUrl` + `baseUrlOptions[*].value` indexed together; on hit, **force-overwrite** `apiFormat / authScheme / extraHeaders` + set `isBuiltin=true`, while **leaving** id / name / baseUrl / apiKey / models alone. Admin path **persists to disk** so legacy residue heals on first launch. |
| 65 | + |
| 66 | +| Provider scenario | v2.0.9 | v2.0.10 | |
| 67 | +|---|---|---| |
| 68 | +| MiMo Token Plan (any cluster) | ❌ 404 + zero observability | ✓ proxied + converted | |
| 69 | +| Kimi For Coding · Windows | ❌ 403 anti-scraper | ✓ KimiCLI UA force-injected | |
| 70 | +| Old config / hand-edited bad apiFormat | ❌ propagated bad value | ✓ healed at launch + persisted | |
| 71 | +| Self-built reverse proxy (baseUrl miss) | ✓ untouched | ✓ untouched | |
| 72 | + |
| 73 | +--- |
| 74 | + |
| 75 | +## Mainline changes |
| 76 | + |
| 77 | +**(1) admin/handlers.rs 5229-line split → 13 modules** (PR #54 + #55): Sliced by responsibility into `common / desktop / providers/{crud,test,models,balance} / proxy / settings / feedback / update / mod`; `_legacy` entirely removed. No runtime behavior change. |
| 78 | + |
| 79 | +**(2) Codex-CLI identity headers stripped on outbound + neutral default UA** (PR #57): Adapts to Kimi / generic anti-scraper fingerprinting. `originator / x-codex-* / x-openai-*` etc. are force-stripped; when no UA in extras, the proxy no longer leaks `codex_cli_rs/...`. Combined with the healing baseUrl match, Kimi For Coding's KimiCLI UA injection is now stable. |
| 80 | + |
| 81 | +**(3) extra_headers validated at submit + docs cleanup** (PR #58 + #56): Moves "header silently dropped at runtime" (e.g. UA string with newline → `HeaderValue::from_str` fails) into hard error at save time; sorts docs into setup / refactor / litellm / archive subfolders. |
| 82 | + |
0 commit comments