refactor: dedupe provider lowercase helpers

This commit is contained in:
Peter Steinberger
2026-04-07 15:41:05 +01:00
parent 761e12008d
commit bbcc95948e
13 changed files with 47 additions and 26 deletions

View File

@@ -3,6 +3,7 @@ import {
type OpenClawConfig,
type ProviderAuthResult,
} from "openclaw/plugin-sdk/provider-auth";
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
import {
readClaudeCliCredentialsForSetup,
readClaudeCliCredentialsForSetupNonInteractive,
@@ -19,11 +20,11 @@ type ClaudeCliCredential = NonNullable<ReturnType<typeof readClaudeCliCredential
function toClaudeCliModelRef(raw: string): string | null {
const trimmed = raw.trim();
if (!trimmed.toLowerCase().startsWith("anthropic/")) {
if (!normalizeLowercaseStringOrEmpty(trimmed).startsWith("anthropic/")) {
return null;
}
const modelId = trimmed.slice("anthropic/".length).trim();
if (!modelId.toLowerCase().startsWith("claude-")) {
if (!normalizeLowercaseStringOrEmpty(modelId).startsWith("claude-")) {
return null;
}
return `claude-cli/${modelId}`;

View File

@@ -1,5 +1,6 @@
import type { OpenClawConfig } from "openclaw/plugin-sdk/plugin-entry";
import { normalizeProviderId } from "openclaw/plugin-sdk/provider-model-shared";
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
import { CLAUDE_CLI_BACKEND_ID, CLAUDE_CLI_DEFAULT_ALLOWLIST_REFS } from "./cli-shared.js";
const ANTHROPIC_PROVIDER_API = "anthropic-messages";
@@ -84,7 +85,7 @@ function resolveAnthropicPrimaryModelRef(raw?: string): string | null {
if (!trimmed) {
return null;
}
const aliasKey = trimmed.toLowerCase();
const aliasKey = normalizeLowercaseStringOrEmpty(trimmed);
if (aliasKey === "opus") {
return "anthropic/claude-opus-4-6";
}
@@ -124,7 +125,7 @@ function isAnthropicCacheRetentionTarget(
parsed &&
(parsed.provider === "anthropic" ||
(parsed.provider === "amazon-bedrock" &&
parsed.model.toLowerCase().includes("anthropic.claude"))),
normalizeLowercaseStringOrEmpty(parsed.model).includes("anthropic.claude"))),
);
}

View File

@@ -8,11 +8,7 @@ import {
streamWithPayloadPatch,
} from "openclaw/plugin-sdk/provider-stream-shared";
import { createSubsystemLogger } from "openclaw/plugin-sdk/runtime-env";
import {
normalizeLowercaseStringOrEmpty,
normalizeOptionalString,
readStringValue,
} from "openclaw/plugin-sdk/text-runtime";
import { normalizeLowercaseStringOrEmpty, readStringValue } from "openclaw/plugin-sdk/text-runtime";
const log = createSubsystemLogger("anthropic-stream");
@@ -50,7 +46,9 @@ function mergeAnthropicBetaHeader(
betas: string[],
): Record<string, string> {
const merged = { ...headers };
const existingKey = Object.keys(merged).find((key) => key.toLowerCase() === "anthropic-beta");
const existingKey = Object.keys(merged).find(
(key) => normalizeLowercaseStringOrEmpty(key) === "anthropic-beta",
);
const existing = existingKey ? parseHeaderList(merged[existingKey]) : [];
const values = Array.from(new Set([...existing, ...betas]));
const key = existingKey ?? "anthropic-beta";
@@ -73,7 +71,7 @@ function normalizeFastMode(raw?: string | boolean | null): boolean | undefined {
if (!raw) {
return undefined;
}
const key = raw.toLowerCase();
const key = normalizeLowercaseStringOrEmpty(raw);
if (["off", "false", "no", "0", "disable", "disabled", "normal"].includes(key)) {
return false;
}
@@ -87,7 +85,7 @@ function normalizeAnthropicServiceTier(value: unknown): AnthropicServiceTier | u
if (typeof value !== "string") {
return undefined;
}
const normalized = normalizeOptionalString(value)?.toLowerCase();
const normalized = normalizeLowercaseStringOrEmpty(value);
if (normalized === "auto" || normalized === "standard_only") {
return normalized;
}

View File

@@ -1,8 +1,10 @@
import path from "node:path";
import { fileURLToPath, URL } from "node:url";
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
function isLocalFileUrlHost(hostname: string): boolean {
return hostname === "" || hostname.toLowerCase() === "localhost";
const normalized = normalizeLowercaseStringOrEmpty(hostname);
return normalized === "" || normalized === "localhost";
}
function assertNoWindowsNetworkPath(filePath: string, label = "Path"): void {

View File

@@ -2,6 +2,7 @@ import type { IncomingMessage, ServerResponse } from "node:http";
import { safeEqualSecret } from "openclaw/plugin-sdk/browser-security-runtime";
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
import { isPrivateNetworkOptInEnabled } from "openclaw/plugin-sdk/ssrf-runtime";
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
import { createBlueBubblesDebounceRegistry } from "./monitor-debounce.js";
import {
asRecord,
@@ -110,7 +111,7 @@ function normalizeAuthToken(raw: string): string {
if (!value) {
return "";
}
if (value.toLowerCase().startsWith("bearer ")) {
if (normalizeLowercaseStringOrEmpty(value).startsWith("bearer ")) {
return value.slice("bearer ".length).trim();
}
return value;

View File

@@ -2,6 +2,7 @@ import type { ImageGenerationProvider } from "openclaw/plugin-sdk/image-generati
import { isProviderApiKeyConfigured } from "openclaw/plugin-sdk/provider-auth";
import { resolveApiKeyForProvider } from "openclaw/plugin-sdk/provider-auth-runtime";
import { assertOkOrThrowHttpError, postJsonRequest } from "openclaw/plugin-sdk/provider-http";
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
import { normalizeGoogleModelId, resolveGoogleGenerativeAiHttpRequestConfig } from "./api.js";
const DEFAULT_GOOGLE_IMAGE_MODEL = "gemini-3.1-flash-image-preview";
@@ -57,7 +58,7 @@ function mapSizeToImageConfig(
return undefined;
}
const normalized = trimmed.toLowerCase();
const normalized = normalizeLowercaseStringOrEmpty(trimmed);
const mapping = new Map<string, string>([
["1024x1024", "1:1"],
["1024x1536", "2:3"],

View File

@@ -4,6 +4,7 @@ import {
resolveLoggerBackedRuntime,
safeParseJsonWithSchema,
} from "openclaw/plugin-sdk/extension-shared";
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
import { z } from "zod";
import {
WEBHOOK_RATE_LIMIT_DEFAULTS,
@@ -72,7 +73,7 @@ function formatError(err: unknown): string {
function normalizeOrigin(value: string): string | null {
try {
return new URL(value).origin.toLowerCase();
return normalizeLowercaseStringOrEmpty(new URL(value).origin);
} catch {
return null;
}

View File

@@ -1,3 +1,5 @@
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
export function stripNextcloudTalkTargetPrefix(raw: string): string | undefined {
const trimmed = raw.trim();
if (!trimmed) {
@@ -27,7 +29,7 @@ export function stripNextcloudTalkTargetPrefix(raw: string): string | undefined
export function normalizeNextcloudTalkMessagingTarget(raw: string): string | undefined {
const normalized = stripNextcloudTalkTargetPrefix(raw);
return normalized ? `nextcloud-talk:${normalized}`.toLowerCase() : undefined;
return normalized ? normalizeLowercaseStringOrEmpty(`nextcloud-talk:${normalized}`) : undefined;
}
export function looksLikeNextcloudTalkTargetId(raw: string): boolean {

View File

@@ -5,7 +5,10 @@ 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 {
normalizeLowercaseStringOrEmpty,
normalizeOptionalLowercaseString,
} from "openclaw/plugin-sdk/text-runtime";
import { OLLAMA_DEFAULT_BASE_URL, OLLAMA_DEFAULT_MODEL } from "./defaults.js";
import {
buildOllamaBaseUrlSsrFPolicy,
@@ -42,7 +45,7 @@ function normalizeOllamaModelName(value: string | undefined): string | undefined
if (!trimmed) {
return undefined;
}
if (trimmed.toLowerCase().startsWith("ollama/")) {
if (normalizeLowercaseStringOrEmpty(trimmed).startsWith("ollama/")) {
const normalized = trimmed.slice("ollama/".length).trim();
return normalized || undefined;
}

View File

@@ -5,6 +5,10 @@ import type {
SpeechProviderOverrides,
SpeechProviderPlugin,
} from "openclaw/plugin-sdk/speech";
import {
normalizeLowercaseStringOrEmpty,
normalizeOptionalLowercaseString,
} from "openclaw/plugin-sdk/text-runtime";
import {
asFiniteNumber,
asObjectRecord,
@@ -44,7 +48,7 @@ type OpenAITtsProviderOverrides = {
function normalizeOpenAISpeechResponseFormat(
value: unknown,
): OpenAiSpeechResponseFormat | undefined {
const next = trimToUndefined(typeof value === "string" ? value : undefined)?.toLowerCase();
const next = normalizeOptionalLowercaseString(value);
if (!next) {
return undefined;
}
@@ -58,7 +62,7 @@ function normalizeOpenAISpeechResponseFormat(
function isGroqSpeechBaseUrl(baseUrl: string): boolean {
try {
const hostname = new URL(baseUrl).hostname.toLowerCase();
const hostname = normalizeLowercaseStringOrEmpty(new URL(baseUrl).hostname);
return hostname === "groq.com" || hostname.endsWith(".groq.com");
} catch {
return false;

View File

@@ -32,6 +32,7 @@ import {
wrapWebContent,
writeCachedSearchPayload,
} from "openclaw/plugin-sdk/provider-web-search";
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
const DEFAULT_PERPLEXITY_BASE_URL = "https://openrouter.ai/api/v1";
const PERPLEXITY_DIRECT_BASE_URL = "https://api.perplexity.ai";
@@ -85,7 +86,7 @@ function inferPerplexityBaseUrlFromApiKey(apiKey?: string): PerplexityBaseUrlHin
if (!apiKey) {
return undefined;
}
const normalized = apiKey.toLowerCase();
const normalized = normalizeLowercaseStringOrEmpty(apiKey);
if (PERPLEXITY_KEY_PREFIXES.some((prefix) => normalized.startsWith(prefix))) {
return "direct";
}
@@ -147,7 +148,9 @@ function resolvePerplexityModel(perplexity?: PerplexityConfig): string {
function isDirectPerplexityBaseUrl(baseUrl: string): boolean {
try {
return new URL(baseUrl.trim()).hostname.toLowerCase() === "api.perplexity.ai";
return (
normalizeLowercaseStringOrEmpty(new URL(baseUrl.trim()).hostname) === "api.perplexity.ai"
);
} catch {
return false;
}

View File

@@ -4,6 +4,7 @@ import {
type ProviderCatalogContext,
} from "openclaw/plugin-sdk/plugin-entry";
import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth-api-key";
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
import {
applyStepFunPlanConfig,
applyStepFunPlanConfigCn,
@@ -38,7 +39,7 @@ function inferRegionFromBaseUrl(baseUrl: string | undefined): StepFunRegion | un
return undefined;
}
try {
const host = new URL(baseUrl).hostname.toLowerCase();
const host = normalizeLowercaseStringOrEmpty(new URL(baseUrl).hostname);
if (host === "api.stepfun.com") {
return "cn";
}

View File

@@ -1,7 +1,10 @@
import type { OpenClawConfig } from "../config/config.js";
import { resolveProviderReasoningOutputModeWithPlugin } from "../plugins/provider-runtime.js";
import type { ProviderRuntimeModel } from "../plugins/types.js";
import { normalizeOptionalString } from "../shared/string-coerce.js";
import {
normalizeOptionalLowercaseString,
normalizeOptionalString,
} from "../shared/string-coerce.js";
const BUILTIN_REASONING_OUTPUT_MODES = {
"google-generative-ai": "tagged",
@@ -25,7 +28,7 @@ export function resolveReasoningOutputMode(params: {
return "native";
}
const normalized = provider.toLowerCase();
const normalized = normalizeOptionalLowercaseString(provider) ?? "";
const pluginMode = resolveProviderReasoningOutputModeWithPlugin({
provider,
config: params.config,