UNPKG

@adonis-agora/agent

Version:

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

136 lines • 6.36 kB
/** * Framework-independent GenUI owned by the Adonis agent: schemas, catalogs, presentations, * renderers and UI tools. The same local definitions can be imported by browser code. */ import { assertGenuiChannels } from './channels.js'; import { defineCatalog, genuiTools, } from './public.js'; import { defineSandbox, sandboxPolicyOf, } from './sandbox.js'; export { AgentGenui, } from './factory.js'; export * from './public.js'; /** * Picks the catalog for ONE request — a tenant's own components, a plan's versions. Extend it and * pass the class as `resolver`: the provider builds it through the container, so it can `@inject()` * whatever it reads (a model, a service). */ export class GenuiCatalogResolver { } /** What every configured sandbox (top level and channels) needs from the browser, together. */ function mergeSandboxClients(clients) { const merged = { theme: false }; for (const client of clients) { const one = client(); merged.theme ||= one.theme; if (one.name !== undefined && merged.kit === undefined && one.kit !== undefined) merged.name = one.name; if (one.kit !== undefined) merged.kit ??= one.kit; if (one.css !== undefined) merged.css ??= one.css; if (one.tailwind !== undefined) merged.tailwind ??= one.tailwind; } return merged; } function isResolverClass(value) { return typeof value === 'function' && value.prototype instanceof GenuiCatalogResolver; } async function resolveCatalogFn(resolver, ctx) { if (resolver === undefined) return undefined; if (resolver instanceof GenuiCatalogResolver) return (scope) => resolver.resolve(scope); if (isResolverClass(resolver)) { const instance = await ctx.make(resolver); return (scope) => instance.resolve(scope); } return resolver; } /** * Generative UI for `config/agent.ts`: * * ```ts * import { genui } from '@adonis-agora/agent/genui' * import { catalog } from '#genui/catalog' // defineCatalog([...]) — shared with the browser * * export default defineConfig({ model, genui: genui({ catalog }) }) * ``` * * Registers one `ui__render` tool taking a component tree composed from the catalog (or, with * `mode: 'per-component'`, a `ui__show_<component>` tool per model-facing component; and/or the * generic `ui__show` with `showTool: true`). Each call validates * against the catalog and pushes through `ctx.emitUi`, so it streams as a `ui` frame and is persisted * on the message. Tools without `roles` get the config's `defaultRoles`, like every other tool. */ export function genui(options = {}) { return async (ctx) => { const { catalog: given, resolver, sandbox: asked, channels: askedChannels, sandboxKit: _urls, ...rest } = options; assertGenuiChannels(askedChannels); const base = given ?? defineCatalog([]); // A sandbox's kit (styles, components, wrapper) and theme names are found through the app's Vite setup. const clients = []; const discoveries = new Map(); const resolveSandbox = async (value) => { if (value === undefined || value === false) return value; // Node-only, and only with a sandbox: kept out of a browser bundle of this module. const [{ adonisSandboxKitDiscovery }, { resolveSandboxServer }] = await Promise.all([ import('./kit/adonis.js'), import('./kit/discover.js'), ]); const kitName = value === true ? undefined : value.kitName; let discovery = discoveries.get(kitName ?? ''); if (discovery === undefined) { discovery = await adonisSandboxKitDiscovery(ctx.app, { ...(kitName !== undefined ? { name: kitName } : {}), ...(options.sandboxKit ?? {}), }); discoveries.set(kitName ?? '', discovery); } const resolved = resolveSandboxServer(value, discovery); clients.push(resolved.client); return resolved.define; }; const sandbox = await resolveSandbox(asked); // A kit sandbox defined in the (shared) catalog itself: found the same way, and put // back under its own name, so the browser's copy of the catalog validates what the model writes. const own = base.components.find((each) => sandboxPolicyOf(each) !== undefined); const ownOptions = asked === undefined && own?.sandboxOptions !== undefined && own.sandboxView?.kit === true ? await resolveSandbox({ ...own.sandboxOptions, name: own.name }) : undefined; let channels; if (askedChannels !== undefined) { channels = {}; for (const [name, entry] of Object.entries(askedChannels)) { if (entry === undefined) continue; const own = await resolveSandbox(entry.sandbox); channels[name] = { ...entry, ...(own !== undefined ? { sandbox: own } : {}) }; } } const catalog = sandbox === undefined || sandbox === false ? typeof ownOptions === 'object' ? base.extend([defineSandbox(ownOptions)]) : base : base.extend([defineSandbox(sandbox === true ? {} : sandbox)]); const resolveCatalog = await resolveCatalogFn(resolver, ctx); const tools = genuiTools(catalog, { ...rest, ...(channels !== undefined ? { channels } : {}), ...(sandbox !== undefined ? { sandbox } : {}), ...(resolveCatalog !== undefined ? { resolveCatalog } : {}), }); return { catalog, ...(resolveCatalog === undefined ? {} : { resolveCatalog }), tools, ...(channels !== undefined ? { channels } : {}), ...(clients.length > 0 ? { sandboxClient: () => mergeSandboxClients(clients) } : {}), base: { ...(options.mode !== undefined ? { mode: options.mode } : {}), ...(options.streaming !== undefined ? { streaming: options.streaming } : {}), ...(sandbox !== undefined ? { sandbox } : {}), }, }; }; } //# sourceMappingURL=index.js.map