From 7d032ed38c432e3c06981da078f9f319ae46ba62 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Mon, 23 Mar 2026 01:39:10 +0000 Subject: [PATCH] refactor: add provider onboarding preset appliers --- docs/plugins/sdk-provider-plugins.md | 7 ++ extensions/huggingface/onboard.ts | 16 +-- extensions/kilocode/onboard.ts | 22 ++-- extensions/kimi-coding/onboard.ts | 38 ++++--- extensions/mistral/onboard.ts | 16 +-- extensions/modelstudio/onboard.ts | 52 ++++----- extensions/moonshot/onboard.ts | 54 ++++----- extensions/qianfan/onboard.ts | 32 +++--- extensions/synthetic/onboard.ts | 16 +-- extensions/together/onboard.ts | 16 +-- extensions/venice/onboard.ts | 16 +-- extensions/xai/onboard.ts | 24 ++-- extensions/xiaomi/onboard.ts | 36 +++--- src/plugin-sdk/provider-onboard.ts | 8 +- .../provider-onboarding-config.test.ts | 106 ++++++++++++++++++ src/plugins/provider-onboarding-config.ts | 87 ++++++++++++++ 16 files changed, 368 insertions(+), 178 deletions(-) create mode 100644 src/plugins/provider-onboarding-config.test.ts diff --git a/docs/plugins/sdk-provider-plugins.md b/docs/plugins/sdk-provider-plugins.md index 56c8ce50cab..6477b6c2db0 100644 --- a/docs/plugins/sdk-provider-plugins.md +++ b/docs/plugins/sdk-provider-plugins.md @@ -185,6 +185,13 @@ API key auth, and dynamic model resolution. }); ``` + If your auth flow also needs to patch `models.providers.*`, aliases, and + the agent default model during onboarding, use the preset helpers from + `openclaw/plugin-sdk/provider-onboard`. The narrowest helpers are + `createDefaultModelPresetAppliers(...)`, + `createDefaultModelsPresetAppliers(...)`, and + `createModelCatalogPresetAppliers(...)`. + diff --git a/extensions/huggingface/onboard.ts b/extensions/huggingface/onboard.ts index e8f7412768c..c72259ebc72 100644 --- a/extensions/huggingface/onboard.ts +++ b/extensions/huggingface/onboard.ts @@ -4,27 +4,27 @@ import { HUGGINGFACE_MODEL_CATALOG, } from "openclaw/plugin-sdk/provider-models"; import { - applyProviderConfigWithModelCatalogPreset, + createModelCatalogPresetAppliers, type OpenClawConfig, } from "openclaw/plugin-sdk/provider-onboard"; export const HUGGINGFACE_DEFAULT_MODEL_REF = "huggingface/deepseek-ai/DeepSeek-R1"; -function applyHuggingfacePreset(cfg: OpenClawConfig, primaryModelRef?: string): OpenClawConfig { - return applyProviderConfigWithModelCatalogPreset(cfg, { +const huggingfacePresetAppliers = createModelCatalogPresetAppliers({ + primaryModelRef: HUGGINGFACE_DEFAULT_MODEL_REF, + resolveParams: (_cfg: OpenClawConfig) => ({ providerId: "huggingface", api: "openai-completions", baseUrl: HUGGINGFACE_BASE_URL, catalogModels: HUGGINGFACE_MODEL_CATALOG.map(buildHuggingfaceModelDefinition), aliases: [{ modelRef: HUGGINGFACE_DEFAULT_MODEL_REF, alias: "Hugging Face" }], - primaryModelRef, - }); -} + }), +}); export function applyHuggingfaceProviderConfig(cfg: OpenClawConfig): OpenClawConfig { - return applyHuggingfacePreset(cfg); + return huggingfacePresetAppliers.applyProviderConfig(cfg); } export function applyHuggingfaceConfig(cfg: OpenClawConfig): OpenClawConfig { - return applyHuggingfacePreset(cfg, HUGGINGFACE_DEFAULT_MODEL_REF); + return huggingfacePresetAppliers.applyConfig(cfg); } diff --git a/extensions/kilocode/onboard.ts b/extensions/kilocode/onboard.ts index 88533dd64a0..59c142d0f14 100644 --- a/extensions/kilocode/onboard.ts +++ b/extensions/kilocode/onboard.ts @@ -1,29 +1,27 @@ import { KILOCODE_BASE_URL, KILOCODE_DEFAULT_MODEL_REF } from "openclaw/plugin-sdk/provider-models"; import { - applyProviderConfigWithModelCatalogPreset, + createModelCatalogPresetAppliers, type OpenClawConfig, } from "openclaw/plugin-sdk/provider-onboard"; import { buildKilocodeProvider } from "./provider-catalog.js"; export { KILOCODE_BASE_URL, KILOCODE_DEFAULT_MODEL_REF }; -export function applyKilocodeProviderConfig(cfg: OpenClawConfig): OpenClawConfig { - return applyProviderConfigWithModelCatalogPreset(cfg, { +const kilocodePresetAppliers = createModelCatalogPresetAppliers({ + primaryModelRef: KILOCODE_DEFAULT_MODEL_REF, + resolveParams: (_cfg: OpenClawConfig) => ({ providerId: "kilocode", api: "openai-completions", baseUrl: KILOCODE_BASE_URL, catalogModels: buildKilocodeProvider().models ?? [], aliases: [{ modelRef: KILOCODE_DEFAULT_MODEL_REF, alias: "Kilo Gateway" }], - }); + }), +}); + +export function applyKilocodeProviderConfig(cfg: OpenClawConfig): OpenClawConfig { + return kilocodePresetAppliers.applyProviderConfig(cfg); } export function applyKilocodeConfig(cfg: OpenClawConfig): OpenClawConfig { - return applyProviderConfigWithModelCatalogPreset(cfg, { - providerId: "kilocode", - api: "openai-completions", - baseUrl: KILOCODE_BASE_URL, - catalogModels: buildKilocodeProvider().models ?? [], - aliases: [{ modelRef: KILOCODE_DEFAULT_MODEL_REF, alias: "Kilo Gateway" }], - primaryModelRef: KILOCODE_DEFAULT_MODEL_REF, - }); + return kilocodePresetAppliers.applyConfig(cfg); } diff --git a/extensions/kimi-coding/onboard.ts b/extensions/kimi-coding/onboard.ts index 65d2e7aabe7..1e8e6061af7 100644 --- a/extensions/kimi-coding/onboard.ts +++ b/extensions/kimi-coding/onboard.ts @@ -1,5 +1,5 @@ import { - applyProviderConfigWithDefaultModelPreset, + createDefaultModelPresetAppliers, type OpenClawConfig, } from "openclaw/plugin-sdk/provider-onboard"; import { @@ -15,26 +15,28 @@ function resolveKimiCodingDefaultModel() { return buildKimiCodingProvider().models[0]; } -function applyKimiCodingPreset(cfg: OpenClawConfig, primaryModelRef?: string): OpenClawConfig { - const defaultModel = resolveKimiCodingDefaultModel(); - if (!defaultModel) { - return cfg; - } - return applyProviderConfigWithDefaultModelPreset(cfg, { - providerId: "kimi", - api: "anthropic-messages", - baseUrl: KIMI_CODING_BASE_URL, - defaultModel, - defaultModelId: KIMI_CODING_DEFAULT_MODEL_ID, - aliases: [{ modelRef: KIMI_MODEL_REF, alias: "Kimi" }], - primaryModelRef, - }); -} +const kimiCodingPresetAppliers = createDefaultModelPresetAppliers({ + primaryModelRef: KIMI_MODEL_REF, + resolveParams: (_cfg: OpenClawConfig) => { + const defaultModel = resolveKimiCodingDefaultModel(); + if (!defaultModel) { + return null; + } + return { + providerId: "kimi", + api: "anthropic-messages", + baseUrl: KIMI_CODING_BASE_URL, + defaultModel, + defaultModelId: KIMI_CODING_DEFAULT_MODEL_ID, + aliases: [{ modelRef: KIMI_MODEL_REF, alias: "Kimi" }], + }; + }, +}); export function applyKimiCodeProviderConfig(cfg: OpenClawConfig): OpenClawConfig { - return applyKimiCodingPreset(cfg); + return kimiCodingPresetAppliers.applyProviderConfig(cfg); } export function applyKimiCodeConfig(cfg: OpenClawConfig): OpenClawConfig { - return applyKimiCodingPreset(cfg, KIMI_MODEL_REF); + return kimiCodingPresetAppliers.applyConfig(cfg); } diff --git a/extensions/mistral/onboard.ts b/extensions/mistral/onboard.ts index 02093d6a9bb..8615fa9d6bd 100644 --- a/extensions/mistral/onboard.ts +++ b/extensions/mistral/onboard.ts @@ -1,5 +1,5 @@ import { - applyProviderConfigWithDefaultModelPreset, + createDefaultModelPresetAppliers, type OpenClawConfig, } from "openclaw/plugin-sdk/provider-onboard"; import { @@ -10,22 +10,22 @@ import { export const MISTRAL_DEFAULT_MODEL_REF = `mistral/${MISTRAL_DEFAULT_MODEL_ID}`; -function applyMistralPreset(cfg: OpenClawConfig, primaryModelRef?: string): OpenClawConfig { - return applyProviderConfigWithDefaultModelPreset(cfg, { +const mistralPresetAppliers = createDefaultModelPresetAppliers({ + primaryModelRef: MISTRAL_DEFAULT_MODEL_REF, + resolveParams: (_cfg: OpenClawConfig) => ({ providerId: "mistral", api: "openai-completions", baseUrl: MISTRAL_BASE_URL, defaultModel: buildMistralModelDefinition(), defaultModelId: MISTRAL_DEFAULT_MODEL_ID, aliases: [{ modelRef: MISTRAL_DEFAULT_MODEL_REF, alias: "Mistral" }], - primaryModelRef, - }); -} + }), +}); export function applyMistralProviderConfig(cfg: OpenClawConfig): OpenClawConfig { - return applyMistralPreset(cfg); + return mistralPresetAppliers.applyProviderConfig(cfg); } export function applyMistralConfig(cfg: OpenClawConfig): OpenClawConfig { - return applyMistralPreset(cfg, MISTRAL_DEFAULT_MODEL_REF); + return mistralPresetAppliers.applyConfig(cfg); } diff --git a/extensions/modelstudio/onboard.ts b/extensions/modelstudio/onboard.ts index 5252915bf25..ade5a02c8f4 100644 --- a/extensions/modelstudio/onboard.ts +++ b/extensions/modelstudio/onboard.ts @@ -1,5 +1,5 @@ import { - applyProviderConfigWithModelCatalogPreset, + createModelCatalogPresetAppliers, type OpenClawConfig, } from "openclaw/plugin-sdk/provider-onboard"; import { @@ -11,45 +11,35 @@ import { buildModelStudioProvider } from "./provider-catalog.js"; export { MODELSTUDIO_CN_BASE_URL, MODELSTUDIO_DEFAULT_MODEL_REF, MODELSTUDIO_GLOBAL_BASE_URL }; -function applyModelStudioProviderConfigWithBaseUrl( - cfg: OpenClawConfig, - baseUrl: string, - primaryModelRef?: string, -): OpenClawConfig { - const provider = buildModelStudioProvider(); - return applyProviderConfigWithModelCatalogPreset(cfg, { - providerId: "modelstudio", - api: provider.api ?? "openai-completions", - baseUrl, - catalogModels: provider.models ?? [], - aliases: [ - ...(provider.models ?? []).map((model) => `modelstudio/${model.id}`), - { modelRef: MODELSTUDIO_DEFAULT_MODEL_REF, alias: "Qwen" }, - ], - primaryModelRef, - }); -} +const modelStudioPresetAppliers = createModelCatalogPresetAppliers<[string]>({ + primaryModelRef: MODELSTUDIO_DEFAULT_MODEL_REF, + resolveParams: (_cfg: OpenClawConfig, baseUrl: string) => { + const provider = buildModelStudioProvider(); + return { + providerId: "modelstudio", + api: provider.api ?? "openai-completions", + baseUrl, + catalogModels: provider.models ?? [], + aliases: [ + ...(provider.models ?? []).map((model) => `modelstudio/${model.id}`), + { modelRef: MODELSTUDIO_DEFAULT_MODEL_REF, alias: "Qwen" }, + ], + }; + }, +}); export function applyModelStudioProviderConfig(cfg: OpenClawConfig): OpenClawConfig { - return applyModelStudioProviderConfigWithBaseUrl(cfg, MODELSTUDIO_GLOBAL_BASE_URL); + return modelStudioPresetAppliers.applyProviderConfig(cfg, MODELSTUDIO_GLOBAL_BASE_URL); } export function applyModelStudioProviderConfigCn(cfg: OpenClawConfig): OpenClawConfig { - return applyModelStudioProviderConfigWithBaseUrl(cfg, MODELSTUDIO_CN_BASE_URL); + return modelStudioPresetAppliers.applyProviderConfig(cfg, MODELSTUDIO_CN_BASE_URL); } export function applyModelStudioConfig(cfg: OpenClawConfig): OpenClawConfig { - return applyModelStudioProviderConfigWithBaseUrl( - cfg, - MODELSTUDIO_GLOBAL_BASE_URL, - MODELSTUDIO_DEFAULT_MODEL_REF, - ); + return modelStudioPresetAppliers.applyConfig(cfg, MODELSTUDIO_GLOBAL_BASE_URL); } export function applyModelStudioConfigCn(cfg: OpenClawConfig): OpenClawConfig { - return applyModelStudioProviderConfigWithBaseUrl( - cfg, - MODELSTUDIO_CN_BASE_URL, - MODELSTUDIO_DEFAULT_MODEL_REF, - ); + return modelStudioPresetAppliers.applyConfig(cfg, MODELSTUDIO_CN_BASE_URL); } diff --git a/extensions/moonshot/onboard.ts b/extensions/moonshot/onboard.ts index a4e937b3df5..28397b90e14 100644 --- a/extensions/moonshot/onboard.ts +++ b/extensions/moonshot/onboard.ts @@ -1,5 +1,5 @@ import { - applyProviderConfigWithDefaultModelPreset, + createDefaultModelPresetAppliers, type OpenClawConfig, } from "openclaw/plugin-sdk/provider-onboard"; import { @@ -11,43 +11,37 @@ import { export const MOONSHOT_CN_BASE_URL = "https://api.moonshot.cn/v1"; export const MOONSHOT_DEFAULT_MODEL_REF = `moonshot/${MOONSHOT_DEFAULT_MODEL_ID}`; +const moonshotPresetAppliers = createDefaultModelPresetAppliers<[string]>({ + primaryModelRef: MOONSHOT_DEFAULT_MODEL_REF, + resolveParams: (_cfg: OpenClawConfig, baseUrl: string) => { + const defaultModel = buildMoonshotProvider().models[0]; + if (!defaultModel) { + return null; + } + + return { + providerId: "moonshot", + api: "openai-completions", + baseUrl, + defaultModel, + defaultModelId: MOONSHOT_DEFAULT_MODEL_ID, + aliases: [{ modelRef: MOONSHOT_DEFAULT_MODEL_REF, alias: "Kimi" }], + }; + }, +}); + export function applyMoonshotProviderConfig(cfg: OpenClawConfig): OpenClawConfig { - return applyMoonshotProviderConfigWithBaseUrl(cfg, MOONSHOT_BASE_URL); + return moonshotPresetAppliers.applyProviderConfig(cfg, MOONSHOT_BASE_URL); } export function applyMoonshotProviderConfigCn(cfg: OpenClawConfig): OpenClawConfig { - return applyMoonshotProviderConfigWithBaseUrl(cfg, MOONSHOT_CN_BASE_URL); -} - -function applyMoonshotProviderConfigWithBaseUrl( - cfg: OpenClawConfig, - baseUrl: string, - primaryModelRef?: string, -): OpenClawConfig { - const defaultModel = buildMoonshotProvider().models[0]; - if (!defaultModel) { - return cfg; - } - - return applyProviderConfigWithDefaultModelPreset(cfg, { - providerId: "moonshot", - api: "openai-completions", - baseUrl, - defaultModel, - defaultModelId: MOONSHOT_DEFAULT_MODEL_ID, - aliases: [{ modelRef: MOONSHOT_DEFAULT_MODEL_REF, alias: "Kimi" }], - primaryModelRef, - }); + return moonshotPresetAppliers.applyProviderConfig(cfg, MOONSHOT_CN_BASE_URL); } export function applyMoonshotConfig(cfg: OpenClawConfig): OpenClawConfig { - return applyMoonshotProviderConfigWithBaseUrl(cfg, MOONSHOT_BASE_URL, MOONSHOT_DEFAULT_MODEL_REF); + return moonshotPresetAppliers.applyConfig(cfg, MOONSHOT_BASE_URL); } export function applyMoonshotConfigCn(cfg: OpenClawConfig): OpenClawConfig { - return applyMoonshotProviderConfigWithBaseUrl( - cfg, - MOONSHOT_CN_BASE_URL, - MOONSHOT_DEFAULT_MODEL_REF, - ); + return moonshotPresetAppliers.applyConfig(cfg, MOONSHOT_CN_BASE_URL); } diff --git a/extensions/qianfan/onboard.ts b/extensions/qianfan/onboard.ts index 0485c8b9676..269f4dd114a 100644 --- a/extensions/qianfan/onboard.ts +++ b/extensions/qianfan/onboard.ts @@ -1,5 +1,5 @@ import { - applyProviderConfigWithDefaultModelsPreset, + createDefaultModelsPresetAppliers, type ModelApi, type OpenClawConfig, } from "openclaw/plugin-sdk/provider-onboard"; @@ -37,23 +37,25 @@ function resolveQianfanPreset(cfg: OpenClawConfig): { }; } -function applyQianfanPreset(cfg: OpenClawConfig, primaryModelRef?: string): OpenClawConfig { - const preset = resolveQianfanPreset(cfg); - return applyProviderConfigWithDefaultModelsPreset(cfg, { - providerId: "qianfan", - api: preset.api, - baseUrl: preset.baseUrl, - defaultModels: preset.defaultModels, - defaultModelId: QIANFAN_DEFAULT_MODEL_ID, - aliases: [{ modelRef: QIANFAN_DEFAULT_MODEL_REF, alias: "QIANFAN" }], - primaryModelRef, - }); -} +const qianfanPresetAppliers = createDefaultModelsPresetAppliers({ + primaryModelRef: QIANFAN_DEFAULT_MODEL_REF, + resolveParams: (cfg: OpenClawConfig) => { + const preset = resolveQianfanPreset(cfg); + return { + providerId: "qianfan", + api: preset.api, + baseUrl: preset.baseUrl, + defaultModels: preset.defaultModels, + defaultModelId: QIANFAN_DEFAULT_MODEL_ID, + aliases: [{ modelRef: QIANFAN_DEFAULT_MODEL_REF, alias: "QIANFAN" }], + }; + }, +}); export function applyQianfanProviderConfig(cfg: OpenClawConfig): OpenClawConfig { - return applyQianfanPreset(cfg); + return qianfanPresetAppliers.applyProviderConfig(cfg); } export function applyQianfanConfig(cfg: OpenClawConfig): OpenClawConfig { - return applyQianfanPreset(cfg, QIANFAN_DEFAULT_MODEL_REF); + return qianfanPresetAppliers.applyConfig(cfg); } diff --git a/extensions/synthetic/onboard.ts b/extensions/synthetic/onboard.ts index feae2c312d9..87a1d12b7de 100644 --- a/extensions/synthetic/onboard.ts +++ b/extensions/synthetic/onboard.ts @@ -5,27 +5,27 @@ import { SYNTHETIC_MODEL_CATALOG, } from "openclaw/plugin-sdk/provider-models"; import { - applyProviderConfigWithModelCatalogPreset, + createModelCatalogPresetAppliers, type OpenClawConfig, } from "openclaw/plugin-sdk/provider-onboard"; export { SYNTHETIC_DEFAULT_MODEL_REF }; -function applySyntheticPreset(cfg: OpenClawConfig, primaryModelRef?: string): OpenClawConfig { - return applyProviderConfigWithModelCatalogPreset(cfg, { +const syntheticPresetAppliers = createModelCatalogPresetAppliers({ + primaryModelRef: SYNTHETIC_DEFAULT_MODEL_REF, + resolveParams: (_cfg: OpenClawConfig) => ({ providerId: "synthetic", api: "anthropic-messages", baseUrl: SYNTHETIC_BASE_URL, catalogModels: SYNTHETIC_MODEL_CATALOG.map(buildSyntheticModelDefinition), aliases: [{ modelRef: SYNTHETIC_DEFAULT_MODEL_REF, alias: "MiniMax M2.5" }], - primaryModelRef, - }); -} + }), +}); export function applySyntheticProviderConfig(cfg: OpenClawConfig): OpenClawConfig { - return applySyntheticPreset(cfg); + return syntheticPresetAppliers.applyProviderConfig(cfg); } export function applySyntheticConfig(cfg: OpenClawConfig): OpenClawConfig { - return applySyntheticPreset(cfg, SYNTHETIC_DEFAULT_MODEL_REF); + return syntheticPresetAppliers.applyConfig(cfg); } diff --git a/extensions/together/onboard.ts b/extensions/together/onboard.ts index f23b5b5dbda..2eee2c7f6ef 100644 --- a/extensions/together/onboard.ts +++ b/extensions/together/onboard.ts @@ -4,27 +4,27 @@ import { TOGETHER_MODEL_CATALOG, } from "openclaw/plugin-sdk/provider-models"; import { - applyProviderConfigWithModelCatalogPreset, + createModelCatalogPresetAppliers, type OpenClawConfig, } from "openclaw/plugin-sdk/provider-onboard"; export const TOGETHER_DEFAULT_MODEL_REF = "together/moonshotai/Kimi-K2.5"; -function applyTogetherPreset(cfg: OpenClawConfig, primaryModelRef?: string): OpenClawConfig { - return applyProviderConfigWithModelCatalogPreset(cfg, { +const togetherPresetAppliers = createModelCatalogPresetAppliers({ + primaryModelRef: TOGETHER_DEFAULT_MODEL_REF, + resolveParams: (_cfg: OpenClawConfig) => ({ providerId: "together", api: "openai-completions", baseUrl: TOGETHER_BASE_URL, catalogModels: TOGETHER_MODEL_CATALOG.map(buildTogetherModelDefinition), aliases: [{ modelRef: TOGETHER_DEFAULT_MODEL_REF, alias: "Together AI" }], - primaryModelRef, - }); -} + }), +}); export function applyTogetherProviderConfig(cfg: OpenClawConfig): OpenClawConfig { - return applyTogetherPreset(cfg); + return togetherPresetAppliers.applyProviderConfig(cfg); } export function applyTogetherConfig(cfg: OpenClawConfig): OpenClawConfig { - return applyTogetherPreset(cfg, TOGETHER_DEFAULT_MODEL_REF); + return togetherPresetAppliers.applyConfig(cfg); } diff --git a/extensions/venice/onboard.ts b/extensions/venice/onboard.ts index 5d3787bb171..b05ef68ce7c 100644 --- a/extensions/venice/onboard.ts +++ b/extensions/venice/onboard.ts @@ -5,27 +5,27 @@ import { VENICE_MODEL_CATALOG, } from "openclaw/plugin-sdk/provider-models"; import { - applyProviderConfigWithModelCatalogPreset, + createModelCatalogPresetAppliers, type OpenClawConfig, } from "openclaw/plugin-sdk/provider-onboard"; export { VENICE_DEFAULT_MODEL_REF }; -function applyVenicePreset(cfg: OpenClawConfig, primaryModelRef?: string): OpenClawConfig { - return applyProviderConfigWithModelCatalogPreset(cfg, { +const venicePresetAppliers = createModelCatalogPresetAppliers({ + primaryModelRef: VENICE_DEFAULT_MODEL_REF, + resolveParams: (_cfg: OpenClawConfig) => ({ providerId: "venice", api: "openai-completions", baseUrl: VENICE_BASE_URL, catalogModels: VENICE_MODEL_CATALOG.map(buildVeniceModelDefinition), aliases: [{ modelRef: VENICE_DEFAULT_MODEL_REF, alias: "Kimi K2.5" }], - primaryModelRef, - }); -} + }), +}); export function applyVeniceProviderConfig(cfg: OpenClawConfig): OpenClawConfig { - return applyVenicePreset(cfg); + return venicePresetAppliers.applyProviderConfig(cfg); } export function applyVeniceConfig(cfg: OpenClawConfig): OpenClawConfig { - return applyVenicePreset(cfg, VENICE_DEFAULT_MODEL_REF); + return venicePresetAppliers.applyConfig(cfg); } diff --git a/extensions/xai/onboard.ts b/extensions/xai/onboard.ts index d137631d2cf..32b7e5b5f75 100644 --- a/extensions/xai/onboard.ts +++ b/extensions/xai/onboard.ts @@ -1,5 +1,5 @@ import { - applyProviderConfigWithDefaultModelsPreset, + createDefaultModelsPresetAppliers, type OpenClawConfig, } from "openclaw/plugin-sdk/provider-onboard"; import { XAI_BASE_URL, XAI_DEFAULT_MODEL_ID } from "./model-definitions.js"; @@ -7,30 +7,28 @@ import { buildXaiCatalogModels } from "./model-definitions.js"; export const XAI_DEFAULT_MODEL_REF = `xai/${XAI_DEFAULT_MODEL_ID}`; -function applyXaiProviderConfigWithApi( - cfg: OpenClawConfig, - api: "openai-completions" | "openai-responses", - primaryModelRef?: string, -): OpenClawConfig { - return applyProviderConfigWithDefaultModelsPreset(cfg, { +const xaiPresetAppliers = createDefaultModelsPresetAppliers< + ["openai-completions" | "openai-responses"] +>({ + primaryModelRef: XAI_DEFAULT_MODEL_REF, + resolveParams: (_cfg: OpenClawConfig, api) => ({ providerId: "xai", api, baseUrl: XAI_BASE_URL, defaultModels: buildXaiCatalogModels(), defaultModelId: XAI_DEFAULT_MODEL_ID, aliases: [{ modelRef: XAI_DEFAULT_MODEL_REF, alias: "Grok" }], - primaryModelRef, - }); -} + }), +}); export function applyXaiProviderConfig(cfg: OpenClawConfig): OpenClawConfig { - return applyXaiProviderConfigWithApi(cfg, "openai-completions"); + return xaiPresetAppliers.applyProviderConfig(cfg, "openai-completions"); } export function applyXaiResponsesApiConfig(cfg: OpenClawConfig): OpenClawConfig { - return applyXaiProviderConfigWithApi(cfg, "openai-responses"); + return xaiPresetAppliers.applyProviderConfig(cfg, "openai-responses"); } export function applyXaiConfig(cfg: OpenClawConfig): OpenClawConfig { - return applyXaiProviderConfigWithApi(cfg, "openai-completions", XAI_DEFAULT_MODEL_REF); + return xaiPresetAppliers.applyConfig(cfg, "openai-completions"); } diff --git a/extensions/xiaomi/onboard.ts b/extensions/xiaomi/onboard.ts index 80d0ad1cd16..1072b9f46f2 100644 --- a/extensions/xiaomi/onboard.ts +++ b/extensions/xiaomi/onboard.ts @@ -1,30 +1,30 @@ import { - applyAgentDefaultModelPrimary, - applyProviderConfigWithDefaultModels, + createDefaultModelsPresetAppliers, type OpenClawConfig, } from "openclaw/plugin-sdk/provider-onboard"; import { buildXiaomiProvider, XIAOMI_DEFAULT_MODEL_ID } from "./provider-catalog.js"; export const XIAOMI_DEFAULT_MODEL_REF = `xiaomi/${XIAOMI_DEFAULT_MODEL_ID}`; +const xiaomiPresetAppliers = createDefaultModelsPresetAppliers({ + primaryModelRef: XIAOMI_DEFAULT_MODEL_REF, + resolveParams: (_cfg: OpenClawConfig) => { + const defaultProvider = buildXiaomiProvider(); + return { + providerId: "xiaomi", + api: defaultProvider.api ?? "openai-completions", + baseUrl: defaultProvider.baseUrl, + defaultModels: defaultProvider.models ?? [], + defaultModelId: XIAOMI_DEFAULT_MODEL_ID, + aliases: [{ modelRef: XIAOMI_DEFAULT_MODEL_REF, alias: "Xiaomi" }], + }; + }, +}); + export function applyXiaomiProviderConfig(cfg: OpenClawConfig): OpenClawConfig { - const models = { ...cfg.agents?.defaults?.models }; - models[XIAOMI_DEFAULT_MODEL_REF] = { - ...models[XIAOMI_DEFAULT_MODEL_REF], - alias: models[XIAOMI_DEFAULT_MODEL_REF]?.alias ?? "Xiaomi", - }; - const defaultProvider = buildXiaomiProvider(); - const resolvedApi = defaultProvider.api ?? "openai-completions"; - return applyProviderConfigWithDefaultModels(cfg, { - agentModels: models, - providerId: "xiaomi", - api: resolvedApi, - baseUrl: defaultProvider.baseUrl, - defaultModels: defaultProvider.models ?? [], - defaultModelId: XIAOMI_DEFAULT_MODEL_ID, - }); + return xiaomiPresetAppliers.applyProviderConfig(cfg); } export function applyXiaomiConfig(cfg: OpenClawConfig): OpenClawConfig { - return applyAgentDefaultModelPrimary(applyXiaomiProviderConfig(cfg), XIAOMI_DEFAULT_MODEL_REF); + return xiaomiPresetAppliers.applyConfig(cfg); } diff --git a/src/plugin-sdk/provider-onboard.ts b/src/plugin-sdk/provider-onboard.ts index 3c72205f581..78aecb99f59 100644 --- a/src/plugin-sdk/provider-onboard.ts +++ b/src/plugin-sdk/provider-onboard.ts @@ -9,6 +9,9 @@ export type { export { applyAgentDefaultModelPrimary, applyOnboardAuthAgentModelsAndProviders, + createDefaultModelPresetAppliers, + createDefaultModelsPresetAppliers, + createModelCatalogPresetAppliers, applyProviderConfigWithDefaultModelPreset, applyProviderConfigWithDefaultModelsPreset, applyProviderConfigWithDefaultModel, @@ -17,5 +20,8 @@ export { applyProviderConfigWithModelCatalog, withAgentModelAliases, } from "../plugins/provider-onboarding-config.js"; -export type { AgentModelAliasEntry } from "../plugins/provider-onboarding-config.js"; +export type { + AgentModelAliasEntry, + ProviderOnboardPresetAppliers, +} from "../plugins/provider-onboarding-config.js"; export { ensureModelAllowlistEntry } from "../plugins/provider-model-allowlist.js"; diff --git a/src/plugins/provider-onboarding-config.test.ts b/src/plugins/provider-onboarding-config.test.ts new file mode 100644 index 00000000000..1b9be9aa81d --- /dev/null +++ b/src/plugins/provider-onboarding-config.test.ts @@ -0,0 +1,106 @@ +import { describe, expect, it } from "vitest"; +import { + createDefaultModelPresetAppliers, + createDefaultModelsPresetAppliers, + createModelCatalogPresetAppliers, +} from "./provider-onboarding-config.js"; + +describe("provider onboarding preset appliers", () => { + it("creates provider and primary-model appliers for a default model preset", () => { + const appliers = createDefaultModelPresetAppliers({ + primaryModelRef: "demo/demo-default", + resolveParams: () => ({ + providerId: "demo", + api: "openai-completions" as const, + baseUrl: "https://demo.test/v1", + defaultModel: { + id: "demo-default", + name: "Demo Default", + }, + defaultModelId: "demo-default", + aliases: [{ modelRef: "demo/demo-default", alias: "Demo" }], + }), + }); + + const providerOnly = appliers.applyProviderConfig({}); + expect(providerOnly.agents?.defaults?.models).toMatchObject({ + "demo/demo-default": { + alias: "Demo", + }, + }); + expect(providerOnly.agents?.defaults?.model).toBeUndefined(); + + const withPrimary = appliers.applyConfig({}); + expect(withPrimary.agents?.defaults?.model).toEqual({ + primary: "demo/demo-default", + }); + }); + + it("passes variant args through default-models resolvers", () => { + const appliers = createDefaultModelsPresetAppliers<[string]>({ + primaryModelRef: "demo/a", + resolveParams: (_cfg, baseUrl) => ({ + providerId: "demo", + api: "openai-completions" as const, + baseUrl, + defaultModels: [ + { id: "a", name: "Model A" }, + { id: "b", name: "Model B" }, + ], + aliases: [{ modelRef: "demo/a", alias: "Demo A" }], + }), + }); + + const cfg = appliers.applyConfig({}, "https://alt.test/v1"); + expect(cfg.models?.providers?.demo).toMatchObject({ + baseUrl: "https://alt.test/v1", + models: [ + { id: "a", name: "Model A" }, + { id: "b", name: "Model B" }, + ], + }); + expect(cfg.agents?.defaults?.model).toEqual({ + primary: "demo/a", + }); + }); + + it("creates model-catalog appliers that preserve existing aliases", () => { + const appliers = createModelCatalogPresetAppliers({ + primaryModelRef: "catalog/default", + resolveParams: () => ({ + providerId: "catalog", + api: "openai-completions" as const, + baseUrl: "https://catalog.test/v1", + catalogModels: [ + { id: "default", name: "Catalog Default" }, + { id: "backup", name: "Catalog Backup" }, + ], + aliases: [ + "catalog/default", + { modelRef: "catalog/default", alias: "Catalog Default" }, + ], + }), + }); + + const cfg = appliers.applyConfig({ + agents: { + defaults: { + models: { + "catalog/default": { + alias: "Existing Alias", + }, + }, + }, + }, + }); + + expect(cfg.agents?.defaults?.models).toMatchObject({ + "catalog/default": { + alias: "Existing Alias", + }, + }); + expect(cfg.agents?.defaults?.model).toEqual({ + primary: "catalog/default", + }); + }); +}); diff --git a/src/plugins/provider-onboarding-config.ts b/src/plugins/provider-onboarding-config.ts index cd86f9e52b5..e8aec68a479 100644 --- a/src/plugins/provider-onboarding-config.ts +++ b/src/plugins/provider-onboarding-config.ts @@ -174,6 +174,59 @@ export function applyProviderConfigWithDefaultModelPreset( : next; } +export type ProviderOnboardPresetAppliers = { + applyProviderConfig: (cfg: OpenClawConfig, ...args: TArgs) => OpenClawConfig; + applyConfig: (cfg: OpenClawConfig, ...args: TArgs) => OpenClawConfig; +}; + +function createProviderPresetAppliers< + TArgs extends unknown[], + TParams extends { + primaryModelRef?: string; + }, +>(params: { + resolveParams: ( + cfg: OpenClawConfig, + ...args: TArgs + ) => Omit | null | undefined; + applyPreset: (cfg: OpenClawConfig, preset: TParams) => OpenClawConfig; + primaryModelRef: string; +}): ProviderOnboardPresetAppliers { + return { + applyProviderConfig(cfg, ...args) { + const resolved = params.resolveParams(cfg, ...args); + return resolved ? params.applyPreset(cfg, resolved as TParams) : cfg; + }, + applyConfig(cfg, ...args) { + const resolved = params.resolveParams(cfg, ...args); + if (!resolved) { + return cfg; + } + return params.applyPreset(cfg, { + ...(resolved as TParams), + primaryModelRef: params.primaryModelRef, + }); + }, + }; +} + +export function createDefaultModelPresetAppliers(params: { + resolveParams: ( + cfg: OpenClawConfig, + ...args: TArgs + ) => + | Omit[1], "primaryModelRef"> + | null + | undefined; + primaryModelRef: string; +}): ProviderOnboardPresetAppliers { + return createProviderPresetAppliers({ + resolveParams: params.resolveParams, + applyPreset: applyProviderConfigWithDefaultModelPreset, + primaryModelRef: params.primaryModelRef, + }); +} + export function applyProviderConfigWithDefaultModelsPreset( cfg: OpenClawConfig, params: { @@ -199,6 +252,23 @@ export function applyProviderConfigWithDefaultModelsPreset( : next; } +export function createDefaultModelsPresetAppliers(params: { + resolveParams: ( + cfg: OpenClawConfig, + ...args: TArgs + ) => + | Omit[1], "primaryModelRef"> + | null + | undefined; + primaryModelRef: string; +}): ProviderOnboardPresetAppliers { + return createProviderPresetAppliers({ + resolveParams: params.resolveParams, + applyPreset: applyProviderConfigWithDefaultModelsPreset, + primaryModelRef: params.primaryModelRef, + }); +} + export function applyProviderConfigWithModelCatalog( cfg: OpenClawConfig, params: { @@ -254,6 +324,23 @@ export function applyProviderConfigWithModelCatalogPreset( : next; } +export function createModelCatalogPresetAppliers(params: { + resolveParams: ( + cfg: OpenClawConfig, + ...args: TArgs + ) => + | Omit[1], "primaryModelRef"> + | null + | undefined; + primaryModelRef: string; +}): ProviderOnboardPresetAppliers { + return createProviderPresetAppliers({ + resolveParams: params.resolveParams, + applyPreset: applyProviderConfigWithModelCatalogPreset, + primaryModelRef: params.primaryModelRef, + }); +} + type ProviderModelMergeState = { providers: Record; existingProvider?: ModelProviderConfig;