Files
openclaw/src/plugins/provider-onboarding-config.ts
2026-03-23 01:56:00 +00:00

414 lines
12 KiB
TypeScript

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";
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((v) => String(v)) : undefined;
}
export type AgentModelAliasEntry =
| string
| {
modelRef: string;
alias?: string;
};
function normalizeAgentModelAliasEntry(entry: AgentModelAliasEntry): {
modelRef: string;
alias?: string;
} {
if (typeof entry === "string") {
return { modelRef: entry };
}
return entry;
}
export function withAgentModelAliases(
existing: Record<string, AgentModelEntryConfig> | undefined,
aliases: readonly AgentModelAliasEntry[],
): Record<string, AgentModelEntryConfig> {
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<string, AgentModelEntryConfig>;
providers: Record<string, ModelProviderConfig>;
},
): 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<string, AgentModelEntryConfig>;
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<string, AgentModelEntryConfig>;
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 type ProviderOnboardPresetAppliers<TArgs extends unknown[]> = {
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<TParams, "primaryModelRef"> | null | undefined;
applyPreset: (cfg: OpenClawConfig, preset: TParams) => OpenClawConfig;
primaryModelRef: string;
}): ProviderOnboardPresetAppliers<TArgs> {
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<TArgs extends unknown[]>(params: {
resolveParams: (
cfg: OpenClawConfig,
...args: TArgs
) =>
| Omit<Parameters<typeof applyProviderConfigWithDefaultModelPreset>[1], "primaryModelRef">
| null
| undefined;
primaryModelRef: string;
}): ProviderOnboardPresetAppliers<TArgs> {
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<TArgs extends unknown[]>(params: {
resolveParams: (
cfg: OpenClawConfig,
...args: TArgs
) =>
| Omit<Parameters<typeof applyProviderConfigWithDefaultModelsPreset>[1], "primaryModelRef">
| null
| undefined;
primaryModelRef: string;
}): ProviderOnboardPresetAppliers<TArgs> {
return createProviderPresetAppliers({
resolveParams: params.resolveParams,
applyPreset: applyProviderConfigWithDefaultModelsPreset,
primaryModelRef: params.primaryModelRef,
});
}
export function applyProviderConfigWithModelCatalog(
cfg: OpenClawConfig,
params: {
agentModels: Record<string, AgentModelEntryConfig>;
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<TArgs extends unknown[]>(params: {
resolveParams: (
cfg: OpenClawConfig,
...args: TArgs
) =>
| Omit<Parameters<typeof applyProviderConfigWithModelCatalogPreset>[1], "primaryModelRef">
| null
| undefined;
primaryModelRef: string;
}): ProviderOnboardPresetAppliers<TArgs> {
return createProviderPresetAppliers({
resolveParams: params.resolveParams,
applyPreset: applyProviderConfigWithModelCatalogPreset,
primaryModelRef: params.primaryModelRef,
});
}
type ProviderModelMergeState = {
providers: Record<string, ModelProviderConfig>;
existingProvider?: ModelProviderConfig;
existingModels: ModelDefinitionConfig[];
};
function resolveProviderModelMergeState(
cfg: OpenClawConfig,
providerId: string,
): ProviderModelMergeState {
const providers = { ...cfg.models?.providers } as Record<string, ModelProviderConfig>;
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 applyProviderConfigWithMergedModels(
cfg: OpenClawConfig,
params: {
agentModels: Record<string, AgentModelEntryConfig>;
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 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,
};
}