export interface DelegateUsage { input: number; output: number; cacheRead: number; cacheWrite: number; cost: number; } export interface DelegateEvent { type: "assistant"; text: string; usage: DelegateUsage; } export interface DelegateProcess { events(): AsyncIterable; terminate(): void | Promise; } export interface DelegateRequest { task: string; model: string; cwd: string; tools: string[]; tokenCeiling: number; costCeiling: number; signal?: AbortSignal; } export interface DelegateResult { output: string; usage: DelegateUsage; limitExceeded?: "tokens" | "cost"; aborted?: boolean; } export interface DelegateProcessAdapter { start(request: DelegateRequest): Promise; } export interface DelegateRunner { run(request: DelegateRequest): Promise; } function emptyUsage(): DelegateUsage { return { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0 }; } function addUsage(total: DelegateUsage, next: DelegateUsage): void { total.input += next.input; total.output += next.output; total.cacheRead += next.cacheRead; total.cacheWrite += next.cacheWrite; total.cost += next.cost; } function totalTokens(usage: DelegateUsage): number { return usage.input + usage.output + usage.cacheRead + usage.cacheWrite; } export function createDelegateRunner( adapter: DelegateProcessAdapter, ): DelegateRunner { return { async run(request) { const process = await adapter.start(request); const usage = emptyUsage(); let output = ""; if (request.signal?.aborted) { await process.terminate(); return { output, aborted: true, usage }; } for await (const event of process.events()) { output = event.text || output; addUsage(usage, event.usage); if (request.signal?.aborted) { await process.terminate(); return { output, aborted: true, usage }; } const limitExceeded = totalTokens(usage) > request.tokenCeiling ? "tokens" : usage.cost > request.costCeiling ? "cost" : undefined; if (limitExceeded) { await process.terminate(); return { output, limitExceeded, usage }; } } return { output, usage }; }, }; }