diff --git a/src-tauri/src/openclaw_config.rs b/src-tauri/src/openclaw_config.rs index 14864e6ea..b0338e381 100644 --- a/src-tauri/src/openclaw_config.rs +++ b/src-tauri/src/openclaw_config.rs @@ -100,6 +100,8 @@ pub struct OpenClawProviderConfig { pub api: Option, #[serde(default, skip_serializing_if = "Vec::is_empty")] pub models: Vec, + #[serde(default, skip_serializing_if = "HashMap::is_empty")] + pub headers: HashMap, #[serde(flatten)] pub extra: HashMap, } diff --git a/src/components/providers/forms/OpenClawFormFields.tsx b/src/components/providers/forms/OpenClawFormFields.tsx index 87d1cd4b8..745682645 100644 --- a/src/components/providers/forms/OpenClawFormFields.tsx +++ b/src/components/providers/forms/OpenClawFormFields.tsx @@ -42,6 +42,10 @@ interface OpenClawFormFieldsProps { // Models models: OpenClawModel[]; onModelsChange: (models: OpenClawModel[]) => void; + + // User-Agent + userAgent: boolean; + onUserAgentChange: (checked: boolean) => void; } export function OpenClawFormFields({ @@ -58,6 +62,8 @@ export function OpenClawFormFields({ onApiChange, models, onModelsChange, + userAgent, + onUserAgentChange, }: OpenClawFormFieldsProps) { const { t } = useTranslation(); const [expandedModels, setExpandedModels] = useState>( @@ -205,6 +211,21 @@ export function OpenClawFormFields({ partnerPromotionKey={partnerPromotionKey} /> + {/* User-Agent */} +
+
+ + {t("openclaw.userAgent", { defaultValue: "发送 User-Agent" })} + +

+ {t("openclaw.userAgentHint", { + defaultValue: "部分供应商需要浏览器 User-Agent 才能正常访问。", + })} +

+
+ +
+ {/* Models Editor */}
diff --git a/src/components/providers/forms/ProviderForm.tsx b/src/components/providers/forms/ProviderForm.tsx index 4bc2250b6..98d279a92 100644 --- a/src/components/providers/forms/ProviderForm.tsx +++ b/src/components/providers/forms/ProviderForm.tsx @@ -1450,6 +1450,8 @@ export function ProviderForm({ onApiChange={openclawForm.handleOpenclawApiChange} models={openclawForm.openclawModels} onModelsChange={openclawForm.handleOpenclawModelsChange} + userAgent={openclawForm.openclawUserAgent} + onUserAgentChange={openclawForm.handleOpenclawUserAgentChange} /> )} diff --git a/src/components/providers/forms/hooks/useOpenclawFormState.ts b/src/components/providers/forms/hooks/useOpenclawFormState.ts index 45217a587..4191d14a6 100644 --- a/src/components/providers/forms/hooks/useOpenclawFormState.ts +++ b/src/components/providers/forms/hooks/useOpenclawFormState.ts @@ -14,6 +14,9 @@ interface UseOpenclawFormStateParams { getSettingsConfig: () => string; } +export const OPENCLAW_DEFAULT_USER_AGENT = + "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:148.0) Gecko/20100101 Firefox/148.0"; + export interface OpenclawFormState { openclawProviderKey: string; setOpenclawProviderKey: (key: string) => void; @@ -21,16 +24,19 @@ export interface OpenclawFormState { openclawApiKey: string; openclawApi: string; openclawModels: OpenClawModel[]; + openclawUserAgent: boolean; existingOpenclawKeys: string[]; handleOpenclawBaseUrlChange: (baseUrl: string) => void; handleOpenclawApiKeyChange: (apiKey: string) => void; handleOpenclawApiChange: (api: string) => void; handleOpenclawModelsChange: (models: OpenClawModel[]) => void; + handleOpenclawUserAgentChange: (enabled: boolean) => void; resetOpenclawState: (config?: { baseUrl?: string; apiKey?: string; api?: string; models?: OpenClawModel[]; + headers?: Record; }) => void; } @@ -92,6 +98,16 @@ export function useOpenclawFormState({ return parseOpenclawField(initialData, "models", []); }); + const [openclawUserAgent, setOpenclawUserAgent] = useState(() => { + if (appId !== "openclaw") return true; + const headers = parseOpenclawField>( + initialData, + "headers", + {}, + ); + return "User-Agent" in headers; + }); + const updateOpenclawConfig = useCallback( (updater: (config: Record) => void) => { try { @@ -147,18 +163,35 @@ export function useOpenclawFormState({ [updateOpenclawConfig], ); + const handleOpenclawUserAgentChange = useCallback( + (enabled: boolean) => { + setOpenclawUserAgent(enabled); + updateOpenclawConfig((config) => { + if (enabled) { + config.headers = { "User-Agent": OPENCLAW_DEFAULT_USER_AGENT }; + } else { + delete config.headers; + } + }); + }, + [updateOpenclawConfig], + ); + const resetOpenclawState = useCallback( (config?: { baseUrl?: string; apiKey?: string; api?: string; models?: OpenClawModel[]; + headers?: Record; }) => { setOpenclawProviderKey(""); setOpenclawBaseUrl(config?.baseUrl || ""); setOpenclawApiKey(config?.apiKey || ""); setOpenclawApi(config?.api || "openai-completions"); setOpenclawModels(config?.models || []); + const ua = config?.headers ? "User-Agent" in config.headers : false; + setOpenclawUserAgent(ua); }, [], ); @@ -170,11 +203,13 @@ export function useOpenclawFormState({ openclawApiKey, openclawApi, openclawModels, + openclawUserAgent, existingOpenclawKeys, handleOpenclawBaseUrlChange, handleOpenclawApiKeyChange, handleOpenclawApiChange, handleOpenclawModelsChange, + handleOpenclawUserAgentChange, resetOpenclawState, }; } diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index a60dd9cf3..8eadfd453 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -1343,6 +1343,8 @@ "cacheWriteCost": "Cache Write Cost ($/M tokens)", "cacheCostHint": "Cache costs are used to calculate Prompt Caching costs. Leave empty if not using caching.", "modelsHint": "Configure the models supported by this provider. Model ID is used for API calls, Display Name for the interface.", + "userAgent": "Send User-Agent", + "userAgentHint": "Some providers require a browser User-Agent header to work properly.", "env": { "title": "Environment Variables", "description": "Manage environment variables in openclaw.json (API keys, custom variables, etc.)", diff --git a/src/i18n/locales/ja.json b/src/i18n/locales/ja.json index 6f67e6ff2..56c450795 100644 --- a/src/i18n/locales/ja.json +++ b/src/i18n/locales/ja.json @@ -1343,6 +1343,8 @@ "cacheWriteCost": "キャッシュ書込コスト ($/M トークン)", "cacheCostHint": "キャッシュコストは Prompt Caching のコスト計算に使用されます。キャッシュを使用しない場合は空欄のままにしてください。", "modelsHint": "このプロバイダーがサポートするモデルを設定します。モデル ID は API 呼び出しに、表示名はインターフェースに使用されます。", + "userAgent": "User-Agent を送信", + "userAgentHint": "一部のプロバイダーはブラウザの User-Agent ヘッダーが必要です。", "env": { "title": "環境変数", "description": "openclaw.json の環境変数設定を管理(APIキー、カスタム変数など)", diff --git a/src/i18n/locales/zh.json b/src/i18n/locales/zh.json index a8b1fc214..a850fcd66 100644 --- a/src/i18n/locales/zh.json +++ b/src/i18n/locales/zh.json @@ -1343,6 +1343,8 @@ "cacheWriteCost": "缓存写入价格 ($/M tokens)", "cacheCostHint": "缓存价格用于计算 Prompt Caching 的成本。如不使用缓存可留空。", "modelsHint": "配置该供应商支持的模型。模型 ID 用于 API 调用,显示名称用于界面展示。", + "userAgent": "发送 User-Agent", + "userAgentHint": "部分供应商需要浏览器 User-Agent 才能正常访问。", "env": { "title": "环境变量", "description": "管理 openclaw.json 中的环境变量配置(API Key、自定义变量等)", diff --git a/src/types.ts b/src/types.ts index 2957b897f..1a7ad90d8 100644 --- a/src/types.ts +++ b/src/types.ts @@ -522,6 +522,7 @@ export interface OpenClawProviderConfig { apiKey?: string; // API 密钥 api?: string; // API 协议类型(如 "openai-completions"、"anthropic") models?: OpenClawModel[]; // 可用模型列表 + headers?: Record; // 自定义请求头(如 User-Agent) } // OpenClaw agents.defaults 完整配置