SPB Git

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%
5.7 KB · 205 lines typescript
Raw Blame History
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