UNPKG

@adonis-agora/agent

Version:

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

253 lines • 11.1 kB
/** * The contract between a text channel (WhatsApp, Telegram, …) and the agent: what an adapter reads * off a webhook and what it is asked to deliver. Framework-free — `channels.handle()` builds the * {@link ChannelRequest} from Adonis's `HttpContext`, and a test builds one by hand. */ /** The markdown a channel renders: WhatsApp's, Telegram's MarkdownV2, or none (plain text). */ export type ChannelMarkdown = 'whatsapp' | 'telegram' | 'none'; /** What a channel can show — what the service shapes every reply to. */ export interface ChannelCapabilities { /** * The most reply buttons one message can carry. Omitted (or below 2) → the channel has none, and a * proposal is put to the person as a text instruction instead of Confirm/Cancel buttons. */ buttons?: number; /** The markdown dialect replies are converted to. */ markdown: ChannelMarkdown; /** The longest text one message may carry; longer replies are split into several messages. */ maxLength: number; /** * The adapter can send files ({@link OutboundMessage} with `media`) — images and documents with a * caption. Omitted → a file reply is sent as its caption / fallback text. */ media?: boolean; /** The longest caption a file may carry. Default: {@link maxLength}. */ maxCaptionLength?: number; /** * The most entries a list message can carry (a WhatsApp list, a Telegram keyboard of one row per * entry). Omitted → no lists: a component's list goes out as reply buttons when it fits, else as * numbered text. */ lists?: number; } /** One webhook request, as an adapter sees it. */ export interface ChannelRequest { /** Upper-case HTTP method. */ method: string; /** Path and query string, e.g. `/webhooks/whatsapp?hub.mode=subscribe`. */ url: string; /** A request header by (case-insensitive) name. */ header(name: string): string | undefined; /** Route params (`/webhooks/whatsapp/:token` → `{ token }`). */ params: Record<string, unknown>; /** The parsed body. */ body: unknown; /** * The body exactly as it arrived — what a signature is computed over. `null` when the body parser * did not keep it (a request it does not parse). */ rawBody: string | null; } /** A message a person sent on the channel. */ export interface InboundMessage { /** The provider's id for this message — retries of one delivery carry the same id. */ id: string; /** Who sent it: a phone number, a Telegram user id… — what `actor()` maps to an account. */ from: string; /** Where replies go: the chat / conversation id {@link ChannelAdapter.send} is called with. */ conversation: string; /** * What the person wrote — for a button press, the button's label (or its id when there is none); * for a media message, its caption (`''` when it has none). */ text: string; /** Files the message carries (an image, a voice note, a document) — downloaded and attached to the turn. */ media?: InboundMedia[]; /** The id of the reply button the person pressed, when the message is a button press. */ buttonId?: string; /** * A button press whose id the provider did not forward — only its label, in {@link text} * (Whatsmiau). The handler maps it to the proposal card it can only have come from, if there is * exactly one. */ buttonWithoutId?: boolean; /** * When the person sent it, in epoch milliseconds — the provider's own timestamp (Evolution / * Whatsmiau `messageTimestamp`, WhatsApp Cloud `timestamp`, Telegram `date`). Stored with the turn's * message (`StoredMessage.sentAt`), so a turn that waited (a queue, a `coalesce` burst) still knows * when it was said — what `messageTimestamps` shows the model. Absent when the provider sent none * (a Telegram button press): the message's `createdAt` stands for it. */ sentAt?: number; /** The provider's payload, untouched. */ raw: unknown; } /** One file in an inbound message, before it is downloaded. */ export interface InboundMedia { kind: 'image' | 'audio' | 'video' | 'document' | 'sticker'; /** MIME type, when the webhook says (it usually does). */ contentType?: string; /** The file's name, when the webhook says (documents). */ filename?: string; /** Size in bytes, when the webhook says. */ sizeBytes?: number; /** Whatever the adapter needs to download it (a media id, a file id, the message key). */ ref: unknown; } /** A downloaded file. */ export interface ChannelMediaFile { data: Buffer; contentType: string; filename?: string; } export interface ChannelButton { /** What comes back as {@link InboundMessage.buttonId} when it is pressed. */ id: string; label: string; } /** One entry of a list message. */ export interface ChannelListRow { /** What comes back as {@link InboundMessage.buttonId} when it is picked. */ id: string; title: string; description?: string; } /** A list to pick one entry from — see {@link ChannelCapabilities.lists}. */ export interface OutboundList { /** The label of the button that opens the list (WhatsApp). */ button: string; /** A heading over the entries. */ title?: string; rows: ChannelListRow[]; } /** A file to send: by a URL the provider downloads it from, or by its bytes. */ export interface OutboundMedia { kind: 'image' | 'document' | 'audio' | 'video'; /** An `https` URL the provider fetches the file from (short-lived URLs are fine). */ url?: string; /** The file itself, when there is no URL. One of `url` / `data` is required. */ data?: Buffer; /** MIME type — `image/png`, `application/pdf`. Recommended (some providers require it). */ contentType?: string; /** The name a document is shown under. */ filename?: string; } /** * One message to deliver: text, text with reply buttons, or a file with a caption (`text`, `''` for * none). An adapter without {@link ChannelCapabilities.media} is never handed a file: the handler * sends the caption (or the reply's `fallbackText`) instead. */ export type OutboundMessage = { text: string; buttons?: undefined; fallbackText?: undefined; instruction?: undefined; footer?: undefined; media?: undefined; list?: undefined; } | { text: string; buttons: ChannelButton[]; /** * The same message as text only — a reply instruction in place of the buttons. What an adapter * sends when the provider definitely refused the buttons (a 4xx), so the person still gets a * way to answer. */ fallbackText: string; /** * The reply instruction alone ("Reply *yes* to confirm or *no* to cancel."), already in the * channel's markdown. An adapter whose buttons may not render on every phone puts it in the * buttons message too, so the person can still answer by text. */ instruction?: string; /** * A short line under the card (`texts.footer` — "Valid for 5 minutes."), already in the * channel's markdown. Sent as the provider's footer where it has one, else after the text. * `fallbackText` already ends with it. */ footer?: string; media?: undefined; list?: undefined; } | { /** The body over the list, already in the channel's markdown. */ text: string; list: OutboundList; /** The same message as numbered text — what an adapter sends when the list is refused. */ fallbackText: string; buttons?: undefined; instruction?: undefined; footer?: undefined; media?: undefined; } | { /** The caption, already in the channel's markdown — `''` for none. */ text: string; media: OutboundMedia; buttons?: undefined; fallbackText?: undefined; instruction?: undefined; footer?: undefined; list?: undefined; }; /** A reply the adapter answers the webhook with instead of acknowledging it (a verification GET). */ export interface ChannelChallengeResponse { status: number; body: string; contentType?: string; } /** Why a webhook body carried no message to answer — see {@link ChannelAdapter.ignored}. */ export interface ChannelIgnored { /** The provider's event name, when the body has one. */ event?: string; /** Short and content-free: `own message`, `group`, `fromMe missing and status PENDING`… */ reason: string; /** It looked like a person's message but could not be read — logged as a warning, not debug. */ unexpected?: boolean; } /** * One text channel. The built-in ones are `evolutionApi()`, `whatsmiau()`, `whatsappCloud()` and * `telegram()`; any object of this shape works the same way. */ export interface ChannelAdapter { /** Names the channel: the dedupe key's prefix, and `via` on a decision made with its buttons. */ readonly name: string; /** * What the channel is — `whatsapp`, `telegram` — whatever it is named: the turn's channel * (`turnChannel`), so `genui({ channels: { whatsapp } })` applies to every WhatsApp adapter. * Default: {@link name}. */ readonly kind?: string; readonly capabilities: ChannelCapabilities; /** * Answer a request that is not a message — WhatsApp Cloud's `GET ?hub.challenge=…` subscription * check. `null` → not one of those; the request goes on to {@link verify}. */ challenge?(request: ChannelRequest): ChannelChallengeResponse | null; /** Is this request really from the provider (a signature, a shared token)? `false` → `401`. */ verify(request: ChannelRequest): boolean | Promise<boolean>; /** * The person's message(s) in a verified webhook body. `null` (or `[]`) → nothing to answer: a * delivery receipt, the bot's own message, a group, a media message… */ parse(body: unknown): InboundMessage | InboundMessage[] | null; /** * Why a body {@link parse}d to no message — the handler logs it, so a dropped webhook can be * diagnosed. Called only then; never put message content in it. `null` → no reason to give. */ ignored?(body: unknown): ChannelIgnored | null; /** * Tell the provider the message was received, where it waits for that — Telegram's * `answerCallbackQuery` after a button press. Runs in the background, before the turn. */ acknowledge?(message: InboundMessage): Promise<void>; /** * Download one of a message's {@link InboundMessage.media}. Throws {@link * import('./http.js').ChannelMediaTooLargeError} past `maxBytes` (without reading the rest). * Absent → media messages are answered with `texts.mediaRefused`. */ download?(media: InboundMedia, options: { maxBytes: number; }): Promise<ChannelMediaFile>; /** Deliver one message to a conversation. Throws when the provider refused it. */ send(conversation: string, message: OutboundMessage): Promise<void>; } //# sourceMappingURL=types.d.ts.map