spb/khaelor Public
KHAELOR — a terminal-native autonomous engineering agent powered by Anthropic.
TypeScript 82.9%
HTML 14.9%
CSS 1.1%
JavaScript 0.7%
1/**2 * KHAELOR3 * File: tests/context/fixtures.ts4 * Description: Context-engine test fixtures — event stream builder and a capturing fake ModelClient.5 *6 * Author: Simon-Pierre Boucher7 * Contact: contact@spboucher.ai8 */910import type { ModelClient, ModelEvent, ModelRequest } from "../../src/anthropic/index.js";11import { EVENT_SCHEMA_VERSION } from "../../src/session/events.js";12import type { DurableEvent, DurableEventInput } from "../../src/session/events.js";13import { ulid } from "../../src/shared/index.js";1415export const SESSION_ID = "01CTXTESTSESSION0000000000";1617export function envelope(seq: number, input: DurableEventInput): DurableEvent {18 return {19 v: EVENT_SCHEMA_VERSION,20 id: ulid(),21 sessionId: SESSION_ID,22 seq,23 ts: 1_700_000_000_000 + seq,24 type: input.type,25 payload: input.payload,26 } as DurableEvent;27}2829/** Incrementing-seq stream builder. */30export class StreamBuilder {31 readonly events: DurableEvent[] = [];32 #seq = 0;3334 add(input: DurableEventInput): DurableEvent {35 this.#seq += 1;36 const event = envelope(this.#seq, input);37 this.events.push(event);38 return event;39 }4041 get lastSeq(): number {42 return this.#seq;43 }44}4546export const USAGE = {47 inputTokens: 1000,48 outputTokens: 200,49 cacheReadTokens: 500,50 cacheWriteTokens: 100,51};5253/**54 * A realistic small session: user objective, two tool round-trips, a final55 * answer, then a follow-up user message (the protected current task).56 */57export function sampleSession(opts: { bigToolOutput?: string } = {}): StreamBuilder {58 const b = new StreamBuilder();59 b.add({60 type: "session.started",61 payload: {62 title: "Fix the store",63 projectHash: "abc",64 workingDirectory: "/home/u/proj",65 gitBranch: "main",66 model: "main-model",67 auxModel: "aux-model",68 khaelorVersion: "0.1.0",69 },70 }); // seq 171 b.add({ type: "user.message-created", payload: { text: "Fix the bug in the session store", mentions: [] } }); // 272 b.add({73 type: "model.request-started",74 payload: {75 requestId: "req_1",76 model: "main-model",77 purpose: "main",78 contextStats: { estimatedInputTokens: 100, sections: [] },79 },80 }); // 381 b.add({82 type: "model.text-block-completed",83 payload: { requestId: "req_1", blockIndex: 0, text: "Let me read the store." },84 }); // 485 b.add({86 type: "tool.requested",87 payload: {88 requestId: "req_1",89 blockIndex: 1,90 toolUseId: "toolu_1",91 toolName: "read",92 input: { path: "src/store.ts" },93 },94 }); // 595 b.add({96 type: "model.response-completed",97 payload: { requestId: "req_1", stopReason: "tool_use", usage: USAGE, durationMs: 900 },98 }); // 699 b.add({100 type: "tool.completed",101 payload: {102 toolUseId: "toolu_1",103 modelText: opts.bigToolOutput ?? "const x = 1;",104 durationMs: 10,105 ui: { kind: "read", summary: "Read src/store.ts" },106 },107 }); // 7108 b.add({109 type: "model.request-started",110 payload: {111 requestId: "req_2",112 model: "main-model",113 purpose: "main",114 contextStats: { estimatedInputTokens: 200, sections: [] },115 },116 }); // 8117 b.add({118 type: "model.text-block-completed",119 payload: { requestId: "req_2", blockIndex: 0, text: "Now searching for callers." },120 }); // 9121 b.add({122 type: "tool.requested",123 payload: {124 requestId: "req_2",125 blockIndex: 1,126 toolUseId: "toolu_2",127 toolName: "grep",128 input: { pattern: "SessionStore" },129 },130 }); // 10131 b.add({132 type: "model.response-completed",133 payload: { requestId: "req_2", stopReason: "tool_use", usage: USAGE, durationMs: 800 },134 }); // 11135 b.add({136 type: "tool.completed",137 payload: {138 toolUseId: "toolu_2",139 modelText: "src/store.ts:12: class SessionStore",140 durationMs: 5,141 ui: { kind: "search", summary: "1 match" },142 },143 }); // 12144 b.add({145 type: "model.request-started",146 payload: {147 requestId: "req_3",148 model: "main-model",149 purpose: "main",150 contextStats: { estimatedInputTokens: 300, sections: [] },151 },152 }); // 13153 b.add({154 type: "model.text-block-completed",155 payload: { requestId: "req_3", blockIndex: 0, text: "Fixed the leak in SessionStore." },156 }); // 14157 b.add({158 type: "model.response-completed",159 payload: { requestId: "req_3", stopReason: "end_turn", usage: USAGE, durationMs: 700 },160 }); // 15161 b.add({ type: "user.message-created", payload: { text: "Now add tests for it", mentions: [] } }); // 16162 return b;163}164165/** Capturing fake ModelClient — yields one settled text block. */166export class FakeModelClient implements ModelClient {167 readonly requests: ModelRequest[] = [];168 readonly signals: (AbortSignal | undefined)[] = [];169 readonly #responseText: string;170171 constructor(responseText: string) {172 this.#responseText = responseText;173 }174175 async *stream(request: ModelRequest, signal?: AbortSignal): AsyncIterable<ModelEvent> {176 if (signal?.aborted) throw new Error("cancelled");177 this.requests.push(request);178 this.signals.push(signal);179 yield { type: "started", requestId: "req_fake" };180 yield { type: "text-block-completed", blockIndex: 0, text: this.#responseText };181 yield {182 type: "completed",183 stopReason: "end_turn",184 usage: { inputTokens: 50, outputTokens: 20, cacheReadTokens: 0, cacheWriteTokens: 0 },185 durationMs: 3,186 };187 }188}189190export const FAKE_CHECKPOINT_TEXT = `objective: Fix the bug in the session store191completed:192 - Located the leak in SessionStore193current_state: Fix applied, tests pending194important_files:195 - path: src/store.ts196 reason: contains the fixed leak197changes:198 - src/store.ts rewritten append path199failed_attempts: []200decisions:201 - kept the JSONL format202next_steps:203 - add tests204`;205