refactor: dedupe extension lowercase helpers

This commit is contained in:
Peter Steinberger
2026-04-07 22:09:21 +01:00
parent a44a26f0a0
commit 2187b19d7e
17 changed files with 41 additions and 35 deletions

View File

@@ -1,3 +1,4 @@
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
import { createFeishuClient } from "./client.js";
import type { ResolvedFeishuAccount } from "./types.js";
@@ -37,7 +38,7 @@ function correctFeishuScopeInUrl(url: string): string {
}
function shouldSuppressPermissionErrorNotice(permissionError: FeishuPermissionError): boolean {
const message = permissionError.message.toLowerCase();
const message = normalizeLowercaseStringOrEmpty(permissionError.message);
return IGNORED_PERMISSION_SCOPE_TOKENS.some((token) => message.includes(token));
}

View File

@@ -1,3 +1,4 @@
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
import type { RuntimeEnv } from "../runtime-api.js";
import { probeFeishu } from "./probe.js";
import type { ResolvedFeishuAccount } from "./types.js";
@@ -33,13 +34,12 @@ export type FeishuMonitorBotIdentity = {
};
function isTimeoutErrorMessage(message: string | undefined): boolean {
return !!(
message?.toLowerCase().includes("timeout") || message?.toLowerCase().includes("timed out")
);
const lower = normalizeLowercaseStringOrEmpty(message);
return lower.includes("timeout") || lower.includes("timed out");
}
function isAbortErrorMessage(message: string | undefined): boolean {
return message?.toLowerCase().includes("aborted") ?? false;
return normalizeLowercaseStringOrEmpty(message).includes("aborted");
}
export async function fetchBotIdentityForMonitor(

View File

@@ -12,6 +12,7 @@ import {
type OpenClawConfig,
type SecretInput,
} from "openclaw/plugin-sdk/setup";
import { normalizeOptionalLowercaseString } from "openclaw/plugin-sdk/text-runtime";
import {
inspectFeishuCredentials,
resolveDefaultFeishuAccountId,
@@ -102,7 +103,7 @@ function isFeishuConfigured(cfg: OpenClawConfig, accountId?: string | null): boo
return false;
}
const rec = value as Record<string, unknown>;
const source = normalizeString(rec.source)?.toLowerCase();
const source = normalizeOptionalLowercaseString(normalizeString(rec.source));
const id = normalizeString(rec.id);
if (source === "env" && id) {
return Boolean(normalizeString(process.env[id]));

View File

@@ -47,7 +47,7 @@ export function normalizeIMessageHandle(raw: string): string {
return "";
}
const value = trimmed.slice(prefix.length).trim();
return `${prefix.toLowerCase()}${value}`;
return `${normalizeLowercaseStringOrEmpty(prefix)}${value}`;
}
if (trimmed.includes("@")) {
return normalizeLowercaseStringOrEmpty(trimmed);

View File

@@ -3,6 +3,7 @@ import { loadConfig } from "openclaw/plugin-sdk/config-runtime";
import { runCommandWithTimeout } from "openclaw/plugin-sdk/process-runtime";
import type { RuntimeEnv } from "openclaw/plugin-sdk/runtime-env";
import { detectBinary } from "openclaw/plugin-sdk/setup";
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
import { createIMessageRpcClient } from "./client.js";
import { DEFAULT_IMESSAGE_PROBE_TIMEOUT_MS } from "./constants.js";
@@ -35,7 +36,7 @@ async function probeRpcSupport(cliPath: string, timeoutMs: number): Promise<RpcS
try {
const result = await runCommandWithTimeout([cliPath, "rpc", "--help"], { timeoutMs });
const combined = `${result.stdout}\n${result.stderr}`.trim();
const normalized = combined.toLowerCase();
const normalized = normalizeLowercaseStringOrEmpty(combined);
if (normalized.includes("unknown command") && normalized.includes("rpc")) {
const fatal = {
supported: false,

View File

@@ -21,7 +21,7 @@ function normalizeIMessageTestHandle(raw: string): string {
}
if (/^(chat_id:|chat_guid:|chat_identifier:)/i.test(trimmed)) {
return trimmed.replace(/^(chat_id:|chat_guid:|chat_identifier:)/i, (match) =>
match.toLowerCase(),
normalizeLowercaseStringOrEmpty(match),
);
}
if (trimmed.includes("@")) {

View File

@@ -2,16 +2,16 @@ import {
createResolvedApproverActionAuthAdapter,
resolveApprovalApprovers,
} from "openclaw/plugin-sdk/approval-auth-runtime";
import { normalizeOptionalLowercaseString } from "openclaw/plugin-sdk/text-runtime";
import { resolveNextcloudTalkAccount } from "./accounts.js";
import type { CoreConfig } from "./types.js";
function normalizeNextcloudTalkApproverId(value: string | number): string | undefined {
const normalized = String(value)
.trim()
.replace(/^(nextcloud-talk|nc-talk|nc):/i, "")
.trim()
.toLowerCase();
return normalized || undefined;
return normalizeOptionalLowercaseString(
String(value)
.trim()
.replace(/^(nextcloud-talk|nc-talk|nc):/i, ""),
);
}
export const nextcloudTalkApprovalAuth = createResolvedApproverActionAuthAdapter({

View File

@@ -39,11 +39,7 @@ export const nextcloudTalkSecurityAdapter = {
resolveAllowFrom: (account) => account.config.allowFrom,
policyPathSuffix: "dmPolicy",
normalizeEntry: (raw) =>
raw
.trim()
.replace(/^(nextcloud-talk|nc-talk|nc):/i, "")
.trim()
.toLowerCase(),
normalizeLowercaseStringOrEmpty(raw.trim().replace(/^(nextcloud-talk|nc-talk|nc):/i, "")),
}),
};

View File

@@ -1,4 +1,5 @@
import { resolveInboundMentionDecision } from "openclaw/plugin-sdk/channel-inbound";
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
import type {
AllowlistMatch,
ChannelGroupContext,
@@ -15,10 +16,7 @@ import {
import type { NextcloudTalkRoomConfig } from "./types.js";
function normalizeAllowEntry(raw: string): string {
return raw
.trim()
.toLowerCase()
.replace(/^(nextcloud-talk|nc-talk|nc):/i, "");
return normalizeLowercaseStringOrEmpty(raw.trim().replace(/^(nextcloud-talk|nc-talk|nc):/i, ""));
}
export function normalizeNextcloudTalkAllowlist(

View File

@@ -1,4 +1,5 @@
import { createHmac, randomBytes } from "node:crypto";
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
import type { NextcloudTalkWebhookHeaders } from "./types.js";
const SIGNATURE_HEADER = "x-nextcloud-talk-signature";
@@ -41,7 +42,7 @@ export function extractNextcloudTalkHeaders(
headers: Record<string, string | string[] | undefined>,
): NextcloudTalkWebhookHeaders | null {
const getHeader = (name: string): string | undefined => {
const value = headers[name] ?? headers[name.toLowerCase()];
const value = headers[name] ?? headers[normalizeLowercaseStringOrEmpty(name)];
return Array.isArray(value) ? value[0] : value;
};

View File

@@ -19,7 +19,7 @@ import {
} from "openclaw/plugin-sdk/provider-model-shared";
import { buildProviderStreamFamilyHooks } from "openclaw/plugin-sdk/provider-stream-family";
import { fetchCodexUsage } from "openclaw/plugin-sdk/provider-usage";
import { readStringValue } from "openclaw/plugin-sdk/text-runtime";
import { normalizeLowercaseStringOrEmpty, readStringValue } from "openclaw/plugin-sdk/text-runtime";
import { OPENAI_CODEX_DEFAULT_MODEL } from "./default-models.js";
import { resolveCodexAuthIdentity } from "./openai-codex-auth-identity.js";
import { buildOpenAICodexProvider } from "./openai-codex-catalog.js";
@@ -110,7 +110,7 @@ function resolveCodexForwardCompatModel(
ctx: ProviderResolveDynamicModelContext,
): ProviderRuntimeModel | undefined {
const trimmedModelId = ctx.modelId.trim();
const lower = trimmedModelId.toLowerCase();
const lower = normalizeLowercaseStringOrEmpty(trimmedModelId);
let templateIds: readonly string[];
let patch: Partial<ProviderRuntimeModel> | undefined;

View File

@@ -10,6 +10,7 @@ import {
type ProviderPlugin,
} from "openclaw/plugin-sdk/provider-model-shared";
import { buildProviderStreamFamilyHooks } from "openclaw/plugin-sdk/provider-stream-family";
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
import { applyOpenAIConfig, OPENAI_DEFAULT_MODEL } from "./default-models.js";
import { buildOpenAIReplayPolicy } from "./replay-policy.js";
import {
@@ -106,7 +107,7 @@ function resolveOpenAIGpt54ForwardCompatModel(
ctx: ProviderResolveDynamicModelContext,
): ProviderRuntimeModel | undefined {
const trimmedModelId = ctx.modelId.trim();
const lower = trimmedModelId.toLowerCase();
const lower = normalizeLowercaseStringOrEmpty(trimmedModelId);
let templateIds: readonly string[];
let patch: Partial<ProviderRuntimeModel>;
if (lower === OPENAI_GPT_54_MODEL_ID) {
@@ -252,7 +253,7 @@ export function buildOpenAIProvider(): ProviderPlugin {
suppressBuiltInModel: (ctx) => {
if (
!SUPPRESSED_SPARK_PROVIDERS.has(normalizeProviderId(ctx.provider)) ||
ctx.modelId.toLowerCase() !== OPENAI_DIRECT_SPARK_MODEL_ID
normalizeLowercaseStringOrEmpty(ctx.modelId) !== OPENAI_DIRECT_SPARK_MODEL_ID
) {
return undefined;
}

View File

@@ -5,6 +5,7 @@ import type {
ProviderWebSocketSessionPolicy,
} from "openclaw/plugin-sdk/plugin-entry";
import { normalizeProviderId } from "openclaw/plugin-sdk/provider-model-shared";
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
import { isOpenAIApiBaseUrl, isOpenAICodexBaseUrl } from "./shared.js";
const DEFAULT_OPENAI_WS_DEGRADE_COOLDOWN_MS = 60_000;
@@ -17,7 +18,7 @@ function isAzureOpenAIBaseUrl(baseUrl?: string): boolean {
return false;
}
try {
return new URL(trimmed).hostname.toLowerCase().endsWith(".openai.azure.com");
return normalizeLowercaseStringOrEmpty(new URL(trimmed).hostname).endsWith(".openai.azure.com");
} catch {
return false;
}

View File

@@ -35,7 +35,8 @@ function extractSlackSessionKind(
return null;
}
const match = sessionKey.match(/slack:(direct|channel|group):/i);
return match?.[1] ? (match[1].toLowerCase() as "direct" | "channel" | "group") : null;
const kind = normalizeLowercaseStringOrEmpty(match?.[1]);
return kind ? (kind as "direct" | "channel" | "group") : null;
}
function normalizeComparableTarget(value: string): string {

View File

@@ -5,6 +5,7 @@ import {
type ChannelMatchSource,
} from "openclaw/plugin-sdk/channel-targets";
import type { SlackReactionNotificationMode } from "openclaw/plugin-sdk/config-runtime";
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
import type { SlackMessageEvent } from "../types.js";
import { allowListMatches, normalizeAllowListLower, normalizeSlackSlug } from "./allow-list.js";
@@ -108,7 +109,7 @@ export function resolveSlackChannelConfig(params: {
// operators commonly write them in lowercase in their config. Add both
// case variants so the lookup is case-insensitive without requiring a full
// entry-scan. buildChannelKeyCandidates deduplicates identical keys.
const channelIdLower = channelId.toLowerCase();
const channelIdLower = normalizeLowercaseStringOrEmpty(channelId);
const channelIdUpper = channelId.toUpperCase();
const candidates = buildChannelKeyCandidates(
channelId,

View File

@@ -12,6 +12,7 @@ import { resolveAgentRoute } from "openclaw/plugin-sdk/routing";
import { logVerbose } from "openclaw/plugin-sdk/runtime-env";
import { getChildLogger } from "openclaw/plugin-sdk/runtime-env";
import type { RuntimeEnv } from "openclaw/plugin-sdk/runtime-env";
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
import type { SlackMessageEvent } from "../types.js";
import { normalizeAllowList, normalizeAllowListLower, normalizeSlackSlug } from "./allow-list.js";
import type { SlackChannelConfigEntries } from "./channel-config.js";
@@ -314,7 +315,7 @@ export function createSlackMonitorContext(params: {
p.channelName ? normalizeSlackSlug(p.channelName) : undefined,
]
.filter((value): value is string => Boolean(value))
.map((value) => value.toLowerCase());
.map((value) => normalizeLowercaseStringOrEmpty(value));
const permitted =
groupDmChannelsLower.includes("*") ||
candidates.some((candidate) => groupDmChannelsLower.includes(candidate));

View File

@@ -25,7 +25,10 @@ import { resolveAgentRoute } from "openclaw/plugin-sdk/routing";
import { resolveThreadSessionKeys } from "openclaw/plugin-sdk/routing";
import { logVerbose, shouldLogVerbose } from "openclaw/plugin-sdk/runtime-env";
import { resolvePinnedMainDmOwnerFromAllowlist } from "openclaw/plugin-sdk/security-runtime";
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
import {
normalizeLowercaseStringOrEmpty,
normalizeOptionalString,
} from "openclaw/plugin-sdk/text-runtime";
import { resolveSlackReplyToMode, type ResolvedSlackAccount } from "../../accounts.js";
import { reactSlackMessage } from "../../actions.js";
import { hasSlackThreadParticipation } from "../../sent-thread-cache.js";
@@ -788,7 +791,7 @@ export async function prepareSlackMessage(params: {
pinnedMainDmOwner && message.user
? {
ownerRecipient: pinnedMainDmOwner,
senderRecipient: message.user.toLowerCase(),
senderRecipient: normalizeLowercaseStringOrEmpty(message.user),
onSkip: ({ ownerRecipient, senderRecipient }) => {
logVerbose(
`slack: skip main-session last route for ${senderRecipient} (pinned owner ${ownerRecipient})`,