summaryrefslogtreecommitdiffhomepage
path: root/packages/api/src/agent-manager.ts
blob: f60b8d3ae635ddc5fa39933d332a3df7bc788ecf (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
import {
	Agent,
	type AgentEvent,
	type AgentStatus,
	createListFilesTool,
	createReadFileTool,
	createWriteFileTool,
} from "@dispatch/core";

const SYSTEM_PROMPT = `You are Dispatch, a helpful AI coding assistant. You have access to the following tools for working with files in the current working directory:

- read_file: Read the contents of a file
- write_file: Write content to a file (creates parent directories if needed)
- list_files: List files and directories

When asked to work with files, use these tools. Always confirm what you did after completing an action. Be concise and helpful.`;

export class AgentManager {
	private agent: Agent | null = null;
	private status: AgentStatus = "idle";
	private messageCount = 0;
	private eventListeners: Set<(event: AgentEvent) => void> = new Set();

	private getOrCreateAgent(): Agent {
		if (!this.agent) {
			const apiKey = process.env.OPENCODE_API_KEY ?? "";
			const model = process.env.DISPATCH_MODEL ?? "deepseek-v4-flash-free";
			const workingDirectory = process.env.DISPATCH_WORKING_DIR ?? process.cwd();

			const tools = [
				createReadFileTool(workingDirectory),
				createWriteFileTool(workingDirectory),
				createListFilesTool(workingDirectory),
			];

			this.agent = new Agent({
				model,
				apiKey,
				baseURL: "https://opencode.ai/zen/v1",
				systemPrompt: SYSTEM_PROMPT,
				tools,
				workingDirectory,
			});
		}
		return this.agent;
	}

	getStatus(): AgentStatus {
		return this.status;
	}

	getMessageCount(): number {
		return this.messageCount;
	}

	onEvent(listener: (event: AgentEvent) => void): () => void {
		this.eventListeners.add(listener);
		return () => {
			this.eventListeners.delete(listener);
		};
	}

	private emit(event: AgentEvent): void {
		for (const listener of this.eventListeners) {
			listener(event);
		}
	}

	async processMessage(message: string): Promise<void> {
		const agent = this.getOrCreateAgent();

		this.messageCount += 1;

		try {
			for await (const event of agent.run(message)) {
				this.status = event.type === "status" ? event.status : this.status;
				this.emit(event);
			}
		} catch (err) {
			const errorMsg = err instanceof Error ? err.message : String(err);
			this.status = "error";
			this.emit({ type: "error", error: errorMsg });
			this.emit({ type: "status", status: "error" });
		}
	}
}