**Motivations:** - Seed AnythingLLM workspace from cloned repo using gitignore-style filters **Root causes:** - N/A **Correctifs:** - N/A **Evolutions:** - Template 4nkaiignore.default; server copies after clone; extension uploads via POST /api/v1/document/upload - New commands /workspace-sync; settings initialSync*; dependency ignore **Pages affectées:** - extensions/anythingllm-workspaces/* - services/repos-devtools-server/* - docs/features/initial-rag-sync-4nkaiignore.md
123 lines
3.6 KiB
TypeScript
123 lines
3.6 KiB
TypeScript
import type { AnythingWorkspace } from "./types";
|
|
|
|
const trimTrailingSlashes = (value: string): string => value.replace(/\/+$/, "");
|
|
|
|
export const normalizeAnythingLlmBaseUrl = (raw: string): string => {
|
|
const trimmed = raw.trim();
|
|
if (trimmed.length === 0) {
|
|
throw new Error("anythingllm.baseUrl is empty");
|
|
}
|
|
return trimTrailingSlashes(trimmed);
|
|
};
|
|
|
|
const parseJson = (text: string): unknown => {
|
|
try {
|
|
return JSON.parse(text) as unknown;
|
|
} catch (cause) {
|
|
throw new Error("Invalid JSON from AnythingLLM API", { cause });
|
|
}
|
|
};
|
|
|
|
const isRecord = (value: unknown): value is Record<string, unknown> =>
|
|
typeof value === "object" && value !== null && !Array.isArray(value);
|
|
|
|
const isWorkspace = (value: unknown): value is AnythingWorkspace => {
|
|
if (!isRecord(value)) {
|
|
return false;
|
|
}
|
|
const id = value.id;
|
|
const name = value.name;
|
|
const slug = value.slug;
|
|
return typeof id === "number" && typeof name === "string" && typeof slug === "string";
|
|
};
|
|
|
|
const parseListWorkspaces = (payload: unknown): readonly AnythingWorkspace[] => {
|
|
if (!isRecord(payload)) {
|
|
throw new Error("AnythingLLM API: expected object body");
|
|
}
|
|
const list = payload.workspaces;
|
|
if (!Array.isArray(list)) {
|
|
throw new Error("AnythingLLM API: missing workspaces array");
|
|
}
|
|
const workspaces: AnythingWorkspace[] = [];
|
|
for (const item of list) {
|
|
if (!isWorkspace(item)) {
|
|
throw new Error("AnythingLLM API: invalid workspace entry");
|
|
}
|
|
workspaces.push(item);
|
|
}
|
|
return workspaces;
|
|
};
|
|
|
|
const normalizeApiSecret = (raw: string): string => {
|
|
const trimmed = raw.trim();
|
|
const bearerPrefix = /^Bearer\s+/i;
|
|
return bearerPrefix.test(trimmed) ? trimmed.replace(bearerPrefix, "").trim() : trimmed;
|
|
};
|
|
|
|
const parseWorkspaceEnvelope = (payload: unknown): AnythingWorkspace => {
|
|
if (!isRecord(payload)) {
|
|
throw new Error("AnythingLLM API: expected object body");
|
|
}
|
|
const ws = payload.workspace;
|
|
if (!isWorkspace(ws)) {
|
|
throw new Error("AnythingLLM API: missing workspace in response");
|
|
}
|
|
return ws;
|
|
};
|
|
|
|
export const listWorkspaces = async (
|
|
baseUrl: string,
|
|
apiKey: string,
|
|
): Promise<readonly AnythingWorkspace[]> => {
|
|
const normalized = normalizeAnythingLlmBaseUrl(baseUrl);
|
|
const key = normalizeApiSecret(apiKey);
|
|
if (key.length === 0) {
|
|
throw new Error("anythingllm.apiKey is empty");
|
|
}
|
|
const url = `${normalized}/api/v1/workspaces`;
|
|
const response = await fetch(url, {
|
|
method: "GET",
|
|
headers: {
|
|
Accept: "application/json",
|
|
Authorization: `Bearer ${key}`,
|
|
},
|
|
});
|
|
const text = await response.text();
|
|
if (!response.ok) {
|
|
throw new Error(`AnythingLLM API ${response.status}: ${text.slice(0, 500)}`);
|
|
}
|
|
return parseListWorkspaces(parseJson(text));
|
|
};
|
|
|
|
export const createWorkspace = async (
|
|
baseUrl: string,
|
|
apiKey: string,
|
|
name: string,
|
|
): Promise<AnythingWorkspace> => {
|
|
const normalized = normalizeAnythingLlmBaseUrl(baseUrl);
|
|
const key = normalizeApiSecret(apiKey);
|
|
if (key.length === 0) {
|
|
throw new Error("anythingllm.apiKey is empty");
|
|
}
|
|
const label = name.trim();
|
|
if (label.length === 0) {
|
|
throw new Error("workspace name is empty");
|
|
}
|
|
const url = `${normalized}/api/v1/workspace/new`;
|
|
const response = await fetch(url, {
|
|
method: "POST",
|
|
headers: {
|
|
Accept: "application/json",
|
|
"Content-Type": "application/json",
|
|
Authorization: `Bearer ${key}`,
|
|
},
|
|
body: JSON.stringify({ name: label }),
|
|
});
|
|
const text = await response.text();
|
|
if (!response.ok) {
|
|
throw new Error(`AnythingLLM API ${response.status}: ${text.slice(0, 500)}`);
|
|
}
|
|
return parseWorkspaceEnvelope(parseJson(text));
|
|
};
|