@adonis-agora/agent
Version:
Governed, durable-backed AI agent (chat + tool-calling + governance) for AdonisJS — part of the Agora ecosystem.
103 lines • 5.15 kB
TypeScript
import type { AgentService } from '../agent-service.js';
import type { StreamFrame } from '../spi/token-stream-sink.js';
import type { Actor, PageContext } from '../types.js';
/**
* What an A2A turn does with an `action` call that parks for approval:
* - `'approve-delegated'` (default) — approve it when the caller's delegation grants one of the
* tool's roles: the user approved that scope on their own account page, which IS the consent the
* in-app approval stands for. Anything else is rejected. An independent proposal
* (`actionApprovalMode: 'independent'`) is decided the same way once the run ends, and the turn
* waits for it to execute, so the reply never claims an action that did not happen.
* - `'reject'` — reject every action; personal agents only read.
*/
export type A2aActionPolicy = 'approve-delegated' | 'reject';
export interface A2aTurnInput {
actor: Actor;
text: string;
/** The registered agent that answers. Omitted → the service's default. */
agentName?: string;
/** Continue this thread; omitted → a new (transient) one. */
threadId?: string;
/** Scopes the caller's delegation grants; `null` without a delegation. */
delegatedScopes: string[] | null;
actions: A2aActionPolicy;
/** Give up (and cancel the run) after this long. */
timeoutMs: number;
/** Called once the run started, with the thread it runs in — before its frames are read. */
onStarted?: (threadId: string, runId: string) => Promise<void>;
/** The page context the turn runs with. Default `{ channel: 'a2a' }`. */
pageContext?: PageContext;
/** What approvals and answers are recorded as coming through. Default `'a2a'`. */
via?: string;
/** Each piece of the reply's text as it streams — for a surface that relays it live. */
onText?: (delta: string) => void | Promise<void>;
/** Each component frame other than a permission request, as it arrives. */
onComponent?: (frame: Extract<StreamFrame, {
t: 'component';
}>) => void | Promise<void>;
/**
* Execute, in this process, what of the run waits for a worker (`AgentService.drive`) while the
* turn is read — for a durable runner whose dispatcher only persists a started run.
*/
drive?: (runId: string) => Promise<void>;
}
export interface A2aTurnAction {
tool: string;
argsHash: string;
/** The delegated scopes that authorized it. */
scopes: string[];
}
/**
* An independent proposal that executed during the turn: the app's own summary of it (the
* confirmation it would have shown the person) and what the tool returned.
*/
export interface A2aTurnOutcome {
tool: string;
/**
* `succeeded` — approved and executed; `failed` — approved but did not run (`error` says why);
* `rejected` — the policy refused it (no delegated scope, or `actions: 'reject'`).
*/
status: 'succeeded' | 'failed' | 'rejected';
summary: {
title: string;
detail?: string;
};
result?: unknown;
/** Why a `failed` action did not run. */
error?: string;
}
export interface A2aTurnResult {
threadId: string;
text: string;
/** Scopes the agent asked the user for (`request_permission`); `null` when it asked for none. */
permission: string[] | null;
/** Tools that RAN under a delegated scope (reads and approved actions) — what a receipt lists. */
actions: A2aTurnAction[];
/** Approved proposals that executed — the reply carries each as a data part. */
outcomes: A2aTurnOutcome[];
/** Why the run failed, when it did. */
error: string | null;
}
/**
* What runs an A2A turn: start it, read its frames, settle what it parks on. `AgentService` (the
* agent provider's) is one; an app with its own runtime — a `runAgentLoop` built per request, say —
* writes an adapter with the same shape (`defineA2aConfig({ service })`).
*/
export interface A2aTurnService extends Pick<AgentService, 'chat' | 'subscribe' | 'approve' | 'reject' | 'skip' | 'cancel'>, Partial<Pick<AgentService, 'decideActionProposal' | 'listActionProposals'>> {
/**
* The roles a tool declares — what an approval and a receipt match the delegated scopes against.
* Omitted → the shared `ToolRegistry`. An app whose registry is built per turn answers it here.
*/
toolRoles?(toolName: string): string[] | undefined;
}
/**
* Run one A2A turn to completion and read its outcome off the stream. A2A's `message:send` is
* synchronous (PACT §4.2), so the run's frames are consumed here until the stream ends: text is the
* reply, an approval is decided on the spot by {@link A2aActionPolicy}, a question set is skipped
* (there is no human on this side to answer it — the agent proceeds on its own assumptions), and a
* `request_permission` call becomes a step-up.
*/
export declare function runA2aTurn(service: A2aTurnService,
/** The roles a tool declares (see {@link A2aTurnService.toolRoles}). */
toolRoles: (toolName: string) => string[] | undefined, input: A2aTurnInput): Promise<A2aTurnResult>;
//# sourceMappingURL=turn.d.ts.map