@adonis-agora/agent
Version:
Governed, durable-backed AI agent (chat + tool-calling + governance) for AdonisJS — part of the Agora ecosystem.
77 lines • 3.39 kB
JavaScript
import { throwForResponse } from './company.js';
import { PoppyError } from './errors.js';
import { readJson } from './http.js';
import { requireSecureUrl } from './util.js';
/** API types this client understands (section 6). Others are skipped. */
export const KNOWN_API_TYPES = ['openapi', 'mcp'];
export function listApis(company) {
return (company.document.apis ?? []).filter((a) => a && KNOWN_API_TYPES.includes(a.type) && typeof a.url === 'string');
}
export function pickApi(company, type, index) {
const apis = listApis(company).filter((a) => a.type === type);
if (apis.length === 0)
throw new PoppyError('no_api', `Company lists no ${type} API`);
const api = apis[index ?? 0];
if (!api)
throw new PoppyError('no_api', `no ${type} API at index ${index}`);
requireSecureUrl(api.url, `${type} url`, company.security);
return api;
}
/** Loads an OpenAPI description. Tried without a token first; with DPoP if that's refused. */
export async function loadOpenApi(company, api) {
const res = await company.http.request(api.url, { headers: { accept: 'application/json' } });
if (res.ok)
return (await readJson(res)) ?? {};
if (res.status === 401 || res.status === 403) {
const { body } = await company.json(api.url, {
headers: { accept: 'application/json' },
resource: api.resource,
});
return body ?? {};
}
throw new PoppyError(`http_${res.status}`, `could not load ${api.url}`);
}
/** The API's base URL: `servers[0].url` resolved against the description's URL. */
export function apiBaseUrl(doc, api) {
const server = doc.servers?.[0]?.url;
return new URL(server ?? '/', api.url).toString().replace(/\/+$/, '');
}
/**
* Calls an endpoint of a listed OpenAPI API with a DPoP Session Token (section 6). Refuses
* absolute URLs outside the API's own origin, so tokens never go to a third party.
*/
export async function callOpenApi(company, input) {
const api = pickApi(company, 'openapi', input.apiIndex);
const doc = await loadOpenApi(company, api);
const base = apiBaseUrl(doc, api);
const url = /^https?:\/\//i.test(input.path)
? new URL(input.path)
: new URL(base + (input.path.startsWith('/') ? input.path : `/${input.path}`));
const allowed = new Set([new URL(base).origin, new URL(api.url).origin]);
if (!allowed.has(url.origin)) {
throw new PoppyError('foreign_origin', `${url.origin} is not this API's origin; refusing to send the token`);
}
requireSecureUrl(url.toString(), 'API URL', company.security);
const headers = { accept: 'application/json' };
if (input.body !== undefined)
headers['content-type'] = 'application/json';
const res = await company.fetch(url.toString(), {
method: input.method.toUpperCase(),
headers,
body: input.body === undefined ? undefined : JSON.stringify(input.body),
resource: api.resource,
scope: input.scope,
});
if (res.status === 401 || res.status === 403) {
if (res.headers.get('www-authenticate'))
await throwForResponse(res);
}
const text = await res.text();
let body = text;
try {
body = text ? JSON.parse(text) : undefined;
}
catch { }
return { status: res.status, headers: res.headers, body };
}
//# sourceMappingURL=api.js.map