import type { ActionProposal, ActionProposalMutationResult, AgentCatalogEntry, AgentClientConfig, ChatQueueState, MessageAttachment, MessageFeedback, MessageFeedbackValue, ModelCatalogView, QuotaReport, SkillCatalogEntry, ThreadDetail, ThreadSummary, ToolCatalogEntry } from '../../index.js';
import type { HttpErrorListener } from './http-error.js';
/**
* Partial update accepted by `PATCH /threads/:threadId`. `defaultAgent: null` clears a
* previously-set default back to the module's own default; omitting it leaves the thread's
* current default untouched.
*/
export interface ThreadPatch {
title?: string;
defaultAgent?: string | null;
/** Pin a catalog model on the thread; `null` unpins it. */
model?: string | null;
/** Pin one of the agent's personas on the thread; `null` clears it (the agent's default applies). */
persona?: string | null;
}
/** Starting a turn: the body `POST /chat` takes (see docs/stream-protocol.md). */
export interface ChatStreamRequest {
/** `{ message, threadId?, agent?, persona?, model?, attachments?, pageContext?, regenerate?, … }`. */
body: Record;
/** Per-request headers the AI SDK was handed for this send. */
headers?: Record;
/** Aborted when the user stops the turn. */
signal?: AbortSignal;
/**
* What the message's staged uploads are, as it shows them: the body's `attachments` refs carry
* only ids. For a backend that describes a file on its own wire (AG-UI's media parts).
*/
files?: ChatStreamFile[];
}
/** One staged upload of the message being sent. */
export interface ChatStreamFile {
mediaId: string;
contentType?: string;
name?: string;
}
/** Attaching to a run that is already streaming: `GET /chat/:runId/stream?after=`. */
export interface ResumeStreamRequest {
runId: string;
/**
* The sequence number (SSE `id:`) of the last frame the client already has. Omitted → replay
* from the first frame. A backend that cannot skip may ignore it: the transport also drops frames
* at or below it.
*/
after?: number;
headers?: Record;
signal?: AbortSignal;
}
/**
* An open chat stream: the raw `text/event-stream` bytes in the stream-protocol framing, plus the
* identity a backend may learn before the first frame (the `X-Agent-Run-Id` / `X-Agent-Thread-Id`
* headers). The body's own `event: meta` frame supplies them otherwise.
*/
export interface ChatStreamResponse {
proposalDecision?: {
threadId: string;
proposalDecision: ActionProposalMutationResult | {
status: 'ambiguous';
proposalIds: string[];
};
text?: string;
};
body: ReadableStream;
runId?: string;
threadId?: string;
/**
* The server queued the message instead of starting a turn (`202`): the thread already had one
* running — another tab, another device. `body` is then empty; the transport reports this as a
* transient `data-queue` part and `useAgentChat` moves the message into `chat.queue`.
*/
queued?: QueuedSendResult;
}
/**
* `POST /chat` answered `202`: the message waits in the thread's queue (see
* docs/stream-protocol.md, "Message queue").
*/
export interface QueuedSendResult {
queued: true;
threadId: string;
/** The queued message's id — also the id of the run it will start. */
messageId: string;
/** 0-based place in the queue when it was queued. */
position: number;
queue: ChatQueueState;
/** Set when it started straight away (the thread was idle after all): attach to this run. */
runId?: string;
/** The run an interrupt cancelled to make room for it. */
interrupting?: string;
}
/** What `PATCH /queue/:messageId` takes. */
export interface QueuedMessageUpdate {
message?: string;
/** `[{ mediaId }]` refs replacing the message's attachments; `null` drops them. */
attachments?: Array<{
mediaId: string;
}> | null;
/** Move it to this 0-based place in the queue. */
position?: number;
}
/** What `POST /messages/:id/feedback` takes. `value: null` clears the rating. */
export interface MessageFeedbackInput {
value: MessageFeedbackValue | null;
comment?: string;
}
export interface UploadAttachmentOptions {
signal?: AbortSignal;
/** Called with 0..1 as the upload progresses, when the backend can observe it. */
onProgress?: (fraction: number) => void;
}
/** How {@link AgentClient} reaches the server — handed to an {@link AttachmentUploadStrategy}. */
export interface AgentConnection {
/** The server's origin, trailing slash removed (`''` for same-origin). */
baseUrl: string;
/** The agent's route prefix, normalized to a leading slash (`'/agent'`), or `''`. */
path: string;
/** The client's static + per-request headers, resolved now (auth, CSRF). */
headers: () => Promise>;
credentials?: RequestCredentials;
fetch: typeof fetch;
/** The client's `onHttpError` — call it with an upload's error answer before throwing it. */
onHttpError?: HttpErrorListener;
}
/**
* Replaces {@link AgentClient}'s own `uploadAttachment` (`POST /attachments`) — e.g.
* `mediaAttachments()` from `@adonis-agora/agent/react/media`, or your own storage. Gets the
* client's connection so it needs no configuration of its own. Resolve with the attachment your
* server's `AGENT_ATTACHMENT_STAGING` recognises by `mediaId`.
*/
export type AttachmentUploadStrategy = (file: File, options: UploadAttachmentOptions, connection: AgentConnection) => Promise;
/**
* Everything the React layer asks of a server — the seam between `useAgentChat` (and the other
* hooks) and whatever serves the agent. {@link AgentClient} is the default implementation, over the
* library's own REST routes with `fetch`. An app with its own client — a generated one, a different
* auth scheme (cookie session + CSRF header), a backend that is not this library at all but speaks
* docs/stream-protocol.md — implements this instead and passes it as `` (or
* per hook, `useAgentChat({ backend })`).
*
* The streaming, thread and cancel members are required: without them there is no chat. The rest
* are optional; a hook that needs one the backend does not have throws
* {@link AgentBackendUnsupportedError} when it is called, so a backend only implements what its
* server supports.
*/
export interface AgentBackend {
/** Start a turn and return its SSE stream. Throw on a non-2xx answer. */
openChatStream(request: ChatStreamRequest): Promise;
/** Attach to a streaming run. Resolve `null` when nothing is streaming under that id (HTTP 404). */
resumeChatStream(request: ResumeStreamRequest): Promise;
/** Hard-stop a run server-side. */
cancelStream(runId: string): Promise;
listThreads(): Promise;
getThread(id: string): Promise;
updateThread(id: string, patch: ThreadPatch): Promise;
deleteThread(id: string): Promise;
/**
* Queue a message on a thread (`POST /chat` with `mode: 'queue'` or `'interrupt'`), which
* the server answers with `202` JSON rather than a stream. What `chat.queue` sends through; a
* backend without it keeps the composer blocked while a turn runs.
*/
enqueueMessage?(request: ChatStreamRequest): Promise;
/** `GET /threads/:id/queue`. */
getQueue?(threadId: string): Promise;
/** `PATCH /queue/:messageId` — edit and/or move a waiting message. */
updateQueuedMessage?(messageId: string, update: QueuedMessageUpdate): Promise;
/** `DELETE /queue/:messageId`. */
removeQueuedMessage?(messageId: string): Promise;
/**
* `POST /queue/:messageId/interrupt` — run a waiting message now: it moves to the head as an
* interrupt and the running turn is cancelled for it. `interrupting` is the run that was
* cancelled; `runId` is set instead when nothing was running and the message started at once.
* What `chat.queue.interrupt(id)` calls.
*/
interruptQueuedMessage?(messageId: string): Promise;
/** `DELETE /threads/:id/queue`. */
clearQueue?(threadId: string): Promise;
/** `POST /threads/:id/queue/resume` — `runId` when the head started. */
resumeQueue?(threadId: string): Promise;
forkFromMessage?(threadId: string, messageId: string): Promise;
promoteThread?(id: string): Promise;
truncateFromMessage?(threadId: string, messageId: string): Promise;
listActionProposals?(input: {
threadId: string;
}): Promise;
approveActionProposal?(input: {
threadId: string;
proposalId: string;
remember?: boolean;
}): Promise;
rejectActionProposal?(input: {
threadId: string;
proposalId: string;
reason?: string;
}): Promise;
approveToolCall?(input: {
toolCallId: string;
remember?: boolean;
via?: string;
}): Promise;
rejectToolCall?(input: {
toolCallId: string;
reason?: string;
via?: string;
}): Promise;
answerToolCall?(input: {
toolCallId: string;
answers?: Record;
via?: string;
}): Promise;
skipToolCall?(input: {
toolCallId: string;
via?: string;
}): Promise;
uploadAttachment?(file: File, options?: UploadAttachmentOptions): Promise;
listTools?(agent?: string): Promise;
listSkills?(threadId?: string): Promise;
/** `GET /quota` — every budget window, and which one blocks sends, if any. */
getQuota?(): Promise;
/** `GET /models?agent=` — what a model picker offers. */
listModels?(agent?: string): Promise;
/** `GET /agents` — what an agent picker offers. */
listAgents?(): Promise;
/** `GET /config` — attachment limits and upload mode, and which features are on. */
getConfig?(): Promise;
setMessageFeedback?(messageId: string, input: MessageFeedbackInput): Promise<{
feedback: MessageFeedback | null;
}>;
}
/** An optional {@link AgentBackend} member the bound backend does not implement was called. */
export declare class AgentBackendUnsupportedError extends Error {
readonly method: string;
constructor(method: string);
}
type OptionalMethod = {
[K in keyof AgentBackend]-?: undefined extends AgentBackend[K] ? K : never;
}[keyof AgentBackend];
/**
* The optional backend method `name`, bound — or a throw naming it. For hook code that has to call
* something a backend may not offer.
*/
export declare function requireBackendMethod(backend: AgentBackend, name: K): NonNullable;
export {};
//# sourceMappingURL=backend.d.ts.map