import type { ChatSummary, ProviderSettingsUpdate, SettingsPayload, SettingsUpdate, SlashCommand, WebSearchSettingsUpdate, WebuiThreadPersistedPayload, } from "./types"; export class ApiError extends Error { status: number; constructor(status: number, message: string) { super(message); this.status = status; this.name = "ApiError"; } } async function request( url: string, token: string, init?: RequestInit, ): Promise { const res = await fetch(url, { ...(init ?? {}), headers: { ...(init?.headers ?? {}), Authorization: `Bearer ${token}`, }, credentials: "same-origin", }); if (!res.ok) { throw new ApiError(res.status, `HTTP ${res.status}`); } return (await res.json()) as T; } function splitKey(key: string): { channel: string; chatId: string } { const idx = key.indexOf(":"); if (idx === -1) return { channel: "", chatId: key }; return { channel: key.slice(0, idx), chatId: key.slice(idx + 1) }; } export async function listSessions( token: string, base: string = "", ): Promise { type Row = { key: string; created_at: string | null; updated_at: string | null; title?: string; preview?: string; }; const body = await request<{ sessions: Row[] }>( `${base}/api/sessions`, token, ); return body.sessions.map((s) => ({ key: s.key, ...splitKey(s.key), createdAt: s.created_at, updatedAt: s.updated_at, title: s.title ?? "", preview: s.preview ?? "", })); } /** Disk-backed WebUI display thread snapshot (separate from agent session). */ export async function fetchWebuiThread( token: string, key: string, base: string = "", ): Promise { const url = `${base}/api/sessions/${encodeURIComponent(key)}/webui-thread`; const res = await fetch(url, { headers: { Authorization: `Bearer ${token}` }, credentials: "same-origin", }); if (res.status === 404) return null; if (!res.ok) throw new ApiError(res.status, `HTTP ${res.status}`); return (await res.json()) as WebuiThreadPersistedPayload; } export async function deleteSession( token: string, key: string, base: string = "", ): Promise { const body = await request<{ deleted: boolean }>( `${base}/api/sessions/${encodeURIComponent(key)}/delete`, token, ); return body.deleted; } export async function fetchSettings( token: string, base: string = "", ): Promise { return request(`${base}/api/settings`, token); } export async function listSlashCommands( token: string, base: string = "", ): Promise { type Row = { command: string; title: string; description: string; icon: string; arg_hint?: string; }; const body = await request<{ commands: Row[] }>(`${base}/api/commands`, token); return body.commands .filter((command) => !["/stop", "/restart"].includes(command.command)) .map((command) => ({ command: command.command, title: command.title, description: command.description, icon: command.icon, argHint: command.arg_hint ?? "", })); } export async function updateSettings( token: string, update: SettingsUpdate, base: string = "", ): Promise { const query = new URLSearchParams(); if (update.model !== undefined) query.set("model", update.model); if (update.provider !== undefined) query.set("provider", update.provider); return request(`${base}/api/settings/update?${query}`, token); } export async function updateProviderSettings( token: string, update: ProviderSettingsUpdate, base: string = "", ): Promise { const query = new URLSearchParams(); query.set("provider", update.provider); if (update.apiKey !== undefined) query.set("api_key", update.apiKey); if (update.apiBase !== undefined) query.set("api_base", update.apiBase); return request( `${base}/api/settings/provider/update?${query}`, token, ); } export async function updateWebSearchSettings( token: string, update: WebSearchSettingsUpdate, base: string = "", ): Promise { const query = new URLSearchParams(); query.set("provider", update.provider); if (update.apiKey !== undefined) query.set("api_key", update.apiKey); if (update.baseUrl !== undefined) query.set("base_url", update.baseUrl); return request( `${base}/api/settings/web-search/update?${query}`, token, ); }