/** * KHAELOR * File: tests/context/fixtures.ts * Description: Context-engine test fixtures — event stream builder and a capturing fake ModelClient. * * Author: Simon-Pierre Boucher * Contact: contact@spboucher.ai */ import type { ModelClient, ModelEvent, ModelRequest } from "../../src/anthropic/index.js"; import { EVENT_SCHEMA_VERSION } from "../../src/session/events.js"; import type { DurableEvent, DurableEventInput } from "../../src/session/events.js"; import { ulid } from "../../src/shared/index.js"; export const SESSION_ID = "01CTXTESTSESSION0000000000"; export function envelope(seq: number, input: DurableEventInput): DurableEvent { return { v: EVENT_SCHEMA_VERSION, id: ulid(), sessionId: SESSION_ID, seq, ts: 1_700_000_000_000 + seq, type: input.type, payload: input.payload, } as DurableEvent; } /** Incrementing-seq stream builder. */ export class StreamBuilder { readonly events: DurableEvent[] = []; #seq = 0; add(input: DurableEventInput): DurableEvent { this.#seq += 1; const event = envelope(this.#seq, input); this.events.push(event); return event; } get lastSeq(): number { return this.#seq; } } export const USAGE = { inputTokens: 1000, outputTokens: 200, cacheReadTokens: 500, cacheWriteTokens: 100, }; /** * A realistic small session: user objective, two tool round-trips, a final * answer, then a follow-up user message (the protected current task). */ export function sampleSession(opts: { bigToolOutput?: string } = {}): StreamBuilder { const b = new StreamBuilder(); b.add({ type: "session.started", payload: { title: "Fix the store", projectHash: "abc", workingDirectory: "/home/u/proj", gitBranch: "main", model: "main-model", auxModel: "aux-model", khaelorVersion: "0.1.0", }, }); // seq 1 b.add({ type: "user.message-created", payload: { text: "Fix the bug in the session store", mentions: [] } }); // 2 b.add({ type: "model.request-started", payload: { requestId: "req_1", model: "main-model", purpose: "main", contextStats: { estimatedInputTokens: 100, sections: [] }, }, }); // 3 b.add({ type: "model.text-block-completed", payload: { requestId: "req_1", blockIndex: 0, text: "Let me read the store." }, }); // 4 b.add({ type: "tool.requested", payload: { requestId: "req_1", blockIndex: 1, toolUseId: "toolu_1", toolName: "read", input: { path: "src/store.ts" }, }, }); // 5 b.add({ type: "model.response-completed", payload: { requestId: "req_1", stopReason: "tool_use", usage: USAGE, durationMs: 900 }, }); // 6 b.add({ type: "tool.completed", payload: { toolUseId: "toolu_1", modelText: opts.bigToolOutput ?? "const x = 1;", durationMs: 10, ui: { kind: "read", summary: "Read src/store.ts" }, }, }); // 7 b.add({ type: "model.request-started", payload: { requestId: "req_2", model: "main-model", purpose: "main", contextStats: { estimatedInputTokens: 200, sections: [] }, }, }); // 8 b.add({ type: "model.text-block-completed", payload: { requestId: "req_2", blockIndex: 0, text: "Now searching for callers." }, }); // 9 b.add({ type: "tool.requested", payload: { requestId: "req_2", blockIndex: 1, toolUseId: "toolu_2", toolName: "grep", input: { pattern: "SessionStore" }, }, }); // 10 b.add({ type: "model.response-completed", payload: { requestId: "req_2", stopReason: "tool_use", usage: USAGE, durationMs: 800 }, }); // 11 b.add({ type: "tool.completed", payload: { toolUseId: "toolu_2", modelText: "src/store.ts:12: class SessionStore", durationMs: 5, ui: { kind: "search", summary: "1 match" }, }, }); // 12 b.add({ type: "model.request-started", payload: { requestId: "req_3", model: "main-model", purpose: "main", contextStats: { estimatedInputTokens: 300, sections: [] }, }, }); // 13 b.add({ type: "model.text-block-completed", payload: { requestId: "req_3", blockIndex: 0, text: "Fixed the leak in SessionStore." }, }); // 14 b.add({ type: "model.response-completed", payload: { requestId: "req_3", stopReason: "end_turn", usage: USAGE, durationMs: 700 }, }); // 15 b.add({ type: "user.message-created", payload: { text: "Now add tests for it", mentions: [] } }); // 16 return b; } /** Capturing fake ModelClient — yields one settled text block. */ export class FakeModelClient implements ModelClient { readonly requests: ModelRequest[] = []; readonly signals: (AbortSignal | undefined)[] = []; readonly #responseText: string; constructor(responseText: string) { this.#responseText = responseText; } async *stream(request: ModelRequest, signal?: AbortSignal): AsyncIterable { if (signal?.aborted) throw new Error("cancelled"); this.requests.push(request); this.signals.push(signal); yield { type: "started", requestId: "req_fake" }; yield { type: "text-block-completed", blockIndex: 0, text: this.#responseText }; yield { type: "completed", stopReason: "end_turn", usage: { inputTokens: 50, outputTokens: 20, cacheReadTokens: 0, cacheWriteTokens: 0 }, durationMs: 3, }; } } export const FAKE_CHECKPOINT_TEXT = `objective: Fix the bug in the session store completed: - Located the leak in SessionStore current_state: Fix applied, tests pending important_files: - path: src/store.ts reason: contains the fixed leak changes: - src/store.ts rewritten append path failed_attempts: [] decisions: - kept the JSONL format next_steps: - add tests `;