// Keep provider onboarding helpers dependency-light so bundled provider plugins // do not pull heavyweight runtime graphs at activation time. import type { OpenClawConfig } from "../config/config.js"; import type { AgentModelEntryConfig } from "../config/types.agent-defaults.js"; import type { ModelApi, ModelDefinitionConfig, ModelProviderConfig, } from "../config/types.models.js"; const DEFAULT_PROVIDER = "anthropic"; type NormalizedModelRef = { provider: string; model: string; }; export type { OpenClawConfig, ModelApi, ModelDefinitionConfig, ModelProviderConfig }; export { resolveAgentModelFallbackValues, resolveAgentModelPrimaryValue, } from "../config/model-input.js"; export type AgentModelAliasEntry = | string | { modelRef: string; alias?: string; }; export type ProviderOnboardPresetAppliers = { applyProviderConfig: (cfg: OpenClawConfig, ...args: TArgs) => OpenClawConfig; applyConfig: (cfg: OpenClawConfig, ...args: TArgs) => OpenClawConfig; }; function normalizeProviderId(provider: string): string { const normalized = provider.trim().toLowerCase(); if (normalized === "z.ai" || normalized === "z-ai") { return "zai"; } if (normalized === "opencode-zen") { return "opencode"; } if (normalized === "opencode-go-auth") { return "opencode-go"; } if (normalized === "kimi" || normalized === "kimi-code" || normalized === "kimi-coding") { return "kimi"; } if (normalized === "bedrock" || normalized === "aws-bedrock") { return "amazon-bedrock"; } if (normalized === "bytedance" || normalized === "doubao") { return "volcengine"; } return normalized; } function findNormalizedProviderKey( entries: Record | undefined, provider: string, ): string | undefined { if (!entries) { return undefined; } const providerKey = normalizeProviderId(provider); return Object.keys(entries).find((key) => normalizeProviderId(key) === providerKey); } function modelKey(provider: string, model: string): string { const providerId = provider.trim(); const modelId = model.trim(); if (!providerId) { return modelId; } if (!modelId) { return providerId; } return modelId.toLowerCase().startsWith(`${providerId.toLowerCase()}/`) ? modelId : `${providerId}/${modelId}`; } function parseModelRef(raw: string, defaultProvider: string): NormalizedModelRef | null { const trimmed = raw.trim(); if (!trimmed) { return null; } const slash = trimmed.indexOf("/"); if (slash === -1) { return { provider: normalizeProviderId(defaultProvider), model: trimmed }; } const providerRaw = trimmed.slice(0, slash).trim(); const model = trimmed.slice(slash + 1).trim(); if (!providerRaw || !model) { return null; } return { provider: normalizeProviderId(providerRaw), model }; } function resolveAllowlistModelKey(raw: string, defaultProvider: string): string | null { const parsed = parseModelRef(raw, defaultProvider); if (!parsed) { return null; } return modelKey(parsed.provider, parsed.model); } function extractAgentDefaultModelFallbacks(model: unknown): string[] | undefined { if (!model || typeof model !== "object") { return undefined; } if (!("fallbacks" in model)) { return undefined; } const fallbacks = (model as { fallbacks?: unknown }).fallbacks; return Array.isArray(fallbacks) ? fallbacks.map((value) => String(value)) : undefined; } function normalizeAgentModelAliasEntry(entry: AgentModelAliasEntry): { modelRef: string; alias?: string; } { if (typeof entry === "string") { return { modelRef: entry }; } return entry; } type ProviderModelMergeState = { providers: Record; existingProvider?: ModelProviderConfig; existingModels: ModelDefinitionConfig[]; }; function resolveProviderModelMergeState( cfg: OpenClawConfig, providerId: string, ): ProviderModelMergeState { const providers = { ...cfg.models?.providers } as Record; const existingProviderKey = findNormalizedProviderKey(providers, providerId); const existingProvider = existingProviderKey !== undefined ? (providers[existingProviderKey] as ModelProviderConfig | undefined) : undefined; const existingModels: ModelDefinitionConfig[] = Array.isArray(existingProvider?.models) ? existingProvider.models : []; if (existingProviderKey && existingProviderKey !== providerId) { delete providers[existingProviderKey]; } return { providers, existingProvider, existingModels }; } function buildProviderConfig(params: { existingProvider: ModelProviderConfig | undefined; api: ModelApi; baseUrl: string; mergedModels: ModelDefinitionConfig[]; fallbackModels: ModelDefinitionConfig[]; }): ModelProviderConfig { const { apiKey: existingApiKey, ...existingProviderRest } = (params.existingProvider ?? {}) as { apiKey?: string; }; const normalizedApiKey = typeof existingApiKey === "string" ? existingApiKey.trim() : undefined; return { ...existingProviderRest, baseUrl: params.baseUrl, api: params.api, ...(normalizedApiKey ? { apiKey: normalizedApiKey } : {}), models: params.mergedModels.length > 0 ? params.mergedModels : params.fallbackModels, }; } function applyProviderConfigWithMergedModels( cfg: OpenClawConfig, params: { agentModels: Record; providerId: string; providerState: ProviderModelMergeState; api: ModelApi; baseUrl: string; mergedModels: ModelDefinitionConfig[]; fallbackModels: ModelDefinitionConfig[]; }, ): OpenClawConfig { params.providerState.providers[params.providerId] = buildProviderConfig({ existingProvider: params.providerState.existingProvider, api: params.api, baseUrl: params.baseUrl, mergedModels: params.mergedModels, fallbackModels: params.fallbackModels, }); return applyOnboardAuthAgentModelsAndProviders(cfg, { agentModels: params.agentModels, providers: params.providerState.providers, }); } 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 withAgentModelAliases( existing: Record | undefined, aliases: readonly AgentModelAliasEntry[], ): Record { const next = { ...existing }; for (const entry of aliases) { const normalized = normalizeAgentModelAliasEntry(entry); next[normalized.modelRef] = { ...next[normalized.modelRef], ...(normalized.alias ? { alias: next[normalized.modelRef]?.alias ?? normalized.alias } : {}), }; } return next; } export function applyOnboardAuthAgentModelsAndProviders( cfg: OpenClawConfig, params: { agentModels: Record; providers: Record; }, ): OpenClawConfig { return { ...cfg, agents: { ...cfg.agents, defaults: { ...cfg.agents?.defaults, models: params.agentModels, }, }, models: { mode: cfg.models?.mode ?? "merge", providers: params.providers, }, }; } export function applyAgentDefaultModelPrimary( cfg: OpenClawConfig, primary: string, ): OpenClawConfig { const existingFallbacks = extractAgentDefaultModelFallbacks(cfg.agents?.defaults?.model); return { ...cfg, agents: { ...cfg.agents, defaults: { ...cfg.agents?.defaults, model: { ...(existingFallbacks ? { fallbacks: existingFallbacks } : undefined), primary, }, }, }, }; } export function applyProviderConfigWithDefaultModels( cfg: OpenClawConfig, params: { agentModels: Record; providerId: string; api: ModelApi; baseUrl: string; defaultModels: ModelDefinitionConfig[]; defaultModelId?: string; }, ): OpenClawConfig { const providerState = resolveProviderModelMergeState(cfg, params.providerId); const defaultModels = params.defaultModels; const defaultModelId = params.defaultModelId ?? defaultModels[0]?.id; const hasDefaultModel = defaultModelId ? providerState.existingModels.some((model) => model.id === defaultModelId) : true; const mergedModels = providerState.existingModels.length > 0 ? hasDefaultModel || defaultModels.length === 0 ? providerState.existingModels : [...providerState.existingModels, ...defaultModels] : defaultModels; return applyProviderConfigWithMergedModels(cfg, { agentModels: params.agentModels, providerId: params.providerId, providerState, api: params.api, baseUrl: params.baseUrl, mergedModels, fallbackModels: defaultModels, }); } export function applyProviderConfigWithDefaultModel( cfg: OpenClawConfig, params: { agentModels: Record; providerId: string; api: ModelApi; baseUrl: string; defaultModel: ModelDefinitionConfig; defaultModelId?: string; }, ): OpenClawConfig { return applyProviderConfigWithDefaultModels(cfg, { agentModels: params.agentModels, providerId: params.providerId, api: params.api, baseUrl: params.baseUrl, defaultModels: [params.defaultModel], defaultModelId: params.defaultModelId ?? params.defaultModel.id, }); } export function applyProviderConfigWithDefaultModelPreset( cfg: OpenClawConfig, params: { providerId: string; api: ModelApi; baseUrl: string; defaultModel: ModelDefinitionConfig; defaultModelId?: string; aliases?: readonly AgentModelAliasEntry[]; primaryModelRef?: string; }, ): OpenClawConfig { const next = applyProviderConfigWithDefaultModel(cfg, { agentModels: withAgentModelAliases(cfg.agents?.defaults?.models, params.aliases ?? []), providerId: params.providerId, api: params.api, baseUrl: params.baseUrl, defaultModel: params.defaultModel, defaultModelId: params.defaultModelId, }); return params.primaryModelRef ? applyAgentDefaultModelPrimary(next, params.primaryModelRef) : next; } 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: { providerId: string; api: ModelApi; baseUrl: string; defaultModels: ModelDefinitionConfig[]; defaultModelId?: string; aliases?: readonly AgentModelAliasEntry[]; primaryModelRef?: string; }, ): OpenClawConfig { const next = applyProviderConfigWithDefaultModels(cfg, { agentModels: withAgentModelAliases(cfg.agents?.defaults?.models, params.aliases ?? []), providerId: params.providerId, api: params.api, baseUrl: params.baseUrl, defaultModels: params.defaultModels, defaultModelId: params.defaultModelId, }); return params.primaryModelRef ? applyAgentDefaultModelPrimary(next, params.primaryModelRef) : 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: { agentModels: Record; providerId: string; api: ModelApi; baseUrl: string; catalogModels: ModelDefinitionConfig[]; }, ): OpenClawConfig { const providerState = resolveProviderModelMergeState(cfg, params.providerId); const catalogModels = params.catalogModels; const mergedModels = providerState.existingModels.length > 0 ? [ ...providerState.existingModels, ...catalogModels.filter( (model) => !providerState.existingModels.some((existing) => existing.id === model.id), ), ] : catalogModels; return applyProviderConfigWithMergedModels(cfg, { agentModels: params.agentModels, providerId: params.providerId, providerState, api: params.api, baseUrl: params.baseUrl, mergedModels, fallbackModels: catalogModels, }); } export function applyProviderConfigWithModelCatalogPreset( cfg: OpenClawConfig, params: { providerId: string; api: ModelApi; baseUrl: string; catalogModels: ModelDefinitionConfig[]; aliases?: readonly AgentModelAliasEntry[]; primaryModelRef?: string; }, ): OpenClawConfig { const next = applyProviderConfigWithModelCatalog(cfg, { agentModels: withAgentModelAliases(cfg.agents?.defaults?.models, params.aliases ?? []), providerId: params.providerId, api: params.api, baseUrl: params.baseUrl, catalogModels: params.catalogModels, }); return params.primaryModelRef ? applyAgentDefaultModelPrimary(next, params.primaryModelRef) : 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, }); } export function ensureModelAllowlistEntry(params: { cfg: OpenClawConfig; modelRef: string; defaultProvider?: string; }): OpenClawConfig { const rawModelRef = params.modelRef.trim(); if (!rawModelRef) { return params.cfg; } const models = { ...params.cfg.agents?.defaults?.models }; const keySet = new Set([rawModelRef]); const canonicalKey = resolveAllowlistModelKey( rawModelRef, params.defaultProvider ?? DEFAULT_PROVIDER, ); if (canonicalKey) { keySet.add(canonicalKey); } for (const key of keySet) { models[key] = { ...models[key], }; } return { ...params.cfg, agents: { ...params.cfg.agents, defaults: { ...params.cfg.agents?.defaults, models, }, }, }; }