Files
AI-Harness-Basic/src/agent.ts
2026-05-15 14:20:08 -04:00

101 lines
3.1 KiB
TypeScript

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<string> {
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;
}
}