import { mkdtempSync } from 'node:fs'; import { join } from 'node:path'; import type { LLM } from '../agent/llm.ts'; import { RUNS_DIR } from '../agent/workspace.ts'; import { Runtime } from './runtime.ts'; import { asynchronous, runAsync } from './scenarios/async.ts'; import { background, runBackground } from './scenarios/background.ts'; import { blocking, runBlocking } from './scenarios/blocking.ts'; import { sync, runSync } from './scenarios/sync.ts'; import type { LiveBody, LiveEvent } from './types.ts'; export const scenarios = [ { ...sync, execute: runSync }, { ...background, execute: runBackground }, { ...blocking, execute: runBlocking }, { ...asynchronous, execute: runAsync }, ]; export class LiveRun { readonly id: string; readonly workspace: string; readonly events: LiveEvent[] = []; readonly listeners = new Set<(event: LiveEvent) => void>(); readonly runtime: Runtime; private startedAt = Date.now(); private closed = false; private scenario: (typeof scenarios)[number]; private llm: LLM; constructor(scenario: (typeof scenarios)[number], llm: LLM) { this.scenario = scenario; this.llm = llm; this.workspace = mkdtempSync(join(RUNS_DIR, `${scenario.id}-`)); this.id = this.workspace.split('/').at(-1)!; this.runtime = new Runtime(scenario, this.workspace, llm, (event) => this.record(event)); this.record({ type: 'run.start', id: this.id, scenario: scenario.id, workspace: this.workspace, startedAt: this.startedAt }); } private record(body: LiveBody) { if (this.closed) return; const secret = this.llm.provider.apiKey; const serialized = JSON.stringify(body); const safe = secret && serialized.includes(secret) ? JSON.parse(serialized.replaceAll(secret, '[已隐藏]')) as LiveBody : body; const event = { ...safe, seq: this.events.length + 1, t: Date.now() - this.startedAt }; this.events.push(event); for (const listener of this.listeners) listener(event); } async start() { this.record({ type: 'run.state', state: 'running' }); try { const summary = await this.scenario.execute(this.runtime); this.record({ type: 'run.state', state: 'done', summary }); } catch (error) { this.record({ type: 'run.state', state: 'error', note: (error as Error).message }); } finally { this.closed = true; this.runtime.stop(); } } stop() { this.record({ type: 'run.state', state: 'stopped' }); this.closed = true; this.runtime.stop(); } }