From 948d139399ccf4f5c4e48a6fdf2060ebca647661 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Tue, 7 Apr 2026 14:48:12 +0100 Subject: [PATCH] refactor: dedupe lowercase helper readers --- src/auto-reply/reply/model-selection.ts | 9 ++++--- .../reply/post-compaction-context.ts | 5 ++-- src/channels/model-overrides.ts | 2 +- src/cli/capability-cli.ts | 5 ++-- src/image-generation/live-test-helpers.ts | 17 +++++++++--- src/memory-host-sdk/dreaming.ts | 8 ++++-- src/memory-host-sdk/host/multimodal.ts | 6 +++-- src/memory-host-sdk/host/qmd-query-parser.ts | 3 ++- src/memory-host-sdk/host/qmd-scope.ts | 10 ++++--- src/memory-host-sdk/host/query-expansion.ts | 4 ++- src/music-generation/live-test-helpers.ts | 17 +++++++++--- src/secrets/audit.ts | 3 ++- src/video-generation/live-test-helpers.ts | 26 ++++++++++++++----- 13 files changed, 82 insertions(+), 33 deletions(-) diff --git a/src/auto-reply/reply/model-selection.ts b/src/auto-reply/reply/model-selection.ts index f605bf542a4..e799bebd169 100644 --- a/src/auto-reply/reply/model-selection.ts +++ b/src/auto-reply/reply/model-selection.ts @@ -19,7 +19,10 @@ import { resolveSessionParentSessionKey } from "../../channels/plugins/session-c import type { OpenClawConfig } from "../../config/config.js"; import type { SessionEntry } from "../../config/sessions/types.js"; import { applyModelOverrideToSessionEntry } from "../../sessions/model-overrides.js"; -import { normalizeOptionalString } from "../../shared/string-coerce.js"; +import { + normalizeLowercaseStringOrEmpty, + normalizeOptionalString, +} from "../../shared/string-coerce.js"; import type { ThinkLevel } from "./directives.js"; export type ModelDirectiveSelection = { @@ -211,7 +214,7 @@ function scoreFuzzyMatch(params: { } { const provider = normalizeProviderId(params.provider); const model = params.model; - const fragment = params.fragment.trim().toLowerCase(); + const fragment = normalizeLowercaseStringOrEmpty(params.fragment); const providerLower = provider.toLowerCase(); const modelLower = model.toLowerCase(); const haystack = `${providerLower}/${modelLower}`; @@ -541,7 +544,7 @@ export function resolveModelDirectiveSelection(params: { provider?: string; fragment: string; }): { selection?: ModelDirectiveSelection; error?: string } => { - const fragment = params.fragment.trim().toLowerCase(); + const fragment = normalizeLowercaseStringOrEmpty(params.fragment); if (!fragment) { return {}; } diff --git a/src/auto-reply/reply/post-compaction-context.ts b/src/auto-reply/reply/post-compaction-context.ts index 27558b75e81..3ede399d2f1 100644 --- a/src/auto-reply/reply/post-compaction-context.ts +++ b/src/auto-reply/reply/post-compaction-context.ts @@ -4,6 +4,7 @@ import { resolveCronStyleNow } from "../../agents/current-time.js"; import { resolveUserTimezone } from "../../agents/date-time.js"; import type { OpenClawConfig } from "../../config/config.js"; import { openBoundaryFile } from "../../infra/boundary-file-read.js"; +import { normalizeLowercaseStringOrEmpty } from "../../shared/string-coerce.js"; const MAX_CONTEXT_CHARS = 3000; const DEFAULT_POST_COMPACTION_SECTIONS = ["Session Startup", "Red Lines"]; @@ -18,12 +19,12 @@ function matchesSectionSet(sectionNames: string[], expectedSections: string[]): const counts = new Map(); for (const name of expectedSections) { - const normalized = name.trim().toLowerCase(); + const normalized = normalizeLowercaseStringOrEmpty(name); counts.set(normalized, (counts.get(normalized) ?? 0) + 1); } for (const name of sectionNames) { - const normalized = name.trim().toLowerCase(); + const normalized = normalizeLowercaseStringOrEmpty(name); const count = counts.get(normalized); if (!count) { return false; diff --git a/src/channels/model-overrides.ts b/src/channels/model-overrides.ts index ccb560b1b17..6b07bf055d8 100644 --- a/src/channels/model-overrides.ts +++ b/src/channels/model-overrides.ts @@ -171,7 +171,7 @@ export function resolveChannelModelOverride( keys, parentKeys, wildcardKey: "*", - normalizeKey: (value) => value.trim().toLowerCase(), + normalizeKey: (value) => normalizeOptionalLowercaseString(value) ?? "", }); const raw = match.entry ?? match.wildcardEntry; if (typeof raw !== "string") { diff --git a/src/cli/capability-cli.ts b/src/cli/capability-cli.ts index d0994c7e77c..24477d398f2 100644 --- a/src/cli/capability-cli.ts +++ b/src/cli/capability-cli.ts @@ -35,6 +35,7 @@ import { registerMemoryEmbeddingProvider, } from "../plugins/memory-embedding-providers.js"; import { writeRuntimeJson, defaultRuntime, type RuntimeEnv } from "../runtime.js"; +import { normalizeLowercaseStringOrEmpty } from "../shared/string-coerce.js"; import { formatDocsLink } from "../terminal/links.js"; import { theme } from "../terminal/theme.js"; import { canonicalizeSpeechProviderId, listSpeechProviders } from "../tts/provider-registry.js"; @@ -1725,11 +1726,11 @@ export function registerCapabilityCli(program: Command) { const cfg = loadConfig(); const selectedSearchProvider = typeof cfg.tools?.web?.search?.provider === "string" - ? cfg.tools.web.search.provider.trim().toLowerCase() + ? normalizeLowercaseStringOrEmpty(cfg.tools.web.search.provider) : ""; const selectedFetchProvider = typeof cfg.tools?.web?.fetch?.provider === "string" - ? cfg.tools.web.fetch.provider.trim().toLowerCase() + ? normalizeLowercaseStringOrEmpty(cfg.tools.web.fetch.provider) : ""; const result = { search: listWebSearchProviders({ config: cfg }).map((provider) => ({ diff --git a/src/image-generation/live-test-helpers.ts b/src/image-generation/live-test-helpers.ts index 1aafdf8ec16..8d068309528 100644 --- a/src/image-generation/live-test-helpers.ts +++ b/src/image-generation/live-test-helpers.ts @@ -1,5 +1,6 @@ import type { AuthProfileStore } from "../agents/auth-profiles.js"; import type { OpenClawConfig } from "../config/config.js"; +import { normalizeOptionalLowercaseString } from "../shared/string-coerce.js"; export const DEFAULT_LIVE_IMAGE_MODELS: Record = { fal: "fal/fal-ai/flux/dev", @@ -16,8 +17,8 @@ export function parseCaseFilter(raw?: string): Set | null { } const values = trimmed .split(",") - .map((entry) => entry.trim().toLowerCase()) - .filter(Boolean); + .map((entry) => normalizeOptionalLowercaseString(entry)) + .filter((entry): entry is string => Boolean(entry)); return values.length > 0 ? new Set(values) : null; } @@ -55,7 +56,11 @@ export function parseProviderModelMap(raw?: string): Map { if (slash <= 0 || slash === trimmed.length - 1) { continue; } - entries.set(trimmed.slice(0, slash).trim().toLowerCase(), trimmed); + const providerId = normalizeOptionalLowercaseString(trimmed.slice(0, slash)); + if (!providerId) { + continue; + } + entries.set(providerId, trimmed); } return entries; } @@ -72,7 +77,11 @@ export function resolveConfiguredLiveImageModels(cfg: OpenClawConfig): Map line.trim().toLowerCase().replace(/\s+/g, " ")) + .map((line) => normalizeLowercaseStringOrEmpty(line).replace(/\s+/g, " ")) .filter((line) => line.length > 0); return lines.some((line) => isQmdNoResultsLine(line)); } diff --git a/src/memory-host-sdk/host/qmd-scope.ts b/src/memory-host-sdk/host/qmd-scope.ts index a206cc9c2bd..7eb522dd55c 100644 --- a/src/memory-host-sdk/host/qmd-scope.ts +++ b/src/memory-host-sdk/host/qmd-scope.ts @@ -1,4 +1,8 @@ import { parseAgentSessionKey } from "../../sessions/session-key-utils.js"; +import { + normalizeLowercaseStringOrEmpty, + normalizeOptionalLowercaseString, +} from "../../shared/string-coerce.js"; import type { ResolvedQmdConfig } from "./backend-config.js"; type ParsedQmdSessionScope = { @@ -15,7 +19,7 @@ export function isQmdScopeAllowed(scope: ResolvedQmdConfig["scope"], sessionKey? const channel = parsed.channel; const chatType = parsed.chatType; const normalizedKey = parsed.normalizedKey ?? ""; - const rawKey = sessionKey?.trim().toLowerCase() ?? ""; + const rawKey = normalizeLowercaseStringOrEmpty(sessionKey); for (const rule of scope.rules ?? []) { if (!rule) { continue; @@ -27,8 +31,8 @@ export function isQmdScopeAllowed(scope: ResolvedQmdConfig["scope"], sessionKey? if (match.chatType && match.chatType !== chatType) { continue; } - const normalizedPrefix = match.keyPrefix?.trim().toLowerCase() || undefined; - const rawPrefix = match.rawKeyPrefix?.trim().toLowerCase() || undefined; + const normalizedPrefix = normalizeOptionalLowercaseString(match.keyPrefix); + const rawPrefix = normalizeOptionalLowercaseString(match.rawKeyPrefix); if (rawPrefix && !rawKey.startsWith(rawPrefix)) { continue; diff --git a/src/memory-host-sdk/host/query-expansion.ts b/src/memory-host-sdk/host/query-expansion.ts index 5ce120f1453..161f89ea59e 100644 --- a/src/memory-host-sdk/host/query-expansion.ts +++ b/src/memory-host-sdk/host/query-expansion.ts @@ -8,6 +8,8 @@ * This module extracts meaningful keywords from such queries to improve FTS results. */ +import { normalizeLowercaseStringOrEmpty } from "../../shared/string-coerce.js"; + // Common stop words that don't add search value const STOP_WORDS_EN = new Set([ // Articles and determiners @@ -673,7 +675,7 @@ function isValidKeyword(token: string): boolean { function tokenize(text: string, opts?: { ftsTokenizer?: "unicode61" | "trigram" }): string[] { const useTrigram = opts?.ftsTokenizer === "trigram"; const tokens: string[] = []; - const normalized = text.toLowerCase().trim(); + const normalized = normalizeLowercaseStringOrEmpty(text); // Split into segments (English words, Chinese character sequences, etc.) const segments = normalized.split(/[\s\p{P}]+/u).filter(Boolean); diff --git a/src/music-generation/live-test-helpers.ts b/src/music-generation/live-test-helpers.ts index ecca280c4c7..aa04bfe9560 100644 --- a/src/music-generation/live-test-helpers.ts +++ b/src/music-generation/live-test-helpers.ts @@ -1,5 +1,6 @@ import type { AuthProfileStore } from "../agents/auth-profiles.js"; import type { OpenClawConfig } from "../config/config.js"; +import { normalizeOptionalLowercaseString } from "../shared/string-coerce.js"; export const DEFAULT_LIVE_MUSIC_MODELS: Record = { google: "google/lyria-3-clip-preview", @@ -24,8 +25,8 @@ export function parseCsvFilter(raw?: string): Set | null { } const values = trimmed .split(",") - .map((entry) => entry.trim().toLowerCase()) - .filter(Boolean); + .map((entry) => normalizeOptionalLowercaseString(entry)) + .filter((entry): entry is string => Boolean(entry)); return values.length > 0 ? new Set(values) : null; } @@ -40,7 +41,11 @@ export function parseProviderModelMap(raw?: string): Map { if (slash <= 0 || slash === trimmed.length - 1) { continue; } - entries.set(trimmed.slice(0, slash).trim().toLowerCase(), trimmed); + const providerId = normalizeOptionalLowercaseString(trimmed.slice(0, slash)); + if (!providerId) { + continue; + } + entries.set(providerId, trimmed); } return entries; } @@ -57,7 +62,11 @@ export function resolveConfiguredLiveMusicModels(cfg: OpenClawConfig): Map = { alibaba: "alibaba/wan2.6-t2v", @@ -22,7 +26,7 @@ export function resolveLiveVideoResolution(params: { providerId: string; modelRef: string; }): "480P" | "768P" | "1080P" { - const providerId = params.providerId.trim().toLowerCase(); + const providerId = normalizeLowercaseStringOrEmpty(params.providerId); if (providerId === "minimax") { return "768P"; } @@ -47,8 +51,8 @@ export function parseCsvFilter(raw?: string): Set | null { } const values = trimmed .split(",") - .map((entry) => entry.trim().toLowerCase()) - .filter(Boolean); + .map((entry) => normalizeOptionalLowercaseString(entry)) + .filter((entry): entry is string => Boolean(entry)); return values.length > 0 ? new Set(values) : null; } @@ -63,7 +67,11 @@ export function parseProviderModelMap(raw?: string): Map { if (slash <= 0 || slash === trimmed.length - 1) { continue; } - entries.set(trimmed.slice(0, slash).trim().toLowerCase(), trimmed); + const providerId = normalizeOptionalLowercaseString(trimmed.slice(0, slash)); + if (!providerId) { + continue; + } + entries.set(providerId, trimmed); } return entries; } @@ -80,7 +88,11 @@ export function resolveConfiguredLiveVideoModels(cfg: OpenClawConfig): Map