feat: init

This commit is contained in:
hh
2026-05-21 10:23:01 +02:00
commit 2b00fa44d5
13 changed files with 1189 additions and 0 deletions
+91
View File
@@ -0,0 +1,91 @@
import { requestUrl, RequestUrlParam } from "obsidian";
import type { BeaverSettings } from "./settings";
export interface ChatRequest {
filename: string;
content: string;
agent?: string;
}
export interface ChatResponse {
status: "ok" | "nothing_to_do" | "in_progress";
reason?: string;
agent?: string;
turns_appended?: number;
new_content?: string;
}
export class BeaverApiError extends Error {
status: number;
body: unknown;
constructor(status: number, message: string, body: unknown) {
super(message);
this.status = status;
this.body = body;
}
}
function baseUrl(settings: BeaverSettings): string {
const url = settings.baseUrl.trim().replace(/\/+$/, "");
if (!url) throw new Error("Beaver: base URL is not configured");
return url;
}
function authHeader(settings: BeaverSettings): Record<string, string> {
const token = settings.token.trim();
if (!token) throw new Error("Beaver: bearer token is not configured");
return { Authorization: `Bearer ${token}` };
}
async function call(
settings: BeaverSettings,
init: Omit<RequestUrlParam, "url"> & { path: string },
): Promise<unknown> {
const { path, ...rest } = init;
const url = `${baseUrl(settings)}${path}`;
const headers = {
...authHeader(settings),
...(rest.headers ?? {}),
};
// throw=false → we handle non-2xx ourselves so we can extract FastAPI's
// {detail: "..."} body and surface a useful message.
const res = await requestUrl({ url, throw: false, ...rest, headers });
if (res.status < 200 || res.status >= 300) {
let detail: unknown;
try {
detail = res.json;
} catch {
detail = res.text;
}
const detailMsg =
detail && typeof detail === "object" && "detail" in detail
? String((detail as { detail: unknown }).detail)
: typeof detail === "string"
? detail
: `HTTP ${res.status}`;
throw new BeaverApiError(res.status, detailMsg, detail);
}
return res.json;
}
export async function listAgents(settings: BeaverSettings): Promise<string[]> {
const body = (await call(settings, { path: "/agents", method: "GET" })) as {
agents?: Array<{ name?: unknown }>;
};
const list = body.agents ?? [];
return list
.map((a) => (typeof a?.name === "string" ? a.name : null))
.filter((n): n is string => !!n);
}
export async function sendChat(
settings: BeaverSettings,
req: ChatRequest,
): Promise<ChatResponse> {
return (await call(settings, {
path: "/chat",
method: "POST",
contentType: "application/json",
body: JSON.stringify(req),
})) as ChatResponse;
}