// Keep provider onboarding helpers dependency-light so bundled provider plugins // do not pull heavyweight runtime graphs at activation time. import { DEFAULT_PROVIDER } from "../agents/defaults.js"; import { resolveStaticAllowlistModelKey } from "../agents/model-ref-shared.js"; import { findNormalizedProviderKey } from "../agents/provider-id.js"; 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"; export type { OpenClawConfig, ModelApi, ModelDefinitionConfig, ModelProviderConfig }; export { resolveAgentModelFallbackValues, resolveAgentModelPrimaryValue, } from "../config/model-input.js"; export type AgentModelAliasEntry = | string | { modelRef: string; alias?: string; }; const LEGACY_OPENCODE_ZEN_DEFAULT_MODELS = new Set([ "opencode/claude-opus-4-5", "opencode-zen/claude-opus-4-5", ]); export const OPENCODE_ZEN_DEFAULT_MODEL = "opencode/claude-opus-4-6"; export type ProviderOnboardPresetAppliers = { applyProviderConfig: (cfg: OpenClawConfig, ...args: TArgs) => OpenClawConfig; applyConfig: (cfg: OpenClawConfig, ...args: TArgs) => OpenClawConfig; }; 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; } function resolveCurrentPrimaryModel(model: unknown): string | undefined { if (typeof model === "string") { return model.trim() || undefined; } if ( model && typeof model === "object" && typeof (model as { primary?: unknown }).primary === "string" ) { return ((model as { primary: string }).primary || "").trim() || undefined; } return undefined; } 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 applyOpencodeZenModelDefault(cfg: OpenClawConfig): { next: OpenClawConfig; changed: boolean; } { const current = resolveCurrentPrimaryModel(cfg.agents?.defaults?.model); const normalizedCurrent = current && LEGACY_OPENCODE_ZEN_DEFAULT_MODELS.has(current) ? OPENCODE_ZEN_DEFAULT_MODEL : current; if (normalizedCurrent === OPENCODE_ZEN_DEFAULT_MODEL) { return { next: cfg, changed: false }; } return { next: applyAgentDefaultModelPrimary(cfg, OPENCODE_ZEN_DEFAULT_MODEL), changed: true, }; } 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 = resolveStaticAllowlistModelKey( 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, }, }, }; }