/* * Veskslop agent client — the daemon side of the local LLM harness. * * Respond-only for now: the daemon posts a task and the harness returns the * reply text. The daemon sends that text through the normal paced path, so the * harness never talks to Discord and no tools are exposed to the model. */ export interface AgentTask { id: string; authorId: string; authorName: string; channelId: string; guildId?: string; content: string; context: Array<{ author: string; content: string; timestamp: string }>; } export interface AgentClientOptions { url: string; token?: string; timeoutMs?: number; } export class AgentClient { private readonly base: string; private readonly timeoutMs: number; constructor(private readonly options: AgentClientOptions) { this.base = options.url.replace(/\/+$/, ""); this.timeoutMs = options.timeoutMs ?? 120_000; } get url(): string { return this.base; } /** * Ask the harness for a reply. Returns the reply text, or undefined when * the harness answers with an empty reply. */ async respond(task: AgentTask): Promise { const controller = new AbortController(); const timer = setTimeout(() => controller.abort(), this.timeoutMs); try { const response = await fetch(`${this.base}/task`, { method: "POST", headers: { "content-type": "application/json", ...(this.options.token ? { authorization: `Bearer ${this.options.token}` } : {}) }, // mode: respond tells the harness to answer without tool calls. body: JSON.stringify({ ...task, mode: "respond" }), signal: controller.signal }); if (!response.ok) { const detail = await response.text().catch(() => ""); throw new Error(`agent returned ${response.status}${detail ? `: ${detail.slice(0, 200)}` : ""}`); } const body = (await response.json().catch(() => ({}))) as { reply?: unknown }; const reply = typeof body.reply === "string" ? body.reply.trim() : ""; return reply || undefined; } finally { clearTimeout(timer); } } } /** * Build the client from the environment. The harness stays off unless * VESKSLOP_AGENT_ENABLED=1 is set. */ export function agentFromEnv(): AgentClient | undefined { if (process.env.VESKSLOP_AGENT_ENABLED !== "1") return undefined; const url = process.env.VESKSLOP_AGENT_URL ?? "http://127.0.0.1:8790"; const token = process.env.VESKSLOP_AGENT_TOKEN; const timeoutMs = Number(process.env.VESKSLOP_AGENT_TIMEOUT_MS ?? 120_000); return new AgentClient({ url, ...(token ? { token } : {}), timeoutMs: Number.isFinite(timeoutMs) && timeoutMs > 0 ? timeoutMs : 120_000 }); }