@adonis-agora/agent
Version:
Governed, durable-backed AI agent (chat + tool-calling + governance) for AdonisJS — part of the Agora ecosystem.
109 lines • 6.33 kB
TypeScript
import type { ChatStreamFile, ChatStreamRequest, ChatStreamResponse } from './backend.js';
/**
* Talk to an AG-UI 1.0 producer (https://docs.ag-ui.com/spec/1.0) through `useAgentChat`.
*
* `agUiChatStream` POSTs a `RunAgentInput` and hands the transport the answer re-framed in this
* library's stream protocol, so everything built on `useAgentChat` — the transcript, tool activity,
* generative UI — renders an AG-UI agent unchanged. Put it behind `openChatStream` of any backend:
*
* ```ts
* const backend: AgentBackend = {
* ...httpBackend,
* openChatStream: (request) => agUiChatStream(request, { url: '/agent/ag-ui' }),
* }
* ```
*
* What maps where (the inverse of the producer in `@adonis-agora/agent/ag-ui`):
* - `TEXT_MESSAGE_*` → `text`; `REASONING_MESSAGE_*` → `reasoning`; `STEP_*` → `step-start` /
* `step-finish` (with the step's usage, cost and model from the `agora.step-usage` that follows
* it);
* - `TOOL_CALL_START` / `ARGS` / `END` → `tool-input-start` / `-delta` / `-available`, the call's
* kind from `metadata['agora.toolKind']` (`read` when a producer does not say);
* `TOOL_CALL_RESULT` → `tool-output` (or `tool-output-error` / `-denied`, from
* `metadata['agora.outcome']`);
* - `ACTIVITY_SNAPSHOT` / `ACTIVITY_DELTA` → a `ui` part named `AgUiActivity`, id
* `activity:<messageId>`, props `{ activityType, content }` — the whole content each time (the
* delta's JSON Patch applied here), so a repeat replaces the widget in place;
* - `RUN_ERROR` → the stream's `event: error`; `RUN_FINISHED` → `event: done` (a cancelled
* outcome writes `cancelled` first). An interrupt outcome whose every interrupt the stream
* already carried as an approval card or a question set ends the stream WITHOUT `done`: the run
* is parked, not over, so the transport re-attaches to it on the native route
* (`chat/:runId/stream?after=`) and the rest of the run — after the person decides through the
* native `tool-call` routes — streams into the same message. Interrupts nothing showed (another
* producer's) become a `ui` part named `AgUiInterrupt` carrying them, for the app to render;
* - the producer's SSE `id:` (the run's own sequence number, which `agUiAdapter()` writes) is
* passed on as the cursor the transport re-attaches with; a producer that numbers nothing gets
* no cursor, and the transport then does not try to re-attach;
* - `CUSTOM` events a producer of this family writes (`agora.ui`, `agora.title`, `agora.queue`,
* `agora.approval-requested` / `-settled`, `agora.elicitation`, `agora.step-usage`,
* `agora.run`) → the frames they stand for; `agora.action-proposal-decision` → the transient
* `data-proposal-decision` part a native text decision answers with (`{ threadId,
* proposalDecision, text }`). Any other `CUSTOM` is ignored, as the protocol requires of a
* consumer that does not know it.
*
* The send's staged uploads (`attachments` refs) travel as media parts of the user message, by
* `file` handle: `{ type: 'image' | …, source: { type: 'file', provider: 'agora', value: <mediaId> } }`
* — what `agUiAdapter()` resolves for the caller. A regenerate travels as
* `forwardedProps.regenerate`.
*/
export interface AgUiChatStreamOptions {
/** The producer's endpoint (`POST`, `RunAgentInput` in, `text/event-stream` out). */
url: string;
/** Headers for every request (auth, CSRF). Per-request headers from the send win. */
headers?: Record<string, string>;
/** Defaults to the global `fetch`, with `credentials: 'same-origin'`. */
fetch?: typeof fetch;
/**
* Shape `forwardedProps` from the send's body. Default: `{ pageContext, agent, model, persona, uiCapabilities }` from the
* body, whichever are present. A regenerate adds `regenerate: true` to whatever this returns (an
* object, or nothing).
*/
forwardedProps?: (body: Record<string, unknown>) => unknown;
/** Read the refusal of a non-2xx answer. Default: the JSON body's `message`, else the status. */
refusal?: (response: Response) => Promise<Error>;
/**
* The user message's `content` for the send. Default: the body's `message` as a string, or —
* when the send carries staged uploads (`attachments`) — a `text` part and one media part per
* upload, by `file` handle (`provider: 'agora'`). Return your own list of AG-UI content parts to
* send files otherwise — `{ type: 'image' | 'document' | …, source: { type: 'data',
* value: <base64>, mimeType } }` — with the text as a `text` part. `files` describes the staged
* uploads (content type, name) when the chat knows them.
*/
content?: (body: Record<string, unknown>, files: readonly ChatStreamFile[]) => string | AgUiContentPart[];
}
/** One AG-UI content part of a user message (text, or media by inline data, url or file handle). */
export type AgUiContentPart = {
type: 'text';
text: string;
} | {
type: 'image' | 'audio' | 'video' | 'document';
source: {
type: 'data';
value: string;
mimeType: string;
} | {
type: 'url';
value: string;
mimeType?: string;
} | {
type: 'file';
value: string;
provider?: string;
mimeType?: string;
};
metadata?: Record<string, unknown>;
};
/** Start a turn against an AG-UI producer; the stream comes back in this library's framing. */
export declare function agUiChatStream(request: ChatStreamRequest, options: AgUiChatStreamOptions): Promise<ChatStreamResponse>;
/**
* Re-frame an AG-UI event stream (SSE, one event per `data:`) in this library's stream protocol:
* `event: meta`, `data:` frames of {@link AgentStreamEvent}, then `event: done` or `event: error`.
* When the producer numbers its events with the run's own sequence (SSE `id:`, as `agUiAdapter()`
* does), that number is passed on as `id:`-only blocks — the cursor the transport re-attaches to
* the native stream with (`chat/:runId/stream?after=`). Exported for a backend that fetches the
* stream itself.
*/
export declare function reframeAgUiStream(source: ReadableStream<Uint8Array>, ids: {
threadId: string;
}): ReadableStream<Uint8Array>;
//# sourceMappingURL=ag-ui-backend.d.ts.map