File size: 4,128 Bytes
4e23b01 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 | import { describe, expect, it } from 'vitest';
import { limitAgentReplayByTurns, type AgentReplayRecord } from '#/replay';
function userTurn(text: string, time: number): AgentReplayRecord {
return {
type: 'message',
time,
message: {
role: 'user',
content: [{ type: 'text', text }],
toolCalls: [],
origin: { kind: 'user' },
},
};
}
function cronTurn(text: string, time: number): AgentReplayRecord {
return {
type: 'message',
time,
message: {
role: 'user',
content: [{ type: 'text', text }],
toolCalls: [],
origin: { kind: 'cron_job', jobId: 'job-1', cron: '*/15 * * * *', recurring: true, coalescedCount: 1, stale: false },
},
};
}
function cronMissedTurn(text: string, time: number): AgentReplayRecord {
return {
type: 'message',
time,
message: {
role: 'user',
content: [{ type: 'text', text }],
toolCalls: [],
origin: { kind: 'cron_missed', count: 3 },
},
};
}
function injection(text: string, time: number): AgentReplayRecord {
return {
type: 'message',
time,
message: {
role: 'user',
content: [{ type: 'text', text }],
toolCalls: [],
origin: { kind: 'injection', variant: 'reminder' },
},
};
}
function assistant(text: string, time: number): AgentReplayRecord {
return {
type: 'message',
time,
message: {
role: 'assistant',
content: [{ type: 'text', text }],
toolCalls: [],
},
};
}
function replayBytes(records: readonly AgentReplayRecord[]): number {
return records.reduce((sum, record) => sum + JSON.stringify(record).length, 0);
}
describe('limitAgentReplayByTurns', () => {
it('keeps only the last N user turns', () => {
const records: AgentReplayRecord[] = [];
for (let turn = 0; turn < 20; turn++) {
records.push(userTurn(`prompt ${turn}`, turn * 10));
records.push(assistant(`answer ${turn}`, turn * 10 + 1));
}
const limited = limitAgentReplayByTurns(records, 5);
expect(limited[0]).toMatchObject({ message: { origin: { kind: 'user' } } });
expect(JSON.stringify(limited)).toContain('prompt 15');
expect(JSON.stringify(limited)).not.toContain('prompt 14');
});
it('bounds replay volume when cron turns dominate the history', () => {
const records: AgentReplayRecord[] = [];
let time = 0;
for (let turn = 0; turn < 30; turn++) {
records.push(userTurn(`manual prompt ${turn}`, time++));
records.push(assistant('manual answer', time++));
for (let cron = 0; cron < 200; cron++) {
records.push(cronTurn(`cron fire ${turn}:${cron} ${'x'.repeat(500)}`, time++));
records.push(assistant(`cron report ${'y'.repeat(2000)}`, time++));
}
}
const total = replayBytes(records);
const limited = limitAgentReplayByTurns(records, 10);
expect(limited.length).toBeLessThan(records.length / 10);
expect(replayBytes(limited)).toBeLessThan(total / 10);
expect(JSON.stringify(limited.at(-2))).toContain('cron fire 29:199');
});
it('treats cron_missed deliveries as turn boundaries', () => {
const records: AgentReplayRecord[] = [];
for (let turn = 0; turn < 20; turn++) {
records.push(cronMissedTurn(`missed ${turn}`, turn * 2));
records.push(assistant('report', turn * 2 + 1));
}
const limited = limitAgentReplayByTurns(records, 5);
expect(JSON.stringify(limited)).toContain('missed 15');
expect(JSON.stringify(limited)).not.toContain('missed 14');
});
it('does not let injection bursts shrink the window', () => {
const records: AgentReplayRecord[] = [];
for (let turn = 0; turn < 8; turn++) {
records.push(userTurn(`prompt ${turn}`, turn * 100));
records.push(assistant(`answer ${turn}`, turn * 100 + 1));
for (let i = 0; i < 50; i++) {
records.push(injection(`reminder ${turn}:${i}`, turn * 100 + 2 + i));
}
}
const limited = limitAgentReplayByTurns(records, 5);
expect(JSON.stringify(limited)).toContain('prompt 3');
expect(JSON.stringify(limited)).not.toContain('prompt 2');
});
});
|