~ knotless Search/
↑↓ move↵ open⇧↵ all results

johnandersen777.bsky.social/atlibs-asyncai

0
0
main
87 lines · 3.9 KB Raw History
// Wiring only: the form runs the agent (agent.js), the transcript shows each step. co/core runs as the
// signed-in user (an OAuth session asking only for the co/core token's scope); the others with a key.
import { pageClientId } from "./atlibs/oauth/index.js";
import { createPluginContext } from "./atlibs/plugin-context/index.js";
import { localModule } from "./atlibs/run-module/index.js";
import { COCORE_SCOPE, rows, runAgent, serverLines } from "./agent.js";

const MODULES = new URL("./modules/", import.meta.url);
const ctx = createPluginContext({ id: "asyncai" });
const loading = localModule(new URL("asyncai.wasm", MODULES), new URL("asyncai.json", MODULES));
const $ = (id) => document.getElementById(id);
let memory = []; // the last run's messages: the next prompt continues them
let stop;

// Sign-in is only for co/core: a page that cannot load the OAuth client still runs the other backends.
let client, session;
try {
	const { BrowserOAuthClient } = await import("@atproto/oauth-client-browser");
	client = await BrowserOAuthClient.load({ clientId: await pageClientId(COCORE_SCOPE), handleResolver: "https://bsky.social" });
	session = (await client.init())?.session;
} catch (error) {
	$("login-error").textContent = `co/core sign-in unavailable: ${error.message}`;
}
const backend = () => $("form").backend.value;
const sync = () => {
	$("custom").hidden = backend() !== "custom";
	$("login").hidden = backend() !== "cocore" || Boolean(session);
	$("signed-in").hidden = backend() !== "cocore" || !session;
};
if (session) $("who").textContent = session.did;
for (const radio of $("form").backend) radio.onchange = sync;
sync();
$("login").onsubmit = async (e) => {
	e.preventDefault();
	await client?.signIn(new FormData(e.target).get("handle")).catch((error) => ($("login-error").textContent = error.message));
};
$("logout").onclick = async () => {
	await client?.revoke(session.did).catch(() => undefined);
	location.reload();
};

function show(list, note = "") {
	$("transcript").replaceChildren(
		...list.map(({ role, kind, text, error }) => {
			const li = document.createElement("li");
			li.className = `${kind}${error ? " error" : ""}`;
			const label = document.createElement("small");
			label.textContent = `${role} · ${kind}${error ? " · error" : ""}`;
			const body = document.createElement("pre");
			body.textContent = text;
			li.append(label, body);
			return li;
		}),
	);
	$("status").textContent = note;
}

$("form").onsubmit = async (e) => {
	e.preventDefault();
	const f = e.target;
	let mcp;
	try {
		mcp = f.mcp.value.trim() ? JSON.parse(f.mcp.value) : [];
	} catch (error) {
		return show(rows(memory), `MCP servers: ${error.message}`);
	}
	stop = new AbortController();
	$("run").setAttribute("aria-busy", "true");
	$("stop").disabled = false;
	try {
		const dynamicMcp = f.dynamicMcp.value.split(/\s+/).filter(Boolean);
		const options = { backend: backend(), endpoint: f.endpoint.value.trim(), format: f.format.value, prompt: f.prompt.value, system: f.system.value, model: f.model.value, apiKey: f.apiKey.value, maxSteps: f.maxSteps.value, contextWindow: f.contextWindow.value, compact: f.compact.value, mcp, dynamicMcp, messages: memory };
		const ran = await runAgent(ctx, await loading, options, { session, signal: stop.signal, progress: (list, { compacting }) => show(list, compacting ? "compacting the transcript..." : "running...") });
		const r = ran.result;
		if (r?.messages) memory = r.messages;
		$("servers").replaceChildren(...serverLines(r?.servers).map((line) => Object.assign(document.createElement("li"), { textContent: line })));
		show(rows(memory), [ran.error ?? r?.error, r && `stop: ${r.stop}, steps: ${r.steps}, usage: ${JSON.stringify(r.usage)}`].filter(Boolean).join(" | "));
		f.prompt.value = "";
	} catch (error) {
		show(rows(memory), error.message);
	} finally {
		$("run").removeAttribute("aria-busy");
		$("stop").disabled = true;
	}
};
$("stop").onclick = () => stop?.abort();
$("forget").onclick = () => show((memory = []));