diff --git a/extensions/line/src/download.ts b/extensions/line/src/download.ts index dda32eb064d..ec7db64d732 100644 --- a/extensions/line/src/download.ts +++ b/extensions/line/src/download.ts @@ -2,6 +2,7 @@ import fs from "node:fs"; import { messagingApi } from "@line/bot-sdk"; import { logVerbose } from "openclaw/plugin-sdk/runtime-env"; import { buildRandomTempFilePath } from "openclaw/plugin-sdk/temp-path"; +import { lowercasePreservingWhitespace } from "openclaw/plugin-sdk/text-runtime"; interface DownloadResult { path: string; @@ -78,7 +79,7 @@ function detectContentType(buffer: Buffer): string { return "image/webp"; } if (hasFtypBox) { - const majorBrand = buffer.toString("ascii", 8, 12).toLowerCase(); + const majorBrand = lowercasePreservingWhitespace(buffer.toString("ascii", 8, 12)); if (AUDIO_BRANDS.has(majorBrand)) { return "audio/mp4"; } diff --git a/src/agents/pi-embedded-runner/model.provider-runtime.test-support.ts b/src/agents/pi-embedded-runner/model.provider-runtime.test-support.ts index 2001abd7954..26932f10f82 100644 --- a/src/agents/pi-embedded-runner/model.provider-runtime.test-support.ts +++ b/src/agents/pi-embedded-runner/model.provider-runtime.test-support.ts @@ -1,3 +1,4 @@ +import { lowercasePreservingWhitespace } from "../../shared/string-coerce.js"; import type { OpenRouterModelCapabilities } from "./openrouter-model-capabilities.js"; const OPENAI_BASE_URL = "https://api.openai.com/v1"; @@ -129,7 +130,7 @@ function buildDynamicModel( >, ) { const modelId = params.modelId.trim(); - const lower = modelId.toLowerCase(); + const lower = lowercasePreservingWhitespace(modelId); switch (params.provider) { case "openrouter": { const capabilities = options.getOpenRouterModelCapabilities(modelId); diff --git a/src/agents/pi-embedded-runner/run/attempt.spawn-workspace.test-support.ts b/src/agents/pi-embedded-runner/run/attempt.spawn-workspace.test-support.ts index 4e913a4a9ce..a57abe219b7 100644 --- a/src/agents/pi-embedded-runner/run/attempt.spawn-workspace.test-support.ts +++ b/src/agents/pi-embedded-runner/run/attempt.spawn-workspace.test-support.ts @@ -13,7 +13,10 @@ import type { IngestResult, } from "../../../context-engine/types.js"; import { formatErrorMessage } from "../../../infra/errors.js"; -import { normalizeLowercaseStringOrEmpty } from "../../../shared/string-coerce.js"; +import { + normalizeLowercaseStringOrEmpty, + normalizeOptionalLowercaseString, +} from "../../../shared/string-coerce.js"; import type { EmbeddedContextFile } from "../../pi-embedded-helpers.js"; import type { MessagingToolSend } from "../../pi-embedded-messaging.js"; import type { WorkspaceBootstrapFile } from "../../workspace.js"; @@ -477,10 +480,18 @@ vi.mock("../cache-ttl.js", () => ({ modelId?: string; } | undefined; - if (context?.provider && entry?.provider?.toLowerCase() !== context.provider.toLowerCase()) { + if ( + context?.provider && + normalizeOptionalLowercaseString(entry?.provider) !== + normalizeOptionalLowercaseString(context.provider) + ) { continue; } - if (context?.modelId && entry?.modelId?.toLowerCase() !== context.modelId.toLowerCase()) { + if ( + context?.modelId && + normalizeOptionalLowercaseString(entry?.modelId) !== + normalizeOptionalLowercaseString(context.modelId) + ) { continue; } const timestamp = entry?.timestamp; diff --git a/src/auto-reply/test-helpers/command-auth-registry-fixture.ts b/src/auto-reply/test-helpers/command-auth-registry-fixture.ts index ba4bf04b832..41b5d623f11 100644 --- a/src/auto-reply/test-helpers/command-auth-registry-fixture.ts +++ b/src/auto-reply/test-helpers/command-auth-registry-fixture.ts @@ -1,6 +1,7 @@ import { afterEach, beforeEach } from "vitest"; import { normalizeE164 } from "../../plugin-sdk/account-resolution.js"; import { setActivePluginRegistry } from "../../plugins/runtime.js"; +import { lowercasePreservingWhitespace } from "../../shared/string-coerce.js"; import { createOutboundTestPlugin, createTestRegistry } from "../../test-utils/channel-plugins.js"; function formatDiscordAllowFromEntries(allowFrom: Array): string[] { @@ -8,7 +9,7 @@ function formatDiscordAllowFromEntries(allowFrom: Array): strin .map((entry) => String(entry).trim()) .filter(Boolean) .map((entry) => entry.replace(/^(discord|user|pk):/i, "").replace(/^<@!?(\d+)>$/, "$1")) - .map((entry) => entry.toLowerCase()); + .map((entry) => lowercasePreservingWhitespace(entry)); } function normalizePhoneAllowFromEntries(allowFrom: Array): string[] { diff --git a/src/memory-host-sdk/host/multimodal.ts b/src/memory-host-sdk/host/multimodal.ts index 1ccbd49c9ee..baf97c39666 100644 --- a/src/memory-host-sdk/host/multimodal.ts +++ b/src/memory-host-sdk/host/multimodal.ts @@ -1,4 +1,7 @@ -import { normalizeLowercaseStringOrEmpty } from "../../shared/string-coerce.js"; +import { + lowercasePreservingWhitespace, + normalizeLowercaseStringOrEmpty, +} from "../../shared/string-coerce.js"; const MEMORY_MULTIMODAL_SPECS = { image: { @@ -79,7 +82,10 @@ export function buildCaseInsensitiveExtensionGlob(extension: string): string { if (!normalized) { return "*"; } - const parts = Array.from(normalized, (char) => `[${char.toLowerCase()}${char.toUpperCase()}]`); + const parts = Array.from(normalized, (char) => { + const lower = lowercasePreservingWhitespace(char); + return `[${lower}${char.toUpperCase()}]`; + }); return `*.${parts.join("")}`; } diff --git a/src/param-key.ts b/src/param-key.ts index 8d62d9a2c9f..ab8790caeb4 100644 --- a/src/param-key.ts +++ b/src/param-key.ts @@ -1,8 +1,10 @@ +import { lowercasePreservingWhitespace } from "./shared/string-coerce.js"; + function toSnakeCaseKey(key: string): string { - return key + const snakeKey = key .replace(/([A-Z]+)([A-Z][a-z])/g, "$1_$2") - .replace(/([a-z0-9])([A-Z])/g, "$1_$2") - .toLowerCase(); + .replace(/([a-z0-9])([A-Z])/g, "$1_$2"); + return lowercasePreservingWhitespace(snakeKey); } export function readSnakeCaseParamRaw(params: Record, key: string): unknown {