@adonis-agora/agent
Version:
Governed, durable-backed AI agent (chat + tool-calling + governance) for AdonisJS — part of the Agora ecosystem.
144 lines • 5.91 kB
TypeScript
/**
* Client-side counterpart to {@link import('../sse.js').AgentSseEncoder}: parses the provider's SSE
* back into typed frames the browser can render. Kept framework-agnostic (no React, no Adonis) so
* any consumer — a React hook, a Vue composable, a plain fetch loop — decodes the wire format the
* same way, and so the envelope stays owned by the package that emits it.
*
* Wire format — the agent stream protocol shared with `@dudousxd/nestjs-agent`:
* - `event: meta\ndata: {runId,threadId}` — sent once, first, before any token
* - `id: <seq>\ndata: <AgentStreamEvent>` — one event per frame (`{"kind":"text","text":…}`,
* `ui`, `elicitation`, `approval-requested`, reasoning, tool calls, steps, title, …)
* - `event: error\ndata: {code,message}` — the run failed (terminal)
* - `event: done\ndata: {}` — the run's stream finished
*/
import type { ElicitationRequest } from '../elicitation.js';
import type { ToolConfirmation } from '../tool-presentation.js';
/** A rendered part of an assistant message: streamed text, or a named component with its props. */
export type ChatPart = {
type: 'text';
text: string;
} | {
type: 'component';
name: string;
data: unknown;
id?: string;
version?: number;
fallbackText?: string;
componentVersions?: Record<string, number>;
/**
* A preview of a layout the model is still writing (genui `streaming: 'partial'`): unvalidated,
* replaced in place by the final component under the same `id`.
*/
partial?: true;
};
/** A decoded stream frame — the typed form of one SSE event. */
export type ChatFrame = {
type: 'text';
delta: string;
} | {
type: 'component';
name: string;
data: unknown;
id?: string;
version?: number;
fallbackText?: string;
componentVersions?: Record<string, number>;
/**
* A preview of a layout the model is still writing (genui `streaming: 'partial'`): unvalidated,
* replaced in place by the final component under the same `id`.
*/
partial?: true;
} | {
type: 'meta';
runId?: string;
threadId?: string;
}
/** A failed run (`event: error` under the agent protocol). Terminal, like `done`. */
| {
type: 'error';
code: string;
message: string;
}
/** Any other agent-protocol event — reasoning, tool calls, steps, title, … */
| {
type: 'event';
event: Record<string, unknown> & {
kind: string;
};
}
/**
* A question set the run is parked on, with everything needed to answer it: `runId` and
* `toolCallId` are the body `POST /agent/tool-call/answer` takes, and `request` is the form.
*
* `runId` is NOT necessarily the run this stream was opened for. A delegated sub-agent forwards
* its frames into its top-level ancestor's stream — that is the only stream a human is watching —
* so the run to answer is the child's, and reading it off `meta` would address the wrong one.
*/
| {
type: 'elicitation';
runId: string;
toolCallId: string;
request: ElicitationRequest;
}
/**
* An action tool awaiting an approve/reject, with what it means to run. `runId`/`toolCallId` are
* the body `POST /agent/tool-call/approve` (or `/reject`) takes — and `runId` is the parked run,
* which for a delegated sub-agent is not the run this stream was opened for.
*/
| {
type: 'approval';
target?: {
kind: 'proposal';
proposalId: string;
};
runId: string;
toolCallId: string;
toolName: string;
input: unknown;
confirmation?: ToolConfirmation;
approver?: string;
expiresAt?: string;
reason?: string;
} | {
type: 'done';
};
/** One raw SSE event: the `event:` name (default `message`) and the joined `data:` payload. */
export interface SseEvent {
event: string;
data: string;
/**
* The frame's `id:` — its sequence number in the run. Strictly increasing within a run, gaps
* allowed; `meta`, `done` and `error` carry none.
*/
id?: string;
}
/**
* Parses one raw SSE frame (the text between `\n\n` separators, without them) into `{event, data}`.
* A frame with no `data:` line (e.g. a `:` keep-alive comment) returns `null`.
*/
export declare function parseSseEvent(frame: string): SseEvent | null;
/**
* Decodes a parsed {@link SseEvent} into a typed {@link ChatFrame}, or `null` when the payload is
* malformed (non-JSON, or missing required fields) so callers can skip it without crashing.
*/
export declare function decodeFrame(event: SseEvent): ChatFrame | null;
/**
* Folds a renderable {@link ChatFrame} (text or component) into the message's parts, concatenating
* consecutive text deltas into the trailing text part and appending components in order. A
* `step-start` closes the text before it with a paragraph break ({@link STEP_SEPARATOR}), so two
* model steps never run into each other.
* `meta`/`elicitation`/`approval`/`done` are control frames and are ignored here — a form to put to
* the user is not a part of the assistant's message. Returns a new array (never mutates the input).
*/
export declare function foldPart(parts: ChatPart[], frame: ChatFrame): ChatPart[];
/** What separates the text of two consecutive model steps in one message: a paragraph break. */
export declare const STEP_SEPARATOR = "\n\n";
/**
* Reads a byte {@link ReadableStream} (a `fetch` `response.body`) as a sequence of raw SSE events,
* splitting on the `\n\n` frame separator and buffering partial frames across chunks. Yields only
* frames that carry a `data:` payload (keep-alives are dropped).
*/
export declare function readSseStream(body: ReadableStream<Uint8Array>): AsyncGenerator<SseEvent>;
/** The parts with every preview left standing removed — what a finished stream keeps. */
export declare function settleParts(parts: ChatPart[]): ChatPart[];
//# sourceMappingURL=sse.d.ts.map