164 lines
5.5 KiB
TypeScript
164 lines
5.5 KiB
TypeScript
import { describe, it } from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import {
|
|
readLifecycleEngine,
|
|
extractInvocations,
|
|
findSupersededReadCallIds,
|
|
} from "../../../open-sse/services/compression/engines/readLifecycle/index.ts";
|
|
|
|
// T08/H7 — read-lifecycle: collapse superseded file-Read tool results (opt-in, lossy).
|
|
|
|
const STUB_MARK = "read superseded";
|
|
|
|
function run(messages: unknown[], enabled = true) {
|
|
return readLifecycleEngine.apply({ messages }, { stepConfig: { enabled } });
|
|
}
|
|
|
|
// ── Anthropic fixtures ──
|
|
const anthropicRead = (id: string, path: string) => ({
|
|
role: "assistant",
|
|
content: [{ type: "tool_use", id, name: "Read", input: { file_path: path } }],
|
|
});
|
|
const anthropicWrite = (id: string, path: string) => ({
|
|
role: "assistant",
|
|
content: [{ type: "tool_use", id, name: "Edit", input: { file_path: path } }],
|
|
});
|
|
const anthropicResult = (id: string, text: string) => ({
|
|
role: "user",
|
|
content: [{ type: "tool_result", tool_use_id: id, content: text }],
|
|
});
|
|
function anthropicResultText(msg: unknown, id: string): string | undefined {
|
|
const blocks = (msg as { content?: unknown[] }).content;
|
|
const b = Array.isArray(blocks)
|
|
? (blocks.find((x) => (x as { tool_use_id?: string }).tool_use_id === id) as
|
|
{ content?: string } | undefined)
|
|
: undefined;
|
|
return b?.content;
|
|
}
|
|
|
|
// ── OpenAI fixtures ──
|
|
const openaiRead = (id: string, path: string) => ({
|
|
role: "assistant",
|
|
tool_calls: [
|
|
{
|
|
id,
|
|
type: "function",
|
|
function: { name: "read_file", arguments: JSON.stringify({ file_path: path }) },
|
|
},
|
|
],
|
|
});
|
|
const openaiResult = (id: string, text: string) => ({
|
|
role: "tool",
|
|
tool_call_id: id,
|
|
content: text,
|
|
});
|
|
|
|
describe("read-lifecycle — pure analysis", () => {
|
|
it("extracts invocations from both shapes", () => {
|
|
const { invocations, readPathByCallId } = extractInvocations([
|
|
anthropicRead("c1", "/a.ts"),
|
|
openaiRead("c2", "/b.ts"),
|
|
]);
|
|
assert.equal(invocations.length, 2);
|
|
assert.equal(invocations[0].kind, "read");
|
|
assert.equal(invocations[0].path, "/a.ts");
|
|
assert.equal(readPathByCallId.get("c1"), "/a.ts");
|
|
assert.equal(readPathByCallId.get("c2"), "/b.ts");
|
|
});
|
|
|
|
it("marks a read superseded by a later read of the same path", () => {
|
|
const { invocations } = extractInvocations([
|
|
anthropicRead("c1", "/a.ts"),
|
|
anthropicRead("c2", "/a.ts"),
|
|
]);
|
|
const s = findSupersededReadCallIds(invocations);
|
|
assert.equal(s.has("c1"), true);
|
|
assert.equal(s.has("c2"), false);
|
|
});
|
|
|
|
it("marks a read superseded by a later write of the same path", () => {
|
|
const { invocations } = extractInvocations([
|
|
anthropicRead("c1", "/a.ts"),
|
|
anthropicWrite("w1", "/a.ts"),
|
|
]);
|
|
const s = findSupersededReadCallIds(invocations);
|
|
assert.equal(s.has("c1"), true);
|
|
assert.equal(s.has("w1"), false); // writes are never collapsed
|
|
});
|
|
|
|
it("does not supersede reads of different paths", () => {
|
|
const { invocations } = extractInvocations([
|
|
anthropicRead("c1", "/a.ts"),
|
|
anthropicRead("c2", "/b.ts"),
|
|
]);
|
|
const s = findSupersededReadCallIds(invocations);
|
|
assert.equal(s.size, 0);
|
|
});
|
|
});
|
|
|
|
describe("read-lifecycle — engine integration", () => {
|
|
it("collapses an earlier Anthropic read, keeping the latest intact", () => {
|
|
const out = run([
|
|
anthropicRead("c1", "/a.ts"),
|
|
anthropicResult("c1", "OLD content of a.ts"),
|
|
anthropicRead("c2", "/a.ts"),
|
|
anthropicResult("c2", "NEW content of a.ts"),
|
|
]);
|
|
assert.equal(out.compressed, true);
|
|
const msgs = (out.body as { messages: unknown[] }).messages;
|
|
assert.match(anthropicResultText(msgs[1], "c1") ?? "", new RegExp(STUB_MARK));
|
|
assert.equal(anthropicResultText(msgs[3], "c2"), "NEW content of a.ts");
|
|
});
|
|
|
|
it("collapses a read superseded by a later write", () => {
|
|
const out = run([
|
|
anthropicRead("c1", "/a.ts"),
|
|
anthropicResult("c1", "OLD"),
|
|
anthropicWrite("w1", "/a.ts"),
|
|
anthropicResult("w1", "edit applied"),
|
|
]);
|
|
assert.equal(out.compressed, true);
|
|
const msgs = (out.body as { messages: unknown[] }).messages;
|
|
assert.match(anthropicResultText(msgs[1], "c1") ?? "", new RegExp(STUB_MARK));
|
|
assert.equal(anthropicResultText(msgs[3], "w1"), "edit applied"); // write result untouched
|
|
});
|
|
|
|
it("collapses an earlier OpenAI read (role:tool message)", () => {
|
|
const out = run([
|
|
openaiRead("c1", "/a.ts"),
|
|
openaiResult("c1", "OLD"),
|
|
openaiRead("c2", "/a.ts"),
|
|
openaiResult("c2", "NEW"),
|
|
]);
|
|
assert.equal(out.compressed, true);
|
|
const msgs = (out.body as { messages: Array<{ content?: string }> }).messages;
|
|
assert.match(msgs[1].content ?? "", new RegExp(STUB_MARK));
|
|
assert.equal(msgs[3].content, "NEW");
|
|
});
|
|
|
|
it("no-ops for a single read of a path", () => {
|
|
const out = run([anthropicRead("c1", "/a.ts"), anthropicResult("c1", "only read")]);
|
|
assert.equal(out.compressed, false);
|
|
});
|
|
|
|
it("is a pass-through when disabled (default)", () => {
|
|
const messages = [
|
|
anthropicRead("c1", "/a.ts"),
|
|
anthropicResult("c1", "OLD"),
|
|
anthropicRead("c2", "/a.ts"),
|
|
anthropicResult("c2", "NEW"),
|
|
];
|
|
const out = run(messages, false);
|
|
assert.equal(out.compressed, false);
|
|
assert.equal(
|
|
anthropicResultText((out.body as { messages: unknown[] }).messages[1], "c1"),
|
|
"OLD"
|
|
);
|
|
});
|
|
|
|
it("schema defaults enabled to false (opt-in)", () => {
|
|
const field = readLifecycleEngine.getConfigSchema!().find((f) => f.key === "enabled");
|
|
assert.equal(field?.defaultValue, false);
|
|
});
|
|
});
|