refactor: dedupe lowercase string helpers

This commit is contained in:
Peter Steinberger
2026-04-07 10:11:52 +01:00
parent f1bdfca1ed
commit f54a57b80a
17 changed files with 67 additions and 78 deletions

View File

@@ -428,10 +428,10 @@ function buildArtifactContext(
}
const artifactContext = {
agentId: normalizeContextString(context.agentId),
sessionId: normalizeContextString(context.sessionId),
messageChannel: normalizeContextString(context.messageChannel),
agentAccountId: normalizeContextString(context.agentAccountId),
agentId: normalizeOptionalString(context.agentId),
sessionId: normalizeOptionalString(context.sessionId),
messageChannel: normalizeOptionalString(context.messageChannel),
agentAccountId: normalizeOptionalString(context.agentAccountId),
};
return Object.values(artifactContext).some((value) => value !== undefined)
@@ -439,11 +439,6 @@ function buildArtifactContext(
: undefined;
}
function normalizeContextString(value: string | undefined): string | undefined {
const normalized = normalizeOptionalString(value);
return normalized ? normalized : undefined;
}
function normalizeDiffInput(params: DiffsToolParams): DiffInput {
const patch = params.patch?.trim();
const before = params.before;

View File

@@ -3,7 +3,7 @@ import {
deliverTextOrMediaReply,
resolveSendableOutboundReplyParts,
} from "openclaw/plugin-sdk/reply-payload";
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
import { normalizeOptionalLowercaseString } from "openclaw/plugin-sdk/text-runtime";
import type { OpenClawConfig } from "../runtime-api.js";
import {
createChannelReplyPipeline,
@@ -73,7 +73,7 @@ export function registerGoogleChatWebhookTarget(target: WebhookTarget): () => vo
}
function normalizeAudienceType(value?: string | null): GoogleChatAudienceType | undefined {
const normalized = normalizeOptionalString(value)?.toLowerCase();
const normalized = normalizeOptionalLowercaseString(value);
if (normalized === "app-url" || normalized === "app_url" || normalized === "app") {
return "app-url";
}

View File

@@ -114,11 +114,6 @@ const STATUS_REACTION_EMOJI_KEYS: StatusReactionEmojiKey[] = [
"compacting",
];
function normalizeEmoji(value: string | undefined): string | undefined {
const trimmed = normalizeOptionalString(value);
return trimmed ? trimmed : undefined;
}
function toUniqueNonEmpty(values: string[]): string[] {
return Array.from(new Set(values.map((value) => value.trim()).filter(Boolean)));
}
@@ -128,18 +123,18 @@ export function resolveTelegramStatusReactionEmojis(params: {
overrides?: StatusReactionEmojis;
}): Required<StatusReactionEmojis> {
const { overrides } = params;
const queuedFallback = normalizeEmoji(params.initialEmoji) ?? DEFAULT_EMOJIS.queued;
const queuedFallback = normalizeOptionalString(params.initialEmoji) ?? DEFAULT_EMOJIS.queued;
return {
queued: normalizeEmoji(overrides?.queued) ?? queuedFallback,
thinking: normalizeEmoji(overrides?.thinking) ?? DEFAULT_EMOJIS.thinking,
tool: normalizeEmoji(overrides?.tool) ?? DEFAULT_EMOJIS.tool,
coding: normalizeEmoji(overrides?.coding) ?? DEFAULT_EMOJIS.coding,
web: normalizeEmoji(overrides?.web) ?? DEFAULT_EMOJIS.web,
done: normalizeEmoji(overrides?.done) ?? DEFAULT_EMOJIS.done,
error: normalizeEmoji(overrides?.error) ?? DEFAULT_EMOJIS.error,
stallSoft: normalizeEmoji(overrides?.stallSoft) ?? DEFAULT_EMOJIS.stallSoft,
stallHard: normalizeEmoji(overrides?.stallHard) ?? DEFAULT_EMOJIS.stallHard,
compacting: normalizeEmoji(overrides?.compacting) ?? DEFAULT_EMOJIS.compacting,
queued: normalizeOptionalString(overrides?.queued) ?? queuedFallback,
thinking: normalizeOptionalString(overrides?.thinking) ?? DEFAULT_EMOJIS.thinking,
tool: normalizeOptionalString(overrides?.tool) ?? DEFAULT_EMOJIS.tool,
coding: normalizeOptionalString(overrides?.coding) ?? DEFAULT_EMOJIS.coding,
web: normalizeOptionalString(overrides?.web) ?? DEFAULT_EMOJIS.web,
done: normalizeOptionalString(overrides?.done) ?? DEFAULT_EMOJIS.done,
error: normalizeOptionalString(overrides?.error) ?? DEFAULT_EMOJIS.error,
stallSoft: normalizeOptionalString(overrides?.stallSoft) ?? DEFAULT_EMOJIS.stallSoft,
stallHard: normalizeOptionalString(overrides?.stallHard) ?? DEFAULT_EMOJIS.stallHard,
compacting: normalizeOptionalString(overrides?.compacting) ?? DEFAULT_EMOJIS.compacting,
};
}
@@ -148,7 +143,7 @@ export function buildTelegramStatusReactionVariants(
): Map<string, string[]> {
const variantsByRequested = new Map<string, string[]>();
for (const key of STATUS_REACTION_EMOJI_KEYS) {
const requested = normalizeEmoji(emojis[key]);
const requested = normalizeOptionalString(emojis[key]);
if (!requested) {
continue;
}
@@ -225,7 +220,7 @@ export function resolveTelegramReactionVariant(params: {
variantsByRequestedEmoji: Map<string, string[]>;
allowedEmojiReactions?: Set<TelegramReactionEmoji> | null;
}): TelegramReactionEmoji | undefined {
const requestedEmoji = normalizeEmoji(params.requestedEmoji);
const requestedEmoji = normalizeOptionalString(params.requestedEmoji);
if (!requestedEmoji) {
return undefined;
}

View File

@@ -1,5 +1,5 @@
import type { SessionId } from "@agentclientprotocol/sdk";
import { normalizeOptionalString } from "../shared/string-coerce.js";
import { normalizeOptionalLowercaseString } from "../shared/string-coerce.js";
import { VERSION } from "../version.js";
export const ACP_PROVENANCE_MODE_VALUES = ["off", "meta", "meta+receipt"] as const;
@@ -9,7 +9,7 @@ export type AcpProvenanceMode = (typeof ACP_PROVENANCE_MODE_VALUES)[number];
export function normalizeAcpProvenanceMode(
value: string | undefined,
): AcpProvenanceMode | undefined {
const normalized = normalizeOptionalString(value)?.toLowerCase();
const normalized = normalizeOptionalLowercaseString(value);
if (!normalized) {
return undefined;
}

View File

@@ -2,7 +2,11 @@ import { getChannelPlugin, normalizeChannelId } from "../channels/plugins/index.
import { normalizeTargetForProvider } from "../infra/outbound/target-normalization.js";
import { splitMediaFromOutput } from "../media/parse.js";
import { pluginRegistrationContractRegistry } from "../plugins/contracts/registry.js";
import { normalizeOptionalString, readStringValue } from "../shared/string-coerce.js";
import {
normalizeOptionalLowercaseString,
normalizeOptionalString,
readStringValue,
} from "../shared/string-coerce.js";
import { truncateUtf16Safe } from "../utils.js";
import { collectTextContentBlocks } from "./content-blocks.js";
import { type MessagingToolSend } from "./pi-embedded-messaging.js";
@@ -182,7 +186,7 @@ function readToolResultDetails(result: unknown): Record<string, unknown> | undef
function readToolResultStatus(result: unknown): string | undefined {
const status = readToolResultDetails(result)?.status;
return normalizeOptionalString(status)?.toLowerCase();
return normalizeOptionalLowercaseString(status);
}
function isExternalToolResult(result: unknown): boolean {

View File

@@ -3,7 +3,10 @@ import type { OpenClawConfig } from "../../config/config.js";
import type { TtsAutoMode } from "../../config/types.tts.js";
import { logVerbose } from "../../globals.js";
import { formatErrorMessage } from "../../infra/errors.js";
import { normalizeOptionalString } from "../../shared/string-coerce.js";
import {
normalizeOptionalLowercaseString,
normalizeOptionalString,
} from "../../shared/string-coerce.js";
import { resolveStatusTtsSnapshot } from "../../tts/status-config.js";
import { resolveConfiguredTtsMode } from "../../tts/tts-config.js";
import type { FinalizedMsgContext } from "../templating.js";
@@ -53,11 +56,6 @@ type ToolMessageHandle = {
messageId: string;
};
function normalizeDeliveryChannel(value: string | undefined): string | undefined {
const normalized = normalizeOptionalString(value)?.toLowerCase();
return normalized || undefined;
}
async function shouldTreatDeliveredTextAsVisible(params: {
channel: string | undefined;
kind: ReplyDispatchKind;
@@ -70,7 +68,7 @@ async function shouldTreatDeliveredTextAsVisible(params: {
if (params.kind === "final") {
return true;
}
const channelId = normalizeDeliveryChannel(params.channel);
const channelId = normalizeOptionalLowercaseString(params.channel);
if (!channelId) {
return false;
}
@@ -185,8 +183,8 @@ export function createAcpDispatchDeliveryCoordinator(params: {
},
toolMessageByCallId: new Map(),
};
const directChannel = normalizeDeliveryChannel(params.ctx.Provider ?? params.ctx.Surface);
const routedChannel = normalizeDeliveryChannel(params.originatingChannel);
const directChannel = normalizeOptionalLowercaseString(params.ctx.Provider ?? params.ctx.Surface);
const routedChannel = normalizeOptionalLowercaseString(params.originatingChannel);
const explicitAccountId = normalizeOptionalString(params.ctx.AccountId);
const resolvedAccountId =
explicitAccountId ??

View File

@@ -31,7 +31,7 @@ import { resolveCommitHash } from "../infra/git-commit.js";
import type { MediaUnderstandingDecision } from "../media-understanding/types.js";
import { listPluginCommands } from "../plugins/commands.js";
import { resolveAgentIdFromSessionKey } from "../routing/session-key.js";
import { normalizeOptionalString } from "../shared/string-coerce.js";
import { normalizeOptionalLowercaseString } from "../shared/string-coerce.js";
import { resolveStatusTtsSnapshot } from "../tts/status-config.js";
import {
estimateUsageCost,
@@ -98,7 +98,7 @@ type StatusArgs = {
type NormalizedAuthMode = "api-key" | "oauth" | "token" | "aws-sdk" | "mixed" | "unknown";
function normalizeAuthMode(value?: string): NormalizedAuthMode | undefined {
const normalized = normalizeOptionalString(value)?.toLowerCase();
const normalized = normalizeOptionalLowercaseString(value);
if (!normalized) {
return undefined;
}

View File

@@ -1,9 +1,9 @@
import { normalizeOptionalString } from "../shared/string-coerce.js";
import { normalizeOptionalLowercaseString } from "../shared/string-coerce.js";
export type ChatType = "direct" | "group" | "channel";
export function normalizeChatType(raw?: string): ChatType | undefined {
const value = normalizeOptionalString(raw)?.toLowerCase();
const value = normalizeOptionalLowercaseString(raw);
if (!value) {
return undefined;
}

View File

@@ -235,14 +235,9 @@ export type SessionEntry = {
acp?: SessionAcpMeta;
};
function normalizeRuntimeField(value: string | undefined): string | undefined {
const trimmed = normalizeOptionalString(value);
return trimmed ? trimmed : undefined;
}
export function normalizeSessionRuntimeModelFields(entry: SessionEntry): SessionEntry {
const normalizedModel = normalizeRuntimeField(entry.model);
const normalizedProvider = normalizeRuntimeField(entry.modelProvider);
const normalizedModel = normalizeOptionalString(entry.model);
const normalizedProvider = normalizeOptionalString(entry.modelProvider);
let next = entry;
if (!normalizedModel) {
@@ -327,8 +322,8 @@ export function mergeSessionEntryWithPolicy(
// Guard against stale provider carry-over when callers patch runtime model
// without also patching runtime provider.
if (Object.hasOwn(patch, "model") && !Object.hasOwn(patch, "modelProvider")) {
const patchedModel = normalizeRuntimeField(patch.model);
const existingModel = normalizeRuntimeField(existing.model);
const patchedModel = normalizeOptionalString(patch.model);
const existingModel = normalizeOptionalString(existing.model);
if (patchedModel && patchedModel !== existingModel) {
delete next.modelProvider;
}

View File

@@ -1,5 +1,5 @@
import { spawn } from "node:child_process";
import { normalizeOptionalString } from "../shared/string-coerce.js";
import { normalizeOptionalLowercaseString } from "../shared/string-coerce.js";
import { formatErrorMessage } from "./errors.js";
import { triggerOpenClawRestart } from "./restart.js";
import { detectRespawnSupervisor } from "./supervisor-markers.js";
@@ -13,7 +13,7 @@ export type GatewayRespawnResult = {
};
function isTruthy(value: string | undefined): boolean {
const normalized = normalizeOptionalString(value)?.toLowerCase();
const normalized = normalizeOptionalLowercaseString(value);
return normalized === "1" || normalized === "true" || normalized === "yes" || normalized === "on";
}

View File

@@ -1,4 +1,4 @@
import { normalizeOptionalString } from "../shared/string-coerce.js";
import { normalizeOptionalLowercaseString } from "../shared/string-coerce.js";
export type UpdateChannel = "stable" | "beta" | "dev";
export type UpdateChannelSource = "config" | "git-tag" | "git-branch" | "default";
@@ -8,7 +8,7 @@ export const DEFAULT_GIT_CHANNEL: UpdateChannel = "dev";
export const DEV_BRANCH = "main";
export function normalizeUpdateChannel(value?: string | null): UpdateChannel | null {
const normalized = normalizeOptionalString(value)?.toLowerCase();
const normalized = normalizeOptionalLowercaseString(value);
if (!normalized) {
return null;
}

View File

@@ -1,11 +1,11 @@
import { normalizeChatType } from "../channels/chat-type.js";
import type { MediaUnderstandingScopeConfig } from "../config/types.tools.js";
import { normalizeOptionalString } from "../shared/string-coerce.js";
import { normalizeOptionalLowercaseString } from "../shared/string-coerce.js";
export type MediaUnderstandingScopeDecision = "allow" | "deny";
function normalizeDecision(value?: string | null): MediaUnderstandingScopeDecision | undefined {
const normalized = normalizeOptionalString(value)?.toLowerCase();
const normalized = normalizeOptionalLowercaseString(value);
if (normalized === "allow") {
return "allow";
}
@@ -15,11 +15,6 @@ function normalizeDecision(value?: string | null): MediaUnderstandingScopeDecisi
return undefined;
}
function normalizeMatch(value?: string | null): string | undefined {
const normalized = normalizeOptionalString(value)?.toLowerCase();
return normalized || undefined;
}
export function normalizeMediaUnderstandingChatType(raw?: string | null): string | undefined {
return normalizeChatType(raw ?? undefined);
}
@@ -35,9 +30,9 @@ export function resolveMediaUnderstandingScope(params: {
return "allow";
}
const channel = normalizeMatch(params.channel);
const channel = normalizeOptionalLowercaseString(params.channel);
const chatType = normalizeMediaUnderstandingChatType(params.chatType);
const sessionKey = normalizeMatch(params.sessionKey) ?? "";
const sessionKey = normalizeOptionalLowercaseString(params.sessionKey) ?? "";
for (const rule of scope.rules ?? []) {
if (!rule) {
@@ -45,9 +40,9 @@ export function resolveMediaUnderstandingScope(params: {
}
const action = normalizeDecision(rule.action) ?? "allow";
const match = rule.match ?? {};
const matchChannel = normalizeMatch(match.channel);
const matchChannel = normalizeOptionalLowercaseString(match.channel);
const matchChatType = normalizeMediaUnderstandingChatType(match.chatType);
const matchPrefix = normalizeMatch(match.keyPrefix);
const matchPrefix = normalizeOptionalLowercaseString(match.keyPrefix);
if (matchChannel && matchChannel !== channel) {
continue;

View File

@@ -1,10 +1,10 @@
import type { MsgContext } from "../auto-reply/templating.js";
import { getBootstrapChannelPlugin } from "../channels/plugins/bootstrap-registry.js";
import type { OpenClawConfig } from "../config/config.js";
import { normalizeOptionalString } from "../shared/string-coerce.js";
import { normalizeOptionalLowercaseString } from "../shared/string-coerce.js";
function findChannelMessagingAdapter(channelId?: string | null) {
const normalized = normalizeOptionalString(channelId)?.toLowerCase();
const normalized = normalizeOptionalLowercaseString(channelId);
if (!normalized) {
return undefined;
}

View File

@@ -7,7 +7,7 @@ import type {
SlackChannelStreamingConfig,
TextChunkMode,
} from "../config/types.base.js";
import { normalizeOptionalString } from "../shared/string-coerce.js";
import { normalizeOptionalLowercaseString } from "../shared/string-coerce.js";
export type {
ChannelDeliveryStreamingConfig,
@@ -48,7 +48,7 @@ function normalizeStreamingMode(value: unknown): string | null {
if (typeof value !== "string") {
return null;
}
const normalized = normalizeOptionalString(value)?.toLowerCase();
const normalized = normalizeOptionalLowercaseString(value);
return normalized || null;
}

View File

@@ -14,6 +14,10 @@ export function normalizeOptionalString(value: unknown): string | undefined {
return normalizeNullableString(value) ?? undefined;
}
export function normalizeOptionalLowercaseString(value: unknown): string | undefined {
return normalizeOptionalString(value)?.toLowerCase();
}
export function resolvePrimaryStringValue(value: unknown): string | undefined {
if (typeof value === "string") {
return normalizeOptionalString(value);

View File

@@ -2,7 +2,10 @@ import fs from "node:fs";
import path from "node:path";
import type { OpenClawConfig } from "../config/config.js";
import type { TtsAutoMode, TtsConfig, TtsProvider } from "../config/types.tts.js";
import { normalizeOptionalString } from "../shared/string-coerce.js";
import {
normalizeOptionalLowercaseString,
normalizeOptionalString,
} from "../shared/string-coerce.js";
import { CONFIG_DIR, resolveUserPath } from "../utils.js";
import { normalizeTtsAutoMode } from "./tts-auto-mode.js";
@@ -33,7 +36,7 @@ function resolveConfiguredTtsAutoMode(raw: TtsConfig): TtsAutoMode {
function normalizeConfiguredSpeechProviderId(
providerId: string | undefined,
): TtsProvider | undefined {
const normalized = normalizeOptionalString(providerId)?.toLowerCase();
const normalized = normalizeOptionalLowercaseString(providerId);
if (!normalized) {
return undefined;
}

View File

@@ -1,5 +1,5 @@
import type { TtsAutoMode } from "../config/types.tts.js";
import { normalizeOptionalString } from "../shared/string-coerce.js";
import { normalizeOptionalLowercaseString } from "../shared/string-coerce.js";
export const TTS_AUTO_MODES = new Set<TtsAutoMode>(["off", "always", "inbound", "tagged"]);
@@ -7,7 +7,7 @@ export function normalizeTtsAutoMode(value: unknown): TtsAutoMode | undefined {
if (typeof value !== "string") {
return undefined;
}
const normalized = normalizeOptionalString(value)?.toLowerCase();
const normalized = normalizeOptionalLowercaseString(value);
if (TTS_AUTO_MODES.has(normalized as TtsAutoMode)) {
return normalized as TtsAutoMode;
}