import type { Agent, AgentConfig, Schedule, InstalledSkill, SSEEvent, BackgroundSSEEvent } from './types'; const API = ''; export async function fetchAgents(): Promise { const resp = await fetch(`${API}/api/agents`); if (!resp.ok) throw new Error(`Failed to fetch agents (${resp.status})`); return resp.json(); } export interface HistoryResponse { messages: Array<{ fromId: string; content: string; createdAt: string; conversationId: string }>; conversationId: string | null; } export async function fetchHistory(id: string): Promise { const resp = await fetch(`${API}/api/agents/${encodeURIComponent(id)}/history`); if (!resp.ok) throw new Error(`Failed to fetch history (${resp.status})`); const data = await resp.json(); if (Array.isArray(data)) { return { messages: data, conversationId: null }; } return data as HistoryResponse; } export async function fetchAgentConfig(id: string): Promise { const resp = await fetch(`${API}/api/agents/${encodeURIComponent(id)}/config`); if (!resp.ok) throw new Error(`Failed to fetch config (${resp.status})`); return resp.json(); } export async function fetchSchedules(id: string): Promise { const resp = await fetch(`${API}/api/agents/${encodeURIComponent(id)}/schedules`); if (!resp.ok) throw new Error(`Failed to fetch schedules (${resp.status})`); return resp.json(); } export async function fetchSkills(id: string): Promise { const resp = await fetch(`${API}/api/agents/${encodeURIComponent(id)}/skills`); if (!resp.ok) throw new Error(`Failed to fetch skills (${resp.status})`); return resp.json(); } export async function deleteSchedule(agentId: string, scheduleId: string): Promise { const resp = await fetch( `${API}/api/agents/${encodeURIComponent(agentId)}/schedules/${encodeURIComponent(scheduleId)}`, { method: 'DELETE' }, ); if (!resp.ok) throw new Error(`Failed to delete schedule (${resp.status})`); } export async function startAgent(id: string): Promise { const resp = await fetch(`${API}/api/agents/${encodeURIComponent(id)}/start`, { method: 'POST' }); if (!resp.ok) throw new Error(`Failed to start agent (${resp.status})`); return resp.json(); } export async function stopAgent(id: string): Promise { const resp = await fetch(`${API}/api/agents/${encodeURIComponent(id)}/stop`, { method: 'POST' }); if (!resp.ok) throw new Error(`Failed to stop agent (${resp.status})`); return resp.json(); } export async function removeAgent(id: string): Promise { const resp = await fetch(`${API}/api/agents/${encodeURIComponent(id)}`, { method: 'DELETE' }); if (!resp.ok) throw new Error(`Failed to remove agent (${resp.status})`); } export async function* streamMessage( id: string, content: string, conversationId: string, ): AsyncGenerator { const resp = await fetch(`${API}/api/agents/${encodeURIComponent(id)}/messages`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ content, conversationId }), }); if (!resp.ok || !resp.body) { let detail = ''; try { const body = await resp.text(); const parsed = JSON.parse(body); detail = parsed.error ?? body; } catch { /* use status code alone */ } yield { type: 'error', error: detail || `Request failed (${resp.status})` }; return; } yield* parseSSE(resp.body); } export async function* requestIntro(id: string, conversationId: string): AsyncGenerator { const resp = await fetch(`${API}/api/agents/${encodeURIComponent(id)}/intro`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ conversationId }), }); if (!resp.ok || !resp.body) return; yield* parseSSE(resp.body); } export interface AgentListCallbacks { onUpdate: (agents: Agent[]) => void; onDisconnect?: () => void; } export function subscribeToAgentList( callbacks: AgentListCallbacks, ): { close: () => void } { const { onUpdate, onDisconnect } = callbacks; let aborted = false; let wasConnected = false; const controller = new AbortController(); async function connect() { while (!aborted) { try { const resp = await fetch(`${API}/api/agents/stream`, { signal: controller.signal }); if (!resp.ok || !resp.body) { if (aborted) return; if (wasConnected) { wasConnected = false; onDisconnect?.(); } await new Promise((r) => setTimeout(r, 3000)); continue; } wasConnected = true; const reader = resp.body.getReader(); const decoder = new TextDecoder(); let buffer = ''; while (!aborted) { const { done, value } = await reader.read(); if (done) break; buffer += decoder.decode(value, { stream: true }); const lines = buffer.split('\n'); buffer = lines.pop() ?? ''; for (const line of lines) { if (!line.startsWith('data: ')) continue; const data = line.slice(6); try { const parsed = JSON.parse(data); if (Array.isArray(parsed)) onUpdate(parsed as Agent[]); } catch { // skip malformed or ping } } } } catch { if (aborted) return; } if (!aborted) { if (wasConnected) { wasConnected = false; onDisconnect?.(); } await new Promise((r) => setTimeout(r, 3000)); } } } connect(); return { close: () => { aborted = true; controller.abort(); }, }; } export function subscribeToEvents( id: string, onEvent: (event: BackgroundSSEEvent) => void, ): { close: () => void } { let aborted = false; const controller = new AbortController(); async function connect() { while (!aborted) { try { const resp = await fetch( `${API}/api/agents/${encodeURIComponent(id)}/events`, { signal: controller.signal }, ); if (!resp.ok || !resp.body) { if (aborted) return; await new Promise((r) => setTimeout(r, 3000)); continue; } const reader = resp.body.getReader(); const decoder = new TextDecoder(); let buffer = ''; while (!aborted) { const { done, value } = await reader.read(); if (done) break; buffer += decoder.decode(value, { stream: true }); const lines = buffer.split('\n'); buffer = lines.pop() ?? ''; for (const line of lines) { if (!line.startsWith('data: ')) continue; try { onEvent(JSON.parse(line.slice(6)) as BackgroundSSEEvent); } catch { // skip malformed } } } } catch { if (aborted) return; } if (!aborted) await new Promise((r) => setTimeout(r, 3000)); } } connect(); return { close: () => { aborted = true; controller.abort(); }, }; } export async function sealAgent(id: string): Promise { const resp = await fetch(`${API}/api/agents/${encodeURIComponent(id)}/seal`, { method: 'POST' }); if (!resp.ok) { const data = await resp.json(); throw new Error(data.error ?? `Failed to seal agent (${resp.status})`); } const blob = await resp.blob(); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `${id}.horcrux`; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); } export async function unsealAgent(file: File, name?: string): Promise { const formData = new FormData(); formData.append('file', file); if (name) formData.append('name', name); const resp = await fetch(`${API}/api/agents/unseal`, { method: 'POST', body: formData, }); if (!resp.ok) { const data = await resp.json(); throw new Error(data.error ?? `Failed to unseal agent (${resp.status})`); } return resp.json(); } async function* parseSSE(body: ReadableStream): AsyncGenerator { const reader = body.getReader(); const decoder = new TextDecoder(); let buffer = ''; while (true) { const { done, value } = await reader.read(); if (done) break; buffer += decoder.decode(value, { stream: true }); const lines = buffer.split('\n'); buffer = lines.pop() ?? ''; for (const line of lines) { if (!line.startsWith('data: ')) continue; const data = line.slice(6); if (data === '[DONE]') { yield { type: 'done' }; return; } try { yield JSON.parse(data) as SSEEvent; } catch { // skip malformed events } } } }