refactor: dedupe provider reader helpers

This commit is contained in:
Peter Steinberger
2026-04-07 06:34:23 +01:00
parent 829fe14188
commit 1dea64ab99
7 changed files with 55 additions and 39 deletions
+6 -3
View File
@@ -17,6 +17,7 @@ import { resolveOwningPluginIdsForProvider } from "../plugins/providers.js";
import type { ProviderPlugin } from "../plugins/types.js";
import type { RuntimeEnv } from "../runtime.js";
import { createLazyRuntimeSurface } from "../shared/lazy-runtime.js";
import { normalizeOptionalString } from "../shared/string-coerce.js";
import type { WizardPrompter, WizardSelectOption } from "../wizard/prompts.js";
export { applyPrimaryModel } from "../plugins/provider-model-primary.js";
@@ -221,7 +222,9 @@ async function promptManualModel(params: {
message: params.allowBlank ? "Default model (blank to keep)" : "Default model",
initialValue: params.initialValue,
placeholder: "provider/model",
validate: params.allowBlank ? undefined : (value) => (value?.trim() ? undefined : "Required"),
validate: params.allowBlank
? undefined
: (value) => (normalizeOptionalString(value) ? undefined : "Required"),
});
const model = String(modelInput ?? "").trim();
if (!model) {
@@ -410,7 +413,7 @@ export async function promptDefaultModel(
const includeManual = params.includeManual ?? true;
const includeProviderPluginSetups = params.includeProviderPluginSetups ?? false;
const ignoreAllowlist = params.ignoreAllowlist ?? false;
const preferredProvider = params.preferredProvider?.trim()
const preferredProvider = normalizeOptionalString(params.preferredProvider)
? normalizeProviderId(params.preferredProvider)
: undefined;
const configuredRaw = resolveConfiguredModelRaw(cfg);
@@ -578,7 +581,7 @@ export async function promptModelAllowlist(params: {
const existingKeys = resolveConfiguredModelKeys(cfg);
const allowedKeys = normalizeModelKeys(params.allowedKeys ?? []);
const allowedKeySet = allowedKeys.length > 0 ? new Set(allowedKeys) : null;
const preferredProvider = params.preferredProvider?.trim()
const preferredProvider = normalizeOptionalString(params.preferredProvider)
? normalizeProviderId(params.preferredProvider)
: undefined;
const resolved = resolveConfiguredModelRef({
+3 -2
View File
@@ -5,6 +5,7 @@ import {
} from "../plugins/provider-wizard.js";
import { resolvePluginProviders } from "../plugins/providers.runtime.js";
import type { ProviderPlugin } from "../plugins/types.js";
import { normalizeOptionalString } from "../shared/string-coerce.js";
import type { FlowContribution, FlowOption } from "./types.js";
import { sortFlowContributionsByLabel } from "./types.js";
@@ -56,9 +57,9 @@ function resolveProviderDocsById(params?: {
mode: "setup",
})
.filter((provider): provider is ProviderPlugin & { docsPath: string } =>
Boolean(provider.docsPath?.trim()),
Boolean(normalizeOptionalString(provider.docsPath)),
)
.map((provider) => [provider.id, provider.docsPath.trim()]),
.map((provider) => [provider.id, normalizeOptionalString(provider.docsPath)!]),
);
}
+9 -6
View File
@@ -12,6 +12,7 @@ import type { PluginWebSearchProviderEntry } from "../plugins/types.js";
import { resolvePluginWebSearchProviders } from "../plugins/web-search-providers.runtime.js";
import { sortWebSearchProviders } from "../plugins/web-search-providers.shared.js";
import type { RuntimeEnv } from "../runtime.js";
import { normalizeOptionalString } from "../shared/string-coerce.js";
import type { WizardPrompter } from "../wizard/prompts.js";
import type { FlowContribution, FlowOption } from "./types.js";
import { sortFlowContributionsByLabel } from "./types.js";
@@ -40,7 +41,7 @@ function resolveSearchProviderCredentialLabel(
if (entry.requiresCredential === false) {
return `${entry.label} setup`;
}
return entry.credentialLabel?.trim() || `${entry.label} API key`;
return normalizeOptionalString(entry.credentialLabel) || `${entry.label} API key`;
}
export function listSearchProviderOptions(
@@ -107,7 +108,7 @@ function resolveSearchProviderEntry(
}
export function hasKeyInEnv(entry: Pick<PluginWebSearchProviderEntry, "envVars">): boolean {
return entry.envVars.some((k) => Boolean(process.env[k]?.trim()));
return entry.envVars.some((k) => Boolean(normalizeOptionalString(process.env[k])));
}
function providerNeedsCredential(
@@ -154,13 +155,15 @@ function buildSearchEnvRef(config: OpenClawConfig, provider: SearchProvider): Se
resolveSearchProviderEntry(config, provider) ??
listSearchProviderOptions(config).find((candidate) => candidate.id === provider) ??
listSearchProviderOptions().find((candidate) => candidate.id === provider);
const envVar = entry?.envVars.find((k) => Boolean(process.env[k]?.trim())) ?? entry?.envVars[0];
if (!envVar) {
const resolvedEnvVar =
entry?.envVars.find((k) => Boolean(normalizeOptionalString(process.env[k]))) ??
entry?.envVars[0];
if (!resolvedEnvVar) {
throw new Error(
`No env var mapping for search provider "${provider}" at ${entry?.credentialPath ?? "unknown path"} in secret-input-mode=ref.`,
);
}
return { source: "env", provider: DEFAULT_SECRET_PROVIDER_ALIAS, id: envVar };
return { source: "env", provider: DEFAULT_SECRET_PROVIDER_ALIAS, id: resolvedEnvVar };
}
function resolveSearchSecretInput(
@@ -478,7 +481,7 @@ export async function runSearchSetupFlow(
placeholder: keyConfigured ? "Leave blank to keep current" : entry.placeholder,
});
const key = keyInput?.trim() ?? "";
const key = normalizeOptionalString(keyInput) ?? "";
if (key) {
const secretInput = resolveSearchSecretInput(config, choice, key, opts?.secretInputMode);
return await finalizeSearchProviderSetup({
+5 -4
View File
@@ -9,6 +9,7 @@ import {
isValidFileSecretRefId,
resolveDefaultSecretProviderAlias,
} from "../secrets/ref-contract.js";
import { normalizeOptionalString } from "../shared/string-coerce.js";
import type { WizardPrompter } from "../wizard/prompts.js";
let secretResolvePromise: Promise<typeof import("../secrets/resolve.js")> | undefined;
@@ -40,7 +41,7 @@ export function extractEnvVarFromSourceLabel(source: string): string | undefined
function resolveDefaultProviderEnvVar(provider: string): string | undefined {
const envVars = getProviderEnvVars(provider);
return envVars?.find((candidate) => candidate.trim().length > 0);
return envVars?.find((candidate) => normalizeOptionalString(candidate) !== undefined);
}
function resolveDefaultFilePointerId(provider: string): string {
@@ -60,7 +61,7 @@ export function resolveRefFallbackInput(params: {
);
}
const env = params.env ?? process.env;
const value = env[fallbackEnvVar]?.trim();
const value = normalizeOptionalString(env[fallbackEnvVar]);
if (!value) {
throw new Error(
`Environment variable "${fallbackEnvVar}" is required for --secret-input-mode ref in non-interactive setup.`,
@@ -99,7 +100,7 @@ async function promptEnvSecretRefForSetup(params: {
'Use an env var name like "OPENAI_API_KEY" (uppercase letters, numbers, underscores).'
);
}
if (!env[candidate]?.trim()) {
if (!normalizeOptionalString(env[candidate])) {
return (
params.copy?.envVarMissingError?.(candidate) ??
`Environment variable "${candidate}" is missing or empty in this session.`
@@ -116,7 +117,7 @@ async function promptEnvSecretRefForSetup(params: {
`No valid environment variable name provided for provider "${params.provider}".`,
);
}
const resolvedValue = env[envVar]?.trim();
const resolvedValue = normalizeOptionalString(env[envVar]);
if (!resolvedValue) {
throw new Error(`Environment variable "${envVar}" is missing or empty in this session.`);
}
+23 -17
View File
@@ -1,6 +1,7 @@
import { DEFAULT_PROVIDER } from "../agents/defaults.js";
import { normalizeProviderId } from "../agents/model-selection.js";
import type { OpenClawConfig } from "../config/config.js";
import { normalizeOptionalString } from "../shared/string-coerce.js";
import type { WizardPrompter } from "../wizard/prompts.js";
import { resolvePluginProviders } from "./providers.runtime.js";
import type {
@@ -31,18 +32,18 @@ export type ProviderModelPickerEntry = {
};
function normalizeChoiceId(choiceId: string): string {
return choiceId.trim();
return normalizeOptionalString(choiceId) ?? "";
}
function resolveWizardSetupChoiceId(
provider: ProviderPlugin,
wizard: ProviderPluginWizardSetup,
): string {
const explicit = wizard.choiceId?.trim();
const explicit = normalizeOptionalString(wizard.choiceId);
if (explicit) {
return explicit;
}
const explicitMethodId = wizard.methodId?.trim();
const explicitMethodId = normalizeOptionalString(wizard.methodId);
if (explicitMethodId) {
return buildProviderPluginMethodChoice(provider.id, explicitMethodId);
}
@@ -56,11 +57,13 @@ function resolveMethodById(
provider: ProviderPlugin,
methodId?: string,
): ProviderAuthMethod | undefined {
const normalizedMethodId = methodId?.trim().toLowerCase();
const normalizedMethodId = normalizeOptionalString(methodId)?.toLowerCase();
if (!normalizedMethodId) {
return provider.auth[0];
}
return provider.auth.find((method) => method.id.trim().toLowerCase() === normalizedMethodId);
return provider.auth.find(
(method) => normalizeOptionalString(method.id)?.toLowerCase() === normalizedMethodId,
);
}
function listMethodWizardSetups(provider: ProviderPlugin): Array<{
@@ -80,16 +83,16 @@ function buildSetupOptionForMethod(params: {
method: ProviderAuthMethod;
value: string;
}): ProviderWizardOption {
const normalizedGroupId = params.wizard.groupId?.trim() || params.provider.id;
const normalizedGroupId = normalizeOptionalString(params.wizard.groupId) || params.provider.id;
return {
value: normalizeChoiceId(params.value),
label:
params.wizard.choiceLabel?.trim() ||
normalizeOptionalString(params.wizard.choiceLabel) ||
(params.provider.auth.length === 1 ? params.provider.label : params.method.label),
hint: params.wizard.choiceHint?.trim() || params.method.hint,
hint: normalizeOptionalString(params.wizard.choiceHint) || params.method.hint,
groupId: normalizedGroupId,
groupLabel: params.wizard.groupLabel?.trim() || params.provider.label,
groupHint: params.wizard.groupHint?.trim(),
groupLabel: normalizeOptionalString(params.wizard.groupLabel) || params.provider.label,
groupHint: normalizeOptionalString(params.wizard.groupHint),
...(params.wizard.onboardingScopes ? { onboardingScopes: params.wizard.onboardingScopes } : {}),
...(typeof params.wizard.assistantPriority === "number" &&
Number.isFinite(params.wizard.assistantPriority)
@@ -102,7 +105,7 @@ function buildSetupOptionForMethod(params: {
}
export function buildProviderPluginMethodChoice(providerId: string, methodId: string): string {
return `${PROVIDER_PLUGIN_CHOICE_PREFIX}${providerId.trim()}:${methodId.trim()}`;
return `${PROVIDER_PLUGIN_CHOICE_PREFIX}${normalizeChoiceId(providerId)}:${normalizeChoiceId(methodId)}`;
}
function resolveProviderWizardProviders(params: {
@@ -134,7 +137,9 @@ export function resolveProviderWizardOptions(params: {
provider,
wizard,
method,
value: wizard.choiceId?.trim() || buildProviderPluginMethodChoice(provider.id, method.id),
value:
normalizeOptionalString(wizard.choiceId) ||
buildProviderPluginMethodChoice(provider.id, method.id),
}),
);
}
@@ -177,7 +182,7 @@ function resolveModelPickerChoiceValue(
provider: ProviderPlugin,
modelPicker: ProviderPluginWizardModelPicker,
): string {
const explicitMethodId = modelPicker.methodId?.trim();
const explicitMethodId = normalizeOptionalString(modelPicker.methodId);
if (explicitMethodId) {
return buildProviderPluginMethodChoice(provider.id, explicitMethodId);
}
@@ -202,8 +207,8 @@ export function resolveProviderModelPickerEntries(params: {
}
entries.push({
value: resolveModelPickerChoiceValue(provider, modelPicker),
label: modelPicker.label?.trim() || `${provider.label} (custom)`,
hint: modelPicker.hint?.trim(),
label: normalizeOptionalString(modelPicker.label) || `${provider.label} (custom)`,
hint: normalizeOptionalString(modelPicker.hint),
});
}
@@ -218,7 +223,7 @@ export function resolveProviderPluginChoice(params: {
method: ProviderAuthMethod;
wizard?: ProviderPluginWizardSetup;
} | null {
const choice = params.choice.trim();
const choice = normalizeChoiceId(params.choice);
if (!choice) {
return null;
}
@@ -241,7 +246,8 @@ export function resolveProviderPluginChoice(params: {
for (const provider of params.providers) {
for (const { method, wizard } of listMethodWizardSetups(provider)) {
const choiceId =
wizard.choiceId?.trim() || buildProviderPluginMethodChoice(provider.id, method.id);
normalizeOptionalString(wizard.choiceId) ||
buildProviderPluginMethodChoice(provider.id, method.id);
if (normalizeChoiceId(choiceId) === choice) {
return { provider, method, wizard };
}
+2 -1
View File
@@ -1,6 +1,7 @@
import type { OpenClawConfig } from "../config/config.js";
import { resolveProviderReasoningOutputModeWithPlugin } from "../plugins/provider-runtime.js";
import type { ProviderRuntimeModel } from "../plugins/types.js";
import { normalizeOptionalString } from "../shared/string-coerce.js";
const BUILTIN_REASONING_OUTPUT_MODES = {
"google-generative-ai": "tagged",
@@ -19,7 +20,7 @@ export function resolveReasoningOutputMode(params: {
modelApi?: string | null;
model?: ProviderRuntimeModel;
}): "native" | "tagged" {
const provider = params.provider?.trim();
const provider = normalizeOptionalString(params.provider);
if (!provider) {
return "native";
}
+7 -6
View File
@@ -6,6 +6,7 @@ import type { NormalizedUsage } from "../agents/usage.js";
import type { OpenClawConfig } from "../config/config.js";
import type { ModelProviderConfig } from "../config/types.models.js";
import { getCachedGatewayModelPricing } from "../gateway/model-pricing-cache.js";
import { normalizeOptionalString } from "../shared/string-coerce.js";
export type ModelCostConfig = {
input: number;
@@ -69,8 +70,8 @@ function toResolvedModelKey(params: {
model?: string;
allowPluginNormalization?: boolean;
}): string | null {
const provider = params.provider?.trim();
const model = params.model?.trim();
const provider = normalizeOptionalString(params.provider);
const model = normalizeOptionalString(params.model);
if (!provider || !model) {
return null;
}
@@ -81,8 +82,8 @@ function toResolvedModelKey(params: {
}
function toDirectModelKey(params: { provider?: string; model?: string }): string | null {
const provider = normalizeProviderId(params.provider?.trim() ?? "");
const model = params.model?.trim();
const provider = normalizeProviderId(normalizeOptionalString(params.provider) ?? "");
const model = normalizeOptionalString(params.model);
if (!provider || !model) {
return null;
}
@@ -90,8 +91,8 @@ function toDirectModelKey(params: { provider?: string; model?: string }): string
}
function shouldUseNormalizedCostLookup(params: { provider?: string; model?: string }): boolean {
const provider = normalizeProviderId(params.provider?.trim() ?? "");
const model = params.model?.trim() ?? "";
const provider = normalizeProviderId(normalizeOptionalString(params.provider) ?? "");
const model = normalizeOptionalString(params.model) ?? "";
if (!provider || !model) {
return false;
}