diff --git a/src/components/providers/AddProviderDialog.tsx b/src/components/providers/AddProviderDialog.tsx index 5bd4d386d..0040f4a14 100644 --- a/src/components/providers/AddProviderDialog.tsx +++ b/src/components/providers/AddProviderDialog.tsx @@ -17,6 +17,7 @@ import { UniversalProviderPanel } from "@/components/universal"; import { providerPresets } from "@/config/claudeProviderPresets"; import { codexProviderPresets } from "@/config/codexProviderPresets"; import { geminiProviderPresets } from "@/config/geminiProviderPresets"; +import type { OpenClawSuggestedDefaults } from "@/config/openclawProviderPresets"; import type { UniversalProviderPreset } from "@/config/universalProviderPresets"; interface AddProviderDialogProps { @@ -24,7 +25,10 @@ interface AddProviderDialogProps { onOpenChange: (open: boolean) => void; appId: AppId; onSubmit: ( - provider: Omit & { providerKey?: string }, + provider: Omit & { + providerKey?: string; + suggestedDefaults?: OpenClawSuggestedDefaults; + }, ) => Promise | void; } @@ -83,7 +87,11 @@ export function AddProviderDialog({ unknown >; - const providerData: Omit & { providerKey?: string } = { + // 构造基础提交数据 + const providerData: Omit & { + providerKey?: string; + suggestedDefaults?: OpenClawSuggestedDefaults; + } = { name: values.name.trim(), notes: values.notes?.trim() || undefined, websiteUrl: values.websiteUrl?.trim() || undefined, @@ -95,7 +103,10 @@ export function AddProviderDialog({ }; // OpenCode/OpenClaw: pass providerKey for ID generation - if ((appId === "opencode" || appId === "openclaw") && values.providerKey) { + if ( + (appId === "opencode" || appId === "openclaw") && + values.providerKey + ) { providerData.providerKey = values.providerKey; } @@ -213,6 +224,11 @@ export function AddProviderDialog({ } } + // OpenClaw: pass suggestedDefaults for model registration + if (appId === "openclaw" && values.suggestedDefaults) { + providerData.suggestedDefaults = values.suggestedDefaults; + } + await onSubmit(providerData); onOpenChange(false); }, diff --git a/src/components/providers/forms/ProviderForm.tsx b/src/components/providers/forms/ProviderForm.tsx index 98a667144..2cfb91af8 100644 --- a/src/components/providers/forms/ProviderForm.tsx +++ b/src/components/providers/forms/ProviderForm.tsx @@ -37,6 +37,7 @@ import { import { openclawProviderPresets, type OpenClawProviderPreset, + type OpenClawSuggestedDefaults, } from "@/config/openclawProviderPresets"; import { OpenCodeFormFields } from "./OpenCodeFormFields"; import { OpenClawFormFields } from "./OpenClawFormFields"; @@ -277,6 +278,7 @@ export function ProviderForm({ category?: ProviderCategory; isPartner?: boolean; partnerPromotionKey?: string; + suggestedDefaults?: OpenClawSuggestedDefaults; } | null>(null); const [isEndpointModalOpen, setIsEndpointModalOpen] = useState(false); const [isCodexEndpointModalOpen, setIsCodexEndpointModalOpen] = @@ -1446,6 +1448,10 @@ export function ProviderForm({ if (activePreset.isPartner) { payload.isPartner = activePreset.isPartner; } + // OpenClaw: 传递预设的 suggestedDefaults 到提交数据 + if (activePreset.suggestedDefaults) { + payload.suggestedDefaults = activePreset.suggestedDefaults; + } } if (!isEditMode && draftCustomEndpoints.length > 0) { @@ -1726,6 +1732,15 @@ export function ProviderForm({ const preset = entry.preset as OpenClawProviderPreset; const config = preset.settingsConfig; + // Update activePreset with suggestedDefaults for OpenClaw + setActivePreset({ + id: value, + category: preset.category, + isPartner: preset.isPartner, + partnerPromotionKey: preset.partnerPromotionKey, + suggestedDefaults: preset.suggestedDefaults, + }); + // Clear provider key (user must enter their own unique key) setOpenclawProviderKey(""); @@ -2206,5 +2221,6 @@ export type ProviderFormValues = ProviderFormData & { presetCategory?: ProviderCategory; isPartner?: boolean; meta?: ProviderMeta; - providerKey?: string; + providerKey?: string; // OpenCode/OpenClaw: user-defined provider key + suggestedDefaults?: OpenClawSuggestedDefaults; // OpenClaw: suggested default model configuration }; diff --git a/src/hooks/useProviderActions.ts b/src/hooks/useProviderActions.ts index daf4229b4..0f1d9bb91 100644 --- a/src/hooks/useProviderActions.ts +++ b/src/hooks/useProviderActions.ts @@ -2,8 +2,9 @@ import { useCallback } from "react"; import { useQueryClient } from "@tanstack/react-query"; import { toast } from "sonner"; import { useTranslation } from "react-i18next"; -import { providersApi, settingsApi, type AppId } from "@/lib/api"; +import { providersApi, settingsApi, openclawApi, type AppId } from "@/lib/api"; import type { Provider, UsageScript } from "@/types"; +import type { OpenClawSuggestedDefaults } from "@/config/openclawProviderPresets"; import { useAddProviderMutation, useUpdateProviderMutation, @@ -54,10 +55,55 @@ export function useProviderActions(activeApp: AppId) { // 添加供应商 const addProvider = useCallback( - async (provider: Omit & { providerKey?: string }) => { + async ( + provider: Omit & { + providerKey?: string; + suggestedDefaults?: OpenClawSuggestedDefaults; + }, + ) => { await addProviderMutation.mutateAsync(provider); + + // OpenClaw: register models to allowlist after adding provider + if (activeApp === "openclaw" && provider.suggestedDefaults) { + const { model, modelCatalog } = provider.suggestedDefaults; + let modelsRegistered = false; + + try { + // 1. Merge model catalog (allowlist) + if (modelCatalog && Object.keys(modelCatalog).length > 0) { + const existingCatalog = (await openclawApi.getModelCatalog()) || {}; + const mergedCatalog = { ...existingCatalog, ...modelCatalog }; + await openclawApi.setModelCatalog(mergedCatalog); + modelsRegistered = true; + } + + // 2. Set default model (only if not already set) + if (model) { + const existingDefault = await openclawApi.getDefaultModel(); + if (!existingDefault?.primary) { + await openclawApi.setDefaultModel(model); + } + } + + // Show success toast if models were registered + if (modelsRegistered) { + toast.success( + t("notifications.openclawModelsRegistered", { + defaultValue: "模型已注册到 /model 列表", + }), + { closeButton: true }, + ); + } + } catch (error) { + // Log warning but don't block main flow - provider config is already saved + console.warn( + "[OpenClaw] Failed to register models to allowlist:", + error, + ); + } + } }, - [addProviderMutation], + [addProviderMutation, activeApp, t], ); // 更新供应商 diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index 2f52a6b49..8d9a687ed 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -162,7 +162,8 @@ "settingsSaved": "Settings saved", "settingsSaveFailed": "Failed to save settings: {{error}}", "openAIChatFormatHint": "This provider uses OpenAI Chat format and requires the proxy service to be enabled", - "openLinkFailed": "Failed to open link" + "openLinkFailed": "Failed to open link", + "openclawModelsRegistered": "Models have been registered to /model list" }, "confirm": { "deleteProvider": "Delete Provider", diff --git a/src/i18n/locales/ja.json b/src/i18n/locales/ja.json index 4fe768253..3f7458300 100644 --- a/src/i18n/locales/ja.json +++ b/src/i18n/locales/ja.json @@ -162,7 +162,8 @@ "settingsSaved": "設定を保存しました", "settingsSaveFailed": "設定の保存に失敗しました: {{error}}", "openAIChatFormatHint": "このプロバイダーは OpenAI Chat フォーマットを使用しており、プロキシサービスの有効化が必要です", - "openLinkFailed": "リンクを開けませんでした" + "openLinkFailed": "リンクを開けませんでした", + "openclawModelsRegistered": "モデルが /model リストに登録されました" }, "confirm": { "deleteProvider": "プロバイダーを削除", diff --git a/src/i18n/locales/zh.json b/src/i18n/locales/zh.json index 7308d55c8..3d9d9ec54 100644 --- a/src/i18n/locales/zh.json +++ b/src/i18n/locales/zh.json @@ -162,7 +162,8 @@ "settingsSaved": "设置已保存", "settingsSaveFailed": "保存设置失败:{{error}}", "openAIChatFormatHint": "此供应商使用 OpenAI Chat 格式,需要开启代理服务才能正常使用", - "openLinkFailed": "链接打开失败" + "openLinkFailed": "链接打开失败", + "openclawModelsRegistered": "模型已注册到 /model 列表" }, "confirm": { "deleteProvider": "删除供应商", diff --git a/src/lib/api/index.ts b/src/lib/api/index.ts index 8e48d0151..38d6b09e2 100644 --- a/src/lib/api/index.ts +++ b/src/lib/api/index.ts @@ -7,7 +7,7 @@ export { skillsApi } from "./skills"; export { usageApi } from "./usage"; export { vscodeApi } from "./vscode"; export { proxyApi } from "./proxy"; -export { sessionsApi } from "./sessions"; +export { openclawApi } from "./openclaw"; export * as configApi from "./config"; export type { ProviderSwitchEvent } from "./providers"; export type { Prompt } from "./prompts";