fix(tools): escape delimiters in makeTurnKey to prevent cache collision

This commit is contained in:
Aditya kumar singh 2026-09-17 17:07:40 +05:30
parent c927c98f2c
commit 0537e8ca34
2 changed files with 43 additions and 1 deletions

View file

@ -5,6 +5,9 @@ import type { MemoryMode } from "./types"
* Generic memory cache for storing per-turn memories to avoid redundant API calls.
* Used to cache memory retrieval results during tool-call loops within the same turn.
*/
const escapeKeySegment = (segment: string): string =>
segment.replace(/%/g, "%25").replace(/:/g, "%3A")
export class MemoryCache<T = string> {
private cache: LRUCache<string, T> = new LRUCache({ max: 100 })
@ -25,7 +28,9 @@ export class MemoryCache<T = string> {
message: string,
): string {
const normalizedMessage = message.trim().replace(/\s+/g, " ")
return `${containerTag}:${threadId || ""}:${mode}:${normalizedMessage}`
const safeContainerTag = escapeKeySegment(containerTag)
const safeThreadId = escapeKeySegment(threadId ?? "")
return `${safeContainerTag}:${safeThreadId}:${mode}:${normalizedMessage}`
}
/**

View file

@ -1,4 +1,5 @@
import { describe, expect, it } from "vitest"
import { makeTurnKey } from "./shared/cache"
import {
DEFAULT_VALUES,
SEARCH_LIMIT_BOUNDS,
@ -130,3 +131,39 @@ describe("deduplicateMemoriesForMode", () => {
expect(deduplicated.searchResults).toEqual([])
})
})
describe("makeTurnKey", () => {
it("generates predictable turn key for standard inputs", () => {
expect(makeTurnKey("user-123", "thread-456", "full", "hello world")).toBe(
"user-123:thread-456:full:hello world",
)
})
it("normalizes and collapses whitespace in message", () => {
expect(
makeTurnKey("user-123", "thread-456", "full", " hello world \n "),
).toBe("user-123:thread-456:full:hello world")
})
it("handles undefined threadId cleanly", () => {
expect(makeTurnKey("user-123", undefined, "profile", "test")).toBe(
"user-123::profile:test",
)
})
it("escapes colons to prevent cache key collisions between tag and threadId", () => {
const keyA = makeTurnKey("user:123", "456", "profile", "hi")
const keyB = makeTurnKey("user", "123:456", "profile", "hi")
expect(keyA).toBe("user%3A123:456:profile:hi")
expect(keyB).toBe("user:123%3A456:profile:hi")
expect(keyA).not.toBe(keyB)
})
it("escapes percent signs to avoid ambiguity with encoded sequences", () => {
const keyA = makeTurnKey("user%3A123", "456", "profile", "hi")
const keyB = makeTurnKey("user:123", "456", "profile", "hi")
expect(keyA).toBe("user%253A123:456:profile:hi")
expect(keyB).toBe("user%3A123:456:profile:hi")
expect(keyA).not.toBe(keyB)
})
})