Files
openclaw/src/plugin-sdk/provider-selection-runtime.ts
2026-04-24 01:50:43 +01:00

209 lines
5.8 KiB
TypeScript

import { normalizeOptionalString } from "../shared/string-coerce.js";
export type AutoSelectableProvider = {
id: string;
autoSelectOrder?: number;
};
export type ProviderSelection<TProvider> = {
configuredProviderId?: string;
missingConfiguredProvider: boolean;
provider: TProvider | undefined;
};
export type ResolvedConfiguredProvider<TProvider, TConfig> =
| {
ok: true;
configuredProviderId?: string;
provider: TProvider;
providerConfig: TConfig;
}
| {
ok: false;
code: "missing-configured-provider" | "no-registered-provider" | "provider-not-configured";
configuredProviderId?: string;
provider?: TProvider;
};
export function selectConfiguredOrAutoProvider<TProvider extends AutoSelectableProvider>(params: {
configuredProviderId?: string;
getConfiguredProvider: (providerId: string | undefined) => TProvider | undefined;
listProviders: () => Iterable<TProvider>;
}): ProviderSelection<TProvider> {
const configuredProviderId = normalizeOptionalString(params.configuredProviderId);
const configuredProvider = configuredProviderId
? params.getConfiguredProvider(configuredProviderId)
: undefined;
if (configuredProviderId && !configuredProvider) {
return {
configuredProviderId,
missingConfiguredProvider: true,
provider: undefined,
};
}
return {
configuredProviderId,
missingConfiguredProvider: false,
provider:
configuredProvider ?? [...params.listProviders()].toSorted(compareProviderAutoSelectOrder)[0],
};
}
export function resolveProviderRawConfig(params: {
providerId: string;
configuredProviderId?: string;
providerConfigs?: Record<string, Record<string, unknown> | undefined>;
}): Record<string, unknown> {
const canonicalProviderConfig = readProviderConfig(params.providerConfigs, params.providerId);
const selectedProviderConfig = readProviderConfig(
params.providerConfigs,
params.configuredProviderId,
);
return {
...canonicalProviderConfig,
...selectedProviderConfig,
};
}
export function resolveConfiguredCapabilityProvider<
TConfig,
TFullConfig,
TProvider extends AutoSelectableProvider,
>(params: {
configuredProviderId?: string;
providerConfigs?: Record<string, Record<string, unknown> | undefined>;
cfg: TFullConfig | undefined;
cfgForResolve: TFullConfig;
getConfiguredProvider: (providerId: string | undefined) => TProvider | undefined;
listProviders: () => Iterable<TProvider>;
resolveProviderConfig: (params: {
provider: TProvider;
cfg: TFullConfig;
rawConfig: Record<string, unknown>;
}) => TConfig;
isProviderConfigured: (params: {
provider: TProvider;
cfg: TFullConfig | undefined;
providerConfig: TConfig;
}) => boolean;
}): ResolvedConfiguredProvider<TProvider, TConfig> {
const configuredProviderId = normalizeOptionalString(params.configuredProviderId);
if (configuredProviderId) {
const provider = params.getConfiguredProvider(configuredProviderId);
if (!provider) {
return {
ok: false,
code: "missing-configured-provider",
configuredProviderId,
};
}
return resolveProviderCandidate({
...params,
configuredProviderId,
provider,
});
}
const providers = [...params.listProviders()].toSorted(compareProviderAutoSelectOrder);
if (providers.length === 0) {
return {
ok: false,
code: "no-registered-provider",
};
}
let firstUnconfigured: TProvider | undefined;
for (const provider of providers) {
const resolution = resolveProviderCandidate({
...params,
provider,
});
if (resolution.ok) {
return resolution;
}
firstUnconfigured ??= provider;
}
return {
ok: false,
code: "provider-not-configured",
provider: firstUnconfigured,
};
}
function compareProviderAutoSelectOrder<TProvider extends AutoSelectableProvider>(
left: TProvider,
right: TProvider,
): number {
return (
(left.autoSelectOrder ?? Number.MAX_SAFE_INTEGER) -
(right.autoSelectOrder ?? Number.MAX_SAFE_INTEGER)
);
}
function readProviderConfig(
providerConfigs: Record<string, Record<string, unknown> | undefined> | undefined,
providerId: string | undefined,
): Record<string, unknown> | undefined {
if (!providerId) {
return undefined;
}
const providerConfig = providerConfigs?.[providerId];
return providerConfig && typeof providerConfig === "object" ? providerConfig : undefined;
}
function resolveProviderCandidate<
TConfig,
TFullConfig,
TProvider extends AutoSelectableProvider,
>(params: {
configuredProviderId?: string;
providerConfigs?: Record<string, Record<string, unknown> | undefined>;
cfg: TFullConfig | undefined;
cfgForResolve: TFullConfig;
provider: TProvider;
resolveProviderConfig: (params: {
provider: TProvider;
cfg: TFullConfig;
rawConfig: Record<string, unknown>;
}) => TConfig;
isProviderConfigured: (params: {
provider: TProvider;
cfg: TFullConfig | undefined;
providerConfig: TConfig;
}) => boolean;
}): ResolvedConfiguredProvider<TProvider, TConfig> {
const rawProviderConfig = resolveProviderRawConfig({
providerId: params.provider.id,
configuredProviderId: params.configuredProviderId,
providerConfigs: params.providerConfigs,
});
const providerConfig = params.resolveProviderConfig({
provider: params.provider,
cfg: params.cfgForResolve,
rawConfig: rawProviderConfig,
});
if (
!params.isProviderConfigured({ provider: params.provider, cfg: params.cfg, providerConfig })
) {
return {
ok: false,
code: "provider-not-configured",
configuredProviderId: params.configuredProviderId,
provider: params.provider,
};
}
return {
ok: true,
configuredProviderId: params.configuredProviderId,
provider: params.provider,
providerConfig,
};
}