/** * Tauri adapter for {@link AgentGateway} (L6). * * NOTE: The Tauri commands wired here (`list_agents`, `create_agent`, …) are * defined in the backend `app-tauri` crate and will be registered in a * subsequent lot. This adapter is complete on the frontend side; the mock * gateway covers tests and offline dev today. The real mode will work * transparently once the commands are registered. * * Commands use snake_case (Tauri convention); payload keys are camelCase * (matching the backend DTO `#[serde(rename_all = "camelCase")]`), consistent * with the other adapters in this directory. */ import { Channel, invoke } from "@tauri-apps/api/core"; import type { Agent } from "@/domain"; import type { AgentGateway, CreateAgentInput, OpenTerminalOptions, ReattachResult, TerminalHandle, } from "@/ports"; import { makeTerminalHandle } from "./terminal"; /** Wire shape returned by the `launch_agent` command (mirrors `open_terminal`). */ interface LaunchAgentResponse { sessionId: string; cwd: string; rows: number; cols: number; } export class TauriAgentGateway implements AgentGateway { listAgents(projectId: string): Promise { return invoke("list_agents", { projectId }); } createAgent(projectId: string, input: CreateAgentInput): Promise { // The `create_agent` command takes a single `request` DTO; `projectId` must // live *inside* it (camelCase), not at the top level. return invoke("create_agent", { request: { projectId, name: input.name, profileId: input.profileId, initialContent: input.initialContent ?? null, }, }); } readContext(projectId: string, agentId: string): Promise { return invoke("read_agent_context", { projectId, agentId }); } async updateContext( projectId: string, agentId: string, content: string, ): Promise { // `update_agent_context` takes a single `request` DTO. await invoke("update_agent_context", { request: { projectId, agentId, content }, }); } async deleteAgent(projectId: string, agentId: string): Promise { await invoke("delete_agent", { projectId, agentId }); } async launchAgent( projectId: string, agentId: string, options: OpenTerminalOptions, onData: (bytes: Uint8Array) => void, ): Promise { // Per-session output channel. The backend serialises chunks as byte arrays. const channel = new Channel(); channel.onmessage = (chunk) => onData(Uint8Array.from(chunk)); const res = await invoke("launch_agent", { request: { projectId, agentId, rows: options.rows, cols: options.cols, }, onOutput: channel, }); return makeTerminalHandle(res.sessionId, channel); } async reattach( sessionId: string, onData: (bytes: Uint8Array) => void, ): Promise { // Agent sessions reattach through the same session-based `reattach_terminal` // command as plain terminals (the PTY is identified by its session id). const channel = new Channel(); channel.onmessage = (chunk) => onData(Uint8Array.from(chunk)); const res = await invoke<{ sessionId: string; scrollback: number[] }>( "reattach_terminal", { sessionId, onOutput: channel }, ); return { handle: makeTerminalHandle(res.sessionId, channel), scrollback: Uint8Array.from(res.scrollback), }; } }