Skip to content

Commit 05e306d

Browse files
committed
fix(web): bound the cache-break snapshot map and use sha256 for its signature
cacheBreakSnapshots was keyed by chatId and never evicted, so with cache-break detection enabled it grew with the cumulative number of distinct chats served. Add a FIFO cap that drops the oldest entry on overflow, and replace the hand-rolled djb2 signature hash with a sha256 slice matching getOAuthScopeHash (observability-only and compared in-process, so determinism is all it needs).
1 parent b01b034 commit 05e306d

2 files changed

Lines changed: 18 additions & 11 deletions

File tree

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Changed
11+
- [EE] Improved Ask Sourcebot prompt caching by splitting static and dynamic prompt sections and advancing cache breakpoints after every agent step instead of only after each message. [#<id>](https://github.com/sourcebot-dev/sourcebot/pull/<id>)
12+
1013
### Added
1114
- Added per-step token cost tracking and estimated tool call token usage to Ask Sourcebot chat history. [#1353](https://github.com/sourcebot-dev/sourcebot/pull/1353)
1215

packages/web/src/ee/features/chat/promptCaching.ts

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { createHash } from "crypto";
12
import { ProviderOptions } from "@ai-sdk/provider-utils";
23
import { createLogger } from "@sourcebot/shared";
34
import { LanguageModelProvider } from "@/features/chat/types";
@@ -105,19 +106,14 @@ interface CacheBreakSnapshot {
105106
requestCount: number;
106107
}
107108

108-
// Keyed by chatId. In-memory, best-effort: survives within a server process and
109-
// is naturally bounded by the number of concurrent chats. Mirrors the in-memory
110-
// caching pattern used elsewhere (e.g. `anthropicThinkingConfigCache`).
109+
// Keyed by chatId, in-memory, observability-only. Entries are never removed on chat
110+
// end, so the map is bounded by a FIFO cap (oldest insertion evicted first, below);
111+
// otherwise it grows with the cumulative count of distinct chats, not the concurrent count.
112+
const MAX_CACHE_BREAK_SNAPSHOTS = 10_000;
111113
const cacheBreakSnapshots = new Map<string, CacheBreakSnapshot>();
112114

113-
// Lightweight, stable, non-cryptographic hash (djb2). Observability only.
114-
const hashString = (input: string): string => {
115-
let hash = 5381;
116-
for (let i = 0; i < input.length; i++) {
117-
hash = ((hash << 5) + hash + input.charCodeAt(i)) | 0;
118-
}
119-
return (hash >>> 0).toString(36);
120-
};
115+
const hashString = (input: string): string =>
116+
createHash('sha256').update(input).digest('hex').slice(0, 16);
121117

122118
/**
123119
* Records the cache-relevant prefix signature for a chat and logs a warning when
@@ -149,6 +145,14 @@ export const detectPromptCacheBreak = ({
149145
const requestCount = (prev?.requestCount ?? 0) + 1;
150146
cacheBreakSnapshots.set(chatId, { signature, requestCount });
151147

148+
// FIFO eviction: once the map overflows, drop the oldest-inserted entry.
149+
if (cacheBreakSnapshots.size > MAX_CACHE_BREAK_SNAPSHOTS) {
150+
const oldestKey = cacheBreakSnapshots.keys().next().value;
151+
if (oldestKey !== undefined) {
152+
cacheBreakSnapshots.delete(oldestKey);
153+
}
154+
}
155+
152156
if (prev && prev.signature !== signature) {
153157
logger.warn(
154158
`Prompt cache break detected for chat ${chatId} (request #${requestCount}): the ` +

0 commit comments

Comments
 (0)