UNPKG

@adonis-agora/agent

Version:

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

160 lines • 7.93 kB
import type { Actor, AgentHostContext, MessageAttachment, PageContext } from '../types.js'; import type { UiCapabilities } from '../ui-capabilities.js'; import { type AgentStore } from './agent-store.js'; /** * Why a thread's queue stopped draining. A paused queue keeps its messages; nothing starts until * someone resumes it (`POST <base>/threads/:id/queue/resume`, `chat.queue.resume()`). * * - `run_failed` the turn before it failed — the next message would likely fail the same way, * and the person should see the error before more of their messages are spent. * - `cancelled` someone pressed Stop. Stop means stop: the queue does not start behind it. * An interrupt (`mode: 'interrupt'`) is the exception — its message starts. * - `quota_exceeded` the next message's actor is over budget; resuming later retries the check. * - `start_failed` the next message could not be started (the runner refused it). */ export type QueuePauseReason = 'run_failed' | 'cancelled' | 'quota_exceeded' | 'start_failed'; export interface QueuePause { reason: QueuePauseReason; /** Human-facing detail (the failure, the quota window), when there is one. */ message?: string; /** ISO-8601 instant the queue paused. */ at: string; } /** * A message a person sent while a turn was still running on the thread, waiting its turn. It is not * part of the transcript until it starts: the loop appends it as the turn's user message, exactly as * if it had been sent then. Everything a send carries is captured at enqueue time, resolved and * checked there (agent, model, attachments), so a queued message starts with no request around it. */ export interface QueuedMessage { id: string; threadId: string; /** Who queued it. The turn it starts runs as this actor. */ actor: Actor; content: string; attachments?: MessageAttachment[]; agentName?: string; /** * The persona its send resolved — named, the thread's pin, or the agent's default — which the turn * it starts runs under. Absent on a message queued before personas were persisted: it resolves one * as it starts. */ persona?: string; model?: string; pageContext?: PageContext; uiCapabilities?: UiCapabilities; /** The send's `AgentRunInput.hostContext`, carried to the turn it starts. Never on the wire view. */ hostContext?: AgentHostContext; /** * When the person sent it (ISO 8601), when the send named a time (`ChatParams.sentAt`). The turn * it starts records this, else {@link createdAt} — when it was queued — as the message's * `StoredMessage.sentAt`. */ sentAt?: string; /** * Queued by an interrupt (`POST chat { mode: 'interrupt' }`): the running turn was cancelled to * make room for it, so the cancel starts it instead of pausing the queue. */ interrupt?: boolean; createdAt: string; updatedAt: string; } /** A queued message as the wire shows it — {@link QueuedMessage} without the actor or thread. */ export interface QueuedMessageView { id: string; content: string; attachments?: MessageAttachment[]; agentName?: string; persona?: string; model?: string; interrupt?: boolean; createdAt: string; updatedAt: string; } /** * A thread's queue: its waiting messages in the order they will run, and whether it is draining. * What `GET <base>/threads/:id/queue` answers, what `ThreadDetail.queue` carries, and what every * `queue` stream frame snapshots. */ export interface ChatQueueState { items: QueuedMessageView[]; paused: QueuePause | null; } export interface EnqueueMessageInput { threadId: string; actor: Actor; content: string; attachments?: MessageAttachment[]; agentName?: string; persona?: string; model?: string; pageContext?: PageContext; uiCapabilities?: UiCapabilities; hostContext?: AgentHostContext; /** When the person sent it (ISO 8601). See {@link QueuedMessage.sentAt}. */ sentAt?: string; interrupt?: boolean; /** `'tail'` (default) runs it after everything already waiting; `'head'` runs it next. */ at?: 'tail' | 'head'; } /** What `PATCH <base>/queue/:messageId` may change. An omitted key leaves the field as it is. */ export interface QueuedMessagePatch { content?: string; /** `null` drops every attachment. */ attachments?: MessageAttachment[] | null; /** * Mark (or unmark) the message as an interrupt — what `POST <path>/queue/:messageId/interrupt` * does to a message that is already waiting, before it cancels the running turn for it. */ interrupt?: boolean; } /** * A store that can hold a thread's queue of waiting messages, and admit ONE run per thread at a time. * * Probed STRUCTURALLY ({@link isChatQueueStore}), like `ThreadTurnReader`: a store that predates it * keeps the old behaviour (a send on a busy thread starts a second, concurrent run). All members or * none — a queue without the admission primitives cannot be drained safely across processes. * * Admission is the thread's active run (`activeRunId` on a thread read), compare-and-set: * - {@link claimActiveStream} sets it to `runId` only when it is free, already `runId` (a retried * claim is idempotent), or held by `replacing` (a handoff from the run that is settling, or a * takeover from a holder the runner reports dead). Exactly one of two racing claims wins. * - {@link releaseActiveStream} clears it only when `runId` still holds it — so a run that settles * after it handed the thread to the next one cannot clear the next one's claim. */ export interface ChatQueueStore { enqueueMessage(input: EnqueueMessageInput): Promise<QueuedMessage>; /** The thread's waiting messages, in run order (the head first). */ listQueue(threadId: string): Promise<QueuedMessage[]>; getQueuedMessage(id: string): Promise<QueuedMessage | null>; /** `null` when there is no such message (it started, or was removed). */ updateQueuedMessage(id: string, patch: QueuedMessagePatch): Promise<QueuedMessage | null>; /** Move a message to `index` (clamped) in its thread's run order. `false` when it is gone. */ moveQueuedMessage(id: string, index: number): Promise<boolean>; /** * Remove a message. `false` when it was already gone — which is also how a drain learns that the * head it was about to start was deleted under it, so it must be a real conditional delete. */ removeQueuedMessage(id: string): Promise<boolean>; /** Remove every waiting message on the thread; answers how many there were. */ clearQueue(threadId: string): Promise<number>; queuePause(threadId: string): Promise<QueuePause | null>; setQueuePause(threadId: string, pause: QueuePause | null): Promise<void>; /** The run currently holding the thread, or `null`. */ activeRunForThread(threadId: string): Promise<string | null>; claimActiveStream(threadId: string, runId: string, options?: { replacing?: string; }): Promise<boolean>; releaseActiveStream(threadId: string, runId: string): Promise<boolean>; } /** Whether `store` implements every {@link ChatQueueStore} member. */ export declare function isChatQueueStore(store: AgentStore): store is AgentStore & ChatQueueStore; /** * Release the thread `runId` was streaming — conditionally when the store can compare, so a run * that already handed the thread to the next queued turn does not clear that turn's claim; the old * unconditional clear on a store that cannot. */ export declare function releaseThreadRun(store: AgentStore, threadId: string, runId: string): Promise<void>; /** The wire view of a queued message. */ export declare function queuedMessageView(message: QueuedMessage): QueuedMessageView; //# sourceMappingURL=chat-queue.d.ts.map