refactor: dedupe provider lowercase helpers

This commit is contained in:
Peter Steinberger
2026-04-07 14:35:38 +01:00
parent 2cd11565a6
commit 60d9c150b2
17 changed files with 57 additions and 34 deletions

View File

@@ -1,4 +1,5 @@
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
export default definePluginEntry({
id: "acpx",
@@ -6,8 +7,7 @@ export default definePluginEntry({
description: "Lightweight ACPX setup hooks",
register(api) {
api.registerAutoEnableProbe(({ config }) => {
const backendRaw =
typeof config.acp?.backend === "string" ? config.acp.backend.trim().toLowerCase() : "";
const backendRaw = normalizeLowercaseStringOrEmpty(config.acp?.backend);
const configured =
config.acp?.enabled === true ||
config.acp?.dispatch?.enabled === true ||

View File

@@ -1,6 +1,7 @@
import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
import type { z } from "openclaw/plugin-sdk/zod";
import { AcpxPluginConfigSchema } from "./config-schema.js";
import type {
@@ -223,7 +224,7 @@ export function resolveAcpxPluginConfig(params: {
});
const agents = Object.fromEntries(
Object.entries(normalized.agents ?? {}).map(([name, entry]) => [
name.trim().toLowerCase(),
normalizeLowercaseStringOrEmpty(name),
entry.command.trim(),
]),
);

View File

@@ -13,6 +13,7 @@ import type {
ModelDefinitionConfig,
ModelProviderConfig,
} from "openclaw/plugin-sdk/provider-model-shared";
import { normalizeOptionalLowercaseString } from "openclaw/plugin-sdk/text-runtime";
const log = createSubsystemLogger("bedrock-discovery");
@@ -50,7 +51,9 @@ function normalizeProviderFilter(filter?: string[]): string[] {
return [];
}
const normalized = new Set(
filter.map((entry) => entry.trim().toLowerCase()).filter((entry) => entry.length > 0),
filter
.map((entry) => normalizeOptionalLowercaseString(entry))
.filter((entry): entry is string => Boolean(entry)),
);
return Array.from(normalized).toSorted();
}
@@ -118,7 +121,7 @@ function matchesProviderFilter(summary: BedrockModelSummary, filter: string[]):
const providerName =
summary.providerName ??
(typeof summary.modelId === "string" ? summary.modelId.split(".")[0] : undefined);
const normalized = providerName?.trim().toLowerCase();
const normalized = normalizeOptionalLowercaseString(providerName);
if (!normalized) {
return false;
}

View File

@@ -20,6 +20,7 @@ import {
} from "openclaw/plugin-sdk/provider-auth";
import { cloneFirstTemplateModel } from "openclaw/plugin-sdk/provider-model-shared";
import { fetchClaudeUsage } from "openclaw/plugin-sdk/provider-usage";
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
import * as claudeCliAuth from "./cli-auth-seam.js";
import { buildAnthropicCliBackend } from "./cli-backend.js";
import { buildAnthropicCliMigrationResult } from "./cli-migration.js";
@@ -217,7 +218,7 @@ function resolveAnthropic46ForwardCompatModel(params: {
templateIds,
ctx: params.ctx,
patch:
params.ctx.provider.trim().toLowerCase() === CLAUDE_CLI_BACKEND_ID
normalizeLowercaseStringOrEmpty(params.ctx.provider) === CLAUDE_CLI_BACKEND_ID
? { provider: CLAUDE_CLI_BACKEND_ID }
: undefined,
});
@@ -247,7 +248,7 @@ function resolveAnthropicForwardCompatModel(
}
function matchesAnthropicModernModel(modelId: string): boolean {
const lower = modelId.trim().toLowerCase();
const lower = normalizeLowercaseStringOrEmpty(modelId);
return ANTHROPIC_MODERN_MODEL_PREFIXES.some((prefix) => lower.startsWith(prefix));
}

View File

@@ -8,7 +8,11 @@ import {
streamWithPayloadPatch,
} from "openclaw/plugin-sdk/provider-stream-shared";
import { createSubsystemLogger } from "openclaw/plugin-sdk/runtime-env";
import { normalizeOptionalString, readStringValue } from "openclaw/plugin-sdk/text-runtime";
import {
normalizeLowercaseStringOrEmpty,
normalizeOptionalString,
readStringValue,
} from "openclaw/plugin-sdk/text-runtime";
const log = createSubsystemLogger("anthropic-stream");
@@ -27,7 +31,7 @@ const PI_AI_OAUTH_ANTHROPIC_BETAS = [
type AnthropicServiceTier = "auto" | "standard_only";
function isAnthropic1MModel(modelId: string): boolean {
const normalized = modelId.trim().toLowerCase();
const normalized = normalizeLowercaseStringOrEmpty(modelId);
return ANTHROPIC_1M_MODEL_PREFIXES.some((prefix) => normalized.startsWith(prefix));
}

View File

@@ -28,7 +28,11 @@ import type { OpenClawConfig, loadConfig } from "openclaw/plugin-sdk/config-runt
import { loadSessionStore, resolveStorePath } from "openclaw/plugin-sdk/config-runtime";
import type { ResolvedAgentRoute } from "openclaw/plugin-sdk/routing";
import { logVerbose } from "openclaw/plugin-sdk/runtime-env";
import { chunkItems, withTimeout } from "openclaw/plugin-sdk/text-runtime";
import {
chunkItems,
normalizeLowercaseStringOrEmpty,
withTimeout,
} from "openclaw/plugin-sdk/text-runtime";
import { resolveDiscordChannelInfo } from "./message-utils.js";
import {
readDiscordModelPickerRecentModels,
@@ -143,7 +147,7 @@ function parseDiscordCommandArgData(
function resolveDiscordModelPickerCommandContext(
command: ChatCommandDefinition,
): DiscordModelPickerCommandContext | null {
const normalized = (command.nativeName ?? command.key).trim().toLowerCase();
const normalized = normalizeLowercaseStringOrEmpty(command.nativeName ?? command.key);
if (normalized === "model" || normalized === "models") {
return normalized;
}

View File

@@ -47,6 +47,7 @@ import {
} from "openclaw/plugin-sdk/reply-payload";
import { createSubsystemLogger, logVerbose } from "openclaw/plugin-sdk/runtime-env";
import { resolveOpenProviderRuntimeGroupPolicy } from "openclaw/plugin-sdk/runtime-group-policy";
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
import { loadWebMedia } from "openclaw/plugin-sdk/web-media";
import { resolveDiscordMaxLinesPerMessage } from "../accounts.js";
import { chunkDiscordTextWithMode } from "../chunk.js";
@@ -298,8 +299,7 @@ function buildDiscordCommandOptions(params: {
return;
}
const focused = interaction.options.getFocused();
const focusValue =
typeof focused?.value === "string" ? focused.value.trim().toLowerCase() : "";
const focusValue = normalizeLowercaseStringOrEmpty(focused?.value);
const context =
typeof arg.choices === "function" && resolveChoiceContext
? await resolveChoiceContext(interaction)
@@ -340,14 +340,14 @@ function buildDiscordCommandOptions(params: {
}
function shouldBypassConfiguredAcpEnsure(commandName: string): boolean {
const normalized = commandName.trim().toLowerCase();
const normalized = normalizeLowercaseStringOrEmpty(commandName);
// Recovery slash commands still need configured ACP readiness so stale dead
// bindings are recreated before /new or /reset dispatches through them.
return normalized === "acp";
}
function shouldBypassConfiguredAcpGuildGuards(commandName: string): boolean {
const normalized = commandName.trim().toLowerCase();
const normalized = normalizeLowercaseStringOrEmpty(commandName);
return normalized === "new" || normalized === "reset";
}

View File

@@ -17,6 +17,7 @@ import {
requireInRange,
trimToUndefined,
} from "openclaw/plugin-sdk/speech";
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
import { resolveElevenLabsApiKeyWithProfileFallback } from "./config-api.js";
import { isValidElevenLabsVoiceId, normalizeElevenLabsBaseUrl } from "./shared.js";
import { elevenLabsTTS } from "./tts.js";
@@ -54,7 +55,7 @@ type ElevenLabsProviderConfig = {
};
function parseBooleanValue(value: string): boolean | undefined {
const normalized = value.trim().toLowerCase();
const normalized = normalizeLowercaseStringOrEmpty(value);
if (["true", "1", "yes", "on"].includes(normalized)) {
return true;
}

View File

@@ -1,6 +1,6 @@
import { defineSingleProviderPluginEntry } from "openclaw/plugin-sdk/provider-entry";
import { resolveProviderRequestCapabilities } from "openclaw/plugin-sdk/provider-http";
import { readStringValue } from "openclaw/plugin-sdk/text-runtime";
import { normalizeLowercaseStringOrEmpty, readStringValue } from "openclaw/plugin-sdk/text-runtime";
import { applyMistralModelCompat, MISTRAL_MODEL_TRANSPORT_PATCH } from "./api.js";
import { mistralMediaUnderstandingProvider } from "./media-understanding-provider.js";
import { applyMistralConfig, MISTRAL_DEFAULT_MODEL_REF } from "./onboard.js";
@@ -18,7 +18,7 @@ const MISTRAL_MODEL_HINTS = [
] as const;
function isMistralModelHint(modelId: string): boolean {
const normalized = modelId.trim().toLowerCase();
const normalized = normalizeLowercaseStringOrEmpty(modelId);
return MISTRAL_MODEL_HINTS.some(
(hint) =>
normalized === hint ||

View File

@@ -8,7 +8,10 @@
*/
import type { IncomingMessage, ServerResponse } from "node:http";
import { readStringValue } from "openclaw/plugin-sdk/text-runtime";
import {
normalizeOptionalLowercaseString,
readStringValue,
} from "openclaw/plugin-sdk/text-runtime";
import { z } from "openclaw/plugin-sdk/zod";
import {
createFixedWindowRateLimiter,
@@ -291,7 +294,9 @@ function enforceLoopbackMutationGuards(
return false;
}
const secFetchSite = firstHeaderValue(req.headers["sec-fetch-site"])?.trim().toLowerCase();
const secFetchSite = normalizeOptionalLowercaseString(
firstHeaderValue(req.headers["sec-fetch-site"]),
);
if (secFetchSite === "cross-site") {
ctx.log?.warn?.("Rejected mutation with cross-site sec-fetch-site header");
sendJson(res, 403, { ok: false, error: "Forbidden" });

View File

@@ -5,6 +5,7 @@ import { applyAgentDefaultModelPrimary } from "openclaw/plugin-sdk/provider-onbo
import type { RuntimeEnv } from "openclaw/plugin-sdk/runtime";
import { WizardCancelledError, type WizardPrompter } from "openclaw/plugin-sdk/setup";
import { fetchWithSsrFGuard } from "openclaw/plugin-sdk/ssrf-runtime";
import { normalizeOptionalLowercaseString } from "openclaw/plugin-sdk/text-runtime";
import { OLLAMA_DEFAULT_BASE_URL, OLLAMA_DEFAULT_MODEL } from "./defaults.js";
import {
buildOllamaBaseUrlSsrFPolicy,
@@ -49,7 +50,7 @@ function normalizeOllamaModelName(value: string | undefined): string | undefined
}
function isOllamaCloudModel(modelName: string | undefined): boolean {
return Boolean(modelName?.trim().toLowerCase().endsWith(":cloud"));
return normalizeOptionalLowercaseString(modelName)?.endsWith(":cloud") === true;
}
function formatOllamaPullStatus(status: string): { text: string; hidePercent: boolean } {

View File

@@ -26,7 +26,7 @@ import {
streamWithPayloadPatch,
} from "openclaw/plugin-sdk/provider-stream-shared";
import { createSubsystemLogger } from "openclaw/plugin-sdk/runtime-env";
import { readStringValue } from "openclaw/plugin-sdk/text-runtime";
import { normalizeLowercaseStringOrEmpty, readStringValue } from "openclaw/plugin-sdk/text-runtime";
import { OLLAMA_DEFAULT_BASE_URL } from "./defaults.js";
import {
parseJsonObjectPreservingUnsafeIntegers,
@@ -165,7 +165,7 @@ function resolveOllamaCompatNumCtx(model: ProviderRuntimeModel): number {
}
function isOllamaCloudKimiModelRef(modelId: string): boolean {
const normalizedModelId = modelId.trim().toLowerCase();
const normalizedModelId = normalizeLowercaseStringOrEmpty(modelId);
return normalizedModelId.startsWith("kimi-k") && normalizedModelId.includes(":cloud");
}

View File

@@ -1,3 +1,5 @@
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
const OPENAI_PROVIDER_IDS = new Set(["openai", "openai-codex"]);
const OPENAI_GPT5_MODEL_PREFIX = "gpt-5";
@@ -54,10 +56,7 @@ export type OpenAIPromptOverlayMode = "friendly" | "off";
export function resolveOpenAIPromptOverlayMode(
pluginConfig?: Record<string, unknown>,
): OpenAIPromptOverlayMode {
const normalized =
typeof pluginConfig?.personality === "string"
? pluginConfig.personality.trim().toLowerCase()
: "";
const normalized = normalizeLowercaseStringOrEmpty(pluginConfig?.personality);
return normalized === "off" ? "off" : "friendly";
}
@@ -68,7 +67,7 @@ export function shouldApplyOpenAIPromptOverlay(params: {
if (!OPENAI_PROVIDER_IDS.has(params.modelProviderId ?? "")) {
return false;
}
const normalizedModelId = params.modelId?.trim().toLowerCase() ?? "";
const normalizedModelId = normalizeLowercaseStringOrEmpty(params.modelId);
return normalizedModelId.startsWith(OPENAI_GPT5_MODEL_PREFIX);
}

View File

@@ -4,6 +4,7 @@ import {
buildProviderReplayFamilyHooks,
matchesExactOrPrefix,
} from "openclaw/plugin-sdk/provider-model-shared";
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
import { applyOpencodeZenConfig, OPENCODE_ZEN_DEFAULT_MODEL } from "./api.js";
const PROVIDER_ID = "opencode";
@@ -13,7 +14,7 @@ const PASSTHROUGH_GEMINI_REPLAY_HOOKS = buildProviderReplayFamilyHooks({
});
function isModernOpencodeModel(modelId: string): boolean {
const lower = modelId.trim().toLowerCase();
const lower = normalizeLowercaseStringOrEmpty(modelId);
if (lower.endsWith("-free") || lower === "alpha-glm-4.7") {
return false;
}

View File

@@ -25,6 +25,7 @@ import type { ReplyPayload } from "openclaw/plugin-sdk/reply-runtime";
import { isVerbose, logVerbose } from "openclaw/plugin-sdk/runtime-env";
import { resolvePreferredOpenClawTmpDir } from "openclaw/plugin-sdk/sandbox";
import {
normalizeLowercaseStringOrEmpty,
normalizeOptionalString,
resolveConfigDir,
resolveUserPath,
@@ -251,7 +252,7 @@ function resolveLazyProviderConfig(
cfg?: OpenClawConfig,
): SpeechProviderConfig {
const canonical =
normalizeConfiguredSpeechProviderId(providerId) ?? providerId.trim().toLowerCase();
normalizeConfiguredSpeechProviderId(providerId) ?? normalizeLowercaseStringOrEmpty(providerId);
const existing = config.providerConfigs[canonical];
const effectiveCfg = cfg ?? config.sourceConfig;
if (existing && !effectiveCfg) {
@@ -314,7 +315,7 @@ export function getResolvedSpeechProviderConfig(
const canonical =
canonicalizeSpeechProviderId(providerId, cfg) ??
normalizeConfiguredSpeechProviderId(providerId) ??
providerId.trim().toLowerCase();
normalizeLowercaseStringOrEmpty(providerId);
return resolveLazyProviderConfig(config, canonical, cfg);
}

View File

@@ -20,6 +20,7 @@ import {
} from "openclaw/plugin-sdk/channel-policy";
import { attachChannelToResult } from "openclaw/plugin-sdk/channel-send-result";
import { createEmptyChannelDirectoryAdapter } from "openclaw/plugin-sdk/directory-runtime";
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
import { listAccountIds, resolveAccount } from "./accounts.js";
import { synologyChatApprovalAuth } from "./approval-auth.js";
import { sendMessage, sendFileUrl } from "./client.js";
@@ -42,7 +43,7 @@ const resolveSynologyChatDmPolicy = createScopedDmSecurityResolver<ResolvedSynol
policyPathSuffix: "dmPolicy",
defaultPolicy: "allowlist",
approveHint: "openclaw pairing approve synology-chat <code>",
normalizeEntry: (raw) => raw.toLowerCase().trim(),
normalizeEntry: (raw) => normalizeLowercaseStringOrEmpty(raw),
});
type SynologyChannelGatewayContext = {
@@ -89,7 +90,7 @@ const synologyChatConfigAdapter = createHybridChannelConfigAdapter<ResolvedSynol
],
resolveAllowFrom: (account) => account.allowedUserIds,
formatAllowFrom: (allowFrom) =>
allowFrom.map((entry) => String(entry).trim().toLowerCase()).filter(Boolean),
allowFrom.map((entry) => normalizeLowercaseStringOrEmpty(String(entry))).filter(Boolean),
});
const collectSynologyChatSecurityWarnings =

View File

@@ -1,4 +1,5 @@
import { randomUUID } from "node:crypto";
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
/**
* Twitch-specific utility functions
@@ -18,7 +19,7 @@ import { randomUUID } from "node:crypto";
* normalizeTwitchChannel("MyChannel") // "mychannel"
*/
export function normalizeTwitchChannel(channel: string): string {
const trimmed = channel.trim().toLowerCase();
const trimmed = normalizeLowercaseStringOrEmpty(channel);
return trimmed.startsWith("#") ? trimmed.slice(1) : trimmed;
}