diff options
| author | Adam Malczewski <[email protected]> | 2026-05-19 19:40:21 +0900 |
|---|---|---|
| committer | Adam Malczewski <[email protected]> | 2026-05-19 19:40:21 +0900 |
| commit | f78a91c20f658dd404277919a0b872b352c99bb6 (patch) | |
| tree | 58cfffb655da4443f4b7a39543b86f988f15239f /packages/api/src/agent-manager.ts | |
| download | dispatch-main.tar.gz dispatch-main.zip | |
- Bun monorepo with @dispatch/core, @dispatch/api, @dispatch/frontend
- Agent runtime with Vercel AI SDK, streaming via WebSocket
- Tools: read_file, write_file, list_files (scoped to working directory)
- Hono API server with POST /chat, GET /status, GET /health, WS /ws
- Svelte 5 + DaisyUI frontend with chat UI, theme switcher, copy button
- OpenCode Go (Zen) as LLM provider, deepseek-v4-flash-free model
- Docker setup (dev + prod) with bin/ scripts and gopass secrets
- Biome v2 linting/formatting, Vitest tests (44 passing)
- Debug info attached to error messages for diagnostics
Diffstat (limited to 'packages/api/src/agent-manager.ts')
| -rw-r--r-- | packages/api/src/agent-manager.ts | 86 |
1 files changed, 86 insertions, 0 deletions
diff --git a/packages/api/src/agent-manager.ts b/packages/api/src/agent-manager.ts new file mode 100644 index 0000000..f60b8d3 --- /dev/null +++ b/packages/api/src/agent-manager.ts @@ -0,0 +1,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" }); + } + } +} |
