feat(proxy): honor custom User-Agent across stream check and model fetch

Extract a shared `parse_custom_user_agent` helper in provider.rs returning
`Result<Option<HeaderValue>>`, and reuse it in the forwarder, stream check,
and model fetch paths so detection, forwarding, and model listing all apply
the same provider-level User-Agent. Previously only the forwarder honored it,
so stream check could fail (or model listing 403) on UA-gated upstreams that
the proxy itself handled fine.

- stream_check injects the provider's custom UA on the claude/codex paths and
  still skips the GitHub Copilot fingerprint UA.
- model_fetch service + command and the model-fetch.ts wrapper thread an
  optional UA through to GET /v1/models.
- runtime callers silently ignore invalid values via `.ok().flatten()`
  (no save-time block, so deeplink imports stay lenient).
This commit is contained in:
Jason
2026-06-10 16:39:12 +08:00
parent 25983f3420
commit 8b925c2f2f
6 changed files with 138 additions and 17 deletions
+11 -8
View File
@@ -1537,14 +1537,17 @@ impl RequestForwarder {
Vec::new()
};
let custom_user_agent = provider
.meta
.as_ref()
.and_then(|meta| meta.custom_user_agent.as_deref())
.map(str::trim)
.filter(|ua| !ua.is_empty())
.filter(|_| !is_copilot)
.and_then(|ua| http::HeaderValue::from_str(ua).ok());
// 自定义 User-Agent:与 stream_check / model_fetch 共用 parse_custom_user_agent
// 运行时静默忽略非法值(前端在输入处给非阻断提示,不在保存时阻断)。
// Copilot 指纹 UA 不可覆盖。
let custom_user_agent = if is_copilot {
None
} else {
provider
.meta
.as_ref()
.and_then(|meta| meta.custom_user_agent_header().ok().flatten())
};
// --- Copilot 优化器:动态 header 注入 ---
if let Some((ref classification, ref det_request_id, ref interaction_id)) =