refactor: dedupe provider lowercase helpers

This commit is contained in:
Peter Steinberger
2026-04-07 22:15:21 +01:00
parent 2187b19d7e
commit 775b78e186
19 changed files with 55 additions and 26 deletions

View File

@@ -4,6 +4,7 @@ import type {
ModelDefinitionConfig,
ModelProviderConfig,
} from "openclaw/plugin-sdk/provider-model-shared";
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
const log = createSubsystemLogger("bedrock-mantle-discovery");
@@ -145,7 +146,7 @@ const REASONING_PATTERNS = [
];
function inferReasoningSupport(modelId: string): boolean {
const lower = modelId.toLowerCase();
const lower = normalizeLowercaseStringOrEmpty(modelId);
return REASONING_PATTERNS.some((p) => lower.includes(p));
}

View File

@@ -2,6 +2,7 @@ import type {
ModelDefinitionConfig,
ModelProviderConfig,
} from "openclaw/plugin-sdk/provider-model-shared";
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
import { resolveAnthropicVertexRegion } from "./region.js";
export const ANTHROPIC_VERTEX_DEFAULT_MODEL_ID = "claude-sonnet-4-6";
const ANTHROPIC_VERTEX_DEFAULT_CONTEXT_WINDOW = 1_000_000;
@@ -52,7 +53,7 @@ export function buildAnthropicVertexProvider(params?: {
}): ModelProviderConfig {
const region = resolveAnthropicVertexRegion(params?.env);
const baseUrl =
region.toLowerCase() === "global"
normalizeLowercaseStringOrEmpty(region) === "global"
? "https://aiplatform.googleapis.com"
: `https://${region}-aiplatform.googleapis.com`;

View File

@@ -1,5 +1,8 @@
import { Type } from "@sinclair/typebox";
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
import {
normalizeLowercaseStringOrEmpty,
normalizeOptionalString,
} from "openclaw/plugin-sdk/text-runtime";
export type BraveConfig = {
mode?: string;
@@ -126,7 +129,8 @@ function normalizeBraveSearchLang(value: string | undefined): string | undefined
if (!trimmed) {
return undefined;
}
const canonical = BRAVE_SEARCH_LANG_ALIASES[trimmed.toLowerCase()] ?? trimmed.toLowerCase();
const lower = normalizeLowercaseStringOrEmpty(trimmed);
const canonical = BRAVE_SEARCH_LANG_ALIASES[lower] ?? lower;
if (!BRAVE_SEARCH_LANG_CODES.has(canonical)) {
return undefined;
}
@@ -158,7 +162,7 @@ function normalizeBraveUiLang(value: string | undefined): string | undefined {
return undefined;
}
const [, language, region] = match;
return `${language.toLowerCase()}-${region.toUpperCase()}`;
return `${normalizeLowercaseStringOrEmpty(language)}-${region.toUpperCase()}`;
}
export function resolveBraveConfig(searchConfig?: Record<string, unknown>): BraveConfig {

View File

@@ -387,7 +387,7 @@ export function buildElevenLabsSpeechProvider(): SpeechProviderPlugin {
},
resolveTalkOverrides: ({ params }) => {
const normalize = trimToUndefined(params.normalize);
const language = trimToUndefined(params.language)?.toLowerCase();
const language = normalizeLowercaseStringOrEmpty(trimToUndefined(params.language));
const latencyTier = asFiniteNumber(params.latencyTier);
const voiceSettings = {
...(asFiniteNumber(params.speed) == null ? {} : { speed: asFiniteNumber(params.speed) }),

View File

@@ -220,7 +220,7 @@ async function waitForFalQueueResult(params: {
throw new Error(
payload.detail?.trim() ||
payload.error?.message?.trim() ||
`fal video generation ${status.toLowerCase()}`,
`fal video generation ${normalizeLowercaseStringOrEmpty(status)}`,
);
}
await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));

View File

@@ -1,5 +1,7 @@
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
export function buildGithubCopilotReplayPolicy(modelId?: string) {
return (modelId?.toLowerCase() ?? "").includes("claude")
return normalizeLowercaseStringOrEmpty(modelId).includes("claude")
? {
dropThinkingBlocks: true,
}

View File

@@ -1,4 +1,5 @@
import type { ModelDefinitionConfig } from "openclaw/plugin-sdk/provider-model-types";
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
export const HUGGINGFACE_BASE_URL = "https://router.huggingface.co/v1";
export const HUGGINGFACE_POLICY_SUFFIXES = ["cheapest", "fastest"] as const;
@@ -91,7 +92,7 @@ export function buildHuggingfaceModelDefinition(
}
function isReasoningModelHeuristic(modelId: string): boolean {
const lower = modelId.toLowerCase();
const lower = normalizeLowercaseStringOrEmpty(modelId);
return (
lower.includes("r1") ||
lower.includes("reason") ||

View File

@@ -1,6 +1,7 @@
import type { KilocodeModelCatalogEntry } from "openclaw/plugin-sdk/provider-model-shared";
import type { ModelDefinitionConfig } from "openclaw/plugin-sdk/provider-model-shared";
import { createSubsystemLogger } from "openclaw/plugin-sdk/runtime-env";
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
const log = createSubsystemLogger("kilocode-models");
@@ -77,7 +78,9 @@ function parseModality(entry: GatewayModelEntry): Array<"text" | "image"> {
if (!Array.isArray(modalities)) {
return ["text"];
}
const hasImage = modalities.some((m) => typeof m === "string" && m.toLowerCase() === "image");
const hasImage = modalities.some(
(m) => typeof m === "string" && normalizeLowercaseStringOrEmpty(m) === "image",
);
return hasImage ? ["text", "image"] : ["text"];
}

View File

@@ -2,6 +2,7 @@ import {
fetchWithSsrFGuard,
ssrfPolicyFromPrivateNetworkOptIn,
} from "openclaw/plugin-sdk/ssrf-runtime";
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
import { z } from "openclaw/plugin-sdk/zod";
export type MattermostFetch = (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
@@ -344,7 +345,7 @@ export async function createMattermostDirectChannelWithRetry(
function isRetryableError(error: Error): boolean {
const candidates = collectErrorCandidates(error);
const messages = candidates
.map((candidate) => readErrorMessage(candidate)?.toLowerCase())
.map((candidate) => normalizeLowercaseStringOrEmpty(readErrorMessage(candidate)))
.filter((message): message is string => Boolean(message));
// Retry on 5xx server errors FIRST (before checking 4xx)

View File

@@ -1,5 +1,8 @@
import { isPrivateNetworkOptInEnabled } from "openclaw/plugin-sdk/ssrf-runtime";
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
import {
normalizeLowercaseStringOrEmpty,
normalizeOptionalString,
} from "openclaw/plugin-sdk/text-runtime";
import { getMattermostRuntime } from "../runtime.js";
import { resolveMattermostAccount, resolveMattermostReplyToMode } from "./accounts.js";
import {
@@ -1200,7 +1203,11 @@ export async function monitorMattermostProvider(opts: MonitorMattermostOpts = {}
const mentionRegexes = core.channel.mentions.buildMentionRegexes(cfg, route.agentId);
const wasMentioned =
kind !== "direct" &&
((botUsername ? rawText.toLowerCase().includes(`@${botUsername.toLowerCase()}`) : false) ||
((botUsername
? normalizeLowercaseStringOrEmpty(rawText).includes(
`@${normalizeLowercaseStringOrEmpty(botUsername)}`,
)
: false) ||
core.channel.mentions.matchesMentionPatterns(rawText, mentionRegexes));
const pendingBody =
rawText ||

View File

@@ -1,8 +1,9 @@
import { statSync } from "node:fs";
import { EdgeTTS } from "node-edge-tts";
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
export function inferEdgeExtension(outputFormat: string): string {
const normalized = outputFormat.toLowerCase();
const normalized = normalizeLowercaseStringOrEmpty(outputFormat);
if (normalized.includes("webm")) {
return ".webm";
}

View File

@@ -91,7 +91,7 @@ export function isOllamaCompatProvider(model: {
}
try {
const parsed = new URL(model.baseUrl);
const hostname = parsed.hostname.toLowerCase();
const hostname = normalizeLowercaseStringOrEmpty(parsed.hostname);
const isLocalhost =
hostname === "localhost" ||
hostname === "127.0.0.1" ||

View File

@@ -2,6 +2,7 @@ import crypto from "node:crypto";
import fs from "node:fs";
import path from "node:path";
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
import {
getAccessToken,
sendC2CMessage,
@@ -299,7 +300,7 @@ async function handleImagePayload(ctx: ReplyContext, payload: MediaPayload): Pro
try {
const fileBuffer = await readStructuredPayloadLocalFile(imageUrl);
const base64Data = fileBuffer.toString("base64");
const ext = path.extname(imageUrl).toLowerCase();
const ext = normalizeLowercaseStringOrEmpty(path.extname(imageUrl));
const mimeTypes: Record<string, string> = {
".jpg": "image/jpeg",
".jpeg": "image/jpeg",

View File

@@ -6,6 +6,7 @@ import {
postJsonRequest,
resolveProviderHttpRequestConfig,
} from "openclaw/plugin-sdk/provider-http";
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
import type {
GeneratedVideoAsset,
VideoGenerationProvider,
@@ -212,7 +213,7 @@ async function pollRunwayTask(params: {
(typeof payload.failure === "string"
? payload.failure
: payload.failure?.message
)?.trim() || `Runway video generation ${payload.status.toLowerCase()}`,
)?.trim() || `Runway video generation ${normalizeLowercaseStringOrEmpty(payload.status)}`,
);
case "PENDING":
case "RUNNING":

View File

@@ -26,6 +26,7 @@ import { isVerbose, logVerbose } from "openclaw/plugin-sdk/runtime-env";
import { resolvePreferredOpenClawTmpDir } from "openclaw/plugin-sdk/sandbox";
import {
normalizeLowercaseStringOrEmpty,
normalizeOptionalLowercaseString,
normalizeOptionalString,
resolveConfigDir,
resolveUserPath,
@@ -329,9 +330,7 @@ export function resolveTtsConfig(cfg: OpenClawConfig): ResolvedTtsConfig {
mode: raw.mode ?? "final",
provider:
normalizeConfiguredSpeechProviderId(raw.provider) ??
(providerSource === "config"
? (normalizeOptionalString(raw.provider)?.toLowerCase() ?? "")
: ""),
(providerSource === "config" ? (normalizeOptionalLowercaseString(raw.provider) ?? "") : ""),
providerSource,
summaryModel: normalizeOptionalString(raw.summaryModel),
modelOverrides: resolveModelOverridePolicy(raw.modelOverrides),

View File

@@ -5,7 +5,11 @@ import {
fetchWithTimeout,
resolveProviderHttpRequestConfig,
} from "openclaw/plugin-sdk/provider-http";
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
import {
normalizeLowercaseStringOrEmpty,
normalizeOptionalLowercaseString,
normalizeOptionalString,
} from "openclaw/plugin-sdk/text-runtime";
export const DEFAULT_VYDRA_BASE_URL = "https://www.vydra.ai/api/v1";
export const DEFAULT_VYDRA_IMAGE_MODEL = "grok-imagine";
@@ -130,7 +134,7 @@ export function resolveVydraResponseJobId(payload: unknown): string | undefined
}
export function resolveVydraResponseStatus(payload: unknown): string | undefined {
return trimToUndefined(asObject(payload)?.status)?.toLowerCase();
return normalizeOptionalLowercaseString(trimToUndefined(asObject(payload)?.status));
}
export function resolveVydraErrorMessage(payload: unknown): string | undefined {
@@ -187,7 +191,7 @@ export function extractVydraResultUrls(payload: unknown, kind: VydraMediaKind):
}
function inferExtension(kind: VydraMediaKind, mimeType: string): string {
const normalized = mimeType.toLowerCase();
const normalized = normalizeLowercaseStringOrEmpty(mimeType);
if (normalized.includes("jpeg")) {
return "jpg";
}

View File

@@ -204,7 +204,9 @@ export function buildXaiCatalogModels(): ModelDefinitionConfig[] {
export function resolveXaiCatalogEntry(modelId: string) {
const trimmed = modelId.trim();
const lower = normalizeOptionalLowercaseString(modelId) ?? "";
const exact = XAI_MODEL_CATALOG.find((entry) => entry.id.toLowerCase() === lower);
const exact = XAI_MODEL_CATALOG.find(
(entry) => normalizeOptionalLowercaseString(entry.id) === lower,
);
if (exact) {
return toModelDefinition(exact);
}

View File

@@ -40,7 +40,7 @@ function resolveGlm5ForwardCompatModel(
ctx: ProviderResolveDynamicModelContext,
): ProviderRuntimeModel | undefined {
const trimmedModelId = ctx.modelId.trim();
if (!trimmedModelId.toLowerCase().startsWith("glm-5")) {
if (!normalizeLowercaseStringOrEmpty(trimmedModelId).startsWith("glm-5")) {
return undefined;
}

View File

@@ -4,13 +4,14 @@ import {
stripTargetKindPrefix,
type ChannelOutboundSessionRouteParams,
} from "openclaw/plugin-sdk/core";
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
export function resolveZaloOutboundSessionRoute(params: ChannelOutboundSessionRouteParams) {
const trimmed = stripChannelTargetPrefix(params.target, "zalo", "zl");
if (!trimmed) {
return null;
}
const isGroup = trimmed.toLowerCase().startsWith("group:");
const isGroup = normalizeLowercaseStringOrEmpty(trimmed).startsWith("group:");
const peerId = stripTargetKindPrefix(trimmed);
if (!peerId) {
return null;