UNPKG

@adonis-agora/agent

Version:

Governed, durable-backed AI agent (chat + tool-calling + governance) for AdonisJS — part of the Agora ecosystem.

84 lines • 4 kB
import { mkdir, readFile, rm, writeFile } from 'node:fs/promises'; import { createServer } from 'node:http'; import { join } from 'node:path'; import { sleep } from './util.js'; const LOGO_SVG = `<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 64 64"><rect width="64" height="64" rx="14" fill="#7c3aed"/><text x="32" y="42" font-family="sans-serif" font-size="30" text-anchor="middle" fill="#fff">P</text></svg>`; const STATE_RE = /^[A-Za-z0-9_-]{8,128}$/; /** * Serves the agent's Client ID Metadata Document, JWKS (public keys only), logo, and the * Direct Sign-In redirect target, under the paths of `identity.baseUrl` (4.1). */ export async function startIdentityServer(identity, opts = {}) { const base = new URL(identity.baseUrl); const prefix = base.pathname.replace(/\/+$/, ''); const paths = { meta: new URL(identity.clientId).pathname, jwks: new URL(identity.jwksUri).pathname, callback: new URL(identity.redirectUri).pathname, logo: `${prefix}/logo.svg`, }; const server = createServer(async (req, res) => { const url = new URL(req.url ?? '/', 'http://local'); const send = (status, type, body, extra = {}) => { res.writeHead(status, { 'content-type': type, ...extra }); res.end(body); }; opts.log?.(`${req.method} ${url.pathname}`); if (req.method !== 'GET' && req.method !== 'HEAD') return send(405, 'text/plain', 'method not allowed'); if (url.pathname === paths.meta) { return send(200, 'application/json', JSON.stringify(identity.metadata(), null, 2), { 'cache-control': 'public, max-age=300', }); } if (url.pathname === paths.jwks) { return send(200, 'application/json', JSON.stringify(identity.jwks(), null, 2), { 'cache-control': 'public, max-age=300', }); } if (url.pathname === paths.logo) return send(200, 'image/svg+xml', LOGO_SVG); if (url.pathname === paths.callback) { const state = url.searchParams.get('state') ?? ''; if (!STATE_RE.test(state)) return send(400, 'text/plain', 'invalid state'); opts.onCallback?.(url.searchParams); if (opts.relayDir) { const dir = join(opts.relayDir, 'callbacks'); await mkdir(dir, { recursive: true, mode: 0o700 }); await writeFile(join(dir, `${state}.json`), url.searchParams.toString(), { mode: 0o600 }); } const ok = !url.searchParams.get('error'); return send(200, 'text/html; charset=utf-8', `<!doctype html><meta charset="utf-8"><title>poppy-agent</title><p>${ok ? 'Sign-in received.' : 'Sign-in was not completed.'} You can close this tab and return to the terminal.</p>`, { 'cache-control': 'no-store', 'referrer-policy': 'no-referrer' }); } send(404, 'text/plain', 'not found'); }); const host = opts.host ?? '127.0.0.1'; await new Promise((resolve, reject) => { server.once('error', reject); server.listen(opts.port ?? 0, host, () => resolve()); }); const { port } = server.address(); return { server, url: `http://${host}:${port}`, close: () => new Promise((r) => server.close(() => r())), }; } /** Waits for a relayed callback file for `state` (written by `serve-identity`). */ export async function waitForRelayedCallback(relayDir, state, opts = {}) { const file = join(relayDir, 'callbacks', `${state}.json`); for (;;) { try { const raw = await readFile(file, 'utf8'); await rm(file, { force: true }); return new URLSearchParams(raw); } catch (e) { if (e.code !== 'ENOENT') throw e; } await sleep(opts.pollMs ?? 500, opts.signal); } } //# sourceMappingURL=identity_server.js.map