diff --git a/src/cli/command-secret-gateway.ts b/src/cli/command-secret-gateway.ts index d79ba70369e..4c44cda6876 100644 --- a/src/cli/command-secret-gateway.ts +++ b/src/cli/command-secret-gateway.ts @@ -18,6 +18,7 @@ import { discoverConfigSecretTargetsByIds, type DiscoveredConfigSecretTarget, } from "../secrets/target-registry.js"; +import { normalizeLowercaseStringOrEmpty } from "../shared/string-coerce.js"; import { GATEWAY_CLIENT_MODES, GATEWAY_CLIENT_NAMES } from "../utils/message-channel.js"; type ResolveCommandSecretsResult = { @@ -147,8 +148,7 @@ function classifyRuntimeWebTargetPathState(params: { if (fetch?.enabled === false) { return "inactive"; } - const configuredProvider = - typeof fetch?.provider === "string" ? fetch.provider.trim().toLowerCase() : ""; + const configuredProvider = normalizeLowercaseStringOrEmpty(fetch?.provider); if (!configuredProvider) { return "active"; } @@ -165,8 +165,7 @@ function classifyRuntimeWebTargetPathState(params: { if (search?.enabled === false) { return "inactive"; } - const configuredProvider = - typeof search?.provider === "string" ? search.provider.trim().toLowerCase() : ""; + const configuredProvider = normalizeLowercaseStringOrEmpty(search?.provider); if (!configuredProvider) { return "active"; } @@ -216,8 +215,7 @@ function describeInactiveRuntimeWebTargetPath(params: { if (fetch?.enabled === false) { return "tools.web.fetch is disabled."; } - const configuredProvider = - typeof fetch?.provider === "string" ? fetch.provider.trim().toLowerCase() : ""; + const configuredProvider = normalizeLowercaseStringOrEmpty(fetch?.provider); if (configuredProvider) { return `tools.web.fetch.provider is "${configuredProvider}".`; } @@ -227,8 +225,7 @@ function describeInactiveRuntimeWebTargetPath(params: { if (search?.enabled === false) { return "tools.web.search is disabled."; } - const configuredProvider = - typeof search?.provider === "string" ? search.provider.trim().toLowerCase() : ""; + const configuredProvider = normalizeLowercaseStringOrEmpty(search?.provider); const configuredPluginId = configuredProvider ? commandSecretGatewayDeps.resolveManifestContractOwnerPluginId({ contract: "webSearchProviders", diff --git a/src/config/plugin-auto-enable.shared.ts b/src/config/plugin-auto-enable.shared.ts index d6ef4079cd6..68c187da614 100644 --- a/src/config/plugin-auto-enable.shared.ts +++ b/src/config/plugin-auto-enable.shared.ts @@ -12,6 +12,7 @@ import { } from "../plugins/manifest-registry.js"; import { resolveOwningPluginIdsForModelRef } from "../plugins/providers.js"; import { resolvePluginSetupAutoEnableReasons } from "../plugins/setup-registry.js"; +import { normalizeOptionalLowercaseString } from "../shared/string-coerce.js"; import { isRecord } from "../utils.js"; import { isChannelConfigured } from "./channel-configured.js"; import type { OpenClawConfig } from "./config.js"; @@ -226,7 +227,7 @@ function resolvePluginIdForConfiguredWebFetchProvider( ): string | undefined { return resolveManifestContractOwnerPluginId({ contract: "webFetchProviders", - value: typeof providerId === "string" ? providerId.trim().toLowerCase() : "", + value: normalizeOptionalLowercaseString(providerId) ?? "", origin: "bundled", env, }); diff --git a/src/plugin-sdk/provider-model-shared.ts b/src/plugin-sdk/provider-model-shared.ts index e6786a09f01..e8588f4a64f 100644 --- a/src/plugin-sdk/provider-model-shared.ts +++ b/src/plugin-sdk/provider-model-shared.ts @@ -70,9 +70,13 @@ export { cloneFirstTemplateModel, matchesExactOrPrefix, } from "../plugins/provider-model-helpers.js"; +import { normalizeOptionalLowercaseString } from "../shared/string-coerce.js"; export function getModelProviderHint(modelId: string): string | null { - const trimmed = modelId.trim().toLowerCase(); + const trimmed = normalizeOptionalLowercaseString(modelId); + if (!trimmed) { + return null; + } const slashIndex = trimmed.indexOf("/"); if (slashIndex <= 0) { return null; diff --git a/src/secrets/configure.ts b/src/secrets/configure.ts index 3b9553b92c4..59d6de3c562 100644 --- a/src/secrets/configure.ts +++ b/src/secrets/configure.ts @@ -9,6 +9,7 @@ import type { OpenClawConfig } from "../config/config.js"; import type { SecretProviderConfig, SecretRef, SecretRefSource } from "../config/types.secrets.js"; import { isSafeExecutableValue } from "../infra/exec-safety.js"; import { normalizeAgentId } from "../routing/session-key.js"; +import { normalizeOptionalLowercaseString } from "../shared/string-coerce.js"; import { runSecretsApply, type SecretsApplyResult } from "./apply.js"; import { createSecretsConfigIO } from "./config-io.js"; import { @@ -233,13 +234,10 @@ function hasSourceChoice( } function resolveCandidateProviderHint(candidate: ConfigureCandidate): string | undefined { - if (typeof candidate.authProfileProvider === "string" && candidate.authProfileProvider.trim()) { - return candidate.authProfileProvider.trim().toLowerCase(); - } - if (typeof candidate.providerId === "string" && candidate.providerId.trim()) { - return candidate.providerId.trim().toLowerCase(); - } - return undefined; + return ( + normalizeOptionalLowercaseString(candidate.authProfileProvider) ?? + normalizeOptionalLowercaseString(candidate.providerId) + ); } function resolveSuggestedEnvSecretId(candidate: ConfigureCandidate): string | undefined { diff --git a/src/secrets/runtime-config-collectors-core.ts b/src/secrets/runtime-config-collectors-core.ts index ecac077e794..cd1008580c4 100644 --- a/src/secrets/runtime-config-collectors-core.ts +++ b/src/secrets/runtime-config-collectors-core.ts @@ -5,6 +5,7 @@ import { resolveEffectiveMediaEntryCapabilities, } from "../media-understanding/entry-capabilities.js"; import { buildMediaUnderstandingRegistry } from "../media-understanding/provider-registry.js"; +import { normalizeOptionalLowercaseString } from "../shared/string-coerce.js"; import { collectTtsApiKeyAssignments } from "./runtime-config-collectors-tts.js"; import { evaluateGatewayAuthSurfaceStates } from "./runtime-gateway-auth-surfaces.js"; import { @@ -564,7 +565,8 @@ function collectSandboxSshAssignments(params: { "docker"; const effectiveMode = (typeof sandbox?.mode === "string" ? sandbox.mode : undefined) ?? defaultsMode ?? "off"; - const active = effectiveBackend.trim().toLowerCase() === "ssh" && effectiveMode !== "off"; + const active = + normalizeOptionalLowercaseString(effectiveBackend) === "ssh" && effectiveMode !== "off"; for (const key of ["identityData", "certificateData", "knownHostsData"] as const) { if (ssh && Object.prototype.hasOwnProperty.call(ssh, key)) { collectSecretInputAssignment({ @@ -590,7 +592,7 @@ function collectSandboxSshAssignments(params: { } const defaultsActive = - (defaultsBackend?.trim().toLowerCase() === "ssh" && defaultsMode !== "off") || + (normalizeOptionalLowercaseString(defaultsBackend) === "ssh" && defaultsMode !== "off") || inheritedDefaultsUsage.identityData || inheritedDefaultsUsage.certificateData || inheritedDefaultsUsage.knownHostsData; diff --git a/src/secrets/runtime-web-tools.shared.ts b/src/secrets/runtime-web-tools.shared.ts index b5aad716f41..064353916fc 100644 --- a/src/secrets/runtime-web-tools.shared.ts +++ b/src/secrets/runtime-web-tools.shared.ts @@ -1,6 +1,7 @@ import type { OpenClawConfig } from "../config/config.js"; import { resolveSecretInputRef } from "../config/types.secrets.js"; import { resolveManifestContractOwnerPluginId } from "../plugins/manifest-registry.js"; +import { normalizeOptionalLowercaseString } from "../shared/string-coerce.js"; import type { ResolverContext, SecretDefaults, @@ -95,10 +96,10 @@ export function normalizeKnownProvider( value: unknown, providers: TProvider[], ): string | undefined { - if (typeof value !== "string") { + const normalized = normalizeOptionalLowercaseString(value); + if (!normalized) { return undefined; } - const normalized = value.trim().toLowerCase(); if (providers.some((provider) => provider.id === normalized)) { return normalized; } diff --git a/src/secrets/runtime-web-tools.ts b/src/secrets/runtime-web-tools.ts index f8b6720b20f..c9bda4baa18 100644 --- a/src/secrets/runtime-web-tools.ts +++ b/src/secrets/runtime-web-tools.ts @@ -17,6 +17,7 @@ import { } from "../plugins/web-provider-public-artifacts.explicit.js"; import { sortWebSearchProvidersForAutoDetect } from "../plugins/web-search-providers.shared.js"; import { createLazyRuntimeSurface } from "../shared/lazy-runtime.js"; +import { normalizeLowercaseStringOrEmpty } from "../shared/string-coerce.js"; import { normalizeSecretInput } from "../utils/normalize-secret-input.js"; import { secretRefKey } from "./ref-contract.js"; import { resolveSecretRefValues } from "./resolve.js"; @@ -488,8 +489,7 @@ export async function resolveRuntimeWebTools(params: { diagnostics, }; } - const rawProvider = - typeof search?.provider === "string" ? search.provider.trim().toLowerCase() : ""; + const rawProvider = normalizeLowercaseStringOrEmpty(search?.provider); const searchMetadata: RuntimeWebSearchMetadata = { providerSource: "none", diagnostics: [], @@ -605,8 +605,7 @@ export async function resolveRuntimeWebTools(params: { }); } - const rawFetchProvider = - typeof fetch?.provider === "string" ? fetch.provider.trim().toLowerCase() : ""; + const rawFetchProvider = normalizeLowercaseStringOrEmpty(fetch?.provider); const fetchMetadata: RuntimeWebFetchMetadata = { providerSource: "none", diagnostics: [], diff --git a/src/web-fetch/runtime.ts b/src/web-fetch/runtime.ts index 49457fbb481..3c400fa8f1b 100644 --- a/src/web-fetch/runtime.ts +++ b/src/web-fetch/runtime.ts @@ -8,6 +8,7 @@ import { resolvePluginWebFetchProviders } from "../plugins/web-fetch-providers.r import { sortWebFetchProvidersForAutoDetect } from "../plugins/web-fetch-providers.shared.js"; import { getActiveRuntimeWebToolsMetadata } from "../secrets/runtime-web-tools-state.js"; import type { RuntimeWebFetchMetadata } from "../secrets/runtime-web-tools.types.js"; +import { normalizeLowercaseStringOrEmpty } from "../shared/string-coerce.js"; import { hasWebProviderEntryCredential, providerRequiresCredential, @@ -93,8 +94,8 @@ export function resolveWebFetchProviderId(params: { }), ); const raw = - params.fetch && "provider" in params.fetch && typeof params.fetch.provider === "string" - ? params.fetch.provider.trim().toLowerCase() + params.fetch && "provider" in params.fetch + ? normalizeLowercaseStringOrEmpty(params.fetch.provider) : ""; if (raw) { diff --git a/src/web-search/runtime.ts b/src/web-search/runtime.ts index f73cf56aa25..9bfc289ff42 100644 --- a/src/web-search/runtime.ts +++ b/src/web-search/runtime.ts @@ -9,6 +9,7 @@ import { resolveRuntimeWebSearchProviders } from "../plugins/web-search-provider import { sortWebSearchProvidersForAutoDetect } from "../plugins/web-search-providers.shared.js"; import { getActiveRuntimeWebToolsMetadata } from "../secrets/runtime-web-tools-state.js"; import type { RuntimeWebSearchMetadata } from "../secrets/runtime-web-tools.types.js"; +import { normalizeLowercaseStringOrEmpty } from "../shared/string-coerce.js"; import { hasWebProviderEntryCredential, providerRequiresCredential, @@ -110,8 +111,8 @@ export function resolveWebSearchProviderId(params: { }), ); const raw = - params.search && "provider" in params.search && typeof params.search.provider === "string" - ? params.search.provider.trim().toLowerCase() + params.search && "provider" in params.search + ? normalizeLowercaseStringOrEmpty(params.search.provider) : ""; if (raw) {