/** * API client for mini OpenClaw backend. * Custom SSE parser for POST requests (native EventSource only supports GET). */ const API_BASE = typeof window !== "undefined" ? `http://${window.location.hostname}:8002/api` : "http://localhost:8002/api"; export interface SSEEvent { event: string; data: Record; } /** * Stream chat messages via POST SSE. * Yields parsed SSE events as they arrive. */ export async function* streamChat( message: string, sessionId: string ): AsyncGenerator { const response = await fetch(`${API_BASE}/chat`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ message, session_id: sessionId, stream: true }), }); if (!response.ok) { throw new Error(`Chat API error: ${response.status}`); } const reader = response.body?.getReader(); if (!reader) throw new Error("No response body"); 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() || ""; let currentEvent = "message"; for (const line of lines) { if (line.startsWith("event:")) { currentEvent = line.slice(6).trim(); } else if (line.startsWith("data:")) { const dataStr = line.slice(5).trim(); if (dataStr) { try { const data = JSON.parse(dataStr); yield { event: currentEvent, data }; } catch { // Skip malformed JSON } } } // Empty line resets event type if (line === "") { currentEvent = "message"; } } } } /** * Read a file from the backend. */ export async function readFile(path: string): Promise { const resp = await fetch(`${API_BASE}/files?path=${encodeURIComponent(path)}`); if (!resp.ok) throw new Error(`Failed to read file: ${resp.status}`); const data = await resp.json(); return data.content; } /** * Save a file to the backend. */ export async function saveFile(path: string, content: string): Promise { const resp = await fetch(`${API_BASE}/files`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ path, content }), }); if (!resp.ok) throw new Error(`Failed to save file: ${resp.status}`); } /** * List all sessions. */ export async function listSessions(): Promise< Array<{ id: string; title: string; updated_at: number }> > { const resp = await fetch(`${API_BASE}/sessions`); if (!resp.ok) throw new Error(`Failed to list sessions: ${resp.status}`); const data = await resp.json(); return data.sessions; } /** * Create a new session. */ export async function createSession(): Promise<{ id: string; title: string }> { const resp = await fetch(`${API_BASE}/sessions`, { method: "POST" }); if (!resp.ok) throw new Error(`Failed to create session: ${resp.status}`); return resp.json(); } /** * Rename a session. */ export async function renameSession(id: string, title: string): Promise { const resp = await fetch(`${API_BASE}/sessions/${encodeURIComponent(id)}`, { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ title }), }); if (!resp.ok) throw new Error(`Failed to rename session: ${resp.status}`); } /** * Delete a session. */ export async function deleteSession(id: string): Promise { const resp = await fetch(`${API_BASE}/sessions/${encodeURIComponent(id)}`, { method: "DELETE", }); if (!resp.ok) throw new Error(`Failed to delete session: ${resp.status}`); } /** * Get raw messages for a session (including system prompt). */ export async function getRawMessages( sessionId: string ): Promise<{ session_id: string; title: string; messages: Array<{ role: string; content: string }> }> { const resp = await fetch( `${API_BASE}/sessions/${encodeURIComponent(sessionId)}/messages` ); if (!resp.ok) throw new Error(`Failed to get raw messages: ${resp.status}`); return resp.json(); } /** * Get session conversation history (no system prompt, includes tool_calls). */ export async function getSessionHistory( sessionId: string ): Promise<{ session_id: string; messages: Array<{ role: string; content: string; tool_calls?: Array<{ tool: string; input?: string; output?: string }> }>; }> { const resp = await fetch( `${API_BASE}/sessions/${encodeURIComponent(sessionId)}/history` ); if (!resp.ok) throw new Error(`Failed to get session history: ${resp.status}`); return resp.json(); } /** * List available skills. */ export async function listSkills(): Promise< Array<{ name: string; path: string; description: string }> > { const resp = await fetch(`${API_BASE}/skills`); if (!resp.ok) throw new Error(`Failed to list skills: ${resp.status}`); const data = await resp.json(); return data.skills; } /** * Generate a title for a session using AI. */ export async function generateTitle( sessionId: string ): Promise<{ title: string }> { const resp = await fetch( `${API_BASE}/sessions/${encodeURIComponent(sessionId)}/generate-title`, { method: "POST" } ); if (!resp.ok) throw new Error(`Failed to generate title: ${resp.status}`); return resp.json(); } /** * Get token count for a session (system + messages). */ export async function getSessionTokenCount( sessionId: string ): Promise<{ system_tokens: number; message_tokens: number; total_tokens: number }> { const resp = await fetch( `${API_BASE}/tokens/session/${encodeURIComponent(sessionId)}` ); if (!resp.ok) throw new Error(`Failed to get token count: ${resp.status}`); return resp.json(); } /** * Get token counts for a list of files. */ export async function getFileTokenCounts( paths: string[] ): Promise<{ files: Array<{ path: string; tokens: number }> }> { const resp = await fetch(`${API_BASE}/tokens/files`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ paths }), }); if (!resp.ok) throw new Error(`Failed to get file token counts: ${resp.status}`); return resp.json(); } /** * Compress a session's conversation history. */ export async function compressSession( sessionId: string ): Promise<{ archived_count: number; remaining_count: number }> { const resp = await fetch( `${API_BASE}/sessions/${encodeURIComponent(sessionId)}/compress`, { method: "POST" } ); if (!resp.ok) throw new Error(`Failed to compress session: ${resp.status}`); return resp.json(); } /** * Get current RAG mode status. */ export async function getRagMode(): Promise<{ rag_mode: boolean }> { const resp = await fetch(`${API_BASE}/config/rag-mode`); if (!resp.ok) throw new Error(`Failed to get RAG mode: ${resp.status}`); return resp.json(); } /** * Set RAG mode enabled/disabled. */ export async function setRagMode( enabled: boolean ): Promise<{ rag_mode: boolean }> { const resp = await fetch(`${API_BASE}/config/rag-mode`, { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ enabled }), }); if (!resp.ok) throw new Error(`Failed to set RAG mode: ${resp.status}`); return resp.json(); } /* ── 兴邦金租评审平台 API ───────────────────────────── */ /** 兴邦项目配置(数据源/五智能体/档位)。 */ export async function getXingbangConfig(): Promise { const resp = await fetch(`${API_BASE}/xingbang/config`); if (!resp.ok) throw new Error(`Xingbang config error: ${resp.status}`); return resp.json(); } /** * 流式运行兴邦五智能体审批流水线(SSE)。 * 逐步 yield: agent_start / agent_end / done。 */ export async function* streamXingbangWorkflow( task: string, customer: string ): AsyncGenerator { const response = await fetch(`${API_BASE}/xingbang/workflow/stream`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ task, customer }), }); if (!response.ok) throw new Error(`Xingbang workflow error: ${response.status}`); const reader = response.body?.getReader(); if (!reader) throw new Error("No response body"); 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() || ""; let currentEvent = "message"; for (const line of lines) { if (line.startsWith("event:")) { currentEvent = line.slice(6).trim(); } else if (line.startsWith("data:")) { const dataStr = line.slice(5).trim(); if (dataStr) { try { yield { event: currentEvent, data: JSON.parse(dataStr) }; } catch { /* skip */ } } } if (line === "") currentEvent = "message"; } } } /** 兴邦自进化洞察。 */ export async function getXingbangEvolution(): Promise { const resp = await fetch(`${API_BASE}/xingbang/evolution`); if (!resp.ok) throw new Error(`Xingbang evolution error: ${resp.status}`); return resp.json(); } /** 兴邦历史轨迹列表。 */ export async function listXingbangTrajectories(): Promise { const resp = await fetch(`${API_BASE}/xingbang/trajectories`); if (!resp.ok) throw new Error(`Xingbang trajectories error: ${resp.status}`); return resp.json(); } /** 读取单条兴邦轨迹。 */ export async function getXingbangTrajectory(id: string): Promise { const resp = await fetch( `${API_BASE}/xingbang/trajectories/${encodeURIComponent(id)}` ); if (!resp.ok) throw new Error(`Xingbang trajectory error: ${resp.status}`); return resp.json(); }