fix(provider): preserve custom headers and add custom-provider User-Agent setting (#635)
This commit is contained in:
@@ -120,6 +120,7 @@ Skills ページでは OpenClaw の複数ソース(管理ディレクトリ、
|
||||
|
||||
### 🔐 セキュアなプロバイダー統合
|
||||
複数のAIプロバイダー(OpenAI、Anthropicなど)に接続でき、資格情報はシステムのネイティブキーチェーンに安全に保存されます。OpenAI は API キーとブラウザ OAuth(Codex サブスクリプション)の両方に対応しています。
|
||||
OpenAI-compatible ゲートウェイを **Custom プロバイダー** で使う場合、**設定 → AI Providers → Provider 編集** でカスタム `User-Agent` を設定でき、互換性が必要なエンドポイントで有効です。
|
||||
|
||||
### 🌙 アダプティブテーマ
|
||||
ライトモード、ダークモード、またはシステム同期テーマ。ClawXはあなたの好みに自動的に適応します。
|
||||
|
||||
@@ -121,6 +121,7 @@ Environment variables for bundled search skills:
|
||||
|
||||
### 🔐 Secure Provider Integration
|
||||
Connect to multiple AI providers (OpenAI, Anthropic, and more) with credentials stored securely in your system's native keychain. OpenAI supports both API key and browser OAuth (Codex subscription) sign-in.
|
||||
For **Custom** providers used with OpenAI-compatible gateways, you can set a custom `User-Agent` in **Settings → AI Providers → Edit Provider** for compatibility-sensitive endpoints.
|
||||
|
||||
### 🌙 Adaptive Theming
|
||||
Light mode, dark mode, or system-synchronized themes. ClawX adapts to your preferences automatically.
|
||||
|
||||
@@ -121,6 +121,7 @@ Skills 页面可展示来自多个 OpenClaw 来源的技能(托管目录、wor
|
||||
|
||||
### 🔐 安全的供应商集成
|
||||
连接多个 AI 供应商(OpenAI、Anthropic 等),凭证安全存储在系统原生密钥链中。OpenAI 同时支持 API Key 与浏览器 OAuth(Codex 订阅)登录。
|
||||
如果你通过 **自定义(Custom)Provider** 对接 OpenAI-compatible 网关,可以在 **设置 → AI Providers → 编辑 Provider** 中配置自定义 `User-Agent`,以提高兼容性。
|
||||
|
||||
### 🌙 自适应主题
|
||||
支持浅色模式、深色模式或跟随系统主题。ClawX 自动适应你的偏好设置。
|
||||
|
||||
@@ -294,7 +294,7 @@ async function syncRuntimeProviderConfig(
|
||||
baseUrl: normalizeProviderBaseUrl(config, config.baseUrl || context.meta?.baseUrl, context.api),
|
||||
api: context.api,
|
||||
apiKeyEnv: context.meta?.apiKeyEnv,
|
||||
headers: context.meta?.headers,
|
||||
headers: config.headers ?? context.meta?.headers,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -374,7 +374,7 @@ export async function syncUpdatedProviderToRuntime(
|
||||
baseUrl: normalizeProviderBaseUrl(config, config.baseUrl || context.meta?.baseUrl, context.api),
|
||||
api: context.api,
|
||||
apiKeyEnv: context.meta?.apiKeyEnv,
|
||||
headers: context.meta?.headers,
|
||||
headers: config.headers ?? context.meta?.headers,
|
||||
}, fallbackModels);
|
||||
} else {
|
||||
await setOpenClawDefaultModel(ock, modelOverride, fallbackModels);
|
||||
@@ -383,6 +383,7 @@ export async function syncUpdatedProviderToRuntime(
|
||||
await setOpenClawDefaultModelWithOverride(ock, modelOverride, {
|
||||
baseUrl: normalizeProviderBaseUrl(config, config.baseUrl, config.apiProtocol || 'openai-completions'),
|
||||
api: config.apiProtocol || 'openai-completions',
|
||||
headers: config.headers,
|
||||
}, fallbackModels);
|
||||
}
|
||||
}
|
||||
@@ -451,6 +452,7 @@ export async function syncDefaultProviderToRuntime(
|
||||
await setOpenClawDefaultModelWithOverride(ock, modelOverride, {
|
||||
baseUrl: normalizeProviderBaseUrl(provider, provider.baseUrl, provider.apiProtocol || 'openai-completions'),
|
||||
api: provider.apiProtocol || 'openai-completions',
|
||||
headers: provider.headers,
|
||||
}, fallbackModels);
|
||||
} else if (shouldUseExplicitDefaultOverride(provider, ock)) {
|
||||
await setOpenClawDefaultModelWithOverride(ock, modelOverride, {
|
||||
@@ -461,7 +463,7 @@ export async function syncDefaultProviderToRuntime(
|
||||
),
|
||||
api: provider.apiProtocol || getProviderConfig(provider.type)?.api,
|
||||
apiKeyEnv: getProviderConfig(provider.type)?.apiKeyEnv,
|
||||
headers: getProviderConfig(provider.type)?.headers,
|
||||
headers: provider.headers ?? getProviderConfig(provider.type)?.headers,
|
||||
}, fallbackModels);
|
||||
} else {
|
||||
await setOpenClawDefaultModel(ock, modelOverride, fallbackModels);
|
||||
|
||||
@@ -157,6 +157,9 @@ export class ProviderService {
|
||||
authMode: definition?.defaultAuthMode ?? 'api_key',
|
||||
baseUrl,
|
||||
apiProtocol: definition?.providerConfig?.api,
|
||||
headers: (entry.headers && typeof entry.headers === 'object'
|
||||
? (entry.headers as Record<string, string>)
|
||||
: undefined),
|
||||
model,
|
||||
enabled: true,
|
||||
isDefault: false,
|
||||
|
||||
@@ -30,6 +30,7 @@ export function providerConfigToAccount(
|
||||
apiProtocol: config.apiProtocol || (config.type === 'custom' || config.type === 'ollama'
|
||||
? 'openai-completions'
|
||||
: getProviderDefinition(config.type)?.providerConfig?.api),
|
||||
headers: config.headers,
|
||||
model: config.model,
|
||||
fallbackModels: config.fallbackModels,
|
||||
fallbackAccountIds: config.fallbackProviderIds,
|
||||
@@ -47,6 +48,7 @@ export function providerAccountToConfig(account: ProviderAccount): ProviderConfi
|
||||
type: account.vendorId,
|
||||
baseUrl: account.baseUrl,
|
||||
apiProtocol: account.apiProtocol,
|
||||
headers: account.headers,
|
||||
model: account.model,
|
||||
fallbackModels: account.fallbackModels,
|
||||
fallbackProviderIds: account.fallbackAccountIds,
|
||||
|
||||
@@ -55,6 +55,7 @@ export interface ProviderConfig {
|
||||
type: ProviderType;
|
||||
baseUrl?: string;
|
||||
apiProtocol?: ProviderProtocol;
|
||||
headers?: Record<string, string>;
|
||||
model?: string;
|
||||
fallbackModels?: string[];
|
||||
fallbackProviderIds?: string[];
|
||||
@@ -118,6 +119,7 @@ export interface ProviderAccount {
|
||||
authMode: ProviderAuthMode;
|
||||
baseUrl?: string;
|
||||
apiProtocol?: ProviderProtocol;
|
||||
headers?: Record<string, string>;
|
||||
model?: string;
|
||||
fallbackModels?: string[];
|
||||
fallbackAccountIds?: string[];
|
||||
|
||||
@@ -557,10 +557,12 @@ function upsertOpenClawProviderEntry(
|
||||
models: mergeProviderModels(registryModels, existingModels, runtimeModels),
|
||||
};
|
||||
if (options.apiKeyEnv) nextProvider.apiKey = options.apiKeyEnv;
|
||||
if (options.headers && Object.keys(options.headers).length > 0) {
|
||||
nextProvider.headers = options.headers;
|
||||
} else {
|
||||
delete nextProvider.headers;
|
||||
if (options.headers !== undefined) {
|
||||
if (Object.keys(options.headers).length > 0) {
|
||||
nextProvider.headers = options.headers;
|
||||
} else {
|
||||
delete nextProvider.headers;
|
||||
}
|
||||
}
|
||||
if (options.authHeader !== undefined) {
|
||||
nextProvider.authHeader = options.authHeader;
|
||||
|
||||
@@ -34,6 +34,7 @@ export interface ProviderConfig {
|
||||
type: ProviderType;
|
||||
baseUrl?: string;
|
||||
apiProtocol?: 'openai-completions' | 'openai-responses' | 'anthropic-messages';
|
||||
headers?: Record<string, string>;
|
||||
model?: string;
|
||||
fallbackModels?: string[];
|
||||
fallbackProviderIds?: string[];
|
||||
|
||||
@@ -86,6 +86,30 @@ function fallbackModelsEqual(a?: string[], b?: string[]): boolean {
|
||||
return left.length === right.length && left.every((model, index) => model === right[index]);
|
||||
}
|
||||
|
||||
function getUserAgentHeader(headers?: Record<string, string>): string {
|
||||
if (!headers) return '';
|
||||
for (const [key, value] of Object.entries(headers)) {
|
||||
if (key.toLowerCase() === 'user-agent') {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
function mergeHeadersWithUserAgent(
|
||||
headers: Record<string, string> | undefined,
|
||||
userAgent: string,
|
||||
): Record<string, string> {
|
||||
const next = Object.fromEntries(
|
||||
Object.entries(headers ?? {}).filter(([key]) => key.toLowerCase() !== 'user-agent'),
|
||||
);
|
||||
const normalizedUserAgent = userAgent.trim();
|
||||
if (normalizedUserAgent) {
|
||||
next['User-Agent'] = normalizedUserAgent;
|
||||
}
|
||||
return next;
|
||||
}
|
||||
|
||||
function isArkCodePlanMode(
|
||||
vendorId: string,
|
||||
baseUrl: string | undefined,
|
||||
@@ -97,6 +121,14 @@ function isArkCodePlanMode(
|
||||
return (baseUrl || '').trim() === codePlanPresetBaseUrl && (modelId || '').trim() === codePlanPresetModelId;
|
||||
}
|
||||
|
||||
function shouldShowUserAgentField(account: ProviderAccount): boolean {
|
||||
return account.vendorId === 'custom';
|
||||
}
|
||||
|
||||
function shouldShowUserAgentFieldForNewProvider(providerType: ProviderType | null): boolean {
|
||||
return providerType === 'custom';
|
||||
}
|
||||
|
||||
function getAuthModeLabel(
|
||||
authMode: ProviderAccount['authMode'],
|
||||
t: (key: string) => string
|
||||
@@ -150,7 +182,13 @@ export function ProvidersSettings() {
|
||||
type: ProviderType,
|
||||
name: string,
|
||||
apiKey: string,
|
||||
options?: { baseUrl?: string; model?: string; authMode?: ProviderAccount['authMode']; apiProtocol?: ProviderAccount['apiProtocol'] }
|
||||
options?: {
|
||||
baseUrl?: string;
|
||||
model?: string;
|
||||
authMode?: ProviderAccount['authMode'];
|
||||
apiProtocol?: ProviderAccount['apiProtocol'];
|
||||
headers?: Record<string, string>;
|
||||
}
|
||||
) => {
|
||||
const vendor = vendorMap.get(type);
|
||||
const id = buildProviderAccountId(type, null, vendors);
|
||||
@@ -163,6 +201,7 @@ export function ProvidersSettings() {
|
||||
authMode: options?.authMode || vendor?.defaultAuthMode || (type === 'ollama' ? 'local' : 'api_key'),
|
||||
baseUrl: options?.baseUrl,
|
||||
apiProtocol: options?.apiProtocol,
|
||||
headers: options?.headers,
|
||||
model: options?.model,
|
||||
enabled: true,
|
||||
isDefault: false,
|
||||
@@ -246,6 +285,7 @@ export function ProvidersSettings() {
|
||||
if (payload.updates) {
|
||||
if (payload.updates.baseUrl !== undefined) updates.baseUrl = payload.updates.baseUrl;
|
||||
if (payload.updates.apiProtocol !== undefined) updates.apiProtocol = payload.updates.apiProtocol;
|
||||
if (payload.updates.headers !== undefined) updates.headers = payload.updates.headers;
|
||||
if (payload.updates.model !== undefined) updates.model = payload.updates.model;
|
||||
if (payload.updates.fallbackModels !== undefined) updates.fallbackModels = payload.updates.fallbackModels;
|
||||
if (payload.updates.fallbackProviderIds !== undefined) {
|
||||
@@ -318,6 +358,7 @@ function ProviderCard({
|
||||
const [newKey, setNewKey] = useState('');
|
||||
const [baseUrl, setBaseUrl] = useState(account.baseUrl || '');
|
||||
const [apiProtocol, setApiProtocol] = useState<ProviderAccount['apiProtocol']>(account.apiProtocol || 'openai-completions');
|
||||
const [userAgent, setUserAgent] = useState(getUserAgentHeader(account.headers));
|
||||
const [modelId, setModelId] = useState(account.model || '');
|
||||
const [fallbackModelsText, setFallbackModelsText] = useState(
|
||||
normalizeFallbackModels(account.fallbackModels).join('\n')
|
||||
@@ -344,6 +385,7 @@ function ProviderCard({
|
||||
? (typeInfo?.codePlanDocsUrl || providerDocsUrl)
|
||||
: providerDocsUrl;
|
||||
const canEditModelConfig = Boolean(typeInfo?.showBaseUrl || showModelIdField);
|
||||
const showUserAgentField = shouldShowUserAgentField(account);
|
||||
|
||||
useEffect(() => {
|
||||
if (isEditing) {
|
||||
@@ -351,6 +393,7 @@ function ProviderCard({
|
||||
setShowKey(false);
|
||||
setBaseUrl(account.baseUrl || '');
|
||||
setApiProtocol(account.apiProtocol || 'openai-completions');
|
||||
setUserAgent(getUserAgentHeader(account.headers));
|
||||
setModelId(account.model || '');
|
||||
setFallbackModelsText(normalizeFallbackModels(account.fallbackModels).join('\n'));
|
||||
setFallbackProviderIds(normalizeFallbackProviderIds(account.fallbackAccountIds));
|
||||
@@ -364,7 +407,7 @@ function ProviderCard({
|
||||
) ? 'codeplan' : 'apikey'
|
||||
);
|
||||
}
|
||||
}, [isEditing, account.baseUrl, account.fallbackModels, account.fallbackAccountIds, account.model, account.apiProtocol, account.vendorId, typeInfo?.codePlanPresetBaseUrl, typeInfo?.codePlanPresetModelId]);
|
||||
}, [isEditing, account.baseUrl, account.headers, account.fallbackModels, account.fallbackAccountIds, account.model, account.apiProtocol, account.vendorId, typeInfo?.codePlanPresetBaseUrl, typeInfo?.codePlanPresetModelId]);
|
||||
|
||||
const fallbackOptions = allProviders.filter((candidate) => candidate.account.id !== account.id);
|
||||
|
||||
@@ -414,6 +457,11 @@ function ProviderCard({
|
||||
if (showModelIdField && (modelId.trim() || undefined) !== (account.model || undefined)) {
|
||||
updates.model = modelId.trim() || undefined;
|
||||
}
|
||||
const existingUserAgent = getUserAgentHeader(account.headers).trim();
|
||||
const nextUserAgent = userAgent.trim();
|
||||
if (nextUserAgent !== existingUserAgent) {
|
||||
updates.headers = mergeHeadersWithUserAgent(account.headers, nextUserAgent);
|
||||
}
|
||||
if (!fallbackModelsEqual(normalizedFallbackModels, account.fallbackModels)) {
|
||||
updates.fallbackModels = normalizedFallbackModels;
|
||||
}
|
||||
@@ -670,6 +718,17 @@ function ProviderCard({
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{showUserAgentField && (
|
||||
<div className="space-y-1.5 pt-2">
|
||||
<Label className={currentLabelClasses}>{t('aiProviders.dialog.userAgent')}</Label>
|
||||
<Input
|
||||
value={userAgent}
|
||||
onChange={(e) => setUserAgent(e.target.value)}
|
||||
placeholder={t('aiProviders.dialog.userAgentPlaceholder')}
|
||||
className={currentInputClasses}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<div className="space-y-3">
|
||||
@@ -786,6 +845,7 @@ function ProviderCard({
|
||||
|| (
|
||||
!newKey.trim()
|
||||
&& (baseUrl.trim() || undefined) === (account.baseUrl || undefined)
|
||||
&& userAgent.trim() === getUserAgentHeader(account.headers).trim()
|
||||
&& (modelId.trim() || undefined) === (account.model || undefined)
|
||||
&& fallbackModelsEqual(normalizeFallbackModels(fallbackModelsText.split('\n')), account.fallbackModels)
|
||||
&& fallbackProviderIdsEqual(fallbackProviderIds, account.fallbackAccountIds)
|
||||
@@ -831,7 +891,13 @@ interface AddProviderDialogProps {
|
||||
type: ProviderType,
|
||||
name: string,
|
||||
apiKey: string,
|
||||
options?: { baseUrl?: string; model?: string; authMode?: ProviderAccount['authMode']; apiProtocol?: ProviderAccount['apiProtocol'] }
|
||||
options?: {
|
||||
baseUrl?: string;
|
||||
model?: string;
|
||||
authMode?: ProviderAccount['authMode'];
|
||||
apiProtocol?: ProviderAccount['apiProtocol'];
|
||||
headers?: Record<string, string>;
|
||||
}
|
||||
) => Promise<void>;
|
||||
onValidateKey: (
|
||||
type: string,
|
||||
@@ -856,6 +922,8 @@ function AddProviderDialog({
|
||||
const [baseUrl, setBaseUrl] = useState('');
|
||||
const [modelId, setModelId] = useState('');
|
||||
const [apiProtocol, setApiProtocol] = useState<ProviderAccount['apiProtocol']>('openai-completions');
|
||||
const [showAdvancedConfig, setShowAdvancedConfig] = useState(false);
|
||||
const [userAgent, setUserAgent] = useState('');
|
||||
const [arkMode, setArkMode] = useState<ArkMode>('apikey');
|
||||
const [showKey, setShowKey] = useState(false);
|
||||
const [saving, setSaving] = useState(false);
|
||||
@@ -895,6 +963,7 @@ function AddProviderDialog({
|
||||
const supportsApiKey = typeInfo?.supportsApiKey ?? false;
|
||||
const vendorMap = new Map(vendors.map((vendor) => [vendor.id, vendor]));
|
||||
const selectedVendor = selectedType ? vendorMap.get(selectedType) : undefined;
|
||||
const showUserAgentInAddDialog = shouldShowUserAgentFieldForNewProvider(selectedType);
|
||||
const preferredOAuthMode = selectedVendor?.supportedAuthModes.includes('oauth_browser')
|
||||
? 'oauth_browser'
|
||||
: (selectedVendor?.supportedAuthModes.includes('oauth_device')
|
||||
@@ -1120,6 +1189,7 @@ function AddProviderDialog({
|
||||
{
|
||||
baseUrl: baseUrl.trim() || undefined,
|
||||
apiProtocol: (selectedType === 'custom' || selectedType === 'ollama') ? apiProtocol : undefined,
|
||||
headers: userAgent.trim() ? { 'User-Agent': userAgent.trim() } : undefined,
|
||||
model: resolveProviderModelForSave(typeInfo, modelId, devModeUnlocked),
|
||||
authMode: useOAuthFlow ? (preferredOAuthMode || 'oauth_device') : selectedType === 'ollama'
|
||||
? 'local'
|
||||
@@ -1163,6 +1233,8 @@ function AddProviderDialog({
|
||||
setName(type.id === 'custom' ? t('aiProviders.custom') : type.name);
|
||||
setBaseUrl(type.defaultBaseUrl || '');
|
||||
setModelId(type.defaultModelId || '');
|
||||
setUserAgent('');
|
||||
setShowAdvancedConfig(false);
|
||||
setArkMode('apikey');
|
||||
}}
|
||||
className="p-4 rounded-2xl border border-black/5 dark:border-white/5 hover:bg-black/5 dark:hover:bg-white/5 transition-colors text-center group"
|
||||
@@ -1191,15 +1263,17 @@ function AddProviderDialog({
|
||||
<div>
|
||||
<p className="font-semibold text-[15px]">{typeInfo?.id === 'custom' ? t('aiProviders.custom') : typeInfo?.name}</p>
|
||||
<button
|
||||
onClick={() => {
|
||||
setSelectedType(null);
|
||||
setValidationError(null);
|
||||
setBaseUrl('');
|
||||
setModelId('');
|
||||
setArkMode('apikey');
|
||||
}}
|
||||
className="text-[13px] text-blue-500 hover:text-blue-600 font-medium"
|
||||
>
|
||||
onClick={() => {
|
||||
setSelectedType(null);
|
||||
setValidationError(null);
|
||||
setBaseUrl('');
|
||||
setModelId('');
|
||||
setUserAgent('');
|
||||
setShowAdvancedConfig(false);
|
||||
setArkMode('apikey');
|
||||
}}
|
||||
className="text-[13px] text-blue-500 hover:text-blue-600 font-medium"
|
||||
>
|
||||
{t('aiProviders.dialog.change')}
|
||||
</button>
|
||||
{effectiveDocsUrl && (
|
||||
@@ -1409,6 +1483,30 @@ function AddProviderDialog({
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{showUserAgentInAddDialog && (
|
||||
<div className="space-y-2.5">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowAdvancedConfig((value) => !value)}
|
||||
className="flex items-center justify-between w-full text-[14px] font-bold text-foreground/80 hover:text-foreground transition-colors"
|
||||
>
|
||||
<span>{t('aiProviders.dialog.advancedConfig')}</span>
|
||||
<ChevronDown className={cn("h-4 w-4 transition-transform", showAdvancedConfig && "rotate-180")} />
|
||||
</button>
|
||||
{showAdvancedConfig && (
|
||||
<div className="space-y-2.5 pt-1">
|
||||
<Label htmlFor="userAgent" className={labelClasses}>{t('aiProviders.dialog.userAgent')}</Label>
|
||||
<Input
|
||||
id="userAgent"
|
||||
placeholder={t('aiProviders.dialog.userAgentPlaceholder')}
|
||||
value={userAgent}
|
||||
onChange={(e) => setUserAgent(e.target.value)}
|
||||
className={inputClasses}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{/* Device OAuth Trigger — only shown when in OAuth mode */}
|
||||
{useOAuthFlow && (
|
||||
<div className="space-y-4 pt-2">
|
||||
|
||||
@@ -58,6 +58,9 @@
|
||||
"codePlanPresetDesc": "Code Plan uses https://ark.cn-beijing.volces.com/api/coding/v3 and model ark-code-latest. Do not use /api/v3 for Code Plan traffic.",
|
||||
"codePlanDoc": "Code Plan docs",
|
||||
"protocol": "Protocol",
|
||||
"advancedConfig": "Advanced configuration",
|
||||
"userAgent": "User-Agent",
|
||||
"userAgentPlaceholder": "ClawX/1.0",
|
||||
"fallbackModels": "Fallback Models",
|
||||
"fallbackProviders": "Fallback Providers",
|
||||
"fallbackModelIds": "Fallback Model IDs",
|
||||
|
||||
@@ -58,6 +58,9 @@
|
||||
"codePlanPresetDesc": "Code Plan は https://ark.cn-beijing.volces.com/api/coding/v3 と model ark-code-latest を使います。Code Plan 通信に /api/v3 を使わないでください。",
|
||||
"codePlanDoc": "Code Plan ドキュメント",
|
||||
"protocol": "プロトコル",
|
||||
"advancedConfig": "詳細設定",
|
||||
"userAgent": "User-Agent",
|
||||
"userAgentPlaceholder": "ClawX/1.0",
|
||||
"fallbackModels": "フォールバックモデル",
|
||||
"fallbackProviders": "別プロバイダーへのフォールバック",
|
||||
"fallbackModelIds": "同一プロバイダーのフォールバックモデル ID",
|
||||
|
||||
@@ -58,6 +58,9 @@
|
||||
"codePlanPresetDesc": "Code Plan 使用 https://ark.cn-beijing.volces.com/api/coding/v3 与模型 ark-code-latest。请勿把 /api/v3 用于 Code Plan 流量。",
|
||||
"codePlanDoc": "Code Plan 文档",
|
||||
"protocol": "协议",
|
||||
"advancedConfig": "高级配置",
|
||||
"userAgent": "User-Agent",
|
||||
"userAgentPlaceholder": "ClawX/1.0",
|
||||
"fallbackModels": "回退模型",
|
||||
"fallbackProviders": "跨 Provider 回退",
|
||||
"fallbackModelIds": "同 Provider 回退模型 ID",
|
||||
|
||||
@@ -81,6 +81,7 @@ export function legacyProviderToAccount(provider: ProviderWithKeyInfo): Provider
|
||||
label: provider.name,
|
||||
authMode: provider.type === 'ollama' ? 'local' : 'api_key',
|
||||
baseUrl: provider.baseUrl,
|
||||
headers: provider.headers,
|
||||
model: provider.model,
|
||||
fallbackModels: provider.fallbackModels,
|
||||
fallbackAccountIds: provider.fallbackProviderIds,
|
||||
|
||||
@@ -44,6 +44,7 @@ export interface ProviderConfig {
|
||||
type: ProviderType;
|
||||
baseUrl?: string;
|
||||
apiProtocol?: 'openai-completions' | 'openai-responses' | 'anthropic-messages';
|
||||
headers?: Record<string, string>;
|
||||
model?: string;
|
||||
fallbackModels?: string[];
|
||||
fallbackProviderIds?: string[];
|
||||
@@ -107,6 +108,7 @@ export interface ProviderAccount {
|
||||
authMode: ProviderAuthMode;
|
||||
baseUrl?: string;
|
||||
apiProtocol?: 'openai-completions' | 'openai-responses' | 'anthropic-messages';
|
||||
headers?: Record<string, string>;
|
||||
model?: string;
|
||||
fallbackModels?: string[];
|
||||
fallbackAccountIds?: string[];
|
||||
|
||||
Reference in New Issue
Block a user