refactor: split models-config provider normalization helper

This commit is contained in:
Peter Steinberger
2026-03-28 01:00:37 +00:00
parent 8ab8f2c461
commit 2d6f4bf6c6
2 changed files with 135 additions and 134 deletions

View File

@@ -0,0 +1,133 @@
import type { OpenClawConfig } from "../config/config.js";
import { ensureAuthProfileStore } from "./auth-profiles.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;
const authStore = ensureAuthProfileStore(params.agentDir, {
allowKeychainPrompt: false,
});
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 profileApiKey = resolveApiKeyFromProfiles({
provider: normalizedKey,
store: authStore,
env,
});
const providerWithConfiguredApiKey = normalizeConfiguredProviderApiKey({
providerKey: normalizedKey,
provider: normalizedProvider,
secretDefaults: params.secretDefaults,
profileApiKey,
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 providerWithApiKey = resolveMissingProviderApiKey({
providerKey: normalizedKey,
provider: normalizedProvider,
env,
profileApiKey,
secretRefManagedProviders: params.secretRefManagedProviders,
providerApiKeyResolver: resolveProviderConfigApiKeyResolver(normalizedKey),
});
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,
});
}

View File

@@ -1,20 +1,6 @@
import type { OpenClawConfig } from "../config/config.js";
import { ensureAuthProfileStore } from "./auth-profiles.js";
export * from "./models-config.providers.static.js";
export { resolveImplicitProviders } from "./models-config.providers.implicit.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";
export { normalizeProviders } from "./models-config.providers.normalize.js";
export type {
ProfileApiKeyResolution,
ProviderApiKeyResolver,
@@ -22,126 +8,8 @@ export type {
ProviderConfig,
SecretDefaults,
} from "./models-config.providers.secrets.js";
export { enforceSourceManagedProviderSecrets };
export { applyNativeStreamingUsageCompat } from "./models-config.providers.policy.js";
export { enforceSourceManagedProviderSecrets } from "./models-config.providers.source-managed.js";
export { resolveOllamaApiBase } from "../plugin-sdk/ollama-surface.js";
export { normalizeGoogleModelId } from "../plugin-sdk/google.js";
export { normalizeXaiModelId } from "../plugin-sdk/xai.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;
const authStore = ensureAuthProfileStore(params.agentDir, {
allowKeychainPrompt: false,
});
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 profileApiKey = resolveApiKeyFromProfiles({
provider: normalizedKey,
store: authStore,
env,
});
const providerWithConfiguredApiKey = normalizeConfiguredProviderApiKey({
providerKey: normalizedKey,
provider: normalizedProvider,
secretDefaults: params.secretDefaults,
profileApiKey,
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 providerWithApiKey = resolveMissingProviderApiKey({
providerKey: normalizedKey,
provider: normalizedProvider,
env,
profileApiKey,
secretRefManagedProviders: params.secretRefManagedProviders,
providerApiKeyResolver: resolveProviderConfigApiKeyResolver(normalizedKey),
});
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,
});
}