diff --git a/extensions/anthropic/register.runtime.ts b/extensions/anthropic/register.runtime.ts index 9bd17ab8cd7..11849ccae49 100644 --- a/extensions/anthropic/register.runtime.ts +++ b/extensions/anthropic/register.runtime.ts @@ -193,7 +193,7 @@ function resolveAnthropic46ForwardCompatModel(params: { fallbackTemplateIds: readonly string[]; }): ProviderRuntimeModel | undefined { const trimmedModelId = params.ctx.modelId.trim(); - const lower = trimmedModelId.toLowerCase(); + const lower = normalizeLowercaseStringOrEmpty(trimmedModelId); const is46Model = lower === params.dashModelId || lower === params.dotModelId || @@ -247,6 +247,16 @@ function resolveAnthropicForwardCompatModel( ); } +function shouldUseAnthropicAdaptiveThinkingDefault(modelId: string): boolean { + const lowerModelId = normalizeLowercaseStringOrEmpty(modelId); + return ( + lowerModelId.startsWith(ANTHROPIC_OPUS_46_MODEL_ID) || + lowerModelId.startsWith(ANTHROPIC_OPUS_46_DOT_MODEL_ID) || + lowerModelId.startsWith(ANTHROPIC_SONNET_46_MODEL_ID) || + lowerModelId.startsWith(ANTHROPIC_SONNET_46_DOT_MODEL_ID) + ); +} + function matchesAnthropicModernModel(modelId: string): boolean { const lower = normalizeLowercaseStringOrEmpty(modelId); return ANTHROPIC_MODERN_MODEL_PREFIXES.some((prefix) => lower.startsWith(prefix)); @@ -468,11 +478,7 @@ export function registerAnthropicPlugin(api: OpenClawPluginApi): void { resolveReasoningOutputMode: () => "native", wrapStreamFn: wrapAnthropicProviderStream, resolveDefaultThinkingLevel: ({ modelId }) => - matchesAnthropicModernModel(modelId) && - (modelId.toLowerCase().startsWith(ANTHROPIC_OPUS_46_MODEL_ID) || - modelId.toLowerCase().startsWith(ANTHROPIC_OPUS_46_DOT_MODEL_ID) || - modelId.toLowerCase().startsWith(ANTHROPIC_SONNET_46_MODEL_ID) || - modelId.toLowerCase().startsWith(ANTHROPIC_SONNET_46_DOT_MODEL_ID)) + matchesAnthropicModernModel(modelId) && shouldUseAnthropicAdaptiveThinkingDefault(modelId) ? "adaptive" : undefined, resolveUsageAuth: async (ctx) => await ctx.resolveOAuthToken(), diff --git a/extensions/bluebubbles/src/monitor-debounce.ts b/extensions/bluebubbles/src/monitor-debounce.ts index 44940bb6d1c..80050129063 100644 --- a/extensions/bluebubbles/src/monitor-debounce.ts +++ b/extensions/bluebubbles/src/monitor-debounce.ts @@ -1,3 +1,4 @@ +import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime"; import type { NormalizedWebhookMessage } from "./monitor-normalize.js"; import type { BlueBubblesCoreRuntime, WebhookTarget } from "./monitor-shared.js"; import type { OpenClawConfig } from "./runtime-api.js"; @@ -70,7 +71,7 @@ function combineDebounceEntries(entries: BlueBubblesDebounceEntry[]): Normalized continue; } // Skip duplicate text (URL might be in both text message and balloon) - const normalizedText = text.toLowerCase(); + const normalizedText = normalizeLowercaseStringOrEmpty(text); if (seenTexts.has(normalizedText)) { continue; } diff --git a/extensions/bluebubbles/src/monitor-processing.ts b/extensions/bluebubbles/src/monitor-processing.ts index 84be916b8ec..0e1a7fde791 100644 --- a/extensions/bluebubbles/src/monitor-processing.ts +++ b/extensions/bluebubbles/src/monitor-processing.ts @@ -273,7 +273,7 @@ function rememberPendingOutboundMessageId(entry: { chatId: typeof entry.chatId === "number" ? entry.chatId : undefined, snippetRaw, snippetNorm, - isMediaSnippet: snippetRaw.toLowerCase().startsWith(" = (entry.input_modalities || ["text"]).filter( (i): i is "text" | "image" => i === "text" || i === "image", diff --git a/extensions/irc/src/client.ts b/extensions/irc/src/client.ts index 88f21b930a4..a4b5d37cd4a 100644 --- a/extensions/irc/src/client.ts +++ b/extensions/irc/src/client.ts @@ -1,5 +1,6 @@ import net from "node:net"; import tls from "node:tls"; +import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime"; import { parseIrcLine, parseIrcPrefix, @@ -93,6 +94,10 @@ function buildFallbackNick(nick: string): string { return `${base}${suffix}`; } +function normalizeIrcNick(value: string): string { + return normalizeLowercaseStringOrEmpty(value); +} + export function buildIrcNickServCommands(options?: IrcNickServOptions): string[] { if (!options || options.enabled === false) { return []; @@ -187,7 +192,7 @@ export async function connectIrcClient(options: IrcClientOptions): Promise { diff --git a/extensions/irc/src/monitor.ts b/extensions/irc/src/monitor.ts index 2a75b76ee08..c5da51cfd0b 100644 --- a/extensions/irc/src/monitor.ts +++ b/extensions/irc/src/monitor.ts @@ -1,4 +1,5 @@ import { resolveLoggerBackedRuntime } from "openclaw/plugin-sdk/extension-shared"; +import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime"; import { resolveIrcAccount } from "./accounts.js"; import { connectIrcClient, type IrcClient } from "./client.js"; import { buildIrcConnectOptions } from "./connect-options.js"; @@ -79,7 +80,10 @@ export async function monitorIrcProvider(opts: IrcMonitorOptions): Promise<{ sto if (!client) { return; } - if (event.senderNick.toLowerCase() === client.nick.toLowerCase()) { + if ( + normalizeLowercaseStringOrEmpty(event.senderNick) === + normalizeLowercaseStringOrEmpty(client.nick) + ) { return; } diff --git a/extensions/irc/src/policy.ts b/extensions/irc/src/policy.ts index 356f0fae7d8..3b15fb4e0fd 100644 --- a/extensions/irc/src/policy.ts +++ b/extensions/irc/src/policy.ts @@ -1,3 +1,4 @@ +import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime"; import { normalizeIrcAllowlist, resolveIrcAllowlistMatch } from "./normalize.js"; import type { IrcAccountConfig, IrcChannelConfig } from "./types.js"; import type { IrcInboundMessage } from "./types.js"; @@ -36,8 +37,10 @@ export function resolveIrcGroupMatch(params: { }; } - const targetLower = params.target.toLowerCase(); - const directKey = Object.keys(groups).find((key) => key.toLowerCase() === targetLower); + const targetLower = normalizeLowercaseStringOrEmpty(params.target); + const directKey = Object.keys(groups).find( + (key) => normalizeLowercaseStringOrEmpty(key) === targetLower, + ); if (directKey) { const matched = groups[directKey]; if (matched) { diff --git a/extensions/talk-voice/index.ts b/extensions/talk-voice/index.ts index 00cf64507f7..c3d45a1801d 100644 --- a/extensions/talk-voice/index.ts +++ b/extensions/talk-voice/index.ts @@ -1,7 +1,10 @@ import { resolveActiveTalkProviderConfig } from "openclaw/plugin-sdk/config-runtime"; import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime"; import type { SpeechVoiceOption } from "openclaw/plugin-sdk/speech"; -import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime"; +import { + normalizeLowercaseStringOrEmpty, + normalizeOptionalLowercaseString, +} from "openclaw/plugin-sdk/text-runtime"; import { definePluginEntry, type OpenClawPluginApi } from "./api.js"; function mask(s: string, keep: number = 6): string { @@ -75,20 +78,16 @@ function findVoice(voices: SpeechVoiceOption[], query: string): SpeechVoiceOptio if (!q) { return null; } - const lower = q.toLowerCase(); + const lower = normalizeLowercaseStringOrEmpty(q); const byId = voices.find((v) => v.id === q); if (byId) { return byId; } - const exactName = voices.find( - (v) => (normalizeOptionalString(v.name)?.toLowerCase() ?? "") === lower, - ); + const exactName = voices.find((v) => normalizeOptionalLowercaseString(v.name) === lower); if (exactName) { return exactName; } - const partial = voices.find((v) => - (normalizeOptionalString(v.name)?.toLowerCase() ?? "").includes(lower), - ); + const partial = voices.find((v) => normalizeLowercaseStringOrEmpty(v.name).includes(lower)); return partial ?? null; } @@ -133,7 +132,7 @@ export default definePluginEntry({ const commandLabel = resolveCommandLabel(ctx.channel); const args = ctx.args?.trim() ?? ""; const tokens = args.split(/\s+/).filter(Boolean); - const action = (tokens[0] ?? "status").toLowerCase(); + const action = normalizeLowercaseStringOrEmpty(tokens[0] ?? "status"); const cfg = api.runtime.config.loadConfig(); const active = resolveActiveTalkProviderConfig(cfg.talk); diff --git a/extensions/venice/models.ts b/extensions/venice/models.ts index 2c251ad4f18..cd1d7409e47 100644 --- a/extensions/venice/models.ts +++ b/extensions/venice/models.ts @@ -1,5 +1,6 @@ import type { ModelDefinitionConfig } from "openclaw/plugin-sdk/provider-model-shared"; import { createSubsystemLogger, retryAsync } from "openclaw/plugin-sdk/runtime-env"; +import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime"; const log = createSubsystemLogger("venice-models"); @@ -504,7 +505,7 @@ function isRetryableVeniceDiscoveryError(err: unknown): boolean { if (err instanceof Error && err.name === "AbortError") { return true; } - if (err instanceof TypeError && err.message.toLowerCase() === "fetch failed") { + if (err instanceof TypeError && normalizeLowercaseStringOrEmpty(err.message) === "fetch failed") { return true; } return hasRetryableNetworkCode(err); @@ -609,11 +610,12 @@ export async function discoverVeniceModels(): Promise { models.push(definition); } else { const apiSpec = apiModel.model_spec; + const lowerModelId = normalizeLowercaseStringOrEmpty(apiModel.id); const isReasoning = apiSpec?.capabilities?.supportsReasoning || - apiModel.id.toLowerCase().includes("thinking") || - apiModel.id.toLowerCase().includes("reason") || - apiModel.id.toLowerCase().includes("r1"); + lowerModelId.includes("thinking") || + lowerModelId.includes("reason") || + lowerModelId.includes("r1"); const hasVision = apiSpec?.capabilities?.supportsVision === true; diff --git a/ui/src/ui/views/usage-metrics.ts b/ui/src/ui/views/usage-metrics.ts index 1140d83c17b..1d11609ccf4 100644 --- a/ui/src/ui/views/usage-metrics.ts +++ b/ui/src/ui/views/usage-metrics.ts @@ -5,6 +5,7 @@ import { mergeUsageLatency, } from "../../../../src/shared/usage-aggregates.js"; import { t } from "../../i18n/index.ts"; +import { normalizeLowercaseStringOrEmpty } from "../string-coerce.ts"; import { UsageSessionEntry, UsageTotals, UsageAggregates } from "./usageTypes.ts"; const CHARS_PER_TOKEN = 4; @@ -105,7 +106,7 @@ function buildPeakErrorHours(sessions: UsageSessionEntry[], timeZone: "local" | .map((entry) => ({ label: formatHourLabel(entry.hour), value: `${(entry.rate * 100).toFixed(2)}%`, - sub: `${Math.round(entry.errors)} ${t("usage.overview.errors").toLowerCase()} · ${Math.round(entry.msgs)} ${t("usage.overview.messagesAbbrev")}`, + sub: `${Math.round(entry.errors)} ${normalizeLowercaseStringOrEmpty(t("usage.overview.errors"))} · ${Math.round(entry.msgs)} ${t("usage.overview.messagesAbbrev")}`, })); } @@ -198,7 +199,7 @@ function renderUsageMosaic(
${t("usage.mosaic.subtitleEmpty")}
- ${formatTokens(0)} ${t("usage.metrics.tokens").toLowerCase()} + ${formatTokens(0)} ${normalizeLowercaseStringOrEmpty(t("usage.metrics.tokens"))}
@@ -226,7 +227,8 @@ function renderUsageMosaic(
- ${formatTokens(stats.totalTokens)} ${t("usage.metrics.tokens").toLowerCase()} + ${formatTokens(stats.totalTokens)} + ${normalizeLowercaseStringOrEmpty(t("usage.metrics.tokens"))}
@@ -260,7 +262,9 @@ function renderUsageMosaic( value > 0 ? `color-mix(in srgb, var(--accent) ${(8 + intensity * 70).toFixed(1)}%, transparent)` : "transparent"; - const title = `${hour}:00 · ${formatTokens(value)} ${t("usage.metrics.tokens").toLowerCase()}`; + const title = `${hour}:00 · ${formatTokens(value)} ${normalizeLowercaseStringOrEmpty( + t("usage.metrics.tokens"), + )}`; const border = intensity > 0.7 ? "color-mix(in srgb, var(--accent) 60%, transparent)" diff --git a/ui/src/ui/views/usage-render-details.ts b/ui/src/ui/views/usage-render-details.ts index bad55639fa8..703715df1a2 100644 --- a/ui/src/ui/views/usage-render-details.ts +++ b/ui/src/ui/views/usage-render-details.ts @@ -1,6 +1,7 @@ import { html, svg, nothing } from "lit"; import { formatDurationCompact } from "../../../../src/infra/format-time/format-duration.ts"; import { t } from "../../i18n/index.ts"; +import { normalizeLowercaseStringOrEmpty } from "../string-coerce.ts"; import { parseToolSummary } from "../usage-helpers.ts"; import { charsToTokens, formatCost, formatTokens } from "./usage-metrics.ts"; import { renderInsightList } from "./usage-render-overview.ts"; @@ -124,8 +125,10 @@ function renderSessionSummary(
${t("usage.overview.messages")}
${usage.messageCounts?.total ?? 0}
- ${usage.messageCounts?.user ?? 0} ${t("usage.overview.user").toLowerCase()} · - ${usage.messageCounts?.assistant ?? 0} ${t("usage.overview.assistant").toLowerCase()} + ${usage.messageCounts?.user ?? 0} + ${normalizeLowercaseStringOrEmpty(t("usage.overview.user"))} · + ${usage.messageCounts?.assistant ?? 0} + ${normalizeLowercaseStringOrEmpty(t("usage.overview.assistant"))}
@@ -280,9 +283,10 @@ function renderSessionDetailPanel( ${usage ? html` ${formatTokens(headerStats.totalTokens)} ${t( - "usage.metrics.tokens", - ).toLowerCase()}${cursorIndicator}${formatTokens(headerStats.totalTokens)} + ${normalizeLowercaseStringOrEmpty( + t("usage.metrics.tokens"), + )}${cursorIndicator} ${formatCost(headerStats.totalCost)}${cursorIndicator} ` @@ -582,7 +586,7 @@ function renderTimeSeriesCompact( hour: "2-digit", minute: "2-digit", }), - `${formatTokens(val)} ${t("usage.metrics.tokens").toLowerCase()}`, + `${formatTokens(val)} ${normalizeLowercaseStringOrEmpty(t("usage.metrics.tokens"))}`, ]; if (breakdownByType) { tooltipLines.push(`Out ${formatTokens(p.output)}`); @@ -1034,7 +1038,7 @@ function renderSessionLogsCompact( `; } - const normalizedQuery = filters.query.trim().toLowerCase(); + const normalizedQuery = normalizeLowercaseStringOrEmpty(filters.query); const entries = logs.map((log) => { const toolInfo = parseToolSummary(log.content); const cleanContent = toolInfo.cleanContent || log.content; @@ -1069,7 +1073,7 @@ function renderSessionLogsCompact( } } if (normalizedQuery) { - const haystack = entry.cleanContent.toLowerCase(); + const haystack = normalizeLowercaseStringOrEmpty(entry.cleanContent); if (!haystack.includes(normalizedQuery)) { return false; } @@ -1093,7 +1097,7 @@ function renderSessionLogsCompact( ${t("usage.details.conversation")} - (${displayedCount} ${t("usage.overview.messages").toLowerCase()}) + (${displayedCount} ${normalizeLowercaseStringOrEmpty(t("usage.overview.messages"))})