68 lines
2.4 KiB
TypeScript
68 lines
2.4 KiB
TypeScript
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();
|
|
}
|
|
}
|