diff --git a/extensions/diffs/src/tool.ts b/extensions/diffs/src/tool.ts index 0851a292b9d..c0fcb36b4fd 100644 --- a/extensions/diffs/src/tool.ts +++ b/extensions/diffs/src/tool.ts @@ -428,10 +428,10 @@ function buildArtifactContext( } const artifactContext = { - agentId: normalizeContextString(context.agentId), - sessionId: normalizeContextString(context.sessionId), - messageChannel: normalizeContextString(context.messageChannel), - agentAccountId: normalizeContextString(context.agentAccountId), + agentId: normalizeOptionalString(context.agentId), + sessionId: normalizeOptionalString(context.sessionId), + messageChannel: normalizeOptionalString(context.messageChannel), + agentAccountId: normalizeOptionalString(context.agentAccountId), }; return Object.values(artifactContext).some((value) => value !== undefined) @@ -439,11 +439,6 @@ function buildArtifactContext( : undefined; } -function normalizeContextString(value: string | undefined): string | undefined { - const normalized = normalizeOptionalString(value); - return normalized ? normalized : undefined; -} - function normalizeDiffInput(params: DiffsToolParams): DiffInput { const patch = params.patch?.trim(); const before = params.before; diff --git a/extensions/googlechat/src/monitor.ts b/extensions/googlechat/src/monitor.ts index 1071716fbb3..bc916c80641 100644 --- a/extensions/googlechat/src/monitor.ts +++ b/extensions/googlechat/src/monitor.ts @@ -3,7 +3,7 @@ import { deliverTextOrMediaReply, resolveSendableOutboundReplyParts, } from "openclaw/plugin-sdk/reply-payload"; -import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime"; +import { normalizeOptionalLowercaseString } from "openclaw/plugin-sdk/text-runtime"; import type { OpenClawConfig } from "../runtime-api.js"; import { createChannelReplyPipeline, @@ -73,7 +73,7 @@ export function registerGoogleChatWebhookTarget(target: WebhookTarget): () => vo } function normalizeAudienceType(value?: string | null): GoogleChatAudienceType | undefined { - const normalized = normalizeOptionalString(value)?.toLowerCase(); + const normalized = normalizeOptionalLowercaseString(value); if (normalized === "app-url" || normalized === "app_url" || normalized === "app") { return "app-url"; } diff --git a/extensions/telegram/src/status-reaction-variants.ts b/extensions/telegram/src/status-reaction-variants.ts index 137e7b2eeda..32e974c8168 100644 --- a/extensions/telegram/src/status-reaction-variants.ts +++ b/extensions/telegram/src/status-reaction-variants.ts @@ -114,11 +114,6 @@ const STATUS_REACTION_EMOJI_KEYS: StatusReactionEmojiKey[] = [ "compacting", ]; -function normalizeEmoji(value: string | undefined): string | undefined { - const trimmed = normalizeOptionalString(value); - return trimmed ? trimmed : undefined; -} - function toUniqueNonEmpty(values: string[]): string[] { return Array.from(new Set(values.map((value) => value.trim()).filter(Boolean))); } @@ -128,18 +123,18 @@ export function resolveTelegramStatusReactionEmojis(params: { overrides?: StatusReactionEmojis; }): Required { const { overrides } = params; - const queuedFallback = normalizeEmoji(params.initialEmoji) ?? DEFAULT_EMOJIS.queued; + const queuedFallback = normalizeOptionalString(params.initialEmoji) ?? DEFAULT_EMOJIS.queued; return { - queued: normalizeEmoji(overrides?.queued) ?? queuedFallback, - thinking: normalizeEmoji(overrides?.thinking) ?? DEFAULT_EMOJIS.thinking, - tool: normalizeEmoji(overrides?.tool) ?? DEFAULT_EMOJIS.tool, - coding: normalizeEmoji(overrides?.coding) ?? DEFAULT_EMOJIS.coding, - web: normalizeEmoji(overrides?.web) ?? DEFAULT_EMOJIS.web, - done: normalizeEmoji(overrides?.done) ?? DEFAULT_EMOJIS.done, - error: normalizeEmoji(overrides?.error) ?? DEFAULT_EMOJIS.error, - stallSoft: normalizeEmoji(overrides?.stallSoft) ?? DEFAULT_EMOJIS.stallSoft, - stallHard: normalizeEmoji(overrides?.stallHard) ?? DEFAULT_EMOJIS.stallHard, - compacting: normalizeEmoji(overrides?.compacting) ?? DEFAULT_EMOJIS.compacting, + queued: normalizeOptionalString(overrides?.queued) ?? queuedFallback, + thinking: normalizeOptionalString(overrides?.thinking) ?? DEFAULT_EMOJIS.thinking, + tool: normalizeOptionalString(overrides?.tool) ?? DEFAULT_EMOJIS.tool, + coding: normalizeOptionalString(overrides?.coding) ?? DEFAULT_EMOJIS.coding, + web: normalizeOptionalString(overrides?.web) ?? DEFAULT_EMOJIS.web, + done: normalizeOptionalString(overrides?.done) ?? DEFAULT_EMOJIS.done, + error: normalizeOptionalString(overrides?.error) ?? DEFAULT_EMOJIS.error, + stallSoft: normalizeOptionalString(overrides?.stallSoft) ?? DEFAULT_EMOJIS.stallSoft, + stallHard: normalizeOptionalString(overrides?.stallHard) ?? DEFAULT_EMOJIS.stallHard, + compacting: normalizeOptionalString(overrides?.compacting) ?? DEFAULT_EMOJIS.compacting, }; } @@ -148,7 +143,7 @@ export function buildTelegramStatusReactionVariants( ): Map { const variantsByRequested = new Map(); for (const key of STATUS_REACTION_EMOJI_KEYS) { - const requested = normalizeEmoji(emojis[key]); + const requested = normalizeOptionalString(emojis[key]); if (!requested) { continue; } @@ -225,7 +220,7 @@ export function resolveTelegramReactionVariant(params: { variantsByRequestedEmoji: Map; allowedEmojiReactions?: Set | null; }): TelegramReactionEmoji | undefined { - const requestedEmoji = normalizeEmoji(params.requestedEmoji); + const requestedEmoji = normalizeOptionalString(params.requestedEmoji); if (!requestedEmoji) { return undefined; } diff --git a/src/acp/types.ts b/src/acp/types.ts index f12175dcb27..1d0e7c1d828 100644 --- a/src/acp/types.ts +++ b/src/acp/types.ts @@ -1,5 +1,5 @@ import type { SessionId } from "@agentclientprotocol/sdk"; -import { normalizeOptionalString } from "../shared/string-coerce.js"; +import { normalizeOptionalLowercaseString } from "../shared/string-coerce.js"; import { VERSION } from "../version.js"; export const ACP_PROVENANCE_MODE_VALUES = ["off", "meta", "meta+receipt"] as const; @@ -9,7 +9,7 @@ export type AcpProvenanceMode = (typeof ACP_PROVENANCE_MODE_VALUES)[number]; export function normalizeAcpProvenanceMode( value: string | undefined, ): AcpProvenanceMode | undefined { - const normalized = normalizeOptionalString(value)?.toLowerCase(); + const normalized = normalizeOptionalLowercaseString(value); if (!normalized) { return undefined; } diff --git a/src/agents/pi-embedded-subscribe.tools.ts b/src/agents/pi-embedded-subscribe.tools.ts index 115de5264ec..02dbc37ba91 100644 --- a/src/agents/pi-embedded-subscribe.tools.ts +++ b/src/agents/pi-embedded-subscribe.tools.ts @@ -2,7 +2,11 @@ import { getChannelPlugin, normalizeChannelId } from "../channels/plugins/index. import { normalizeTargetForProvider } from "../infra/outbound/target-normalization.js"; import { splitMediaFromOutput } from "../media/parse.js"; import { pluginRegistrationContractRegistry } from "../plugins/contracts/registry.js"; -import { normalizeOptionalString, readStringValue } from "../shared/string-coerce.js"; +import { + normalizeOptionalLowercaseString, + normalizeOptionalString, + readStringValue, +} from "../shared/string-coerce.js"; import { truncateUtf16Safe } from "../utils.js"; import { collectTextContentBlocks } from "./content-blocks.js"; import { type MessagingToolSend } from "./pi-embedded-messaging.js"; @@ -182,7 +186,7 @@ function readToolResultDetails(result: unknown): Record | undef function readToolResultStatus(result: unknown): string | undefined { const status = readToolResultDetails(result)?.status; - return normalizeOptionalString(status)?.toLowerCase(); + return normalizeOptionalLowercaseString(status); } function isExternalToolResult(result: unknown): boolean { diff --git a/src/auto-reply/reply/dispatch-acp-delivery.ts b/src/auto-reply/reply/dispatch-acp-delivery.ts index 0b691b5cd07..44603408200 100644 --- a/src/auto-reply/reply/dispatch-acp-delivery.ts +++ b/src/auto-reply/reply/dispatch-acp-delivery.ts @@ -3,7 +3,10 @@ import type { OpenClawConfig } from "../../config/config.js"; import type { TtsAutoMode } from "../../config/types.tts.js"; import { logVerbose } from "../../globals.js"; import { formatErrorMessage } from "../../infra/errors.js"; -import { normalizeOptionalString } from "../../shared/string-coerce.js"; +import { + normalizeOptionalLowercaseString, + normalizeOptionalString, +} from "../../shared/string-coerce.js"; import { resolveStatusTtsSnapshot } from "../../tts/status-config.js"; import { resolveConfiguredTtsMode } from "../../tts/tts-config.js"; import type { FinalizedMsgContext } from "../templating.js"; @@ -53,11 +56,6 @@ type ToolMessageHandle = { messageId: string; }; -function normalizeDeliveryChannel(value: string | undefined): string | undefined { - const normalized = normalizeOptionalString(value)?.toLowerCase(); - return normalized || undefined; -} - async function shouldTreatDeliveredTextAsVisible(params: { channel: string | undefined; kind: ReplyDispatchKind; @@ -70,7 +68,7 @@ async function shouldTreatDeliveredTextAsVisible(params: { if (params.kind === "final") { return true; } - const channelId = normalizeDeliveryChannel(params.channel); + const channelId = normalizeOptionalLowercaseString(params.channel); if (!channelId) { return false; } @@ -185,8 +183,8 @@ export function createAcpDispatchDeliveryCoordinator(params: { }, toolMessageByCallId: new Map(), }; - const directChannel = normalizeDeliveryChannel(params.ctx.Provider ?? params.ctx.Surface); - const routedChannel = normalizeDeliveryChannel(params.originatingChannel); + const directChannel = normalizeOptionalLowercaseString(params.ctx.Provider ?? params.ctx.Surface); + const routedChannel = normalizeOptionalLowercaseString(params.originatingChannel); const explicitAccountId = normalizeOptionalString(params.ctx.AccountId); const resolvedAccountId = explicitAccountId ?? diff --git a/src/auto-reply/status.ts b/src/auto-reply/status.ts index 8b2662549dc..a65e1258023 100644 --- a/src/auto-reply/status.ts +++ b/src/auto-reply/status.ts @@ -31,7 +31,7 @@ import { resolveCommitHash } from "../infra/git-commit.js"; import type { MediaUnderstandingDecision } from "../media-understanding/types.js"; import { listPluginCommands } from "../plugins/commands.js"; import { resolveAgentIdFromSessionKey } from "../routing/session-key.js"; -import { normalizeOptionalString } from "../shared/string-coerce.js"; +import { normalizeOptionalLowercaseString } from "../shared/string-coerce.js"; import { resolveStatusTtsSnapshot } from "../tts/status-config.js"; import { estimateUsageCost, @@ -98,7 +98,7 @@ type StatusArgs = { type NormalizedAuthMode = "api-key" | "oauth" | "token" | "aws-sdk" | "mixed" | "unknown"; function normalizeAuthMode(value?: string): NormalizedAuthMode | undefined { - const normalized = normalizeOptionalString(value)?.toLowerCase(); + const normalized = normalizeOptionalLowercaseString(value); if (!normalized) { return undefined; } diff --git a/src/channels/chat-type.ts b/src/channels/chat-type.ts index fced60739fb..2f2914df8cf 100644 --- a/src/channels/chat-type.ts +++ b/src/channels/chat-type.ts @@ -1,9 +1,9 @@ -import { normalizeOptionalString } from "../shared/string-coerce.js"; +import { normalizeOptionalLowercaseString } from "../shared/string-coerce.js"; export type ChatType = "direct" | "group" | "channel"; export function normalizeChatType(raw?: string): ChatType | undefined { - const value = normalizeOptionalString(raw)?.toLowerCase(); + const value = normalizeOptionalLowercaseString(raw); if (!value) { return undefined; } diff --git a/src/config/sessions/types.ts b/src/config/sessions/types.ts index 719b9c90ee7..140f8f05aac 100644 --- a/src/config/sessions/types.ts +++ b/src/config/sessions/types.ts @@ -235,14 +235,9 @@ export type SessionEntry = { acp?: SessionAcpMeta; }; -function normalizeRuntimeField(value: string | undefined): string | undefined { - const trimmed = normalizeOptionalString(value); - return trimmed ? trimmed : undefined; -} - export function normalizeSessionRuntimeModelFields(entry: SessionEntry): SessionEntry { - const normalizedModel = normalizeRuntimeField(entry.model); - const normalizedProvider = normalizeRuntimeField(entry.modelProvider); + const normalizedModel = normalizeOptionalString(entry.model); + const normalizedProvider = normalizeOptionalString(entry.modelProvider); let next = entry; if (!normalizedModel) { @@ -327,8 +322,8 @@ export function mergeSessionEntryWithPolicy( // Guard against stale provider carry-over when callers patch runtime model // without also patching runtime provider. if (Object.hasOwn(patch, "model") && !Object.hasOwn(patch, "modelProvider")) { - const patchedModel = normalizeRuntimeField(patch.model); - const existingModel = normalizeRuntimeField(existing.model); + const patchedModel = normalizeOptionalString(patch.model); + const existingModel = normalizeOptionalString(existing.model); if (patchedModel && patchedModel !== existingModel) { delete next.modelProvider; } diff --git a/src/infra/process-respawn.ts b/src/infra/process-respawn.ts index 3c84288aea8..9fac4214600 100644 --- a/src/infra/process-respawn.ts +++ b/src/infra/process-respawn.ts @@ -1,5 +1,5 @@ import { spawn } from "node:child_process"; -import { normalizeOptionalString } from "../shared/string-coerce.js"; +import { normalizeOptionalLowercaseString } from "../shared/string-coerce.js"; import { formatErrorMessage } from "./errors.js"; import { triggerOpenClawRestart } from "./restart.js"; import { detectRespawnSupervisor } from "./supervisor-markers.js"; @@ -13,7 +13,7 @@ export type GatewayRespawnResult = { }; function isTruthy(value: string | undefined): boolean { - const normalized = normalizeOptionalString(value)?.toLowerCase(); + const normalized = normalizeOptionalLowercaseString(value); return normalized === "1" || normalized === "true" || normalized === "yes" || normalized === "on"; } diff --git a/src/infra/update-channels.ts b/src/infra/update-channels.ts index 0ab618c8c52..3d0316257fb 100644 --- a/src/infra/update-channels.ts +++ b/src/infra/update-channels.ts @@ -1,4 +1,4 @@ -import { normalizeOptionalString } from "../shared/string-coerce.js"; +import { normalizeOptionalLowercaseString } from "../shared/string-coerce.js"; export type UpdateChannel = "stable" | "beta" | "dev"; export type UpdateChannelSource = "config" | "git-tag" | "git-branch" | "default"; @@ -8,7 +8,7 @@ export const DEFAULT_GIT_CHANNEL: UpdateChannel = "dev"; export const DEV_BRANCH = "main"; export function normalizeUpdateChannel(value?: string | null): UpdateChannel | null { - const normalized = normalizeOptionalString(value)?.toLowerCase(); + const normalized = normalizeOptionalLowercaseString(value); if (!normalized) { return null; } diff --git a/src/media-understanding/scope.ts b/src/media-understanding/scope.ts index 7a68c901a6c..f61a9cca94a 100644 --- a/src/media-understanding/scope.ts +++ b/src/media-understanding/scope.ts @@ -1,11 +1,11 @@ import { normalizeChatType } from "../channels/chat-type.js"; import type { MediaUnderstandingScopeConfig } from "../config/types.tools.js"; -import { normalizeOptionalString } from "../shared/string-coerce.js"; +import { normalizeOptionalLowercaseString } from "../shared/string-coerce.js"; export type MediaUnderstandingScopeDecision = "allow" | "deny"; function normalizeDecision(value?: string | null): MediaUnderstandingScopeDecision | undefined { - const normalized = normalizeOptionalString(value)?.toLowerCase(); + const normalized = normalizeOptionalLowercaseString(value); if (normalized === "allow") { return "allow"; } @@ -15,11 +15,6 @@ function normalizeDecision(value?: string | null): MediaUnderstandingScopeDecisi return undefined; } -function normalizeMatch(value?: string | null): string | undefined { - const normalized = normalizeOptionalString(value)?.toLowerCase(); - return normalized || undefined; -} - export function normalizeMediaUnderstandingChatType(raw?: string | null): string | undefined { return normalizeChatType(raw ?? undefined); } @@ -35,9 +30,9 @@ export function resolveMediaUnderstandingScope(params: { return "allow"; } - const channel = normalizeMatch(params.channel); + const channel = normalizeOptionalLowercaseString(params.channel); const chatType = normalizeMediaUnderstandingChatType(params.chatType); - const sessionKey = normalizeMatch(params.sessionKey) ?? ""; + const sessionKey = normalizeOptionalLowercaseString(params.sessionKey) ?? ""; for (const rule of scope.rules ?? []) { if (!rule) { @@ -45,9 +40,9 @@ export function resolveMediaUnderstandingScope(params: { } const action = normalizeDecision(rule.action) ?? "allow"; const match = rule.match ?? {}; - const matchChannel = normalizeMatch(match.channel); + const matchChannel = normalizeOptionalLowercaseString(match.channel); const matchChatType = normalizeMediaUnderstandingChatType(match.chatType); - const matchPrefix = normalizeMatch(match.keyPrefix); + const matchPrefix = normalizeOptionalLowercaseString(match.keyPrefix); if (matchChannel && matchChannel !== channel) { continue; diff --git a/src/media/channel-inbound-roots.ts b/src/media/channel-inbound-roots.ts index b6143b2a360..b22f381a965 100644 --- a/src/media/channel-inbound-roots.ts +++ b/src/media/channel-inbound-roots.ts @@ -1,10 +1,10 @@ import type { MsgContext } from "../auto-reply/templating.js"; import { getBootstrapChannelPlugin } from "../channels/plugins/bootstrap-registry.js"; import type { OpenClawConfig } from "../config/config.js"; -import { normalizeOptionalString } from "../shared/string-coerce.js"; +import { normalizeOptionalLowercaseString } from "../shared/string-coerce.js"; function findChannelMessagingAdapter(channelId?: string | null) { - const normalized = normalizeOptionalString(channelId)?.toLowerCase(); + const normalized = normalizeOptionalLowercaseString(channelId); if (!normalized) { return undefined; } diff --git a/src/plugin-sdk/channel-streaming.ts b/src/plugin-sdk/channel-streaming.ts index cd6a1755383..dac5307ea85 100644 --- a/src/plugin-sdk/channel-streaming.ts +++ b/src/plugin-sdk/channel-streaming.ts @@ -7,7 +7,7 @@ import type { SlackChannelStreamingConfig, TextChunkMode, } from "../config/types.base.js"; -import { normalizeOptionalString } from "../shared/string-coerce.js"; +import { normalizeOptionalLowercaseString } from "../shared/string-coerce.js"; export type { ChannelDeliveryStreamingConfig, @@ -48,7 +48,7 @@ function normalizeStreamingMode(value: unknown): string | null { if (typeof value !== "string") { return null; } - const normalized = normalizeOptionalString(value)?.toLowerCase(); + const normalized = normalizeOptionalLowercaseString(value); return normalized || null; } diff --git a/src/shared/string-coerce.ts b/src/shared/string-coerce.ts index 45e14dbb274..f57315f0dc4 100644 --- a/src/shared/string-coerce.ts +++ b/src/shared/string-coerce.ts @@ -14,6 +14,10 @@ export function normalizeOptionalString(value: unknown): string | undefined { return normalizeNullableString(value) ?? undefined; } +export function normalizeOptionalLowercaseString(value: unknown): string | undefined { + return normalizeOptionalString(value)?.toLowerCase(); +} + export function resolvePrimaryStringValue(value: unknown): string | undefined { if (typeof value === "string") { return normalizeOptionalString(value); diff --git a/src/tts/status-config.ts b/src/tts/status-config.ts index f78ecccbfe1..a9412cfa6c2 100644 --- a/src/tts/status-config.ts +++ b/src/tts/status-config.ts @@ -2,7 +2,10 @@ import fs from "node:fs"; import path from "node:path"; import type { OpenClawConfig } from "../config/config.js"; import type { TtsAutoMode, TtsConfig, TtsProvider } from "../config/types.tts.js"; -import { normalizeOptionalString } from "../shared/string-coerce.js"; +import { + normalizeOptionalLowercaseString, + normalizeOptionalString, +} from "../shared/string-coerce.js"; import { CONFIG_DIR, resolveUserPath } from "../utils.js"; import { normalizeTtsAutoMode } from "./tts-auto-mode.js"; @@ -33,7 +36,7 @@ function resolveConfiguredTtsAutoMode(raw: TtsConfig): TtsAutoMode { function normalizeConfiguredSpeechProviderId( providerId: string | undefined, ): TtsProvider | undefined { - const normalized = normalizeOptionalString(providerId)?.toLowerCase(); + const normalized = normalizeOptionalLowercaseString(providerId); if (!normalized) { return undefined; } diff --git a/src/tts/tts-auto-mode.ts b/src/tts/tts-auto-mode.ts index 8800c9b3046..759978ebce3 100644 --- a/src/tts/tts-auto-mode.ts +++ b/src/tts/tts-auto-mode.ts @@ -1,5 +1,5 @@ import type { TtsAutoMode } from "../config/types.tts.js"; -import { normalizeOptionalString } from "../shared/string-coerce.js"; +import { normalizeOptionalLowercaseString } from "../shared/string-coerce.js"; export const TTS_AUTO_MODES = new Set(["off", "always", "inbound", "tagged"]); @@ -7,7 +7,7 @@ export function normalizeTtsAutoMode(value: unknown): TtsAutoMode | undefined { if (typeof value !== "string") { return undefined; } - const normalized = normalizeOptionalString(value)?.toLowerCase(); + const normalized = normalizeOptionalLowercaseString(value); if (TTS_AUTO_MODES.has(normalized as TtsAutoMode)) { return normalized as TtsAutoMode; }