summaryrefslogtreecommitdiffhomepage
path: root/src/features/conversation-cache/cache.ts
diff options
context:
space:
mode:
Diffstat (limited to 'src/features/conversation-cache/cache.ts')
-rw-r--r--src/features/conversation-cache/cache.ts24
1 files changed, 23 insertions, 1 deletions
diff --git a/src/features/conversation-cache/cache.ts b/src/features/conversation-cache/cache.ts
index 431a2a7..600a6e6 100644
--- a/src/features/conversation-cache/cache.ts
+++ b/src/features/conversation-cache/cache.ts
@@ -6,6 +6,19 @@ export interface ConversationCache {
historyEpoch(conversationId: string): Promise<number | undefined>;
reset(conversationId: string, historyEpoch: number): Promise<void>;
+ /**
+ * Replace from a fresh full-history response, including an epoch decrease.
+ * Only succeeds if the cache still has the epoch observed BEFORE that request.
+ * Unlike monotonic reset/commit, this is for confirmed server authority, not
+ * broadcasts or cursor/backfill responses. False means the response is stale.
+ */
+ replace(
+ conversationId: string,
+ incoming: readonly StoredChunk[],
+ historyEpoch: number,
+ expectedEpoch: number | undefined,
+ ): Promise<boolean>;
+
/** Load all cached chunks for a conversation. */
load(conversationId: string): Promise<readonly StoredChunk[]>;
@@ -73,6 +86,13 @@ export function createConversationCache(
const current = await store.historyEpoch(id);
if (current === undefined || current < epoch) await store.reset(id, epoch);
}),
+ replace: (id, incoming, epoch, expectedEpoch) =>
+ exclusive(id, async () => {
+ if ((await store.historyEpoch(id)) !== expectedEpoch) return false;
+ await store.reset(id, epoch);
+ if (incoming.length > 0) await store.append(id, incoming);
+ return true;
+ }),
async load(conversationId) {
await pending.get(conversationId);
return store.load(conversationId);
@@ -82,7 +102,9 @@ export function createConversationCache(
return exclusive(conversationId, async () => {
if (epoch !== undefined) {
const current = await store.historyEpoch(conversationId);
- if (current !== undefined && current > epoch) return store.load(conversationId);
+ // Once an epoch is known, only explicit invalidation or guarded
+ // full-history replacement may change it (in either direction).
+ if (current !== undefined && current !== epoch) return store.load(conversationId);
if (current !== epoch) await store.reset(conversationId, epoch);
}
const cached = await store.load(conversationId);