From f2b13b0a1aea604c1f7850e15bdf6482de8aa34a Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Tue, 7 Apr 2026 14:20:57 +0100 Subject: [PATCH] refactor: dedupe slack matrix venice lowercase helpers --- extensions/matrix/src/approval-native.ts | 2 +- .../matrix/src/matrix/client/private-network-host.ts | 3 ++- extensions/matrix/src/matrix/monitor/location.ts | 5 +++-- extensions/matrix/src/tool-actions.ts | 3 ++- extensions/slack/src/approval-native.ts | 2 +- extensions/slack/src/channel-type.ts | 3 ++- extensions/slack/src/directory-live.ts | 10 +++++++--- extensions/slack/src/interactive-replies.ts | 3 ++- extensions/slack/src/monitor/allow-list.ts | 3 ++- extensions/slack/src/monitor/channel-type.ts | 3 ++- extensions/slack/src/monitor/media.ts | 3 ++- .../slack/src/monitor/message-handler/dispatch.ts | 5 +++-- extensions/slack/src/monitor/slash.ts | 4 ++-- extensions/slack/src/resolve-channels.ts | 3 ++- extensions/slack/src/setup-core.ts | 11 ++++++++--- extensions/slack/src/stream-mode.ts | 3 ++- extensions/venice/index.ts | 3 ++- 17 files changed, 45 insertions(+), 24 deletions(-) diff --git a/extensions/matrix/src/approval-native.ts b/extensions/matrix/src/approval-native.ts index 70398e8f6d6..1f4ba90b65f 100644 --- a/extensions/matrix/src/approval-native.ts +++ b/extensions/matrix/src/approval-native.ts @@ -39,7 +39,7 @@ const MATRIX_PLUGIN_NATIVE_DELIVERY_DISABLED = { function normalizeComparableTarget(value: string): string { const target = resolveMatrixTargetIdentity(value); if (!target) { - return value.trim().toLowerCase(); + return normalizeLowercaseStringOrEmpty(value); } if (target.kind === "user") { return `user:${normalizeMatrixUserId(target.id)}`; diff --git a/extensions/matrix/src/matrix/client/private-network-host.ts b/extensions/matrix/src/matrix/client/private-network-host.ts index 4507d0ea7cb..d8d96b1da01 100644 --- a/extensions/matrix/src/matrix/client/private-network-host.ts +++ b/extensions/matrix/src/matrix/client/private-network-host.ts @@ -1,7 +1,8 @@ import net from "node:net"; +import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime"; function normalizeHost(host: string): string { - const normalized = host.trim().toLowerCase().replace(/\.+$/, ""); + const normalized = normalizeLowercaseStringOrEmpty(host).replace(/\.+$/, ""); return normalized.startsWith("[") && normalized.endsWith("]") ? normalized.slice(1, -1) : normalized; diff --git a/extensions/matrix/src/matrix/monitor/location.ts b/extensions/matrix/src/matrix/monitor/location.ts index fd01e82909f..59f3e05124c 100644 --- a/extensions/matrix/src/matrix/monitor/location.ts +++ b/extensions/matrix/src/matrix/monitor/location.ts @@ -1,3 +1,4 @@ +import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime"; import type { LocationMessageEventContent } from "../sdk.js"; import { formatLocationText, toLocationContext, type NormalizedLocation } from "./runtime-api.js"; import { EventType } from "./types.js"; @@ -18,7 +19,7 @@ function parseGeoUri(value: string): GeoUriParams | null { if (!trimmed) { return null; } - if (!trimmed.toLowerCase().startsWith("geo:")) { + if (!normalizeLowercaseStringOrEmpty(trimmed).startsWith("geo:")) { return null; } const payload = trimmed.slice(4); @@ -42,7 +43,7 @@ function parseGeoUri(value: string): GeoUriParams | null { const eqIndex = segment.indexOf("="); const rawKey = eqIndex === -1 ? segment : segment.slice(0, eqIndex); const rawValue = eqIndex === -1 ? "" : segment.slice(eqIndex + 1); - const key = rawKey.trim().toLowerCase(); + const key = normalizeLowercaseStringOrEmpty(rawKey); if (!key) { continue; } diff --git a/extensions/matrix/src/tool-actions.ts b/extensions/matrix/src/tool-actions.ts index 78c76b555a2..02ba4ff099d 100644 --- a/extensions/matrix/src/tool-actions.ts +++ b/extensions/matrix/src/tool-actions.ts @@ -1,4 +1,5 @@ import type { AgentToolResult } from "@mariozechner/pi-agent-core"; +import { normalizeOptionalLowercaseString } from "openclaw/plugin-sdk/text-runtime"; import { resolveMatrixAccountConfig } from "./matrix/accounts.js"; import { bootstrapMatrixVerification, @@ -433,7 +434,7 @@ export async function handleMatrixAction( } if (action === "verificationStart") { const methodRaw = readStringParam(params, "method"); - const method = methodRaw?.trim().toLowerCase(); + const method = normalizeOptionalLowercaseString(methodRaw); if (method && method !== "sas") { throw new Error( "Matrix verificationStart only supports method=sas; use verificationGenerateQr/verificationScanQr for QR flows.", diff --git a/extensions/slack/src/approval-native.ts b/extensions/slack/src/approval-native.ts index e5eb164029e..6cfbc67ae64 100644 --- a/extensions/slack/src/approval-native.ts +++ b/extensions/slack/src/approval-native.ts @@ -36,7 +36,7 @@ function extractSlackSessionKind( } function normalizeComparableTarget(value: string): string { - return value.trim().toLowerCase(); + return normalizeLowercaseStringOrEmpty(value); } function normalizeSlackThreadMatchKey(threadId?: string): string { diff --git a/extensions/slack/src/channel-type.ts b/extensions/slack/src/channel-type.ts index 4f935cb912a..372740b5d99 100644 --- a/extensions/slack/src/channel-type.ts +++ b/extensions/slack/src/channel-type.ts @@ -1,3 +1,4 @@ +import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime"; import { resolveSlackAccount } from "./accounts.js"; import { createSlackWebClient } from "./client.js"; import { normalizeAllowListLower } from "./monitor/allow-list.js"; @@ -36,7 +37,7 @@ export async function resolveSlackChannelType(params: { const channelKeys = Object.keys(account.channels ?? {}); if ( channelKeys.some((key) => { - const normalized = key.trim().toLowerCase(); + const normalized = normalizeLowercaseStringOrEmpty(key); return ( normalized === channelIdLower || normalized === `channel:${channelIdLower}` || diff --git a/extensions/slack/src/directory-live.ts b/extensions/slack/src/directory-live.ts index 93d83978268..388cbd4dcdd 100644 --- a/extensions/slack/src/directory-live.ts +++ b/extensions/slack/src/directory-live.ts @@ -2,6 +2,10 @@ import type { ChannelDirectoryEntry, DirectoryConfigParams, } from "openclaw/plugin-sdk/directory-runtime"; +import { + normalizeLowercaseStringOrEmpty, + normalizeOptionalLowercaseString, +} from "openclaw/plugin-sdk/text-runtime"; import { resolveSlackAccount } from "./accounts.js"; import { createSlackWebClient } from "./client.js"; @@ -42,7 +46,7 @@ function resolveReadToken(params: DirectoryConfigParams): string | undefined { } function normalizeQuery(value?: string | null): string { - return value?.trim().toLowerCase() ?? ""; + return normalizeLowercaseStringOrEmpty(value); } function buildUserRank(user: SlackUser): number { @@ -89,7 +93,7 @@ export async function listSlackDirectoryPeersLive( const handle = member.name; const email = member.profile?.email; const candidates = [name, handle, email] - .map((item) => item?.trim().toLowerCase()) + .map((item) => normalizeOptionalLowercaseString(item)) .filter(Boolean); if (!query) { return true; @@ -153,7 +157,7 @@ export async function listSlackDirectoryGroupsLive( } while (cursor); const filtered = channels.filter((channel) => { - const name = channel.name?.trim().toLowerCase(); + const name = normalizeOptionalLowercaseString(channel.name); if (!query) { return true; } diff --git a/extensions/slack/src/interactive-replies.ts b/extensions/slack/src/interactive-replies.ts index e1f4a312505..c700ec06863 100644 --- a/extensions/slack/src/interactive-replies.ts +++ b/extensions/slack/src/interactive-replies.ts @@ -1,5 +1,6 @@ import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime"; import type { ReplyPayload } from "openclaw/plugin-sdk/reply-runtime"; +import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime"; import { resolveDefaultSlackAccountId, resolveSlackAccount } from "./accounts.js"; const SLACK_BUTTON_MAX_ITEMS = 5; @@ -154,7 +155,7 @@ function resolveInteractiveRepliesFromCapabilities(capabilities: unknown): boole } if (Array.isArray(capabilities)) { return capabilities.some( - (entry) => String(entry).trim().toLowerCase() === "interactivereplies", + (entry) => normalizeLowercaseStringOrEmpty(String(entry)) === "interactivereplies", ); } if (typeof capabilities === "object") { diff --git a/extensions/slack/src/monitor/allow-list.ts b/extensions/slack/src/monitor/allow-list.ts index 8717ed57bc4..56a5e5aca7d 100644 --- a/extensions/slack/src/monitor/allow-list.ts +++ b/extensions/slack/src/monitor/allow-list.ts @@ -8,6 +8,7 @@ import { normalizeStringEntries, normalizeStringEntriesLower, } from "openclaw/plugin-sdk/string-normalization-runtime"; +import { normalizeOptionalLowercaseString } from "openclaw/plugin-sdk/text-runtime"; const SLACK_SLUG_CACHE_MAX = 512; const slackSlugCache = new Map(); @@ -38,7 +39,7 @@ export function normalizeAllowListLower(list?: Array) { } export function normalizeSlackAllowOwnerEntry(entry: string): string | undefined { - const trimmed = entry.trim().toLowerCase(); + const trimmed = normalizeOptionalLowercaseString(entry); if (!trimmed || trimmed === "*") { return undefined; } diff --git a/extensions/slack/src/monitor/channel-type.ts b/extensions/slack/src/monitor/channel-type.ts index fafb334a19b..e16b49ec191 100644 --- a/extensions/slack/src/monitor/channel-type.ts +++ b/extensions/slack/src/monitor/channel-type.ts @@ -1,3 +1,4 @@ +import { normalizeOptionalLowercaseString } from "openclaw/plugin-sdk/text-runtime"; import type { SlackMessageEvent } from "../types.js"; export function inferSlackChannelType( @@ -23,7 +24,7 @@ export function normalizeSlackChannelType( channelType?: string | null, channelId?: string | null, ): SlackMessageEvent["channel_type"] { - const normalized = channelType?.trim().toLowerCase(); + const normalized = normalizeOptionalLowercaseString(channelType); const inferred = inferSlackChannelType(channelId); if ( normalized === "im" || diff --git a/extensions/slack/src/monitor/media.ts b/extensions/slack/src/monitor/media.ts index 75da10d46f3..d58e356a3b9 100644 --- a/extensions/slack/src/monitor/media.ts +++ b/extensions/slack/src/monitor/media.ts @@ -5,6 +5,7 @@ import type { FetchLike } from "openclaw/plugin-sdk/media-runtime"; import { fetchRemoteMedia } from "openclaw/plugin-sdk/media-runtime"; import { saveMediaBuffer } from "openclaw/plugin-sdk/media-runtime"; import { resolveRequestUrl } from "openclaw/plugin-sdk/request-url"; +import { normalizeOptionalLowercaseString } from "openclaw/plugin-sdk/text-runtime"; import type { SlackAttachment, SlackFile } from "../types.js"; function isSlackHostname(hostname: string): boolean { @@ -239,7 +240,7 @@ export async function resolveSlackMedia(params: { const isExpectedHtml = fileMime === "text/html" || fileName.endsWith(".html") || fileName.endsWith(".htm"); if (!isExpectedHtml) { - const detectedMime = fetched.contentType?.split(";")[0]?.trim().toLowerCase(); + const detectedMime = normalizeOptionalLowercaseString(fetched.contentType?.split(";")[0]); if (detectedMime === "text/html" || looksLikeHtmlBuffer(fetched.buffer)) { return null; } diff --git a/extensions/slack/src/monitor/message-handler/dispatch.ts b/extensions/slack/src/monitor/message-handler/dispatch.ts index 16276247fae..6c09bc3f782 100644 --- a/extensions/slack/src/monitor/message-handler/dispatch.ts +++ b/extensions/slack/src/monitor/message-handler/dispatch.ts @@ -18,6 +18,7 @@ import { resolveSendableOutboundReplyParts } from "openclaw/plugin-sdk/reply-pay import type { ReplyPayload } from "openclaw/plugin-sdk/reply-runtime"; import { danger, logVerbose, shouldLogVerbose } from "openclaw/plugin-sdk/runtime-env"; import { resolvePinnedMainDmOwnerFromAllowlist } from "openclaw/plugin-sdk/security-runtime"; +import { normalizeOptionalLowercaseString } from "openclaw/plugin-sdk/text-runtime"; import { reactSlackMessage, removeSlackReaction } from "../../actions.js"; import { createSlackDraftStream } from "../../draft-stream.js"; import { normalizeSlackOutboundText } from "../../format.js"; @@ -161,11 +162,11 @@ export async function dispatchPreparedSlackMessage(prepared: PreparedSlackMessag allowFrom: ctx.allowFrom, normalizeEntry: normalizeSlackAllowOwnerEntry, }); - const senderRecipient = message.user?.trim().toLowerCase(); + const senderRecipient = normalizeOptionalLowercaseString(message.user); const skipMainUpdate = pinnedMainDmOwner && senderRecipient && - pinnedMainDmOwner.trim().toLowerCase() !== senderRecipient; + normalizeOptionalLowercaseString(pinnedMainDmOwner) !== senderRecipient; if (skipMainUpdate) { logVerbose( `slack: skip main-session last route for ${senderRecipient} (pinned owner ${pinnedMainDmOwner})`, diff --git a/extensions/slack/src/monitor/slash.ts b/extensions/slack/src/monitor/slash.ts index fe09f220bb1..f482c027ba3 100644 --- a/extensions/slack/src/monitor/slash.ts +++ b/extensions/slack/src/monitor/slash.ts @@ -11,7 +11,7 @@ import { } from "openclaw/plugin-sdk/config-runtime"; import type { ReplyPayload } from "openclaw/plugin-sdk/reply-runtime"; import { danger, logVerbose } from "openclaw/plugin-sdk/runtime-env"; -import { chunkItems } from "openclaw/plugin-sdk/text-runtime"; +import { chunkItems, normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime"; import type { ResolvedSlackAccount } from "../accounts.js"; import { truncateSlackText } from "../truncate.js"; import { resolveSlackAllowListMatch, resolveSlackUserAllowed } from "./allow-list.js"; @@ -769,7 +769,7 @@ export async function registerSlackMonitorSlashCommands(params: { await ack({ options: [] }); return; } - const query = typedBody.value?.trim().toLowerCase() ?? ""; + const query = normalizeLowercaseStringOrEmpty(typedBody.value); const options = entry.choices .filter((choice) => !query || choice.label.toLowerCase().includes(query)) .slice(0, SLACK_COMMAND_ARG_SELECT_OPTIONS_MAX) diff --git a/extensions/slack/src/resolve-channels.ts b/extensions/slack/src/resolve-channels.ts index 52ebbaf6835..ab8b8f61036 100644 --- a/extensions/slack/src/resolve-channels.ts +++ b/extensions/slack/src/resolve-channels.ts @@ -1,4 +1,5 @@ import type { WebClient } from "@slack/web-api"; +import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime"; import { createSlackWebClient } from "./client.js"; import { collectSlackCursorItems, @@ -81,7 +82,7 @@ function resolveByName( name: string, channels: SlackChannelLookup[], ): SlackChannelLookup | undefined { - const target = name.trim().toLowerCase(); + const target = normalizeLowercaseStringOrEmpty(name); if (!target) { return undefined; } diff --git a/extensions/slack/src/setup-core.ts b/extensions/slack/src/setup-core.ts index 36a26cee171..aa711dc3fc6 100644 --- a/extensions/slack/src/setup-core.ts +++ b/extensions/slack/src/setup-core.ts @@ -16,7 +16,10 @@ import { type OpenClawConfig, } from "openclaw/plugin-sdk/setup-runtime"; import { formatDocsLink } from "openclaw/plugin-sdk/setup-tools"; -import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime"; +import { + normalizeLowercaseStringOrEmpty, + normalizeOptionalString, +} from "openclaw/plugin-sdk/text-runtime"; import { inspectSlackAccount } from "./account-inspect.js"; import { resolveSlackAccount } from "./accounts.js"; import { @@ -39,7 +42,7 @@ function hasSlackInteractiveRepliesConfig(cfg: OpenClawConfig, accountId: string const capabilities = resolveSlackAccount({ cfg, accountId }).config.capabilities; if (Array.isArray(capabilities)) { return capabilities.some( - (entry) => String(entry).trim().toLowerCase() === "interactivereplies", + (entry) => normalizeLowercaseStringOrEmpty(String(entry)) === "interactivereplies", ); } if (!capabilities || typeof capabilities !== "object") { @@ -57,7 +60,9 @@ function setSlackInteractiveReplies( const nextCapabilities = Array.isArray(capabilities) ? interactiveReplies ? [...new Set([...capabilities, "interactiveReplies"])] - : capabilities.filter((entry) => String(entry).trim().toLowerCase() !== "interactivereplies") + : capabilities.filter( + (entry) => normalizeLowercaseStringOrEmpty(String(entry)) !== "interactivereplies", + ) : { ...((capabilities && typeof capabilities === "object" ? capabilities : {}) as Record< string, diff --git a/extensions/slack/src/stream-mode.ts b/extensions/slack/src/stream-mode.ts index a0871e5bcb1..6b6f6d2ed11 100644 --- a/extensions/slack/src/stream-mode.ts +++ b/extensions/slack/src/stream-mode.ts @@ -1,3 +1,4 @@ +import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime"; import { mapStreamingModeToSlackLegacyDraftStreamMode, resolveSlackNativeStreaming, @@ -14,7 +15,7 @@ export function resolveSlackStreamMode(raw: unknown): SlackStreamMode { if (typeof raw !== "string") { return DEFAULT_STREAM_MODE; } - const normalized = raw.trim().toLowerCase(); + const normalized = normalizeLowercaseStringOrEmpty(raw); if (normalized === "replace" || normalized === "status_final" || normalized === "append") { return normalized; } diff --git a/extensions/venice/index.ts b/extensions/venice/index.ts index c4db8bc3cc0..db22dcebde4 100644 --- a/extensions/venice/index.ts +++ b/extensions/venice/index.ts @@ -1,12 +1,13 @@ import { defineSingleProviderPluginEntry } from "openclaw/plugin-sdk/provider-entry"; import { applyXaiModelCompat } from "openclaw/plugin-sdk/provider-tools"; +import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime"; import { applyVeniceConfig, VENICE_DEFAULT_MODEL_REF } from "./onboard.js"; import { buildVeniceProvider } from "./provider-catalog.js"; const PROVIDER_ID = "venice"; function isXaiBackedVeniceModel(modelId: string): boolean { - return modelId.trim().toLowerCase().includes("grok"); + return normalizeLowercaseStringOrEmpty(modelId).includes("grok"); } export default defineSingleProviderPluginEntry({