openclaw/src/agents/models-config.providers.nor...

148 lines
5.1 KiB
TypeScript

import type { OpenClawConfig } from "../config/config.js";
import { ensureAuthProfileStore } from "./auth-profiles/store.js";
import {
normalizeProviderSpecificConfig,
resolveProviderConfigApiKeyResolver,
} from "./models-config.providers.policy.js";
import type { ProviderConfig, SecretDefaults } from "./models-config.providers.secrets.js";
import {
normalizeConfiguredProviderApiKey,
normalizeHeaderValues,
normalizeResolvedEnvApiKey,
resolveApiKeyFromProfiles,
resolveMissingProviderApiKey,
} from "./models-config.providers.secrets.js";
import { enforceSourceManagedProviderSecrets } from "./models-config.providers.source-managed.js";
type ModelsConfig = NonNullable<OpenClawConfig["models"]>;
export function normalizeProviders(params: {
providers: ModelsConfig["providers"];
agentDir: string;
env?: NodeJS.ProcessEnv;
secretDefaults?: SecretDefaults;
sourceProviders?: ModelsConfig["providers"];
sourceSecretDefaults?: SecretDefaults;
secretRefManagedProviders?: Set<string>;
}): ModelsConfig["providers"] {
const { providers } = params;
if (!providers) {
return providers;
}
const env = params.env ?? process.env;
let authStore: ReturnType<typeof ensureAuthProfileStore> | undefined;
const resolveProfileApiKey = (providerKey: string) => {
authStore ??= ensureAuthProfileStore(params.agentDir, {
allowKeychainPrompt: false,
});
return resolveApiKeyFromProfiles({
provider: providerKey,
store: authStore,
env,
});
};
let mutated = false;
const next: Record<string, ProviderConfig> = {};
for (const [key, provider] of Object.entries(providers)) {
const normalizedKey = key.trim();
if (!normalizedKey) {
mutated = true;
continue;
}
if (normalizedKey !== key) {
mutated = true;
}
let normalizedProvider = provider;
const normalizedHeaders = normalizeHeaderValues({
headers: normalizedProvider.headers,
secretDefaults: params.secretDefaults,
});
if (normalizedHeaders.mutated) {
mutated = true;
normalizedProvider = { ...normalizedProvider, headers: normalizedHeaders.headers };
}
const providerWithConfiguredApiKey = normalizeConfiguredProviderApiKey({
providerKey: normalizedKey,
provider: normalizedProvider,
secretDefaults: params.secretDefaults,
profileApiKey: undefined,
secretRefManagedProviders: params.secretRefManagedProviders,
});
if (providerWithConfiguredApiKey !== normalizedProvider) {
mutated = true;
normalizedProvider = providerWithConfiguredApiKey;
}
// Reverse-lookup: if apiKey looks like a resolved secret value (not an env
// var name), check whether it matches the canonical env var for this provider.
// This prevents resolveConfigEnvVars()-resolved secrets from being persisted
// to models.json as plaintext. (Fixes #38757)
const providerWithResolvedEnvApiKey = normalizeResolvedEnvApiKey({
providerKey: normalizedKey,
provider: normalizedProvider,
env,
secretRefManagedProviders: params.secretRefManagedProviders,
});
if (providerWithResolvedEnvApiKey !== normalizedProvider) {
mutated = true;
normalizedProvider = providerWithResolvedEnvApiKey;
}
const needsProfileApiKey =
Array.isArray(normalizedProvider.models) &&
normalizedProvider.models.length > 0 &&
!(
(typeof normalizedProvider.apiKey === "string" && normalizedProvider.apiKey.trim()) ||
normalizedProvider.apiKey
);
const profileApiKey = needsProfileApiKey ? resolveProfileApiKey(normalizedKey) : undefined;
const providerApiKeyResolver = needsProfileApiKey
? resolveProviderConfigApiKeyResolver(normalizedKey)
: undefined;
const providerWithApiKey = resolveMissingProviderApiKey({
providerKey: normalizedKey,
provider: normalizedProvider,
env,
profileApiKey,
secretRefManagedProviders: params.secretRefManagedProviders,
providerApiKeyResolver,
});
if (providerWithApiKey !== normalizedProvider) {
mutated = true;
normalizedProvider = providerWithApiKey;
}
const providerSpecificNormalized = normalizeProviderSpecificConfig(
normalizedKey,
normalizedProvider,
);
if (providerSpecificNormalized !== normalizedProvider) {
mutated = true;
normalizedProvider = providerSpecificNormalized;
}
const existing = next[normalizedKey];
if (existing) {
// Keep deterministic behavior if users accidentally define duplicate
// provider keys that only differ by surrounding whitespace.
mutated = true;
next[normalizedKey] = {
...existing,
...normalizedProvider,
models: normalizedProvider.models ?? existing.models,
};
continue;
}
next[normalizedKey] = normalizedProvider;
}
const normalizedProviders = mutated ? next : providers;
return enforceSourceManagedProviderSecrets({
providers: normalizedProviders,
sourceProviders: params.sourceProviders,
sourceSecretDefaults: params.sourceSecretDefaults,
secretRefManagedProviders: params.secretRefManagedProviders,
});
}