Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions src/lib/server/chat-stream-buffer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,30 @@ test("a follow-up turn owns the shared conversation key; unknown keys return nul
resetRunBuffersForTest();
});

test("a follow-up preserves the predecessor reap timer", (t) => {
resetRunBuffersForTest();
t.mock.timers.enable({ apis: ["setTimeout"] });

const first = openRunBuffer(["run-old", "conv-shared"]);
first.record({ kind: "assistant_chunk", text: "old transcript" });
first.finish();

openRunBuffer(["run-new", "conv-shared"]);
t.mock.timers.tick(2 * 60_000);

assert.equal(
hasRunBuffer("run-old"),
false,
"the finished predecessor is reaped under its unique run id",
);
assert.equal(
hasRunBuffer("conv-shared"),
true,
"the predecessor timer never deletes the replacement conversation mapping",
);
resetRunBuffersForTest();
});

test("getRunBufferStatus returns payload-free metadata without side effects", () => {
resetRunBuffersForTest();
let attachCount = 0;
Expand Down
6 changes: 4 additions & 2 deletions src/lib/server/chat-stream-buffer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,10 @@ export function openRunBuffer(
};
for (const key of keys) {
if (!key) continue;
const stale = buffers.get(key);
if (stale && stale.reapTimer) clearTimeout(stale.reapTimer);
// A finished predecessor can still be reachable through another key
// (normally its unique run id). Keep its reap timer armed: the callback
// already checks map identity, so it will remove only predecessor
// mappings and leave this replacement untouched.
buffers.set(key, buffer);
buffer.keys.push(key);
}
Expand Down
Loading