refactor: dedupe routing lowercase helpers

This commit is contained in:
Peter Steinberger
2026-04-07 11:17:37 +01:00
parent 36938bccb5
commit 1fdb013599
7 changed files with 65 additions and 33 deletions

View File

@@ -1,4 +1,5 @@
import type { CliBackendConfig } from "../config/types.js";
import { normalizeLowercaseStringOrEmpty } from "../shared/string-coerce.js";
import { isRecord } from "../utils.js";
type CliUsage = {
@@ -23,7 +24,7 @@ export type CliStreamingDelta = {
};
function isClaudeCliProvider(providerId: string): boolean {
return providerId.trim().toLowerCase() === "claude-cli";
return normalizeLowercaseStringOrEmpty(providerId) === "claude-cli";
}
function extractJsonObjectCandidates(raw: string): string[] {

View File

@@ -14,7 +14,11 @@ import { readClaudeCliCredentialsCached } from "../agents/cli-credentials.js";
import { formatCliCommand } from "../cli/command-format.js";
import type { OpenClawConfig } from "../config/config.js";
import { resolveExecutablePath } from "../infra/executable-path.js";
import { normalizeOptionalString, resolvePrimaryStringValue } from "../shared/string-coerce.js";
import {
normalizeOptionalLowercaseString,
normalizeOptionalString,
resolvePrimaryStringValue,
} from "../shared/string-coerce.js";
import { note } from "../terminal/note.js";
import { shortenHomePath } from "../utils.js";
@@ -32,11 +36,11 @@ function usesClaudeCliModelSelection(cfg: OpenClawConfig): boolean {
const primary = resolvePrimaryStringValue(
cfg.agents?.defaults?.model as string | { primary?: string; fallbacks?: string[] } | undefined,
);
if (primary?.trim().toLowerCase().startsWith(`${CLAUDE_CLI_PROVIDER}/`)) {
if (normalizeOptionalLowercaseString(primary)?.startsWith(`${CLAUDE_CLI_PROVIDER}/`)) {
return true;
}
return Object.keys(cfg.agents?.defaults?.models ?? {}).some((key) =>
key.trim().toLowerCase().startsWith(`${CLAUDE_CLI_PROVIDER}/`),
normalizeOptionalLowercaseString(key)?.startsWith(`${CLAUDE_CLI_PROVIDER}/`),
);
}
@@ -45,7 +49,11 @@ function hasClaudeCliConfigSignals(cfg: OpenClawConfig): boolean {
return true;
}
const backendConfig = cfg.agents?.defaults?.cliBackends ?? {};
if (Object.keys(backendConfig).some((key) => key.trim().toLowerCase() === CLAUDE_CLI_PROVIDER)) {
if (
Object.keys(backendConfig).some(
(key) => normalizeOptionalLowercaseString(key) === CLAUDE_CLI_PROVIDER,
)
) {
return true;
}
return Object.values(cfg.auth?.profiles ?? {}).some(
@@ -63,7 +71,7 @@ function hasClaudeCliStoreSignals(store: AuthProfileStore): boolean {
function resolveClaudeCliCommand(cfg: OpenClawConfig): string {
const configured = cfg.agents?.defaults?.cliBackends ?? {};
for (const [key, entry] of Object.entries(configured)) {
if (key.trim().toLowerCase() !== CLAUDE_CLI_PROVIDER) {
if (normalizeOptionalLowercaseString(key) !== CLAUDE_CLI_PROVIDER) {
continue;
}
const command = normalizeOptionalString(entry?.command);

View File

@@ -5,6 +5,10 @@ import { normalizeProviderId } from "../agents/provider-id.js";
import { resolveAgentModelPrimaryValue } from "../config/model-input.js";
import type { SessionEntry } from "../config/sessions/types.js";
import type { OpenClawConfig } from "../config/types.js";
import {
normalizeLowercaseStringOrEmpty,
normalizeOptionalLowercaseString,
} from "../shared/string-coerce.js";
function resolveStatusModelRefFromRaw(params: {
cfg: OpenClawConfig;
@@ -17,11 +21,11 @@ function resolveStatusModelRefFromRaw(params: {
}
const configuredModels = params.cfg.agents?.defaults?.models ?? {};
if (!trimmed.includes("/")) {
const aliasKey = trimmed.toLowerCase();
const aliasKey = normalizeLowercaseStringOrEmpty(trimmed);
for (const [modelKey, entry] of Object.entries(configuredModels)) {
const aliasValue = (entry as { alias?: unknown } | undefined)?.alias;
const alias = typeof aliasValue === "string" ? aliasValue.trim() : "";
if (!alias || alias.toLowerCase() !== aliasKey) {
if (!alias || normalizeOptionalLowercaseString(alias) !== aliasKey) {
continue;
}
const parsed = parseModelRef(modelKey, params.defaultProvider, {

View File

@@ -1,4 +1,8 @@
import type { OpenClawConfig } from "../config/config.js";
import {
normalizeOptionalLowercaseString,
normalizeOptionalString,
} from "../shared/string-coerce.js";
import {
resolveMemorySlotDecisionShared,
resolveEnableStateShared,
@@ -69,12 +73,21 @@ function getBundledPluginAliasLookup(): ReadonlyMap<string, string> {
if (plugin.origin !== "bundled") {
continue;
}
lookup.set(plugin.id.toLowerCase(), plugin.id);
const pluginId = normalizeOptionalLowercaseString(plugin.id);
if (pluginId) {
lookup.set(pluginId, plugin.id);
}
for (const providerId of plugin.providers) {
lookup.set(providerId.toLowerCase(), plugin.id);
const normalizedProviderId = normalizeOptionalLowercaseString(providerId);
if (normalizedProviderId) {
lookup.set(normalizedProviderId, plugin.id);
}
}
for (const legacyPluginId of plugin.legacyPluginIds ?? []) {
lookup.set(legacyPluginId.toLowerCase(), plugin.id);
const normalizedLegacyPluginId = normalizeOptionalLowercaseString(legacyPluginId);
if (normalizedLegacyPluginId) {
lookup.set(normalizedLegacyPluginId, plugin.id);
}
}
}
bundledPluginAliasLookupCache = lookup;
@@ -82,8 +95,9 @@ function getBundledPluginAliasLookup(): ReadonlyMap<string, string> {
}
export function normalizePluginId(id: string): string {
const trimmed = id.trim();
return getBundledPluginAliasLookup().get(trimmed.toLowerCase()) ?? trimmed;
const trimmed = normalizeOptionalString(id) ?? "";
const normalized = normalizeOptionalLowercaseString(trimmed) ?? "";
return getBundledPluginAliasLookup().get(normalized) ?? trimmed;
}
const PLUGIN_ACTIVATION_REASON_BY_CAUSE: Record<PluginActivationCause, string> = {

View File

@@ -1,4 +1,5 @@
import { isBlockedObjectKey } from "../infra/prototype-keys.js";
import { normalizeLowercaseStringOrEmpty } from "../shared/string-coerce.js";
export const DEFAULT_ACCOUNT_ID = "default";
@@ -12,11 +13,11 @@ const normalizeAccountIdCache = new Map<string, string>();
const normalizeOptionalAccountIdCache = new Map<string, string | undefined>();
function canonicalizeAccountId(value: string): string {
const normalized = normalizeLowercaseStringOrEmpty(value);
if (VALID_ID_RE.test(value)) {
return value.toLowerCase();
return normalized;
}
return value
.toLowerCase()
return normalized
.replace(INVALID_CHARS_RE, "-")
.replace(LEADING_DASH_RE, "")
.replace(TRAILING_DASH_RE, "")

View File

@@ -1,3 +1,5 @@
import { normalizeLowercaseStringOrEmpty } from "../shared/string-coerce.js";
export function resolveAccountEntry<T>(
accounts: Record<string, T> | undefined,
accountId: string,
@@ -8,8 +10,10 @@ export function resolveAccountEntry<T>(
if (Object.hasOwn(accounts, accountId)) {
return accounts[accountId];
}
const normalized = accountId.toLowerCase();
const matchKey = Object.keys(accounts).find((key) => key.toLowerCase() === normalized);
const normalized = normalizeLowercaseStringOrEmpty(accountId);
const matchKey = Object.keys(accounts).find(
(key) => normalizeLowercaseStringOrEmpty(key) === normalized,
);
return matchKey ? accounts[matchKey] : undefined;
}

View File

@@ -39,8 +39,7 @@ export function scopedHeartbeatWakeOptions<T extends object>(
}
export function normalizeMainKey(value: string | undefined | null): string {
const trimmed = (value ?? "").trim();
return trimmed ? trimmed.toLowerCase() : DEFAULT_MAIN_KEY;
return normalizeLowercaseStringOrEmpty(value) || DEFAULT_MAIN_KEY;
}
export function toAgentRequestSessionKey(storeKey: string | undefined | null): string | undefined {
@@ -57,14 +56,14 @@ export function toAgentStoreSessionKey(params: {
mainKey?: string | undefined;
}): string {
const raw = (params.requestKey ?? "").trim();
if (!raw || raw.toLowerCase() === DEFAULT_MAIN_KEY) {
const lowered = normalizeLowercaseStringOrEmpty(raw);
if (!raw || lowered === DEFAULT_MAIN_KEY) {
return buildAgentMainSessionKey({ agentId: params.agentId, mainKey: params.mainKey });
}
const parsed = parseAgentSessionKey(raw);
if (parsed) {
return `agent:${parsed.agentId}:${parsed.rest}`;
}
const lowered = raw.toLowerCase();
if (lowered.startsWith("agent:")) {
return lowered;
}
@@ -84,7 +83,9 @@ export function classifySessionKeyShape(sessionKey: string | undefined | null):
if (parseAgentSessionKey(raw)) {
return "agent";
}
return raw.toLowerCase().startsWith("agent:") ? "malformed_agent" : "legacy_or_alias";
return normalizeLowercaseStringOrEmpty(raw).startsWith("agent:")
? "malformed_agent"
: "legacy_or_alias";
}
export function normalizeAgentId(value: string | undefined | null): string {
@@ -92,14 +93,14 @@ export function normalizeAgentId(value: string | undefined | null): string {
if (!trimmed) {
return DEFAULT_AGENT_ID;
}
const normalized = normalizeLowercaseStringOrEmpty(trimmed);
// Keep it path-safe + shell-friendly.
if (VALID_ID_RE.test(trimmed)) {
return trimmed.toLowerCase();
return normalized;
}
// Best-effort fallback: collapse invalid characters to "-"
return (
trimmed
.toLowerCase()
normalized
.replace(INVALID_CHARS_RE, "-")
.replace(LEADING_DASH_RE, "")
.replace(TRAILING_DASH_RE, "")
@@ -151,7 +152,7 @@ export function buildAgentPeerSessionKey(params: {
if (linkedPeerId) {
peerId = linkedPeerId;
}
peerId = peerId.toLowerCase();
peerId = normalizeLowercaseStringOrEmpty(peerId);
if (dmScope === "per-account-channel-peer" && peerId) {
const channel = normalizeLowercaseStringOrEmpty(params.channel) || "unknown";
const accountId = normalizeAccountId(params.accountId);
@@ -170,7 +171,7 @@ export function buildAgentPeerSessionKey(params: {
});
}
const channel = normalizeLowercaseStringOrEmpty(params.channel) || "unknown";
const peerId = ((params.peerId ?? "").trim() || "unknown").toLowerCase();
const peerId = normalizeLowercaseStringOrEmpty(params.peerId) || "unknown";
return `agent:${normalizeAgentId(params.agentId)}:${channel}:${peerKind}:${peerId}`;
}
@@ -228,7 +229,7 @@ export function buildGroupHistoryKey(params: {
}): string {
const channel = normalizeToken(params.channel) || "unknown";
const accountId = normalizeAccountId(params.accountId);
const peerId = params.peerId.trim().toLowerCase() || "unknown";
const peerId = normalizeLowercaseStringOrEmpty(params.peerId) || "unknown";
return `${channel}:${accountId}:${params.peerKind}:${peerId}`;
}
@@ -243,12 +244,11 @@ export function resolveThreadSessionKeys(params: {
if (!threadId) {
return { sessionKey: params.baseSessionKey, parentSessionKey: undefined };
}
const normalizedThreadId = (params.normalizeThreadId ?? ((value: string) => value.toLowerCase()))(
threadId,
);
const normalizedThread =
params.normalizeThreadId?.(threadId) ?? normalizeLowercaseStringOrEmpty(threadId);
const useSuffix = params.useSuffix ?? true;
const sessionKey = useSuffix
? `${params.baseSessionKey}:thread:${normalizedThreadId}`
? `${params.baseSessionKey}:thread:${normalizedThread}`
: params.baseSessionKey;
return { sessionKey, parentSessionKey: params.parentSessionKey };
}