From e0c01bf9567384e6c4506109205c890cac652fb9 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Mon, 20 Apr 2026 23:11:44 +0100 Subject: [PATCH] perf: trim hot path allocations --- packages/memory-host-sdk/src/host/internal.ts | 2 +- .../pi-embedded-runner/run/images.test.ts | 7 ++++ src/agents/pi-embedded-runner/run/images.ts | 24 ++++++++------ src/gateway/session-utils.ts | 33 ++++++++++--------- src/memory-host-sdk/host/internal.ts | 2 +- 5 files changed, 41 insertions(+), 27 deletions(-) diff --git a/packages/memory-host-sdk/src/host/internal.ts b/packages/memory-host-sdk/src/host/internal.ts index 22979832ea0..c8967e572cc 100644 --- a/packages/memory-host-sdk/src/host/internal.ts +++ b/packages/memory-host-sdk/src/host/internal.ts @@ -409,7 +409,7 @@ export function chunkMarkdown( } } current = kept; - currentChars = kept.reduce((sum, entry) => sum + estimateStringChars(entry.line) + 1, 0); + currentChars = acc; }; for (let i = 0; i < lines.length; i += 1) { diff --git a/src/agents/pi-embedded-runner/run/images.test.ts b/src/agents/pi-embedded-runner/run/images.test.ts index adf5d9c923b..d6747d00550 100644 --- a/src/agents/pi-embedded-runner/run/images.test.ts +++ b/src/agents/pi-embedded-runner/run/images.test.ts @@ -83,6 +83,13 @@ describe("detectImageReferences", () => { expect(refs.some((r) => r.type === "path")).toBe(true); }); + it("does not leak parser state between calls", () => { + expectSingleImageReference("[media attached: /tmp/first.png (image/png)]"); + expectSingleImageReference("[Image: source: /tmp/second.jpg]"); + expectSingleImageReference("See file:///tmp/third.webp"); + expectSingleImageReference("See ./fourth.jpeg"); + }); + it("handles various image extensions", () => { const extensions = ["png", "jpg", "jpeg", "gif", "webp", "bmp", "tiff", "heic"]; for (const ext of extensions) { diff --git a/src/agents/pi-embedded-runner/run/images.ts b/src/agents/pi-embedded-runner/run/images.ts index 498d2fe68c8..3c99dac5c09 100644 --- a/src/agents/pi-embedded-runner/run/images.ts +++ b/src/agents/pi-embedded-runner/run/images.ts @@ -41,6 +41,11 @@ const MESSAGE_IMAGE_REGEX_SOURCE = const FILE_URL_REGEX_SOURCE = "file://[^\\s<>\"'`\\]]+\\.(?:" + IMAGE_EXTENSION_PATTERN + ")"; const PATH_REGEX_SOURCE = "(?:^|\\s|[\"'`(])((\\.\\.?/|[~/])[^\\s\"'`()\\[\\]]*\\.(?:" + IMAGE_EXTENSION_PATTERN + "))"; +const MEDIA_ATTACHED_PATTERN = /\[media attached(?:\s+\d+\/\d+)?:\s*([^\]]+)\]/gi; +const MEDIA_ATTACHED_PATH_PATTERN = new RegExp(MEDIA_ATTACHED_PATH_REGEX_SOURCE, "i"); +const MESSAGE_IMAGE_PATTERN = new RegExp(MESSAGE_IMAGE_REGEX_SOURCE, "gi"); +const FILE_URL_PATTERN = new RegExp(FILE_URL_REGEX_SOURCE, "gi"); +const PATH_PATTERN = new RegExp(PATH_REGEX_SOURCE, "gi"); /** * Matches the opaque media URI written by the Gateway's claim-check offload: @@ -251,13 +256,12 @@ export function detectImageReferences(prompt: string): DetectedImageRef[] { // Pattern for [media attached: path (type) | url] or [media attached N/M: path (type) | url] format // Each bracket = ONE file. The | separates path from URL, not multiple files. // Multi-file format uses separate brackets on separate lines. - const mediaAttachedPattern = /\[media attached(?:\s+\d+\/\d+)?:\s*([^\]]+)\]/gi; - const mediaAttachedPathPattern = new RegExp(MEDIA_ATTACHED_PATH_REGEX_SOURCE, "i"); - const messageImagePattern = new RegExp(MESSAGE_IMAGE_REGEX_SOURCE, "gi"); - const fileUrlPattern = new RegExp(FILE_URL_REGEX_SOURCE, "gi"); - const pathPattern = new RegExp(PATH_REGEX_SOURCE, "gi"); + MEDIA_ATTACHED_PATTERN.lastIndex = 0; + MESSAGE_IMAGE_PATTERN.lastIndex = 0; + FILE_URL_PATTERN.lastIndex = 0; + PATH_PATTERN.lastIndex = 0; let match: RegExpExecArray | null; - while ((match = mediaAttachedPattern.exec(prompt)) !== null) { + while ((match = MEDIA_ATTACHED_PATTERN.exec(prompt)) !== null) { const content = match[1]; // Skip "[media attached: N files]" header lines @@ -283,14 +287,14 @@ export function detectImageReferences(prompt: string): DetectedImageRef[] { // Format is: path (type) | url OR just: path (type) // Path may contain spaces (e.g., "ChatGPT Image Apr 21.png") // Use non-greedy .+? to stop at first image extension - const pathMatch = content.match(mediaAttachedPathPattern); + const pathMatch = content.match(MEDIA_ATTACHED_PATH_PATTERN); if (pathMatch?.[1]) { addPathRef(pathMatch[1].trim()); } } // Pattern for [Image: source: /path/...] format from messaging systems - while ((match = messageImagePattern.exec(prompt)) !== null) { + while ((match = MESSAGE_IMAGE_PATTERN.exec(prompt)) !== null) { const raw = match[1]?.trim(); if (raw) { addPathRef(raw); @@ -300,7 +304,7 @@ export function detectImageReferences(prompt: string): DetectedImageRef[] { // Remote HTTP(S) URLs are intentionally ignored. Native image injection is local-only. // Pattern for file:// URLs - treat as paths since loadWebMedia handles them - while ((match = fileUrlPattern.exec(prompt)) !== null) { + while ((match = FILE_URL_PATTERN.exec(prompt)) !== null) { const raw = match[0]; const dedupeKey = normalizeRefForDedupe(raw); if (seen.has(dedupeKey)) { @@ -322,7 +326,7 @@ export function detectImageReferences(prompt: string): DetectedImageRef[] { // - ./relative/path.ext // - ../parent/path.ext // - ~/home/path.ext - while ((match = pathPattern.exec(prompt)) !== null) { + while ((match = PATH_PATTERN.exec(prompt)) !== null) { // Use capture group 1 (the path without delimiter prefix); skip if undefined if (match[1]) { addPathRef(match[1]); diff --git a/src/gateway/session-utils.ts b/src/gateway/session-utils.ts index aacbc9b3a63..f19ef77691b 100644 --- a/src/gateway/session-utils.ts +++ b/src/gateway/session-utils.ts @@ -438,19 +438,18 @@ export function resolveFreshestSessionStoreMatchFromStoreKeys( store: Record, storeKeys: string[], ): { key: string; entry: SessionEntry } | undefined { - const matches = storeKeys - .map((key) => { - const entry = store[key]; - return entry ? { key, entry } : undefined; - }) - .filter((match): match is { key: string; entry: SessionEntry } => match !== undefined); - if (matches.length === 0) { - return undefined; + let freshest: { key: string; entry: SessionEntry } | undefined; + for (const key of storeKeys) { + const entry = store[key]; + if (!entry) { + continue; + } + const match = { key, entry }; + if (!freshest || (match.entry.updatedAt ?? 0) > (freshest.entry.updatedAt ?? 0)) { + freshest = match; + } } - if (matches.length === 1) { - return matches[0]; - } - return [...matches].toSorted((a, b) => (b.entry.updatedAt ?? 0) - (a.entry.updatedAt ?? 0))[0]; + return freshest; } export function resolveFreshestSessionEntryFromStoreKeys( @@ -484,9 +483,13 @@ function findFreshestStoreMatch( if (matches.size === 0) { return undefined; } - return [...matches.values()].toSorted( - (a, b) => (b.entry.updatedAt ?? 0) - (a.entry.updatedAt ?? 0), - )[0]; + let freshest: { entry: SessionEntry; key: string } | undefined; + for (const match of matches.values()) { + if (!freshest || (match.entry.updatedAt ?? 0) > (freshest.entry.updatedAt ?? 0)) { + freshest = match; + } + } + return freshest; } /** diff --git a/src/memory-host-sdk/host/internal.ts b/src/memory-host-sdk/host/internal.ts index f402578ac2b..d67b60bf57d 100644 --- a/src/memory-host-sdk/host/internal.ts +++ b/src/memory-host-sdk/host/internal.ts @@ -388,7 +388,7 @@ export function chunkMarkdown( } } current = kept; - currentChars = kept.reduce((sum, entry) => sum + estimateStringChars(entry.line) + 1, 0); + currentChars = acc; }; for (let i = 0; i < lines.length; i += 1) {