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 => 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 => { 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 => { 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)); };