const API_BASE = '/api' async function request(path, options = {}) { const res = await fetch(`${API_BASE}${path}`, { ...options, headers: { 'Content-Type': 'application/json', ...(options.headers || {}) }, }) if (!res.ok) { const err = await res.json().catch(() => ({ error: res.statusText })) throw new Error(err.error || res.statusText) } return res.json() } const api = { getInfo: () => request('/info'), getSystem: () => request('/system'), getTools: () => request('/tools'), getConfig: () => request('/config'), getProviders: () => request('/providers'), getSkills: () => request('/skills'), getLSP: () => request('/lsp'), getMCP: () => request('/mcp'), getUpdates: () => request('/updates'), getEditors: () => request('/editors'), runScan: () => request('/scan', { method: 'POST' }), installTools: (tools) => request('/install', { method: 'POST', body: JSON.stringify({ tools }) }), configureMCP: () => request('/mcp/configure', { method: 'POST' }), configureMCPForEditor: (editor) => request('/mcp/configure', { method: 'POST', body: JSON.stringify({ editor }) }), getMCPStatus: () => request('/mcp/status'), getMCPRegistry: () => request('/mcp/registry'), getLSPHealth: () => request('/lsp/health'), autoInstallLSP: (projectDir) => request('/lsp/auto-install', { method: 'POST', body: JSON.stringify({ project_dir: projectDir || '' }) }), generateLSPConfig: (editor, names) => request('/lsp/editor-config', { method: 'POST', body: JSON.stringify({ editor, names }) }), validateSkill: (name) => request('/skills/validate', { method: 'POST', body: JSON.stringify({ name }) }), testSkill: (name, sampleTask) => request('/skills/test', { method: 'POST', body: JSON.stringify({ name, sample_task: sampleTask || '' }) }), exportSkill: (name) => request('/skills/export', { method: 'POST', body: JSON.stringify({ name }) }), importSkill: (path) => request('/skills/import', { method: 'POST', body: JSON.stringify({ import_path: path }) }), getDashboardStatus: () => request('/dashboard/status'), getProvidersQuota: () => request('/providers/quota'), getRecentCommands: () => request('/recent-commands'), getRunningProcesses: () => request('/running-processes'), getSystemMetrics: () => request('/system/metrics'), savePreferences: (prefs) => request('/preferences', { method: 'PUT', body: JSON.stringify(prefs) }), saveProfile: (profile) => request('/config/profile', { method: 'PUT', body: JSON.stringify(profile) }), saveProvider: (provider) => request('/config/provider', { method: 'PUT', body: JSON.stringify(provider) }), resetConfig: () => request('/config/reset', { method: 'POST' }), applyStarshipTheme: (theme) => request('/starship/apply-theme', { method: 'POST', body: JSON.stringify({ theme }) }), validateProvider: (provider) => request('/providers/validate', { method: 'POST', body: JSON.stringify(provider) }), runUpdate: (tool) => request('/update/run', { method: 'POST', body: JSON.stringify({ tool: tool || '' }) }), runCommand: (command, cwd) => request('/terminal', { method: 'POST', body: JSON.stringify({ command, cwd }) }), getTerminalSessions: () => request('/terminal/sessions'), addSSHConnection: (conn) => request('/terminal/sessions', { method: 'POST', body: JSON.stringify(conn) }), deleteSSHConnection: (name) => request(`/terminal/sessions/${encodeURIComponent(name)}`, { method: 'DELETE' }), getTerminalThemes: () => request('/terminal/themes'), saveTerminalSettings: (settings) => request('/terminal/settings', { method: 'PUT', body: JSON.stringify(settings) }), getChatHistory: () => request('/chat/history'), clearChat: () => request('/chat/clear', { method: 'POST' }), summarizeChat: () => request('/chat/summarize', { method: 'POST' }), sendChat: (message, stream = true, onChunk, signal) => { if (!stream) { return request('/chat', { method: 'POST', body: JSON.stringify({ message, stream: false }) }) } return new Promise((resolve, reject) => { fetch(`${API_BASE}/chat`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ message, stream: true }), signal, }).then(async (res) => { if (!res.ok) { const err = await res.json().catch(() => ({ error: res.statusText })) reject(new Error(err.error || res.statusText)) return } const reader = res.body.getReader() const decoder = new TextDecoder() let full = '' while (true) { const { done, value } = await reader.read() if (done) break const text = decoder.decode(value, { stream: true }) for (const line of text.split('\n')) { if (!line.startsWith('data: ')) continue try { const data = JSON.parse(line.slice(6)) if (data.error) { reject(new Error(data.error)); return } if (data.done) { resolve(full); return } if (data.content) { full += data.content if (onChunk) onChunk(full, data) } else if (data.thinking !== undefined || data.thinking_end) { if (onChunk) onChunk(full, data) } else if (data.tool_call || data.tool_result) { if (onChunk) onChunk(full, data) } } catch {} } } resolve(full) }).catch(reject) }) }, sendShellChat: (message, context = {}, stream = true, onChunk) => { const payload = { message, context: context.context || '', history: context.history || [], cwd: context.cwd || '', platform: context.platform || '', stream, } if (!stream) { return request('/shell/chat', { method: 'POST', body: JSON.stringify(payload) }) } return new Promise((resolve, reject) => { fetch(`${API_BASE}/shell/chat`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), }).then(async (res) => { if (!res.ok) { const err = await res.json().catch(() => ({ error: res.statusText })) reject(new Error(err.error || res.statusText)) return } const reader = res.body.getReader() const decoder = new TextDecoder() let full = '' let toolCalls = [] while (true) { const { done, value } = await reader.read() if (done) break const text = decoder.decode(value, { stream: true }) for (const line of text.split('\n')) { if (!line.startsWith('data: ')) continue try { const data = JSON.parse(line.slice(6)) if (data.error) { reject(new Error(data.error)); return } if (data.done) { resolve({ content: full, tool_calls: toolCalls }) return } if (data.content) { full += data.content if (onChunk) onChunk(full, data) } else if (data.tool_call) { toolCalls.push(data.tool_call) if (onChunk) onChunk(full, data, toolCalls) } else if (data.tool_result) { const idx = toolCalls.findIndex(tc => tc.tool_call_id === data.tool_result.id) if (idx >= 0) { toolCalls[idx].result = data.tool_result } if (onChunk) onChunk(full, data, toolCalls) } } catch {} } } resolve({ content: full, tool_calls: toolCalls }) }).catch(reject) }) }, } export default api