import type { AgentMessage, ModelClient } from "./types.js"; import { ToolRegistry } from "./tools/registry.js"; import type { EventBus } from "./events.js"; export type AgentOptions = { cwd: string; maxTurns?: number; events?: EventBus; }; export type RunTurnOptions = { onTextDelta?: (delta: string) => void; }; export class Agent { private readonly history: AgentMessage[] = []; constructor( private readonly model: ModelClient, private readonly tools: ToolRegistry, private readonly options: AgentOptions ) {} async runTurn(prompt: string, runOptions: RunTurnOptions = {}): Promise { const events = this.options.events; const maxTurns = this.options.maxTurns ?? 20; this.history.push({ role: "user", content: prompt }); await events?.emit("agent.started", { maxTurns }); for (let turn = 0; turn < maxTurns; turn += 1) { await events?.emit("agent.turn.started", { turn: turn + 1 }); const result = runOptions.onTextDelta && this.model.respondStream ? await this.model.respondStream( this.history, this.tools.list(), runOptions.onTextDelta ) : await this.model.respond(this.history, this.tools.list()); this.history.push(...(result.output as AgentMessage[])); if (result.toolCalls.length === 0) { await events?.emit("agent.completed", { turn: turn + 1 }); return result.outputText; } for (const call of result.toolCalls) { await events?.emit("tool.call.requested", { tool: call.name }); const tool = this.tools.get(call.name); if (!tool) { await events?.emit("tool.call.unknown", { tool: call.name }); this.history.push({ type: "function_call_output", call_id: call.id, output: JSON.stringify({ error: `Unknown tool: ${call.name}` }) }); continue; } try { const parsed = JSON.parse(call.arguments); await events?.emit("tool.call.started", { tool: call.name, input: parsed }); const output = await tool.execute(parsed, { cwd: this.options.cwd }); await events?.emit("tool.call.completed", { tool: call.name }); this.history.push({ type: "function_call_output", call_id: call.id, output: JSON.stringify(output) }); } catch (error) { await events?.emit("tool.call.failed", { tool: call.name, error: error instanceof Error ? error.message : "Tool execution failed" }); this.history.push({ type: "function_call_output", call_id: call.id, output: JSON.stringify({ error: error instanceof Error ? error.message : "Tool execution failed" }) }); } } } await events?.emit("agent.failed", { reason: "max_turns_exceeded", maxTurns }); throw new Error(`Agent exceeded max turns (${maxTurns}).`); } getHistory(): AgentMessage[] { return [...this.history]; } reset(): void { this.history.length = 0; } }