mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-07-24 12:44:18 +08:00
feat: add provider user agent override
This commit is contained in:
@@ -460,6 +460,9 @@ pub struct ProviderMeta {
|
||||
/// Codex Responses -> Chat Completions reasoning capability metadata.
|
||||
#[serde(rename = "codexChatReasoning", skip_serializing_if = "Option::is_none")]
|
||||
pub codex_chat_reasoning: Option<CodexChatReasoningConfig>,
|
||||
/// Custom User-Agent for local proxy routing.
|
||||
#[serde(rename = "customUserAgent", skip_serializing_if = "Option::is_none")]
|
||||
pub custom_user_agent: Option<String>,
|
||||
/// 累加模式应用中,该 provider 是否已写入 live config。
|
||||
/// `None` 表示旧数据/未知状态,`Some(false)` 表示明确仅存在于数据库中。
|
||||
#[serde(rename = "liveConfigManaged", skip_serializing_if = "Option::is_none")]
|
||||
|
||||
@@ -1537,6 +1537,15 @@ 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());
|
||||
|
||||
// --- Copilot 优化器:动态 header 注入 ---
|
||||
if let Some((ref classification, ref det_request_id, ref interaction_id)) =
|
||||
copilot_optimization
|
||||
@@ -1631,6 +1640,7 @@ impl RequestForwarder {
|
||||
let mut ordered_headers = http::HeaderMap::new();
|
||||
let mut saw_auth = false;
|
||||
let mut saw_accept_encoding = false;
|
||||
let mut saw_user_agent = false;
|
||||
let mut saw_anthropic_beta = false;
|
||||
let mut saw_anthropic_version = false;
|
||||
|
||||
@@ -1711,6 +1721,19 @@ impl RequestForwarder {
|
||||
continue;
|
||||
}
|
||||
|
||||
// --- user-agent: provider-level override for local proxy routing ---
|
||||
if !is_copilot && key_str.eq_ignore_ascii_case("user-agent") {
|
||||
if !saw_user_agent {
|
||||
saw_user_agent = true;
|
||||
if let Some(ref ua) = custom_user_agent {
|
||||
ordered_headers.append(http::header::USER_AGENT, ua.clone());
|
||||
} else {
|
||||
ordered_headers.append(key.clone(), value.clone());
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// --- anthropic-beta — 用重建值替换(确保含 claude-code 标记) ---
|
||||
if key_str.eq_ignore_ascii_case("anthropic-beta") {
|
||||
if !saw_anthropic_beta {
|
||||
@@ -1760,6 +1783,12 @@ impl RequestForwarder {
|
||||
);
|
||||
}
|
||||
|
||||
if !saw_user_agent {
|
||||
if let Some(ref ua) = custom_user_agent {
|
||||
ordered_headers.append(http::header::USER_AGENT, ua.clone());
|
||||
}
|
||||
}
|
||||
|
||||
// 如果原始请求中没有 anthropic-beta 且有值需要添加,追加
|
||||
if !saw_anthropic_beta {
|
||||
if let Some(ref beta_val) = anthropic_beta_value {
|
||||
|
||||
@@ -138,6 +138,10 @@ interface ClaudeFormFieldsProps {
|
||||
// Full URL mode
|
||||
isFullUrl: boolean;
|
||||
onFullUrlChange: (value: boolean) => void;
|
||||
|
||||
// Local proxy User-Agent override
|
||||
customUserAgent: string;
|
||||
onCustomUserAgentChange: (value: string) => void;
|
||||
}
|
||||
|
||||
export function ClaudeFormFields({
|
||||
@@ -190,6 +194,8 @@ export function ClaudeFormFields({
|
||||
onApiKeyFieldChange,
|
||||
isFullUrl,
|
||||
onFullUrlChange,
|
||||
customUserAgent,
|
||||
onCustomUserAgentChange,
|
||||
}: ClaudeFormFieldsProps) {
|
||||
const { t } = useTranslation();
|
||||
const hasAnyAdvancedValue = !!(
|
||||
@@ -665,7 +671,30 @@ export function ClaudeFormFields({
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* 高级选项(API 格式 + 认证字段 + 模型映射) */}
|
||||
{category !== "official" && (
|
||||
<div className="space-y-2">
|
||||
<FormLabel htmlFor="claude-custom-user-agent">
|
||||
{t("providerForm.customUserAgent", {
|
||||
defaultValue: "自定义 User-Agent",
|
||||
})}
|
||||
</FormLabel>
|
||||
<Input
|
||||
id="claude-custom-user-agent"
|
||||
type="text"
|
||||
value={customUserAgent}
|
||||
onChange={(e) => onCustomUserAgentChange(e.target.value)}
|
||||
placeholder="Mozilla/5.0 ..."
|
||||
autoComplete="off"
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{t("providerForm.customUserAgentHint", {
|
||||
defaultValue:
|
||||
"仅在开启本地路由/代理接管后生效,会替换转发到供应商 API 请求中的 User-Agent。",
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{shouldShowModelSelector && (
|
||||
<Collapsible open={advancedExpanded} onOpenChange={setAdvancedExpanded}>
|
||||
<CollapsibleTrigger asChild>
|
||||
|
||||
@@ -72,6 +72,10 @@ interface CodexFormFieldsProps {
|
||||
|
||||
// Speed Test Endpoints
|
||||
speedTestEndpoints: EndpointCandidate[];
|
||||
|
||||
// Local proxy User-Agent override
|
||||
customUserAgent: string;
|
||||
onCustomUserAgentChange: (value: string) => void;
|
||||
}
|
||||
|
||||
type CodexCatalogRow = CodexCatalogModel & { rowId: string };
|
||||
@@ -128,6 +132,8 @@ export function CodexFormFields({
|
||||
catalogModels = [],
|
||||
onCatalogModelsChange,
|
||||
speedTestEndpoints,
|
||||
customUserAgent,
|
||||
onCustomUserAgentChange,
|
||||
}: CodexFormFieldsProps) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
@@ -582,6 +588,33 @@ export function CodexFormFields({
|
||||
onCustomEndpointsChange={onCustomEndpointsChange}
|
||||
/>
|
||||
)}
|
||||
|
||||
{category !== "official" && (
|
||||
<div className="space-y-2">
|
||||
<label
|
||||
htmlFor="codex-custom-user-agent"
|
||||
className="block text-sm font-medium text-foreground"
|
||||
>
|
||||
{t("providerForm.customUserAgent", {
|
||||
defaultValue: "自定义 User-Agent",
|
||||
})}
|
||||
</label>
|
||||
<Input
|
||||
id="codex-custom-user-agent"
|
||||
type="text"
|
||||
value={customUserAgent}
|
||||
onChange={(e) => onCustomUserAgentChange(e.target.value)}
|
||||
placeholder="Mozilla/5.0 ..."
|
||||
autoComplete="off"
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{t("providerForm.customUserAgentHint", {
|
||||
defaultValue:
|
||||
"仅在开启本地路由/代理接管后生效,会替换转发到供应商 API 请求中的 User-Agent。",
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -355,6 +355,7 @@ function ProviderFormFull({
|
||||
),
|
||||
});
|
||||
setCodexChatReasoning(initialData?.meta?.codexChatReasoning ?? {});
|
||||
setCustomUserAgent(initialData?.meta?.customUserAgent ?? "");
|
||||
}, [appId, initialData, supportsFullUrl]);
|
||||
|
||||
const defaultValues: ProviderFormData = useMemo(
|
||||
@@ -509,6 +510,9 @@ function ProviderFormFull({
|
||||
useState<CodexChatReasoning>(
|
||||
() => initialData?.meta?.codexChatReasoning ?? {},
|
||||
);
|
||||
const [customUserAgent, setCustomUserAgent] = useState<string>(
|
||||
() => initialData?.meta?.customUserAgent ?? "",
|
||||
);
|
||||
|
||||
const {
|
||||
codexAuth,
|
||||
@@ -1386,6 +1390,10 @@ function ProviderFormFull({
|
||||
localCodexApiFormat === "openai_chat"
|
||||
? normalizeCodexChatReasoningForSave(codexChatReasoning)
|
||||
: undefined,
|
||||
customUserAgent:
|
||||
appId === "claude" || appId === "codex"
|
||||
? customUserAgent.trim() || undefined
|
||||
: undefined,
|
||||
testConfig: testConfig.enabled ? testConfig : undefined,
|
||||
costMultiplier: pricingConfig.enabled
|
||||
? pricingConfig.costMultiplier
|
||||
@@ -2009,6 +2017,8 @@ function ProviderFormFull({
|
||||
onApiKeyFieldChange={handleApiKeyFieldChange}
|
||||
isFullUrl={localIsFullUrl}
|
||||
onFullUrlChange={setLocalIsFullUrl}
|
||||
customUserAgent={customUserAgent}
|
||||
onCustomUserAgentChange={setCustomUserAgent}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -2041,6 +2051,8 @@ function ProviderFormFull({
|
||||
catalogModels={codexCatalogModels}
|
||||
onCatalogModelsChange={setCodexCatalogModels}
|
||||
speedTestEndpoints={speedTestEndpoints}
|
||||
customUserAgent={customUserAgent}
|
||||
onCustomUserAgentChange={setCustomUserAgent}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
||||
@@ -219,6 +219,8 @@ export interface ProviderMeta {
|
||||
codexFastMode?: boolean;
|
||||
// Codex Responses -> Chat Completions reasoning capability metadata
|
||||
codexChatReasoning?: CodexChatReasoning;
|
||||
// Custom User-Agent for local proxy routing. Only applied by the local proxy.
|
||||
customUserAgent?: string;
|
||||
// 供应商类型(用于识别 Copilot 等特殊供应商)
|
||||
providerType?: string;
|
||||
// GitHub Copilot 关联账号 ID(旧字段,保留兼容读取)
|
||||
|
||||
Reference in New Issue
Block a user