UNPKG

@adonis-agora/agent

Version:

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

245 lines • 11.5 kB
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 <base>/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 <base>/chat` takes (see docs/stream-protocol.md). */ export interface ChatStreamRequest { /** `{ message, threadId?, agent?, persona?, model?, attachments?, pageContext?, regenerate?, … }`. */ body: Record<string, unknown>; /** Per-request headers the AI SDK was handed for this send. */ headers?: Record<string, string>; /** 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 <base>/chat/:runId/stream?after=<seq>`. */ 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<string, string>; 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<Uint8Array>; 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 <base>/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 <base>/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 <base>/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<Record<string, string>>; 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 <path>/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<MessageAttachment>; /** * 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 `<AgentProvider backend>` (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<ChatStreamResponse>; /** Attach to a streaming run. Resolve `null` when nothing is streaming under that id (HTTP 404). */ resumeChatStream(request: ResumeStreamRequest): Promise<ChatStreamResponse | null>; /** Hard-stop a run server-side. */ cancelStream(runId: string): Promise<unknown>; listThreads(): Promise<ThreadSummary[]>; getThread(id: string): Promise<ThreadDetail>; updateThread(id: string, patch: ThreadPatch): Promise<unknown>; deleteThread(id: string): Promise<unknown>; /** * Queue a message on a thread (`POST <base>/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<QueuedSendResult>; /** `GET <base>/threads/:id/queue`. */ getQueue?(threadId: string): Promise<ChatQueueState>; /** `PATCH <base>/queue/:messageId` — edit and/or move a waiting message. */ updateQueuedMessage?(messageId: string, update: QueuedMessageUpdate): Promise<ChatQueueState>; /** `DELETE <base>/queue/:messageId`. */ removeQueuedMessage?(messageId: string): Promise<ChatQueueState>; /** * `POST <base>/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<ChatQueueState & { runId?: string; interrupting?: string; }>; /** `DELETE <base>/threads/:id/queue`. */ clearQueue?(threadId: string): Promise<ChatQueueState>; /** `POST <base>/threads/:id/queue/resume` — `runId` when the head started. */ resumeQueue?(threadId: string): Promise<ChatQueueState & { runId?: string; }>; forkFromMessage?(threadId: string, messageId: string): Promise<ThreadSummary>; promoteThread?(id: string): Promise<unknown>; truncateFromMessage?(threadId: string, messageId: string): Promise<unknown>; listActionProposals?(input: { threadId: string; }): Promise<ActionProposal[]>; approveActionProposal?(input: { threadId: string; proposalId: string; remember?: boolean; }): Promise<ActionProposalMutationResult>; rejectActionProposal?(input: { threadId: string; proposalId: string; reason?: string; }): Promise<ActionProposalMutationResult>; approveToolCall?(input: { toolCallId: string; remember?: boolean; via?: string; }): Promise<unknown>; rejectToolCall?(input: { toolCallId: string; reason?: string; via?: string; }): Promise<unknown>; answerToolCall?(input: { toolCallId: string; answers?: Record<string, string[]>; via?: string; }): Promise<unknown>; skipToolCall?(input: { toolCallId: string; via?: string; }): Promise<unknown>; uploadAttachment?(file: File, options?: UploadAttachmentOptions): Promise<MessageAttachment>; listTools?(agent?: string): Promise<ToolCatalogEntry[]>; listSkills?(threadId?: string): Promise<SkillCatalogEntry[]>; /** `GET <base>/quota` — every budget window, and which one blocks sends, if any. */ getQuota?(): Promise<QuotaReport>; /** `GET <base>/models?agent=` — what a model picker offers. */ listModels?(agent?: string): Promise<ModelCatalogView>; /** `GET <base>/agents` — what an agent picker offers. */ listAgents?(): Promise<AgentCatalogEntry[]>; /** `GET <base>/config` — attachment limits and upload mode, and which features are on. */ getConfig?(): Promise<AgentClientConfig>; 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<K extends OptionalMethod>(backend: AgentBackend, name: K): NonNullable<AgentBackend[K]>; export {}; //# sourceMappingURL=backend.d.ts.map