// 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 = []));