refactor: dedupe plugin lowercase helpers

This commit is contained in:
Peter Steinberger
2026-04-07 15:51:58 +01:00
parent 1d7e87580d
commit ea9efc0e81
10 changed files with 31 additions and 22 deletions

View File

@@ -1,5 +1,6 @@
import type { IncomingMessage, ServerResponse } from "node:http";
import { safeEqualSecret } from "openclaw/plugin-sdk/browser-security-runtime";
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
import { z } from "zod";
import type { PluginRuntime } from "../api.js";
import {
@@ -309,7 +310,7 @@ function extractSharedSecret(req: IncomingMessage): string {
const authHeader = Array.isArray(req.headers.authorization)
? String(req.headers.authorization[0] ?? "")
: String(req.headers.authorization ?? "");
if (authHeader.toLowerCase().startsWith("bearer ")) {
if (normalizeLowercaseStringOrEmpty(authHeader).startsWith("bearer ")) {
return authHeader.slice("bearer ".length).trim();
}
const sharedHeader = req.headers["x-openclaw-webhook-secret"];

View File

@@ -2,6 +2,7 @@ import fs from "node:fs";
import { createRequire } from "node:module";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { normalizeLowercaseStringOrEmpty } from "../shared/string-coerce.js";
import { resolveGitHeadPath } from "./git-root.js";
import { resolveOpenClawPackageRootSync } from "./openclaw-root.js";
@@ -17,7 +18,7 @@ const formatCommit = (value?: string | null) => {
if (!match) {
return null;
}
return match[0].slice(0, 7).toLowerCase();
return normalizeLowercaseStringOrEmpty(match[0].slice(0, 7));
};
const cachedGitCommitBySearchDir = new Map<string, string | null>();

View File

@@ -1,4 +1,5 @@
import path from "node:path";
import { normalizeLowercaseStringOrEmpty } from "../shared/string-coerce.js";
const NOT_FOUND_CODES = new Set(["ENOENT", "ENOTDIR"]);
const SYMLINK_OPEN_CODES = new Set(["ELOOP", "EINVAL", "ENOTSUP"]);
@@ -11,7 +12,7 @@ export function normalizeWindowsPathForComparison(input: string): string {
normalized = `\\\\${normalized.slice(4)}`;
}
}
return normalized.replaceAll("/", "\\").toLowerCase();
return normalizeLowercaseStringOrEmpty(normalized.replaceAll("/", "\\"));
}
export function isNodeError(value: unknown): value is NodeJS.ErrnoException {

View File

@@ -2,7 +2,10 @@ import fs from "node:fs";
import path from "node:path";
import JSON5 from "json5";
import { matchBoundaryFileOpenFailure, openBoundaryFileSync } from "../infra/boundary-file-read.js";
import { normalizeOptionalString } from "../shared/string-coerce.js";
import {
normalizeLowercaseStringOrEmpty,
normalizeOptionalString,
} from "../shared/string-coerce.js";
import { isRecord } from "../utils.js";
import { DEFAULT_PLUGIN_ENTRY_CANDIDATES, PLUGIN_MANIFEST_FILENAME } from "./manifest.js";
import type { PluginBundleFormat } from "./types.js";
@@ -77,7 +80,7 @@ function hasInlineCapabilityValue(value: unknown): boolean {
function slugifyPluginId(raw: string | undefined, rootDir: string): string {
const fallback = path.basename(rootDir);
const source = (raw?.trim() || fallback).toLowerCase();
const source = normalizeLowercaseStringOrEmpty(raw) || normalizeLowercaseStringOrEmpty(fallback);
const slug = source
.replace(/[^a-z0-9]+/g, "-")
.replace(/-+/g, "-")

View File

@@ -1,5 +1,8 @@
import { logVerbose } from "../globals.js";
import { normalizeOptionalLowercaseString } from "../shared/string-coerce.js";
import {
normalizeLowercaseStringOrEmpty,
normalizeOptionalLowercaseString,
} from "../shared/string-coerce.js";
import {
clearPluginCommands,
clearPluginCommandsForPlugin,
@@ -166,7 +169,7 @@ export function registerPluginCommand(
description,
};
const invocationKeys = listPluginInvocationKeys(normalizedCommand);
const key = `/${name.toLowerCase()}`;
const key = `/${normalizeLowercaseStringOrEmpty(name)}`;
// Check for duplicate registration
for (const invocationKey of invocationKeys) {

View File

@@ -303,7 +303,7 @@ function isExtensionFile(filePath: string): boolean {
if (filePath.endsWith(".d.ts")) {
return false;
}
const baseName = path.basename(filePath).toLowerCase();
const baseName = normalizeLowercaseStringOrEmpty(path.basename(filePath));
return (
!baseName.includes(".test.") &&
!baseName.includes(".live.test.") &&

View File

@@ -1,6 +1,7 @@
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 type { WizardPrompter } from "../wizard/prompts.js";
import {
resolveSecretInputModeForEnvSelection,
@@ -76,18 +77,13 @@ export function formatApiKeyPreview(
export function normalizeTokenProviderInput(
tokenProvider: string | null | undefined,
): string | undefined {
const normalized = String(tokenProvider ?? "")
.trim()
.toLowerCase();
return normalized || undefined;
return normalizeOptionalLowercaseString(tokenProvider);
}
export function normalizeSecretInputModeInput(
secretInputMode: string | null | undefined,
): SecretInputMode | undefined {
const normalized = String(secretInputMode ?? "")
.trim()
.toLowerCase();
const normalized = normalizeOptionalLowercaseString(secretInputMode);
if (normalized === "plaintext" || normalized === "ref") {
return normalized;
}

View File

@@ -1,5 +1,6 @@
import { normalizeProviderId } from "../agents/provider-id.js";
import type { ModelProviderConfig } from "../config/types.js";
import { normalizeLowercaseStringOrEmpty } from "../shared/string-coerce.js";
import type { ProviderCatalogContext, ProviderCatalogResult } from "./types.js";
export function findCatalogTemplate(params: {
@@ -12,7 +13,7 @@ export function findCatalogTemplate(params: {
params.entries.find(
(entry) =>
normalizeProviderId(entry.provider) === normalizeProviderId(params.providerId) &&
entry.id.toLowerCase() === templateId.toLowerCase(),
normalizeLowercaseStringOrEmpty(entry.id) === normalizeLowercaseStringOrEmpty(templateId),
),
)
.find((entry) => entry !== undefined);

View File

@@ -1,4 +1,5 @@
import type { AgentMessage } from "@mariozechner/pi-agent-core";
import { normalizeLowercaseStringOrEmpty } from "../shared/string-coerce.js";
import type {
ProviderReasoningOutputMode,
ProviderReplayPolicy,
@@ -71,7 +72,7 @@ export function buildStrictAnthropicReplayPolicy(
* See: https://platform.claude.com/docs/en/build-with-claude/extended-thinking#differences-in-thinking-across-model-versions
*/
export function shouldPreserveThinkingBlocks(modelId?: string): boolean {
const id = (modelId ?? "").toLowerCase();
const id = normalizeLowercaseStringOrEmpty(modelId);
if (!id.includes("claude")) {
return false;
}
@@ -96,14 +97,14 @@ export function shouldPreserveThinkingBlocks(modelId?: string): boolean {
}
export function buildAnthropicReplayPolicyForModel(modelId?: string): ProviderReplayPolicy {
const isClaude = (modelId?.toLowerCase() ?? "").includes("claude");
const isClaude = normalizeLowercaseStringOrEmpty(modelId).includes("claude");
return buildStrictAnthropicReplayPolicy({
dropThinkingBlocks: isClaude && !shouldPreserveThinkingBlocks(modelId),
});
}
export function buildNativeAnthropicReplayPolicyForModel(modelId?: string): ProviderReplayPolicy {
const isClaude = (modelId?.toLowerCase() ?? "").includes("claude");
const isClaude = normalizeLowercaseStringOrEmpty(modelId).includes("claude");
return buildStrictAnthropicReplayPolicy({
dropThinkingBlocks: isClaude && !shouldPreserveThinkingBlocks(modelId),
sanitizeToolCallIds: true,
@@ -116,7 +117,7 @@ export function buildHybridAnthropicOrOpenAIReplayPolicy(
options: { anthropicModelDropThinkingBlocks?: boolean } = {},
): ProviderReplayPolicy | undefined {
if (ctx.modelApi === "anthropic-messages" || ctx.modelApi === "bedrock-converse-stream") {
const isClaude = (ctx.modelId?.toLowerCase() ?? "").includes("claude");
const isClaude = normalizeLowercaseStringOrEmpty(ctx.modelId).includes("claude");
return buildStrictAnthropicReplayPolicy({
dropThinkingBlocks:
options.anthropicModelDropThinkingBlocks &&
@@ -187,11 +188,12 @@ export function buildGoogleGeminiReplayPolicy(): ProviderReplayPolicy {
export function buildPassthroughGeminiSanitizingReplayPolicy(
modelId?: string,
): ProviderReplayPolicy {
const normalizedModelId = normalizeLowercaseStringOrEmpty(modelId);
return {
applyAssistantFirstOrderingFix: false,
validateGeminiTurns: false,
validateAnthropicTurns: false,
...((modelId?.toLowerCase() ?? "").includes("gemini")
...(normalizedModelId.includes("gemini")
? {
sanitizeThoughtSignatures: {
allowBase64Only: true,

View File

@@ -6,6 +6,7 @@ import type {
} from "@mariozechner/pi-tui";
import chalk from "chalk";
import { highlight, supportsLanguage } from "cli-highlight";
import { normalizeOptionalLowercaseString } from "../../shared/string-coerce.js";
import type { SearchableSelectListTheme } from "../components/searchable-select-list.js";
import { createSyntaxTheme } from "./syntax-theme.js";
@@ -46,7 +47,7 @@ function pickHigherContrastText(r: number, g: number, b: number): boolean {
}
function isLightBackground(): boolean {
const explicit = process.env.OPENCLAW_THEME?.toLowerCase();
const explicit = normalizeOptionalLowercaseString(process.env.OPENCLAW_THEME);
if (explicit === "light") {
return true;
}