refactor: dedupe plugin trimmed readers

This commit is contained in:
Peter Steinberger
2026-04-08 00:27:27 +01:00
parent ae1cc2d6df
commit 4cfa4b95c3
11 changed files with 43 additions and 26 deletions

View File

@@ -43,7 +43,9 @@ function normalizePathList(value: unknown): string[] {
if (!Array.isArray(value)) {
return [];
}
return value.map((entry) => (typeof entry === "string" ? entry.trim() : "")).filter(Boolean);
return value
.map((entry) => normalizeOptionalString(entry))
.filter((entry): entry is string => Boolean(entry));
}
export function normalizeBundlePathList(value: unknown): string[] {

View File

@@ -25,6 +25,7 @@ import {
type ClawHubPackageVersion,
} from "../infra/clawhub.js";
import { formatErrorMessage } from "../infra/errors.js";
import { normalizeOptionalString } from "../shared/string-coerce.js";
import { resolveCompatibilityHostVersion } from "../version.js";
import type { InstallSafetyOverrides } from "./install-security-scan.js";
import { installPluginFromArchive, type InstallPluginResult } from "./install.js";
@@ -164,7 +165,7 @@ function resolveRequestedVersion(params: {
}
function readTrimmedString(value: unknown): string | null {
return typeof value === "string" ? value.trim() : null;
return normalizeOptionalString(value) ?? null;
}
function normalizeClawHubRelativePath(value: unknown): string | null {

View File

@@ -349,8 +349,7 @@ function loadApprovalsFromDisk(): PluginBindingApprovalsFile {
pluginId: typeof entry.pluginId === "string" ? entry.pluginId : "",
pluginName: typeof entry.pluginName === "string" ? entry.pluginName : undefined,
channel: typeof entry.channel === "string" ? normalizeChannel(entry.channel) : "",
accountId:
typeof entry.accountId === "string" ? entry.accountId.trim() || "default" : "default",
accountId: normalizeOptionalString(entry.accountId) ?? "default",
approvedAt:
typeof entry.approvedAt === "number" && Number.isFinite(entry.approvedAt)
? Math.floor(entry.approvedAt)

View File

@@ -8,6 +8,7 @@ import {
unscopedPackageName,
} from "../infra/install-safe-path.js";
import { type NpmIntegrityDrift, type NpmSpecResolution } from "../infra/install-source-utils.js";
import { normalizeOptionalString } from "../shared/string-coerce.js";
import { CONFIG_DIR, resolveUserPath } from "../utils.js";
import type { InstallSecurityScanResult } from "./install-security-scan.js";
import type { InstallSafetyOverrides } from "./install-security-scan.js";
@@ -624,7 +625,7 @@ async function installPluginFromPackageDir(
}
const extensions = extensionsResult.entries;
const pkgName = typeof manifest.name === "string" ? manifest.name.trim() : "";
const pkgName = normalizeOptionalString(manifest.name) ?? "";
const npmPluginId = pkgName || "plugin";
// Prefer the canonical `id` from openclaw.plugin.json over the npm package name.

View File

@@ -9,6 +9,7 @@ import type { PluginInstallRecord } from "../config/types.plugins.js";
import type { GatewayRequestHandler } from "../gateway/server-methods/types.js";
import { openBoundaryFileSync } from "../infra/boundary-file-read.js";
import { createSubsystemLogger } from "../logging/subsystem.js";
import { normalizeOptionalString } from "../shared/string-coerce.js";
import { resolveUserPath } from "../utils.js";
import { buildPluginApi } from "./api-builder.js";
import { inspectBundleMcpRuntimeSupport } from "./bundle-mcp.js";
@@ -855,8 +856,8 @@ function buildProvenanceIndex(params: {
matcher: createPathMatcher(),
};
const trackedPaths = [install.installPath, install.sourcePath]
.map((entry) => (typeof entry === "string" ? entry.trim() : ""))
.filter(Boolean);
.map((entry) => normalizeOptionalString(entry))
.filter((entry): entry is string => Boolean(entry));
if (trackedPaths.length === 0) {
rule.trackedWithoutPaths = true;
} else {

View File

@@ -266,12 +266,9 @@ function mergePackageChannelMetaIntoChannelConfigs(params: {
}
const existing = params.channelConfigs[channelId];
const label =
existing.label ??
(typeof params.packageChannel?.label === "string" ? params.packageChannel.label.trim() : "");
const label = existing.label ?? normalizeOptionalString(params.packageChannel?.label) ?? "";
const description =
existing.description ??
(typeof params.packageChannel?.blurb === "string" ? params.packageChannel.blurb.trim() : "");
existing.description ?? normalizeOptionalString(params.packageChannel?.blurb) ?? "";
const preferOver =
existing.preferOver ?? normalizePreferredPluginIds(params.packageChannel?.preferOver);

View File

@@ -1,7 +1,10 @@
import { resolveEnvApiKey } from "../agents/model-auth-env.js";
import type { OpenClawConfig } from "../config/types.js";
import type { SecretInput } from "../config/types.secrets.js";
import { normalizeOptionalLowercaseString } from "../shared/string-coerce.js";
import {
normalizeOptionalLowercaseString,
normalizeStringifiedOptionalString,
} from "../shared/string-coerce.js";
import type { WizardPrompter } from "../wizard/prompts.js";
import {
resolveSecretInputModeForEnvSelection,
@@ -29,7 +32,7 @@ export {
const DEFAULT_KEY_PREVIEW = { head: 4, tail: 4 };
export function normalizeApiKeyInput(raw: string): string {
const trimmed = String(raw ?? "").trim();
const trimmed = normalizeStringifiedOptionalString(raw) ?? "";
if (!trimmed) {
return "";
}

View File

@@ -9,7 +9,10 @@ import {
isValidFileSecretRefId,
resolveDefaultSecretProviderAlias,
} from "../secrets/ref-contract.js";
import { normalizeOptionalString } from "../shared/string-coerce.js";
import {
normalizeOptionalString,
normalizeStringifiedOptionalString,
} from "../shared/string-coerce.js";
import type { WizardPrompter } from "../wizard/prompts.js";
let secretResolvePromise: Promise<typeof import("../secrets/resolve.js")> | undefined;
@@ -109,7 +112,7 @@ async function promptEnvSecretRefForSetup(params: {
return undefined;
},
});
const envCandidate = String(envVarRaw ?? "").trim();
const envCandidate = normalizeStringifiedOptionalString(envVarRaw) ?? "";
const envVar =
envCandidate && isValidEnvSecretRefId(envCandidate) ? envCandidate : params.defaultEnvVar;
if (!envVar) {
@@ -218,7 +221,7 @@ async function promptProviderSecretRefForSetup(params: {
return undefined;
},
});
const id = String(idRaw ?? "").trim() || idDefault;
const id = normalizeStringifiedOptionalString(idRaw) || idDefault;
const ref: SecretRef = {
source: providerEntry.source,
provider: selectedProvider,

View File

@@ -1,6 +1,9 @@
import { normalizeProviderId } from "../agents/provider-id.js";
import type { ModelProviderConfig } from "../config/types.js";
import { normalizeLowercaseStringOrEmpty } from "../shared/string-coerce.js";
import {
normalizeLowercaseStringOrEmpty,
normalizeOptionalString,
} from "../shared/string-coerce.js";
import type { ProviderCatalogContext, ProviderCatalogResult } from "./types.js";
export function findCatalogTemplate(params: {
@@ -37,8 +40,7 @@ export async function buildSingleProviderApiKeyCatalog(params: {
([configuredProviderId]) => normalizeProviderId(configuredProviderId) === providerId,
)?.[1]
: undefined;
const explicitBaseUrl =
typeof explicitProvider?.baseUrl === "string" ? explicitProvider.baseUrl.trim() : "";
const explicitBaseUrl = normalizeOptionalString(explicitProvider?.baseUrl) ?? "";
return {
provider: {

View File

@@ -8,6 +8,10 @@ import {
import type { OpenClawConfig } from "../config/config.js";
import type { ModelDefinitionConfig } from "../config/types.models.js";
import { createSubsystemLogger } from "../logging/subsystem.js";
import {
normalizeOptionalString,
normalizeStringifiedOptionalString,
} from "../shared/string-coerce.js";
import { normalizeOptionalSecretInput } from "../utils/normalize-secret-input.js";
import type { WizardPrompter } from "../wizard/prompts.js";
import { applyAuthProfileConfig } from "./provider-auth-helpers.js";
@@ -53,7 +57,7 @@ export async function discoverOpenAICompatibleLocalModels(params: {
const url = `${trimmedBaseUrl}/models`;
try {
const trimmedApiKey = params.apiKey?.trim();
const trimmedApiKey = normalizeOptionalString(params.apiKey);
const response = await fetch(url, {
headers: trimmedApiKey ? { Authorization: `Bearer ${trimmedApiKey}` } : undefined,
signal: AbortSignal.timeout(5000),
@@ -70,7 +74,7 @@ export async function discoverOpenAICompatibleLocalModels(params: {
}
return models
.map((model) => ({ id: typeof model.id === "string" ? model.id.trim() : "" }))
.map((model) => ({ id: normalizeOptionalString(model.id) ?? "" }))
.filter((model) => Boolean(model.id))
.map((model) => {
const modelId = model.id;
@@ -218,8 +222,8 @@ export async function promptAndConfigureOpenAICompatibleSelfHostedProvider(
const baseUrl = String(baseUrlRaw ?? "")
.trim()
.replace(/\/+$/, "");
const apiKey = String(apiKeyRaw ?? "").trim();
const modelId = String(modelIdRaw ?? "").trim();
const apiKey = normalizeStringifiedOptionalString(apiKeyRaw) ?? "";
const modelId = normalizeStringifiedOptionalString(modelIdRaw) ?? "";
const credential: AuthProfileCredential = {
type: "api_key",
provider: params.providerId,

View File

@@ -15,7 +15,10 @@ import {
NODE_SYSTEM_RUN_COMMANDS,
} from "../infra/node-commands.js";
import { normalizePluginGatewayMethodScope } from "../shared/gateway-method-policy.js";
import { normalizeOptionalString } from "../shared/string-coerce.js";
import {
normalizeOptionalString,
normalizeStringifiedOptionalString,
} from "../shared/string-coerce.js";
import { resolveUserPath } from "../utils.js";
import { buildPluginApi } from "./api-builder.js";
import { registerPluginCommand, validatePluginCommandDefinition } from "./command-registration.js";
@@ -611,7 +614,8 @@ export function createPluginRegistry(registryParams: PluginRegistryParams) {
? (registration as OpenClawPluginChannelRegistration)
: { plugin: registration as ChannelPlugin };
const plugin = normalized.plugin;
const id = typeof plugin?.id === "string" ? plugin.id.trim() : String(plugin?.id ?? "").trim();
const id =
normalizeOptionalString(plugin?.id) ?? normalizeStringifiedOptionalString(plugin?.id) ?? "";
if (!id) {
pushDiagnostic({
level: "error",