ReMe/reme_studio/app/api.ts
jinliyl ef3f99f019
refactor(packaging): reorganize published packages (#495)
* refactor(packaging): reorganize published packages

* fix(packaging): install AgentScope extra in wheel smoke

* docs: align package guides and documentation site

* ci(workflow): add core dependency verification step in Python package build

- Add a workflow step to verify released core dependencies by installing the wheel with core extras
- Assert the presence of the static index.html file to ensure proper package contents
- Create and use a temporary virtual environment for isolation during verification
- Keep existing artifacts upload step intact and conditional on inputs.upload_artifacts flag

* fix(ci): update package installation dependencies in Windows workflow

- Change pip install from editable reme_studio and core to only dev and as extras
- Remove installation of reme_studio and core to streamline dependency setup
- Ensure Windows CI uses the correct extras for testing environment

* fix(tests): add missing commas in toml file reads in package version tests

- Added trailing commas in the tomllib.loads calls for auto-fin and daily_paper configs
- Ensured consistent syntax to prevent potential tuple misinterpretation
- Improved readability and correctness of the test setup code

* fix(packaging): protect qwenpaw releases and test Studio health
2026-08-27 14:02:09 +08:00

155 lines
4.4 KiB
TypeScript

import type {
AppConfig,
FileStat,
GraphSnapshot,
ReMeHealth,
ReMeResponse,
StreamChunk,
} from "./types";
import { decodeSseEvent } from "./chat-stream";
import { healthFromResponse } from "./health-status";
import { translate, useLanguageStore, type TranslationKey } from "./i18n";
import {
WORKSPACE_FILE_LIMIT,
workspaceFileListing,
type WorkspaceFileListing,
} from "./workspace-files";
import { displayReMeApiEndpoint, normalizeReMeApiUrl } from "./api-endpoint";
export const REME_API_URL = normalizeReMeApiUrl(
process.env.NEXT_PUBLIC_REME_API_URL || "http://127.0.0.1:2333",
);
export const REME_API_ENDPOINT = displayReMeApiEndpoint(
REME_API_URL,
typeof window === "undefined" ? undefined : window.location.origin,
);
const message = (key: TranslationKey, status: number) =>
translate(useLanguageStore.getState().language, key, {
status: String(status),
});
async function parseResponse<T>(response: Response): Promise<ReMeResponse<T>> {
let payload: ReMeResponse<T>;
try {
payload = (await response.json()) as ReMeResponse<T>;
} catch {
throw new Error(message("invalidResponse", response.status));
}
if (!response.ok || !payload.success) {
const detail =
typeof payload.answer === "string"
? payload.answer
: message("requestFailed", response.status);
throw new Error(detail);
}
return payload;
}
export async function callReMe<T>(
action: string,
body: Record<string, unknown> = {},
): Promise<ReMeResponse<T>> {
const response = await fetch(`${REME_API_URL}/${action}`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(body),
});
return parseResponse<T>(response);
}
export async function getAppConfig(): Promise<AppConfig> {
return (await callReMe<AppConfig>("app_config")).answer;
}
export async function getReMeVersion(): Promise<string> {
return String((await callReMe<string>("version")).answer);
}
export async function getReMeStatus(): Promise<ReMeResponse<string>> {
return callReMe<string>("status");
}
export async function getReMeHealth(): Promise<ReMeHealth | undefined> {
const response = await callReMe<string>("health_check");
return healthFromResponse(response);
}
export async function rebuildReMeIndex(): Promise<ReMeResponse<unknown>> {
return callReMe<unknown>("reindex");
}
export async function getGraphSnapshot(): Promise<GraphSnapshot> {
return (await callReMe<GraphSnapshot>("graph_snapshot")).answer;
}
export async function listWorkspaceFiles(
extensions: string[],
): Promise<WorkspaceFileListing> {
const response = await callReMe<string>("list", {
path: "",
recursive: true,
limit: WORKSPACE_FILE_LIMIT,
sort_by: "mtime",
extensions,
});
return workspaceFileListing(response.metadata.items);
}
export async function readWorkspaceFile(
path: string,
): Promise<{ content: string; stat: FileStat }> {
const response = await callReMe<string>("load", { path });
return {
content: String(response.answer ?? ""),
stat: response.metadata as unknown as FileStat,
};
}
export async function saveWorkspaceFile(
path: string,
content: string,
expectedMtime?: string,
): Promise<FileStat> {
const response = await callReMe<string>("save", {
path,
content,
expected_mtime: expectedMtime || null,
});
return response.metadata as unknown as FileStat;
}
export async function streamChat(
query: string,
sessionId: string | undefined,
signal: AbortSignal,
onChunk: (chunk: StreamChunk) => void,
): Promise<void> {
const response = await fetch(`${REME_API_URL}/chat`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "text/event-stream",
},
body: JSON.stringify({ query, session_id: sessionId || null }),
signal,
});
if (!response.ok || !response.body)
throw new Error(message("agentUnavailable", response.status));
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = "";
while (true) {
const { value, done } = await reader.read();
buffer += decoder.decode(value, { stream: !done });
const events = buffer.split(/\r?\n\r?\n/);
buffer = events.pop() || "";
for (const event of events) {
const chunk = decodeSseEvent(event);
if (chunk) onChunk(chunk);
}
if (done) break;
}
const finalChunk = decodeSseEvent(buffer);
if (finalChunk) onChunk(finalChunk);
}