refactor: dedupe path lowercase helpers

This commit is contained in:
Peter Steinberger
2026-04-07 15:44:27 +01:00
parent bbcc95948e
commit c3074bd513
8 changed files with 48 additions and 21 deletions

View File

@@ -2,6 +2,7 @@ import { readFileSync } from "node:fs";
import { homedir, platform } from "node:os";
import { join } from "node:path";
import { resolveProviderEndpoint } from "openclaw/plugin-sdk/provider-http";
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
const ANTHROPIC_VERTEX_DEFAULT_REGION = "global";
const ANTHROPIC_VERTEX_REGION_RE = /^[a-z0-9-]+$/;
@@ -64,7 +65,10 @@ export function resolveAnthropicVertexClientRegion(params?: {
function hasAnthropicVertexMetadataServerAdc(env: NodeJS.ProcessEnv = process.env): boolean {
const explicitMetadataOptIn = normalizeOptionalSecretInput(env.ANTHROPIC_VERTEX_USE_GCP_METADATA);
return explicitMetadataOptIn === "1" || explicitMetadataOptIn?.toLowerCase() === "true";
return (
explicitMetadataOptIn === "1" ||
normalizeLowercaseStringOrEmpty(explicitMetadataOptIn) === "true"
);
}
function resolveAnthropicVertexDefaultAdcPath(env: NodeJS.ProcessEnv = process.env): string {

View File

@@ -1,3 +1,5 @@
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
const DISCORD_VIDEO_MEDIA_EXTENSIONS = new Set([".avi", ".m4v", ".mkv", ".mov", ".mp4", ".webm"]);
function normalizeMediaPathForExtension(mediaUrl: string): string {
@@ -7,11 +9,11 @@ function normalizeMediaPathForExtension(mediaUrl: string): string {
}
try {
const parsed = new URL(trimmed);
return parsed.pathname.toLowerCase();
return normalizeLowercaseStringOrEmpty(parsed.pathname);
} catch {
const withoutHash = trimmed.split("#", 1)[0] ?? trimmed;
const withoutQuery = withoutHash.split("?", 1)[0] ?? withoutHash;
return withoutQuery.toLowerCase();
return normalizeLowercaseStringOrEmpty(withoutQuery);
}
}

View File

@@ -2,6 +2,7 @@ import {
normalizeGooglePreviewModelId,
normalizeNativeXaiModelId,
} from "../plugin-sdk/provider-model-id-normalize.js";
import { normalizeLowercaseStringOrEmpty } from "../shared/string-coerce.js";
import { normalizeProviderId } from "./provider-id.js";
export type StaticModelRef = {
@@ -18,7 +19,9 @@ export function modelKey(provider: string, model: string): string {
if (!modelId) {
return providerId;
}
return modelId.toLowerCase().startsWith(`${providerId.toLowerCase()}/`)
return normalizeLowercaseStringOrEmpty(modelId).startsWith(
`${normalizeLowercaseStringOrEmpty(providerId)}/`,
)
? modelId
: `${providerId}/${modelId}`;
}
@@ -28,7 +31,7 @@ export function normalizeAnthropicModelId(model: string): string {
if (!trimmed) {
return trimmed;
}
switch (trimmed.toLowerCase()) {
switch (normalizeLowercaseStringOrEmpty(trimmed)) {
case "opus-4.6":
return "claude-opus-4-6";
case "opus-4.5":
@@ -48,7 +51,9 @@ function normalizeHuggingfaceModelId(model: string): string {
return trimmed;
}
const prefix = "huggingface/";
return trimmed.toLowerCase().startsWith(prefix) ? trimmed.slice(prefix.length) : trimmed;
return normalizeLowercaseStringOrEmpty(trimmed).startsWith(prefix)
? trimmed.slice(prefix.length)
: trimmed;
}
export function normalizeStaticProviderModelId(provider: string, model: string): string {

View File

@@ -7,6 +7,7 @@ import {
buildPluginLoaderJitiOptions,
resolvePluginLoaderJitiConfig,
} from "../../plugins/sdk-alias.js";
import { normalizeLowercaseStringOrEmpty } from "../../shared/string-coerce.js";
const nodeRequire = createRequire(import.meta.url);
@@ -36,7 +37,9 @@ function createModuleLoader() {
let loadModule = createModuleLoader();
export function isJavaScriptModulePath(modulePath: string): boolean {
return [".js", ".mjs", ".cjs"].includes(path.extname(modulePath).toLowerCase());
return [".js", ".mjs", ".cjs"].includes(
normalizeLowercaseStringOrEmpty(path.extname(modulePath)),
);
}
export function resolveCompiledBundledModulePath(modulePath: string): string {

View File

@@ -1,7 +1,10 @@
import { readFileSync } from "node:fs";
import { homedir, platform } from "node:os";
import { join } from "node:path";
import { normalizeOptionalString } from "../shared/string-coerce.js";
import {
normalizeLowercaseStringOrEmpty,
normalizeOptionalString,
} from "../shared/string-coerce.js";
import { normalizeOptionalSecretInput } from "../utils/normalize-secret-input.js";
const GCLOUD_DEFAULT_ADC_PATH = join(
@@ -13,7 +16,10 @@ const GCLOUD_DEFAULT_ADC_PATH = join(
function hasAnthropicVertexMetadataServerAdc(env: NodeJS.ProcessEnv = process.env): boolean {
const explicitMetadataOptIn = normalizeOptionalSecretInput(env.ANTHROPIC_VERTEX_USE_GCP_METADATA);
return explicitMetadataOptIn === "1" || explicitMetadataOptIn?.toLowerCase() === "true";
return (
explicitMetadataOptIn === "1" ||
normalizeLowercaseStringOrEmpty(explicitMetadataOptIn) === "true"
);
}
function resolveAnthropicVertexDefaultAdcPath(env: NodeJS.ProcessEnv = process.env): string {

View File

@@ -1,6 +1,9 @@
import { readFileSync, statSync } from "node:fs";
import path from "node:path";
import { normalizeOptionalString } from "../shared/string-coerce.js";
import {
normalizeLowercaseStringOrEmpty,
normalizeOptionalString,
} from "../shared/string-coerce.js";
export type WindowsSpawnResolution =
| "direct"
@@ -82,7 +85,9 @@ export function resolveWindowsExecutablePath(command: string, env: NodeJS.Proces
for (const dir of pathEntries) {
for (const ext of pathExt) {
for (const candidateExt of [ext, ext.toLowerCase(), ext.toUpperCase()]) {
const normalizedExt = normalizeLowercaseStringOrEmpty(ext);
const uppercaseExt = ext.toUpperCase();
for (const candidateExt of [ext, normalizedExt, uppercaseExt]) {
const candidate = path.join(dir, `${command}${candidateExt}`);
if (isFilePath(candidate)) {
return candidate;
@@ -116,7 +121,7 @@ function resolveEntrypointFromCmdShim(wrapperPath: string): string | null {
}
}
const nonNode = candidates.find((candidate) => {
const base = path.basename(candidate).toLowerCase();
const base = normalizeLowercaseStringOrEmpty(path.basename(candidate));
return base !== "node.exe" && base !== "node";
});
return nonNode ?? null;
@@ -211,7 +216,7 @@ export function resolveWindowsSpawnProgramCandidate(
}
const resolvedCommand = resolveWindowsExecutablePath(params.command, env);
const ext = path.extname(resolvedCommand).toLowerCase();
const ext = normalizeLowercaseStringOrEmpty(path.extname(resolvedCommand));
if (ext === ".js" || ext === ".cjs" || ext === ".mjs") {
return {
command: execPath,
@@ -226,7 +231,7 @@ export function resolveWindowsSpawnProgramCandidate(
resolveEntrypointFromCmdShim(resolvedCommand) ??
resolveEntrypointFromPackageJson(resolvedCommand, params.packageName);
if (entrypoint) {
const entryExt = path.extname(entrypoint).toLowerCase();
const entryExt = normalizeLowercaseStringOrEmpty(path.extname(entrypoint));
if (entryExt === ".exe") {
return {
command: entrypoint,

View File

@@ -6,6 +6,7 @@ import { promisify } from "node:util";
import { danger, shouldLogVerbose } from "../globals.js";
import { markOpenClawExecEnv } from "../infra/openclaw-exec-env.js";
import { logDebug, logError } from "../logger.js";
import { normalizeLowercaseStringOrEmpty } from "../shared/string-coerce.js";
import { resolveCommandStdio } from "./spawn-utils.js";
import { resolveWindowsCommandShim } from "./windows-command.js";
@@ -17,7 +18,7 @@ function isWindowsBatchCommand(resolvedCommand: string): boolean {
if (process.platform !== "win32") {
return false;
}
const ext = path.extname(resolvedCommand).toLowerCase();
const ext = normalizeLowercaseStringOrEmpty(path.extname(resolvedCommand));
return ext === ".cmd" || ext === ".bat";
}
@@ -49,10 +50,10 @@ function resolveNpmArgvForWindows(argv: string[]): string[] | null {
if (process.platform !== "win32" || argv.length === 0) {
return null;
}
const basename = path
.basename(argv[0])
.toLowerCase()
.replace(/\.(cmd|exe|bat)$/, "");
const basename = normalizeLowercaseStringOrEmpty(path.basename(argv[0])).replace(
/\.(cmd|exe|bat)$/,
"",
);
const cliName = basename === "npx" ? "npx-cli.js" : basename === "npm" ? "npm-cli.js" : null;
if (!cliName) {
return null;
@@ -64,7 +65,7 @@ function resolveNpmArgvForWindows(argv: string[]): string[] | null {
// Fall back to npm.cmd/npx.cmd so we still route through cmd wrapper
// (avoids direct .cmd spawn EINVAL on patched Node).
const command = argv[0] ?? "";
const ext = path.extname(command).toLowerCase();
const ext = normalizeLowercaseStringOrEmpty(path.extname(command));
const shimmedCommand = ext ? command : `${command}.cmd`;
return [shimmedCommand, ...argv.slice(1)];
}

View File

@@ -1,5 +1,6 @@
import path from "node:path";
import process from "node:process";
import { normalizeLowercaseStringOrEmpty } from "../shared/string-coerce.js";
export function resolveWindowsCommandShim(params: {
command: string;
@@ -9,7 +10,7 @@ export function resolveWindowsCommandShim(params: {
if ((params.platform ?? process.platform) !== "win32") {
return params.command;
}
const basename = path.basename(params.command).toLowerCase();
const basename = normalizeLowercaseStringOrEmpty(path.basename(params.command));
if (path.extname(basename)) {
return params.command;
}