From f476f8211c274897697e6f2d8efbfe7f60ba0147 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Tue, 7 Apr 2026 15:24:18 +0100 Subject: [PATCH] refactor: dedupe acp lowercase helpers --- src/acp/control-plane/manager.core.ts | 5 +++-- src/acp/control-plane/manager.utils.ts | 2 +- src/acp/control-plane/runtime-options.ts | 3 ++- src/acp/event-mapper.ts | 3 ++- src/acp/persistent-bindings.types.ts | 5 +++-- src/acp/runtime/session-identifiers.ts | 3 ++- src/acp/runtime/session-meta.ts | 7 ++++--- 7 files changed, 17 insertions(+), 11 deletions(-) diff --git a/src/acp/control-plane/manager.core.ts b/src/acp/control-plane/manager.core.ts index c1f8d425ef8..0ae4c2cae29 100644 --- a/src/acp/control-plane/manager.core.ts +++ b/src/acp/control-plane/manager.core.ts @@ -4,6 +4,7 @@ import { logVerbose } from "../../globals.js"; import { formatErrorMessage } from "../../infra/errors.js"; import { normalizeAgentId } from "../../routing/session-key.js"; import { isAcpSessionKey } from "../../sessions/session-key-utils.js"; +import { normalizeLowercaseStringOrEmpty } from "../../shared/string-coerce.js"; import { createRunningTaskRun, completeTaskRunByRunId, @@ -1581,12 +1582,12 @@ export class AcpSessionManager { if (!status) { return false; } - const detailsStatus = normalizeText(status.details?.status)?.toLowerCase() ?? ""; + const detailsStatus = normalizeLowercaseStringOrEmpty(status.details?.status); if (detailsStatus === "dead" || detailsStatus === "no-session") { return true; } const summaryMatch = status.summary?.match(/\bstatus=([^\s]+)/i); - const summaryStatus = normalizeText(summaryMatch?.[1])?.toLowerCase() ?? ""; + const summaryStatus = normalizeLowercaseStringOrEmpty(summaryMatch?.[1]); return summaryStatus === "dead" || summaryStatus === "no-session"; } diff --git a/src/acp/control-plane/manager.utils.ts b/src/acp/control-plane/manager.utils.ts index ed5a89ffcd4..502207c39e7 100644 --- a/src/acp/control-plane/manager.utils.ts +++ b/src/acp/control-plane/manager.utils.ts @@ -59,7 +59,7 @@ export function canonicalizeAcpSessionKey(params: { if (!normalized) { return ""; } - const lowered = normalized.toLowerCase(); + const lowered = normalizeLowercaseStringOrEmpty(normalized); if (lowered === "global" || lowered === "unknown") { return lowered; } diff --git a/src/acp/control-plane/runtime-options.ts b/src/acp/control-plane/runtime-options.ts index 73d9242e871..c9d632f8c70 100644 --- a/src/acp/control-plane/runtime-options.ts +++ b/src/acp/control-plane/runtime-options.ts @@ -1,5 +1,6 @@ import { isAbsolute } from "node:path"; import type { AcpSessionRuntimeOptions, SessionAcpMeta } from "../../config/sessions/types.js"; +import { normalizeLowercaseStringOrEmpty } from "../../shared/string-coerce.js"; import { normalizeText } from "../normalize-text.js"; import { AcpRuntimeError } from "../runtime/errors.js"; @@ -319,7 +320,7 @@ export function inferRuntimeOptionPatchFromConfigOption( value: string, ): Partial { const validated = validateRuntimeConfigOptionInput(key, value); - const normalizedKey = validated.key.toLowerCase(); + const normalizedKey = normalizeLowercaseStringOrEmpty(validated.key); if (normalizedKey === "model") { return { model: validateRuntimeModelInput(validated.value) }; } diff --git a/src/acp/event-mapper.ts b/src/acp/event-mapper.ts index f897320e9ff..04a79b07918 100644 --- a/src/acp/event-mapper.ts +++ b/src/acp/event-mapper.ts @@ -7,6 +7,7 @@ import type { } from "@agentclientprotocol/sdk"; import { hasNonEmptyString, + normalizeLowercaseStringOrEmpty, normalizeOptionalString, readStringValue, } from "../shared/string-coerce.js"; @@ -315,7 +316,7 @@ export function inferToolKind(name?: string): ToolKind { if (!name) { return "other"; } - const normalized = name.toLowerCase(); + const normalized = normalizeLowercaseStringOrEmpty(name); if (normalized.includes("read")) { return "read"; } diff --git a/src/acp/persistent-bindings.types.ts b/src/acp/persistent-bindings.types.ts index f6e79ac03da..8efc99af63b 100644 --- a/src/acp/persistent-bindings.types.ts +++ b/src/acp/persistent-bindings.types.ts @@ -3,6 +3,7 @@ import type { ChannelId } from "../channels/plugins/types.js"; import type { SessionBindingRecord } from "../infra/outbound/session-binding-service.js"; import { normalizeAccountId, resolveAgentIdFromSessionKey } from "../routing/session-key.js"; import { sanitizeAgentId } from "../routing/session-key.js"; +import { normalizeOptionalLowercaseString } from "../shared/string-coerce.js"; import { normalizeText } from "./normalize-text.js"; import type { AcpRuntimeSessionMode } from "./runtime/types.js"; @@ -38,7 +39,7 @@ export type AcpBindingConfigShape = { }; export function normalizeMode(value: unknown): AcpRuntimeSessionMode { - const raw = normalizeText(value)?.toLowerCase(); + const raw = normalizeOptionalLowercaseString(value); return raw === "oneshot" ? "oneshot" : "persistent"; } @@ -117,7 +118,7 @@ export function parseConfiguredAcpSessionKey( if (tokens.length !== 5 || tokens[0] !== "acp" || tokens[1] !== "binding") { return null; } - const channel = normalizeText(tokens[2])?.toLowerCase(); + const channel = normalizeOptionalLowercaseString(tokens[2]); if (!channel) { return null; } diff --git a/src/acp/runtime/session-identifiers.ts b/src/acp/runtime/session-identifiers.ts index 4129e9e8eac..7d2ff92172f 100644 --- a/src/acp/runtime/session-identifiers.ts +++ b/src/acp/runtime/session-identifiers.ts @@ -1,4 +1,5 @@ import type { SessionAcpIdentity, SessionAcpMeta } from "../../config/sessions/types.js"; +import { normalizeLowercaseStringOrEmpty } from "../../shared/string-coerce.js"; import { normalizeText } from "../normalize-text.js"; import { isSessionIdentityPending, resolveSessionIdentityFromMeta } from "./session-identity.js"; @@ -40,7 +41,7 @@ function normalizeAgentHintKey(value: unknown): string | undefined { if (!normalized) { return undefined; } - return normalized.toLowerCase().replace(/[\s_]+/g, "-"); + return normalizeLowercaseStringOrEmpty(normalized).replace(/[\s_]+/g, "-"); } function resolveAcpAgentResumeHintLine(params: { diff --git a/src/acp/runtime/session-meta.ts b/src/acp/runtime/session-meta.ts index 384261a9481..5d9d21e7bbb 100644 --- a/src/acp/runtime/session-meta.ts +++ b/src/acp/runtime/session-meta.ts @@ -9,6 +9,7 @@ import { type SessionEntry, } from "../../config/sessions/types.js"; import { parseAgentSessionKey } from "../../routing/session-key.js"; +import { normalizeLowercaseStringOrEmpty } from "../../shared/string-coerce.js"; let sessionStoreRuntimePromise: | Promise @@ -37,12 +38,12 @@ function resolveStoreSessionKey(store: Record, sessionKey: if (store[normalized]) { return normalized; } - const lower = normalized.toLowerCase(); + const lower = normalizeLowercaseStringOrEmpty(normalized); if (store[lower]) { return lower; } for (const key of Object.keys(store)) { - if (key.toLowerCase() === lower) { + if (normalizeLowercaseStringOrEmpty(key) === lower) { return key; } } @@ -171,7 +172,7 @@ export async function upsertAcpSessionMeta(params: { return nextEntry; }, { - activeSessionKey: sessionKey.toLowerCase(), + activeSessionKey: normalizeLowercaseStringOrEmpty(sessionKey), allowDropAcpMetaSessionKeys: [sessionKey], }, );