UNPKG

@adonis-agora/agent

Version:

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

284 lines • 15.3 kB
/** * Per-channel generative UI: what a turn may draw depends on where it is read. A web page draws the * catalog's components (and the sandbox); WhatsApp and Telegram draw text, reply buttons, lists and * images; an email draws HTML. `genui({ channels: { … } })` sets, per channel, which tools the model * is offered and how what it pushes is delivered — and the model is told only what that channel can * draw. * * Framework-free and isomorphic, like the rest of `genui`. */ import type { UiAction } from './actions.js'; import { type Catalog, type ComponentDefinition, type GenuiStreaming } from './catalog.js'; import { type DefineSandboxOptions } from './sandbox.js'; /** The channel a turn runs on when nothing says otherwise: a browser (AG-UI, the HTTP chat, A2UI). */ export declare const WEB_CHANNEL = "web"; /** The `channels` entry that serves every channel without one of its own. */ export declare const DEFAULT_CHANNEL = "default"; /** Channels a person reads in a messaging app or a mailbox: the server draws for them. */ export declare const MESSAGING_CHANNELS: readonly string[]; /** * What the model is offered on a channel: `tree` — one `ui__render` tool composing the catalog (and * the sandbox, when on); `per-component` — one `ui__show_<component>` tool per component that can be * drawn there; `text` — no UI tools, the model answers in text. */ export type GenuiChannelMode = 'tree' | 'per-component' | 'text'; /** * How what the model pushed reaches a channel the server draws for (WhatsApp, Telegram, email): * `native` — each component's channel conversion (`defineComponent(…, { channels })`: text, reply * buttons, a list, an image), its text summary otherwise; `text` — always the text summary; `html` — * HTML (email). A web channel draws in the browser and ignores it. */ export type GenuiChannelRender = 'native' | 'text' | 'html'; /** One channel's generative UI — every field falls back to the `default` entry, then to the top level. */ export interface GenuiChannelOptions { /** Default: the top-level `mode` on a web channel; `per-component` on a messaging channel. */ mode?: GenuiChannelMode; /** Tree mode: draw the layout while the model writes it (`partial`) or once it is whole. */ streaming?: GenuiStreaming; /** * The sandbox on this channel: `false` for none, `true` / a config for the sandbox with those * options (a web channel may use the app's kit while a mobile one does not). Default: the * top-level `sandbox` on a web channel; never on a messaging channel (it cannot run there). */ sandbox?: boolean | DefineSandboxOptions; /** Default `native` (`html` for `email`). */ render?: GenuiChannelRender; /** Charts as PNG images on a channel that takes images ({@link ChartImageRenderer}, `chartImages()`). */ chartImages?: ChartImageRenderer; /** Extra words for the model on this channel, added to the UI tools' descriptions. */ instructions?: string; } /** `genui({ channels })`: an entry per channel name, plus `default` for every other one. */ export interface GenuiChannels { web?: GenuiChannelOptions; mobile?: GenuiChannelOptions; whatsapp?: GenuiChannelOptions; telegram?: GenuiChannelOptions; email?: GenuiChannelOptions; default?: GenuiChannelOptions; [channel: string]: GenuiChannelOptions | undefined; } /** The top-level genui options a channel falls back to. */ export interface GenuiChannelBase { mode?: 'per-component' | 'tree'; streaming?: GenuiStreaming; sandbox?: boolean | DefineSandboxOptions; } /** A channel's options with every default applied — what the tools and the channel handler follow. */ export interface ResolvedGenuiChannel { /** The channel's name (`web`, `whatsapp`, `mobile`, …). */ name: string; /** The server draws for it (WhatsApp, Telegram, email): it never runs a sandbox or a React renderer. */ messaging: boolean; /** `channels` has an entry for it (its own or `default`). `false` → it behaves as it always did. */ configured: boolean; mode: GenuiChannelMode; streaming?: GenuiStreaming; /** `undefined` → the catalog's own sandbox (if it has one) is left as it is. */ sandbox?: boolean | DefineSandboxOptions; render: GenuiChannelRender; chartImages?: ChartImageRenderer; instructions?: string; } /** * THE rule for which channel a turn runs on, read from its page context: * * - `pageContext.channel` is a string → that channel (an explicit override: `send({ channel })`, a * mobile client posting `pageContext: { channel: 'mobile' }`); * - `pageContext.channel` is the address a text-channel adapter stamps (`{ name, conversation, * kind? }`) → its `kind` (`whatsapp`, `telegram`), else its `name`; * - nothing → `web`: what AG-UI, the HTTP chat and A2UI serve (they stamp nothing, so a tool that * skips skeletons when `pageContext.channel` is set keeps working). */ export declare function turnChannel(pageContext: { [key: string]: unknown; } | undefined): string; /** WhatsApp, Telegram and email are drawn by the server; every other channel by a client. */ export declare function isMessagingChannel(channel: string): boolean; /** One channel's options, its own entry over `default` over the top level. */ export declare function resolveGenuiChannel(base: GenuiChannelBase, channels: GenuiChannels | undefined, channel: string | undefined): ResolvedGenuiChannel; /** Throws on a `channels` entry that names an unknown mode or render. */ export declare function assertGenuiChannels(channels: GenuiChannels | undefined): void; /** * A reply button. Pressed, it becomes the user's next turn — a {@link UiAction} whose text is the * label and whose context is `value`, exactly like a sandbox's `agent.send`. */ export interface ChannelNativeButton { label: string; /** The action's name. Default `select`. */ action?: string; /** The values that go with the press (JSON). */ value?: Record<string, unknown>; } export interface ChannelNativeListItem extends ChannelNativeButton { /** A second line under the label. */ description?: string; } /** A list to pick one entry from (a WhatsApp list message, a Telegram keyboard of one row each). */ export interface ChannelNativeList { /** The label of the button that opens the list (WhatsApp). */ button: string; /** A heading over the entries. */ title?: string; items: ChannelNativeListItem[]; } /** An image: by an `https` url the provider fetches, or its bytes. */ export interface ChannelNativeImage { url?: string; data?: Uint8Array; /** Default `image/png`. */ contentType?: string; } /** * What a component is on a channel the server draws for. Each field is optional; a message with an * image sends `text` as its caption, one with buttons or a list sends `text` as its body. */ export interface ChannelNativeMessage { /** In the model's markdown — converted to the channel's. */ text?: string; /** For `render: 'html'` (email). Trusted markup: build it from props, escaping what you insert. */ html?: string; /** Reply buttons — at most 3 on WhatsApp; more become a list. */ buttons?: ChannelNativeButton[]; list?: ChannelNativeList; image?: ChannelNativeImage; } /** * What a conversion is told about the frame it converts. `version` is the schema version the props * were written for (the frame's — a v1 frame persisted before the component moved to v2 still says * 1); convert the props by it, as a renderer would. */ export interface ChannelConversionContext { /** The channel (`whatsapp`, `telegram`, `email`…). */ channel: string; /** The component being converted (inside a tree: the node's). */ component: string; /** The props' schema version: the frame's, or the tree's `componentVersions` entry. Default 1. */ version: number; /** The frame's id, when it has one. */ id?: string; /** The whole frame the component came from (a tree's, for one of its nodes), as given. */ frame?: ChannelComponentInput; } /** What `describe` is told: the channel, and the version the component is at now. */ export interface ChannelDescribeContext { channel: string; component: string; /** The component definition's current version (default 1). */ version: number; } /** `defineComponent(…, { channels: { whatsapp: (props, { version }) => ({ text, buttons }) } })`. */ export type ChannelConversion<P = Record<string, unknown>> = { convert(props: P, context: ChannelConversionContext): ChannelNativeMessage | ChannelNativeMessage[] | null | undefined | Promise<ChannelNativeMessage | ChannelNativeMessage[] | null | undefined>; }['convert']; /** * A conversion with words for the model: `describe` says how the component reaches the user on that * channel — `'a pick-list of the orders; no Refund buttons: to refund, the user picks an order and * replies'` — and goes into the channel's prompt (`channelPrompt`), so the model does not describe the * web view there. A bare conversion is described from what it can send. */ export interface ChannelConversionSpec<P = Record<string, unknown>> { convert: ChannelConversion<P>; /** Words for the model, or a function of the channel and the component's current version. */ describe?: string | ((context: ChannelDescribeContext) => string | undefined); } /** * A component's conversions, by channel name — a function, or `{ convert, describe }`. `false` → * the component is never offered on that channel (it has nothing to say there). */ export type ComponentChannels<P = Record<string, unknown>> = { [channel: string]: ChannelConversion<P> | ChannelConversionSpec<P> | false | undefined; }; /** Renders chart components as images, for a channel that takes them. */ export interface ChartImageRenderer { /** The components it draws (default for `chartImages()`: `['Chart']`). */ readonly components: readonly string[]; render(component: string, props: Record<string, unknown>): Promise<ChannelNativeImage | null>; } /** * Whether `definition` can be drawn on `channel`. On a web channel every component can (the client's * `uiCapabilities` narrow it); on a messaging channel a component can when it has a conversion for * it, a chart renderer draws it, or it has a text summary (`fallbackText`) — a component without one * would arrive as a JSON dump, so it is not offered. A sandbox never can, and `channels: { x: false }` * opts a component out. */ export declare function canRenderOnChannel(definition: ComponentDefinition<unknown>, channel: ResolvedGenuiChannel): boolean; /** * The catalog as `channel` may draw it: only what it can render, and its own sandbox (or none). With * no `channels` configured the catalog is returned as it is. */ export declare function channelCatalog(catalog: Catalog, channel: ResolvedGenuiChannel): Catalog; /** What a UI tool's description says about the channel the conversation is on. */ export declare function channelInstructions(channel: ResolvedGenuiChannel): string | undefined; /** A pushed component, as a channel delivers it. */ export interface ChannelComponentInput { /** The frame's id — what a button press reports as its `componentId`. */ id?: string; name: string; props: Record<string, unknown>; /** The schema version the props were written for (the frame's `version`). Default 1. */ version?: number; /** A tree frame's per-component versions (`componentVersions`), for its nodes. */ componentVersions?: Record<string, number>; /** * The frame's own text (`fallbackText`): what is sent for a component whose props were written * for a schema version this catalog does not have — it is never drawn from them as if they were. */ fallbackText?: string; } /** {@link ChannelNativeMessage} plus the component it came from (so a press can name it). */ export interface ChannelRenderedMessage extends ChannelNativeMessage { component: string; componentId?: string; title?: string; } /** Plain text as minimal HTML: paragraphs, line breaks, `*bold*`. */ export declare function textToHtml(text: string): string; /** * What a pushed component (a tree included) is on a channel the server draws for: one or more * messages — its conversions, chart images, and the text summary of everything else, in order. * Consecutive text is joined. Never throws: a conversion that fails sends the summary. */ export declare function renderChannelMessages(catalog: Catalog, component: ChannelComponentInput, channel: ResolvedGenuiChannel): Promise<ChannelRenderedMessage[]>; /** The UI action a pressed button (or picked list entry) stands for — the user's next turn. */ export declare function channelButtonAction(button: ChannelNativeButton, from: { componentId?: string; title?: string; }): UiAction; /** * The system-prompt block for a turn on a messaging channel (WhatsApp, Telegram, email): that the * user is not on a web page, what the channel can show, and — component by component — how each one * reaches the user there, from its channel conversion (its `describe`, else what a conversion can * send), a chart image, or its text summary. Tool and component descriptions are written for the web * ("a table with a Refund button per row"); this is what keeps the model from describing that view * on WhatsApp. `undefined` on a web-like channel, or without `channels`. */ export declare function channelPrompt(catalog: Catalog, channel: ResolvedGenuiChannel): string | undefined; /** * How many reply buttons and list rows a channel shows — the adapter's `capabilities.buttons` / * `capabilities.lists`, which the channel handler stamps on the turn (`pageContext.channel.choices`). */ export interface ChannelChoiceLimits { buttons: number; lists: number; } /** * What a channel actually delivered, in words for the model: each message (text, a pick-list and its * entries, reply buttons, numbered choices, an image, an email section), and what the user did NOT * get. This is what a tool's result carries as `delivered` on a messaging channel, so the model talks * about the list the user can pick from — not the table with Refund buttons it would have on the web. * * Choices follow the channel handler's rule, within `limits` (default: the channel's own adapter's): * reply buttons when there is no list (or no lists) and they fit, else a list of at most `lists` * rows, else numbered text the user answers by replying. */ export declare function describeChannelDelivery(messages: readonly ChannelRenderedMessage[], channel: ResolvedGenuiChannel, limits?: ChannelChoiceLimits): string; /** * {@link describeChannelDelivery} of the components a tool pushed (last props per id, previews left * out), rendered the way the channel draws them. `undefined` on a channel the server does not draw * for, or without `channels`. */ export declare function describeChannelComponents(catalog: Catalog, components: readonly (ChannelComponentInput & { partial?: boolean; })[], channel: ResolvedGenuiChannel, limits?: ChannelChoiceLimits): Promise<string | undefined>; //# sourceMappingURL=channels.d.ts.map