From dfec7d7f80ce5d381b590e5b456236b862fa21a6 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Tue, 7 Apr 2026 07:18:33 +0100 Subject: [PATCH] refactor: dedupe session helper readers --- src/agents/agent-scope.ts | 11 +++-------- src/agents/auth-profiles/identity.ts | 7 ++----- src/agents/cli-session.ts | 11 ++++++----- src/agents/owner-display.ts | 4 ++-- src/agents/session-transcript-repair.ts | 8 ++------ src/agents/spawned-context.ts | 7 ++----- src/agents/tools/sessions-helpers.ts | 4 ++-- src/agents/tools/sessions-resolution.ts | 4 ++-- src/auto-reply/reply/inbound-meta.ts | 7 ++----- src/gateway/connection-details.ts | 12 ++++-------- src/gateway/model-pricing-cache.ts | 11 +++++------ src/gateway/sessions-history-http.ts | 8 ++++---- 12 files changed, 36 insertions(+), 58 deletions(-) diff --git a/src/agents/agent-scope.ts b/src/agents/agent-scope.ts index 5912b1776e5..f9350e150f9 100644 --- a/src/agents/agent-scope.ts +++ b/src/agents/agent-scope.ts @@ -11,7 +11,7 @@ import { parseAgentSessionKey, resolveAgentIdFromSessionKey, } from "../routing/session-key.js"; -import { readStringValue } from "../shared/string-coerce.js"; +import { normalizeOptionalString, readStringValue } from "../shared/string-coerce.js"; import { resolveUserPath } from "../utils.js"; import { resolveEffectiveAgentSkillFilter } from "./skills/agent-filter.js"; import { resolveDefaultAgentWorkspaceDir } from "./workspace.js"; @@ -172,18 +172,13 @@ export function resolveAgentSkillsFilter( function resolveModelPrimary(raw: unknown): string | undefined { if (typeof raw === "string") { - const trimmed = raw.trim(); - return trimmed || undefined; + return normalizeOptionalString(raw); } if (!raw || typeof raw !== "object") { return undefined; } const primary = (raw as { primary?: unknown }).primary; - if (typeof primary !== "string") { - return undefined; - } - const trimmed = primary.trim(); - return trimmed || undefined; + return normalizeOptionalString(primary); } export function resolveAgentExplicitModelPrimary( diff --git a/src/agents/auth-profiles/identity.ts b/src/agents/auth-profiles/identity.ts index 3a5fcb579a7..33f852ad0bd 100644 --- a/src/agents/auth-profiles/identity.ts +++ b/src/agents/auth-profiles/identity.ts @@ -1,12 +1,9 @@ import type { OpenClawConfig } from "../../config/config.js"; +import { normalizeOptionalString } from "../../shared/string-coerce.js"; import type { AuthProfileStore } from "./types.js"; function trimOptionalString(value: string | null | undefined): string | undefined { - if (typeof value !== "string") { - return undefined; - } - const trimmed = value.trim(); - return trimmed || undefined; + return normalizeOptionalString(value); } function resolveStoredMetadata(store: AuthProfileStore | undefined, profileId: string) { diff --git a/src/agents/cli-session.ts b/src/agents/cli-session.ts index 09fdc701a4c..2449938e7c8 100644 --- a/src/agents/cli-session.ts +++ b/src/agents/cli-session.ts @@ -1,12 +1,12 @@ import crypto from "node:crypto"; import type { CliSessionBinding, SessionEntry } from "../config/sessions.js"; +import { normalizeOptionalString } from "../shared/string-coerce.js"; import { normalizeProviderId } from "./model-selection.js"; const CLAUDE_CLI_BACKEND_ID = "claude-cli"; function trimOptional(value: string | undefined): string | undefined { - const trimmed = value?.trim(); - return trimmed ? trimmed : undefined; + return normalizeOptionalString(value); } export function hashCliSessionText(value: string | undefined): string | undefined { @@ -37,11 +37,12 @@ export function getCliSessionBinding( }; } const fromMap = entry.cliSessionIds?.[normalized]; - if (fromMap?.trim()) { - return { sessionId: fromMap.trim() }; + const normalizedFromMap = normalizeOptionalString(fromMap); + if (normalizedFromMap) { + return { sessionId: normalizedFromMap }; } if (normalized === CLAUDE_CLI_BACKEND_ID) { - const legacy = entry.claudeCliSessionId?.trim(); + const legacy = normalizeOptionalString(entry.claudeCliSessionId); if (legacy) { return { sessionId: legacy }; } diff --git a/src/agents/owner-display.ts b/src/agents/owner-display.ts index 57d2006c656..2c6b1a9831c 100644 --- a/src/agents/owner-display.ts +++ b/src/agents/owner-display.ts @@ -1,5 +1,6 @@ import crypto from "node:crypto"; import type { OpenClawConfig } from "../config/config.js"; +import { normalizeOptionalString } from "../shared/string-coerce.js"; export type OwnerDisplaySetting = { ownerDisplay?: "raw" | "hash"; @@ -12,8 +13,7 @@ export type OwnerDisplaySecretResolution = { }; function trimToUndefined(value?: string): string | undefined { - const trimmed = value?.trim(); - return trimmed ? trimmed : undefined; + return normalizeOptionalString(value); } /** diff --git a/src/agents/session-transcript-repair.ts b/src/agents/session-transcript-repair.ts index 99bcde27b20..cf2704eedac 100644 --- a/src/agents/session-transcript-repair.ts +++ b/src/agents/session-transcript-repair.ts @@ -1,5 +1,5 @@ import type { AgentMessage } from "@mariozechner/pi-agent-core"; -import { readStringValue } from "../shared/string-coerce.js"; +import { normalizeOptionalString, readStringValue } from "../shared/string-coerce.js"; import { extractToolCallsFromAssistant, extractToolResultId } from "./tool-call-id.js"; const TOOL_CALL_NAME_MAX_CHARS = 64; @@ -153,11 +153,7 @@ function makeMissingToolResult(params: { } function trimNonEmptyString(value: unknown): string | undefined { - if (typeof value !== "string") { - return undefined; - } - const trimmed = value.trim(); - return trimmed || undefined; + return normalizeOptionalString(value); } function normalizeToolResultName( diff --git a/src/agents/spawned-context.ts b/src/agents/spawned-context.ts index d0919c86baa..0bdc2c52737 100644 --- a/src/agents/spawned-context.ts +++ b/src/agents/spawned-context.ts @@ -1,5 +1,6 @@ import type { OpenClawConfig } from "../config/config.js"; import { normalizeAgentId, parseAgentSessionKey } from "../routing/session-key.js"; +import { normalizeOptionalString } from "../shared/string-coerce.js"; import { resolveAgentWorkspaceDir } from "./agent-scope.js"; export type SpawnedRunMetadata = { @@ -26,11 +27,7 @@ export type NormalizedSpawnedRunMetadata = { }; function normalizeOptionalText(value?: string | null): string | undefined { - if (typeof value !== "string") { - return undefined; - } - const trimmed = value.trim(); - return trimmed || undefined; + return normalizeOptionalString(value); } export function normalizeSpawnedRunMetadata( diff --git a/src/agents/tools/sessions-helpers.ts b/src/agents/tools/sessions-helpers.ts index ce615addc88..9c3e40eff2e 100644 --- a/src/agents/tools/sessions-helpers.ts +++ b/src/agents/tools/sessions-helpers.ts @@ -34,6 +34,7 @@ export { stripToolMessages, } from "./chat-history-text.js"; import { type OpenClawConfig, loadConfig } from "../../config/config.js"; +import { normalizeOptionalString } from "../../shared/string-coerce.js"; export type SessionKind = "main" | "group" | "cron" | "hook" | "node" | "other"; @@ -87,8 +88,7 @@ export type SessionListRow = { }; function normalizeKey(value?: string) { - const trimmed = value?.trim(); - return trimmed ? trimmed : undefined; + return normalizeOptionalString(value); } export function resolveSessionToolContext(opts?: { diff --git a/src/agents/tools/sessions-resolution.ts b/src/agents/tools/sessions-resolution.ts index d3fb345bee9..6bb3ed159f0 100644 --- a/src/agents/tools/sessions-resolution.ts +++ b/src/agents/tools/sessions-resolution.ts @@ -3,6 +3,7 @@ import { callGateway } from "../../gateway/call.js"; import { formatErrorMessage } from "../../infra/errors.js"; import { isAcpSessionKey, normalizeMainKey } from "../../routing/session-key.js"; import { looksLikeSessionId } from "../../sessions/session-id.js"; +import { normalizeOptionalString } from "../../shared/string-coerce.js"; type GatewayCaller = typeof callGateway; @@ -15,8 +16,7 @@ let sessionsResolutionDeps: { } = defaultSessionsResolutionDeps; function normalizeKey(value?: string) { - const trimmed = value?.trim(); - return trimmed ? trimmed : undefined; + return normalizeOptionalString(value); } export function resolveMainSessionAlias(cfg: OpenClawConfig) { diff --git a/src/auto-reply/reply/inbound-meta.ts b/src/auto-reply/reply/inbound-meta.ts index 8ef484d4dfd..41995624964 100644 --- a/src/auto-reply/reply/inbound-meta.ts +++ b/src/auto-reply/reply/inbound-meta.ts @@ -2,16 +2,13 @@ import { normalizeChatType } from "../../channels/chat-type.js"; import { getBundledChannelPlugin } from "../../channels/plugins/bundled.js"; import { getLoadedChannelPlugin, normalizeChannelId } from "../../channels/plugins/index.js"; import { resolveSenderLabel } from "../../channels/sender-label.js"; +import { normalizeOptionalString } from "../../shared/string-coerce.js"; import type { EnvelopeFormatOptions } from "../envelope.js"; import { formatEnvelopeTimestamp } from "../envelope.js"; import type { TemplateContext } from "../templating.js"; function safeTrim(value: unknown): string | undefined { - if (typeof value !== "string") { - return undefined; - } - const trimmed = value.trim(); - return trimmed ? trimmed : undefined; + return normalizeOptionalString(value); } function formatConversationTimestamp( diff --git a/src/gateway/connection-details.ts b/src/gateway/connection-details.ts index d7366764227..752e1e3cda9 100644 --- a/src/gateway/connection-details.ts +++ b/src/gateway/connection-details.ts @@ -1,5 +1,6 @@ import { resolveConfigPath, resolveGatewayPort } from "../config/paths.js"; import type { OpenClawConfig } from "../config/types.js"; +import { normalizeOptionalString } from "../shared/string-coerce.js"; import { isSecureWebSocketUrl } from "./net.js"; export type GatewayConnectionDetails = { @@ -17,8 +18,7 @@ type GatewayConnectionDetailResolvers = { }; function trimToUndefined(value: string | undefined): string | undefined { - const trimmed = value?.trim(); - return trimmed ? trimmed : undefined; + return normalizeOptionalString(value); } export function buildGatewayConnectionDetailsWithResolvers( @@ -43,16 +43,12 @@ export function buildGatewayConnectionDetailsWithResolvers( const bindMode = config.gateway?.bind ?? "loopback"; const scheme = tlsEnabled ? "wss" : "ws"; const localUrl = `${scheme}://127.0.0.1:${localPort}`; - const cliUrlOverride = - typeof options.url === "string" && options.url.trim().length > 0 - ? options.url.trim() - : undefined; + const cliUrlOverride = normalizeOptionalString(options.url); const envUrlOverride = cliUrlOverride ? undefined : trimToUndefined(process.env.OPENCLAW_GATEWAY_URL); const urlOverride = cliUrlOverride ?? envUrlOverride; - const remoteUrl = - typeof remote?.url === "string" && remote.url.trim().length > 0 ? remote.url.trim() : undefined; + const remoteUrl = normalizeOptionalString(remote?.url); const remoteMisconfigured = isRemoteMode && !urlOverride && !remoteUrl; const urlSourceHint = options.urlSource ?? (cliUrlOverride ? "cli" : envUrlOverride ? "env" : undefined); diff --git a/src/gateway/model-pricing-cache.ts b/src/gateway/model-pricing-cache.ts index ca19a96e75b..145329172dc 100644 --- a/src/gateway/model-pricing-cache.ts +++ b/src/gateway/model-pricing-cache.ts @@ -12,6 +12,7 @@ import { resolvePluginWebSearchConfig } from "../config/plugin-web-search-config import { createSubsystemLogger } from "../logging/subsystem.js"; import { resolveManifestContractPluginIds } from "../plugins/manifest-registry.js"; import { normalizeProviderModelIdWithPlugin } from "../plugins/provider-runtime.js"; +import { normalizeOptionalString } from "../shared/string-coerce.js"; import { clearGatewayModelPricingCacheState, getCachedGatewayModelPricing, @@ -68,11 +69,9 @@ function clearRefreshTimer(): void { function listLikePrimary(value: ModelListLike): string | undefined { if (typeof value === "string") { - const trimmed = value.trim(); - return trimmed || undefined; + return normalizeOptionalString(value); } - const trimmed = value?.primary?.trim(); - return trimmed || undefined; + return normalizeOptionalString(value?.primary); } function listLikeFallbacks(value: ModelListLike): string[] { @@ -82,8 +81,8 @@ function listLikeFallbacks(value: ModelListLike): string[] { return Array.isArray(value.fallbacks) ? value.fallbacks .filter((entry): entry is string => typeof entry === "string") - .map((entry) => entry.trim()) - .filter(Boolean) + .map((entry) => normalizeOptionalString(entry)) + .filter((entry): entry is string => Boolean(entry)) : []; } diff --git a/src/gateway/sessions-history-http.ts b/src/gateway/sessions-history-http.ts index d48b5d6ec62..dd1d4120540 100644 --- a/src/gateway/sessions-history-http.ts +++ b/src/gateway/sessions-history-http.ts @@ -4,6 +4,7 @@ import path from "node:path"; import { loadConfig } from "../config/config.js"; import { loadSessionStore } from "../config/sessions.js"; import { onSessionTranscriptUpdate } from "../sessions/transcript-events.js"; +import { normalizeOptionalString } from "../shared/string-coerce.js"; import type { AuthRateLimiter } from "./auth-rate-limit.js"; import type { ResolvedGatewayAuth } from "./auth.js"; import { @@ -36,7 +37,7 @@ function resolveSessionHistoryPath(req: IncomingMessage): string | null { return null; } try { - return decodeURIComponent(match[1] ?? "").trim() || null; + return normalizeOptionalString(decodeURIComponent(match[1] ?? "")) ?? null; } catch { return ""; } @@ -65,12 +66,11 @@ function resolveLimit(req: IncomingMessage): number | undefined { function resolveCursor(req: IncomingMessage): string | undefined { const raw = getRequestUrl(req).searchParams.get("cursor"); - const trimmed = raw?.trim(); - return trimmed ? trimmed : undefined; + return normalizeOptionalString(raw); } function canonicalizePath(value: string | undefined): string | undefined { - const trimmed = value?.trim(); + const trimmed = normalizeOptionalString(value); if (!trimmed) { return undefined; }