refactor: dedupe metadata string helpers

This commit is contained in:
Peter Steinberger
2026-04-07 09:14:32 +01:00
parent 9d8d1dd4c5
commit 8119915664
6 changed files with 71 additions and 102 deletions

View File

@@ -47,15 +47,6 @@ function _normalizeDurationMs(raw: unknown, fallback: number): number {
return Math.max(0, Math.floor(raw));
}
function normalizeText(raw: unknown): string {
return typeof raw === "string" ? raw.trim() : "";
}
function normalizeConversationId(raw: unknown): string | undefined {
const trimmed = normalizeText(raw);
return trimmed || undefined;
}
function resolveBindingsPath(params: {
auth: MatrixAuth;
accountId: string;
@@ -81,9 +72,9 @@ async function loadBindingsFromDisk(filePath: string, accountId: string) {
}
const loaded: MatrixThreadBindingRecord[] = [];
for (const entry of value.bindings) {
const conversationId = normalizeConversationId(entry?.conversationId);
const parentConversationId = normalizeConversationId(entry?.parentConversationId);
const targetSessionKey = normalizeText(entry?.targetSessionKey);
const conversationId = normalizeOptionalString(entry?.conversationId);
const parentConversationId = normalizeOptionalString(entry?.parentConversationId);
const targetSessionKey = normalizeOptionalString(entry?.targetSessionKey) ?? "";
if (!conversationId || !targetSessionKey) {
continue;
}
@@ -101,9 +92,9 @@ async function loadBindingsFromDisk(filePath: string, accountId: string) {
...(parentConversationId ? { parentConversationId } : {}),
targetKind: entry?.targetKind === "subagent" ? "subagent" : "acp",
targetSessionKey,
agentId: normalizeText(entry?.agentId) || undefined,
label: normalizeText(entry?.label) || undefined,
boundBy: normalizeText(entry?.boundBy) || undefined,
agentId: normalizeOptionalString(entry?.agentId) || undefined,
label: normalizeOptionalString(entry?.label) || undefined,
boundBy: normalizeOptionalString(entry?.boundBy) || undefined,
boundAt,
lastActivityAt: Math.max(lastActivityAt, boundAt),
idleTimeoutMs:
@@ -142,13 +133,13 @@ function buildMatrixBindingIntroText(params: {
metadata?: Record<string, unknown>;
targetSessionKey: string;
}): string {
const introText = normalizeText(params.metadata?.introText);
const introText = normalizeOptionalString(params.metadata?.introText);
if (introText) {
return introText;
}
const label = normalizeText(params.metadata?.label);
const label = normalizeOptionalString(params.metadata?.label);
const agentId =
normalizeText(params.metadata?.agentId) ||
normalizeOptionalString(params.metadata?.agentId) ||
resolveAgentIdFromSessionKey(params.targetSessionKey);
const base = label || agentId || "session";
return `⚙️ ${base} session active. Messages here go directly to this session.`;
@@ -457,9 +448,10 @@ export async function createMatrixThreadBindingManager(params: {
targetKind: toMatrixBindingTargetKind(input.targetKind),
targetSessionKey,
agentId:
normalizeText(input.metadata?.agentId) || resolveAgentIdFromSessionKey(targetSessionKey),
label: normalizeText(input.metadata?.label) || undefined,
boundBy: normalizeText(input.metadata?.boundBy) || "system",
normalizeOptionalString(input.metadata?.agentId) ||
resolveAgentIdFromSessionKey(targetSessionKey),
label: normalizeOptionalString(input.metadata?.label) || undefined,
boundBy: normalizeOptionalString(input.metadata?.boundBy) || "system",
boundAt: now,
lastActivityAt: now,
idleTimeoutMs: defaults.idleTimeoutMs,

View File

@@ -17,6 +17,7 @@ import { writeJsonFileAtomically } from "openclaw/plugin-sdk/json-store";
import { normalizeAccountId } from "openclaw/plugin-sdk/routing";
import { logVerbose } from "openclaw/plugin-sdk/runtime-env";
import { resolveStateDir } from "openclaw/plugin-sdk/state-paths";
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
import { createForumTopicTelegram } from "./send.js";
import { resolveTelegramToken } from "./token.js";
@@ -104,14 +105,6 @@ function normalizeDurationMs(raw: unknown, fallback: number): number {
return Math.max(0, Math.floor(raw));
}
function normalizeConversationId(raw: unknown): string | undefined {
if (typeof raw !== "string") {
return undefined;
}
const trimmed = raw.trim();
return trimmed || undefined;
}
function resolveBindingKey(params: { accountId: string; conversationId: string }): string {
return `${params.accountId}:${params.conversationId}`;
}
@@ -253,7 +246,7 @@ function loadBindingsFromDisk(accountId: string): TelegramThreadBindingRecord[]
}
const bindings: TelegramThreadBindingRecord[] = [];
for (const entry of parsed.bindings) {
const conversationId = normalizeConversationId(entry?.conversationId);
const conversationId = normalizeOptionalString(entry?.conversationId);
const targetSessionKey =
typeof entry?.targetSessionKey === "string" ? entry.targetSessionKey.trim() : "";
const targetKind = entry?.targetKind === "subagent" ? "subagent" : "acp";
@@ -449,7 +442,7 @@ export function createTelegramThreadBindingManager(
getIdleTimeoutMs: () => idleTimeoutMs,
getMaxAgeMs: () => maxAgeMs,
getByConversationId: (conversationIdRaw) => {
const conversationId = normalizeConversationId(conversationIdRaw);
const conversationId = normalizeOptionalString(conversationIdRaw);
if (!conversationId) {
return undefined;
}
@@ -471,7 +464,7 @@ export function createTelegramThreadBindingManager(
},
listBindings: () => listBindingsForAccount(accountId),
touchConversation: (conversationIdRaw, at) => {
const conversationId = normalizeConversationId(conversationIdRaw);
const conversationId = normalizeOptionalString(conversationIdRaw);
if (!conversationId) {
return null;
}
@@ -494,7 +487,7 @@ export function createTelegramThreadBindingManager(
return nextRecord;
},
unbindConversation: (unbindParams) => {
const conversationId = normalizeConversationId(unbindParams.conversationId);
const conversationId = normalizeOptionalString(unbindParams.conversationId);
if (!conversationId) {
return null;
}
@@ -613,7 +606,7 @@ export function createTelegramThreadBindingManager(
return null;
}
} else {
conversationId = normalizeConversationId(input.conversation.conversationId);
conversationId = normalizeOptionalString(input.conversation.conversationId);
}
if (!conversationId) {
@@ -667,7 +660,7 @@ export function createTelegramThreadBindingManager(
if (ref.channel !== "telegram") {
return null;
}
const conversationId = normalizeConversationId(ref.conversationId);
const conversationId = normalizeOptionalString(ref.conversationId);
if (!conversationId) {
return null;
}

View File

@@ -1,3 +1,5 @@
import { normalizeOptionalString } from "../shared/string-coerce.js";
export type DeliveryContext = {
channel?: string;
to?: string;
@@ -19,16 +21,6 @@ type DeliveryContextSource = {
deliveryContext?: DeliveryContext;
};
function normalizeChannel(raw?: string): string | undefined {
const value = raw?.trim().toLowerCase();
return value || undefined;
}
function normalizeText(raw?: string): string | undefined {
const value = raw?.trim();
return value || undefined;
}
function normalizeThreadId(raw?: string | number): string | number | undefined {
if (typeof raw === "number" && Number.isFinite(raw)) {
return Math.trunc(raw);
@@ -45,9 +37,9 @@ function normalizeDeliveryContext(context?: DeliveryContext): DeliveryContext |
return undefined;
}
const normalized: DeliveryContext = {
channel: normalizeChannel(context.channel),
to: normalizeText(context.to),
accountId: normalizeText(context.accountId),
channel: normalizeOptionalString(context.channel)?.toLowerCase(),
to: normalizeOptionalString(context.to),
accountId: normalizeOptionalString(context.accountId),
};
const threadId = normalizeThreadId(context.threadId);
if (threadId != null) {
@@ -108,7 +100,7 @@ function deliveryContextFromSession(entry?: DeliveryContextSource): DeliveryCont
}
function isInternalMessageChannel(raw?: string): boolean {
return normalizeChannel(raw) === "webchat";
return normalizeOptionalString(raw)?.toLowerCase() === "webchat";
}
function normalizeTelegramAnnounceTarget(target: string | undefined): string | undefined {
@@ -138,7 +130,7 @@ function shouldStripThreadFromAnnounceEntry(
) {
return false;
}
const requesterChannel = normalizeChannel(normalizedRequester.channel);
const requesterChannel = normalizeOptionalString(normalizedRequester.channel)?.toLowerCase();
if (requesterChannel === "telegram") {
const requesterTarget = normalizeTelegramAnnounceTarget(normalizedRequester.to);
const entryTarget = normalizeTelegramAnnounceTarget(normalizedEntry?.to);

View File

@@ -1,7 +1,7 @@
import { normalizeConversationText } from "../acp/conversation-id.js";
import type { OpenClawConfig } from "../config/config.js";
import { resolveConversationIdFromTargets } from "../infra/outbound/conversation-id.js";
import { getActivePluginChannelRegistry } from "../plugins/runtime.js";
import { normalizeOptionalString } from "../shared/string-coerce.js";
import { parseExplicitTargetForChannel } from "./plugins/target-parsing.js";
import type { ChannelPlugin } from "./plugins/types.js";
import { normalizeAnyChannelId, normalizeChannelId } from "./registry.js";
@@ -41,13 +41,8 @@ const CANONICAL_TARGET_PREFIXES = [
"spaces/",
] as const;
function normalizeText(value: unknown): string | undefined {
const normalized = normalizeConversationText(value);
return normalized || undefined;
}
function getLoadedChannelPlugin(rawChannel: string): ChannelPlugin | undefined {
const normalized = normalizeAnyChannelId(rawChannel) ?? normalizeText(rawChannel);
const normalized = normalizeAnyChannelId(rawChannel) ?? normalizeOptionalString(rawChannel);
if (!normalized) {
return undefined;
}
@@ -65,8 +60,8 @@ function resolveBindingAccountId(params: {
cfg: OpenClawConfig;
}): string {
return (
normalizeText(params.rawAccountId) ||
normalizeText(params.plugin?.config.defaultAccountId?.(params.cfg)) ||
normalizeOptionalString(params.rawAccountId) ||
normalizeOptionalString(params.plugin?.config.defaultAccountId?.(params.cfg)) ||
"default"
);
}
@@ -75,7 +70,7 @@ function resolveChannelTargetId(params: {
channel: string;
target?: string | null;
}): string | undefined {
const target = normalizeText(params.target);
const target = normalizeOptionalString(params.target);
if (!target) {
return undefined;
}
@@ -93,7 +88,7 @@ function resolveChannelTargetId(params: {
}
const parsed = parseExplicitTargetForChannel(params.channel, target);
const parsedTarget = normalizeText(parsed?.to);
const parsedTarget = normalizeOptionalString(parsed?.to);
if (parsedTarget) {
return (
resolveConversationIdFromTargets({
@@ -116,7 +111,8 @@ function buildThreadingContext(params: {
chatType?: string;
nativeChannelId?: string;
}) {
const to = normalizeText(params.originatingTo) ?? normalizeText(params.fallbackTo);
const to =
normalizeOptionalString(params.originatingTo) ?? normalizeOptionalString(params.fallbackTo);
return {
...(to ? { To: to } : {}),
...(params.from ? { From: params.from } : {}),
@@ -132,7 +128,7 @@ export function resolveConversationBindingContext(
const channel =
normalizeAnyChannelId(params.channel) ??
normalizeChannelId(params.channel) ??
normalizeText(params.channel)?.toLowerCase();
normalizeOptionalString(params.channel)?.toLowerCase();
if (!channel) {
return null;
}
@@ -142,15 +138,17 @@ export function resolveConversationBindingContext(
plugin: loadedPlugin,
cfg: params.cfg,
});
const threadId = normalizeText(params.threadId != null ? String(params.threadId) : undefined);
const threadId = normalizeOptionalString(
params.threadId != null ? String(params.threadId) : undefined,
);
const resolvedByProvider = loadedPlugin?.bindings?.resolveCommandConversation?.({
accountId,
threadId,
threadParentId: normalizeText(params.threadParentId),
senderId: normalizeText(params.senderId),
sessionKey: normalizeText(params.sessionKey),
parentSessionKey: normalizeText(params.parentSessionKey),
threadParentId: normalizeOptionalString(params.threadParentId),
senderId: normalizeOptionalString(params.senderId),
sessionKey: normalizeOptionalString(params.sessionKey),
parentSessionKey: normalizeOptionalString(params.parentSessionKey),
originatingTo: params.originatingTo ?? undefined,
commandTo: params.commandTo ?? undefined,
fallbackTo: params.fallbackTo ?? undefined,
@@ -180,9 +178,9 @@ export function resolveConversationBindingContext(
fallbackTo: params.fallbackTo ?? undefined,
originatingTo: params.originatingTo ?? undefined,
threadId,
from: normalizeText(params.from),
chatType: normalizeText(params.chatType),
nativeChannelId: normalizeText(params.nativeChannelId),
from: normalizeOptionalString(params.from),
chatType: normalizeOptionalString(params.chatType),
nativeChannelId: normalizeOptionalString(params.nativeChannelId),
}),
});
if (focusedBinding?.conversationId) {

View File

@@ -222,10 +222,6 @@ function safeStatMtimeMs(filePath: string): number | null {
}
}
function normalizeManifestLabel(raw: string | undefined): string | undefined {
return normalizeOptionalString(raw);
}
function normalizePreferredPluginIds(raw: unknown): string[] | undefined {
return normalizeOptionalTrimmedStringList(raw);
}
@@ -273,10 +269,10 @@ function buildRecord(params: {
});
return {
id: params.manifest.id,
name: normalizeManifestLabel(params.manifest.name) ?? params.candidate.packageName,
name: normalizeOptionalString(params.manifest.name) ?? params.candidate.packageName,
description:
normalizeManifestLabel(params.manifest.description) ?? params.candidate.packageDescription,
version: normalizeManifestLabel(params.manifest.version) ?? params.candidate.packageVersion,
normalizeOptionalString(params.manifest.description) ?? params.candidate.packageDescription,
version: normalizeOptionalString(params.manifest.version) ?? params.candidate.packageVersion,
enabledByDefault: params.manifest.enabledByDefault === true ? true : undefined,
autoEnableWhenConfiguredProviders: params.manifest.autoEnableWhenConfiguredProviders,
legacyPluginIds: params.manifest.legacyPluginIds,
@@ -343,9 +339,9 @@ function buildBundleRecord(params: {
}): PluginManifestRecord {
return {
id: params.manifest.id,
name: normalizeManifestLabel(params.manifest.name) ?? params.candidate.idHint,
description: normalizeManifestLabel(params.manifest.description),
version: normalizeManifestLabel(params.manifest.version),
name: normalizeOptionalString(params.manifest.name) ?? params.candidate.idHint,
description: normalizeOptionalString(params.manifest.description),
version: normalizeOptionalString(params.manifest.version),
format: "bundle",
bundleFormat: params.candidate.bundleFormat,
bundleCapabilities: params.manifest.capabilities,

View File

@@ -21,10 +21,6 @@ function pushProviderDiagnostic(params: {
});
}
function normalizeText(value: string | undefined): string | undefined {
return normalizeOptionalString(value);
}
function normalizeTextList(values: string[] | undefined): string[] | undefined {
const normalized = Array.from(new Set(normalizeTrimmedStringList(values)));
return normalized.length > 0 ? normalized : undefined;
@@ -99,7 +95,7 @@ function buildNormalizedModelAllowlist(
}
const allowedKeys = normalizeTextList(modelAllowlist.allowedKeys);
const initialSelections = normalizeTextList(modelAllowlist.initialSelections);
const message = normalizeText(modelAllowlist.message);
const message = normalizeOptionalString(modelAllowlist.message);
if (!allowedKeys && !initialSelections && !message) {
return undefined;
}
@@ -114,12 +110,12 @@ function buildNormalizedWizardSetup(params: {
setup: ProviderWizardSetup;
methodId: string | undefined;
}): ProviderWizardSetup {
const choiceId = normalizeText(params.setup.choiceId);
const choiceLabel = normalizeText(params.setup.choiceLabel);
const choiceHint = normalizeText(params.setup.choiceHint);
const groupId = normalizeText(params.setup.groupId);
const groupLabel = normalizeText(params.setup.groupLabel);
const groupHint = normalizeText(params.setup.groupHint);
const choiceId = normalizeOptionalString(params.setup.choiceId);
const choiceLabel = normalizeOptionalString(params.setup.choiceLabel);
const choiceHint = normalizeOptionalString(params.setup.choiceHint);
const groupId = normalizeOptionalString(params.setup.groupId);
const groupLabel = normalizeOptionalString(params.setup.groupLabel);
const groupHint = normalizeOptionalString(params.setup.groupHint);
const onboardingScopes = normalizeOnboardingScopes(params.setup.onboardingScopes);
const modelAllowlist = buildNormalizedModelAllowlist(params.setup.modelAllowlist);
return {
@@ -147,8 +143,8 @@ function buildNormalizedModelPicker(
modelPicker: ProviderWizardModelPicker,
methodId: string | undefined,
): ProviderWizardModelPicker {
const label = normalizeText(modelPicker.label);
const hint = normalizeText(modelPicker.hint);
const label = normalizeOptionalString(modelPicker.label);
const hint = normalizeOptionalString(modelPicker.hint);
return {
...(label ? { label } : {}),
...(hint ? { hint } : {}),
@@ -183,7 +179,7 @@ function normalizeProviderWizardSetup(params: {
pluginId: params.pluginId,
source: params.source,
auth: params.auth,
methodId: normalizeText(params.setup.methodId),
methodId: normalizeOptionalString(params.setup.methodId),
metadataKind: "setup",
pushDiagnostic: params.pushDiagnostic,
});
@@ -204,7 +200,7 @@ function normalizeProviderAuthMethods(params: {
const normalized: ProviderAuthMethod[] = [];
for (const method of params.auth) {
const methodId = normalizeText(method.id);
const methodId = normalizeOptionalString(method.id);
if (!methodId) {
pushProviderDiagnostic({
level: "error",
@@ -240,8 +236,10 @@ function normalizeProviderAuthMethods(params: {
normalized.push({
...method,
id: methodId,
label: normalizeText(method.label) ?? methodId,
...(normalizeText(method.hint) ? { hint: normalizeText(method.hint) } : {}),
label: normalizeOptionalString(method.label) ?? methodId,
...(normalizeOptionalString(method.hint)
? { hint: normalizeOptionalString(method.hint) }
: {}),
...(wizard ? { wizard } : {}),
});
}
@@ -299,7 +297,7 @@ function normalizeProviderWizard(params: {
pluginId: params.pluginId,
source: params.source,
auth: params.auth,
methodId: normalizeText(modelPicker.methodId),
methodId: normalizeOptionalString(modelPicker.methodId),
metadataKind: "model-picker",
pushDiagnostic: params.pushDiagnostic,
}),
@@ -323,7 +321,7 @@ export function normalizeRegisteredProvider(params: {
provider: ProviderPlugin;
pushDiagnostic: (diag: PluginDiagnostic) => void;
}): ProviderPlugin | null {
const id = normalizeText(params.provider.id);
const id = normalizeOptionalString(params.provider.id);
if (!id) {
pushProviderDiagnostic({
level: "error",
@@ -342,7 +340,7 @@ export function normalizeRegisteredProvider(params: {
auth: params.provider.auth ?? [],
pushDiagnostic: params.pushDiagnostic,
});
const docsPath = normalizeText(params.provider.docsPath);
const docsPath = normalizeOptionalString(params.provider.docsPath);
const aliases = normalizeTextList(params.provider.aliases);
const deprecatedProfileIds = normalizeTextList(params.provider.deprecatedProfileIds);
const oauthProfileIdRepairs = normalizeProviderOAuthProfileIdRepairs(
@@ -380,7 +378,7 @@ export function normalizeRegisteredProvider(params: {
return {
...restProvider,
id,
label: normalizeText(params.provider.label) ?? id,
label: normalizeOptionalString(params.provider.label) ?? id,
...(docsPath ? { docsPath } : {}),
...(aliases ? { aliases } : {}),
...(deprecatedProfileIds ? { deprecatedProfileIds } : {}),