Files
mini-openclaw/src/lib/api.ts
T

257 lines
7.2 KiB
TypeScript
Raw Normal View History

2026-09-01 16:33:25 +08:00
/**
* 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<string, unknown>;
}
/**
* Stream chat messages via POST SSE.
* Yields parsed SSE events as they arrive.
*/
export async function* streamChat(
message: string,
sessionId: string
): AsyncGenerator<SSEEvent> {
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<string> {
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<void> {
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<void> {
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<void> {
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();
}