UNPKG

@adonis-agora/agent

Version:

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

254 lines • 10.5 kB
import { type ElicitationRequest, type HumanReply } from '../elicitation.js'; import type { AgentStore, CostSource } from '../spi/agent-store.js'; import type { ApprovalRequirement } from '../spi/approval-policy.js'; import type { SinkWriter } from '../spi/token-stream-sink.js'; import type { AgentUiComponent } from '../stream-events.js'; import { type ToolPresentation } from '../tool-presentation.js'; import type { AgentRunInput, MessageUsage } from '../types.js'; import type { OpenCodeClient, OpenCodeEvent, OpenCodeForm, OpenCodeTokens } from './client.js'; /** How an OpenCode execution ended. */ export type TurnOutcome = { status: 'succeeded'; } | { status: 'failed'; error: string; } | { status: 'interrupted'; }; /** * Something the turn put to a person, with everything needed to hand the answer back to OpenCode * later — serializable, so a durable run journals it and replies from another process. */ export type PendingAsk = { kind: 'approval'; /** The OpenCode permission request id — also the tool-call id the decision is signalled under. */ id: string; action: string; approver: string; expiresAt?: string; /** The assistant message the call hangs on (absent for a request recovered after a restart). */ messageId?: string; } | { kind: 'form'; id: string; form: OpenCodeForm; request: ElicitationRequest; messageId?: string; }; /** * What a turn spent. `inputTokens` is the whole input side, cached input included (as the library's * `MessageUsage` counts it — OpenCode's own `tokens.input` leaves the cache out); `cacheReadTokens`, * `cacheWriteTokens` and `reasoningTokens` are subsets. `costUsd` sums every priced call; `steps` * counts the model steps. */ export interface OpenCodeUsage { inputTokens: number; outputTokens: number; cacheReadTokens: number; cacheWriteTokens: number; reasoningTokens: number; costUsd: number; steps: number; } export declare function emptyUsage(): OpenCodeUsage; /** `a + b`; a missing side (a milestone journaled before usage travelled) counts as nothing. */ export declare function addUsage(a: OpenCodeUsage | undefined, b: Partial<OpenCodeUsage> | undefined): OpenCodeUsage; /** The model a step ran on, as OpenCode names it (`session.step.started`). */ export interface StepModel { providerID: string; id: string; } /** What a model call cost, and where the figure came from. */ export interface StepCost { costUsd?: number; costSource?: CostSource; } /** * Where a turn stands: it needs a person, or it is over. `usage` is what the turn spent since its * previous milestone — journaled with the milestone, so a run resumed in another process still adds * up what every process saw. */ export type Milestone = { kind: 'ask'; ask: PendingAsk; timeoutMs?: number; usage?: OpenCodeUsage; } | { kind: 'finished'; outcome: TurnOutcome; usage?: OpenCodeUsage; }; export interface OpenCodeTurnArgs { runId: string; input: AgentRunInput; client: OpenCodeClient; sessionId: string; writer: SinkWriter; store: AgentStore; /** What an action needs before it runs (the agent's `ApprovalPolicy`). */ approvalFor: (action: string) => Promise<ApprovalRequirement>; /** * The app tool a permission `action` names, with its presentation: its `confirm` templates (filled * from the call's arguments) word the approval card. `undefined` (or omitted) → an OpenCode * built-in, worded from its name. */ approvalPresentation?: (action: string) => { name: string; presentation?: ToolPresentation; } | undefined; /** The model label usage is recorded under until OpenCode names the step's model. */ modelLabel: string; /** * What a model call cost: OpenCode's reported figure, or the library's estimate. Omit → OpenCode's * figure as reported. */ priceStep?: (step: { model: StepModel | undefined; usage: MessageUsage; reportedCostUsd: number | undefined; }) => Promise<StepCost>; /** What the run spent before this turn object existed (an earlier process, an earlier session). */ carried?: OpenCodeUsage; /** * An action OpenCode was allowed to run — by a person, a remembered approval or the policy. The * engine's MCP endpoint runs an `action` tool only against one of these. */ onGranted?: (action: string, permissionId: string) => void; } /** * One turn of an OpenCode session, as the library's stream and store see it. Fed the session's * events in order, it writes the protocol's frames and persists what the loop would have — the * assistant messages with their tool calls and results, the calls put to a person (so the * approve/answer routes find their run), usage per model step — and reports {@link Milestone}s: a * person was asked something, or the execution ended. * * The turn does not wait for people itself. Whoever drives it (`OpenCodeTurns`, under an in-memory * or a durable runner) takes the `ask` milestone, waits however it waits, and hands the answer back * with {@link decide} — which is what lets a durable run wait across a restart and reply from * another process, with a fresh turn object fed the journaled {@link PendingAsk}. */ export declare class OpenCodeTurn { private readonly a; /** Serializes the handling of events, so frames and rows land in the order OpenCode sent them. */ private chain; private segment; private readonly tools; /** Results already persisted per message, so a late settlement adds to them rather than replacing. */ private readonly messageResults; private readonly asked; /** Calls of {@link HIDDEN_TOOLS}, by id. */ private readonly hidden; private readonly milestones; private waiting; /** What this turn object saw spent, and how much of it a milestone already reported. */ private spent; private reported; private priced; private model; /** What the session had spent when this turn prompted it; what OpenCode reported since. */ private baseline; private readonly seen; private compacted; private stepOpen; private sawText; private separator; private reasoningSince; private wroteMessage; private ended; private stepError; constructor(a: OpenCodeTurnArgs); /** * What the session had spent before this turn prompted it (`session.get`). When the execution * ends, what the session spent since that this turn's events did not report — the title OpenCode * generates, a compaction: OpenCode records those calls on the session without streaming them — is * recorded as a usage row of its own. */ setBaseline(info: { cost?: number; tokens?: OpenCodeTokens; } | undefined): void; /** What the run spent so far: carried from before this turn object, plus what it saw. */ totalUsage(): OpenCodeUsage; get sessionId(): string; get finished(): boolean; /** The run's totals so far (see {@link totalUsage}): tokens, cost, model steps. */ totals(): { inputTokens: number; outputTokens: number; costUsd?: number; steps: number; }; private isPriced; /** Feed one of the session's events. */ handle(event: OpenCodeEvent): void; /** End the turn as failed without an event (the prompt was refused, the stream is gone). */ fail(error: string): void; /** * A component a tool pushed (`ctx.emitUi`, through the engine's MCP endpoint): streamed as a `ui` * frame and persisted on the message being written, in the order it arrived. Pushing the same id * again replaces it. */ pushUi(component: AgentUiComponent): Promise<void>; /** The next milestone: one already reached, or the next one to come. */ next(): Promise<Milestone>; /** Whether a milestone is waiting to be taken. */ hasMilestone(): boolean; /** * Requests OpenCode raised while nobody listened (this process started after them, or the event * stream dropped): its open permissions and forms, handled as if their events had just arrived. * One already recorded in the store is reported without being recorded or streamed again. */ catchUp(): Promise<void>; /** * Hand a person's answer to what the turn asked, and let OpenCode go on. `tellOpenCode: false` * only settles the call (stream and store): the request died with a restarted server, and the * new session is told the answer in its prompt instead. */ decide(ask: PendingAsk, reply: HumanReply, opts?: { tellOpenCode?: boolean; }): Promise<void>; private reach; private enqueue; private write; private event; private step; private endReasoning; private tool; private announce; private makeAvailable; private setInput; /** Code-mode calls made inside a tool (`metadata.toolCalls`), nested under it by `parentId`. */ private innerCalls; private onEvent; private settleTool; private modelId; /** One model call's usage and cost, into the ledger and the run's total. */ private spend; private stepEnded; /** The run's usage as its last message carries it: the whole run, cache tokens included. */ private messageUsage; /** * Persist what the turn produced since the last flush as one assistant message — the message a * call put to a person hangs on, or the turn's last one. Returns its id. */ private flush; /** A request recorded by an earlier process (a turn resumed after a restart). */ private alreadyRecorded; /** The persisted assistant message carrying call `id`, so its result lands on the same message. */ private messageOf; private onPermission; private decided; private onForm; private answered; private addResult; /** What the session spent since the baseline that no event of this turn reported. */ private unreported; private end; } /** * OpenCode's token counts as the library's `MessageUsage`. OpenCode reports the uncached input in * `input` and the cache reads and writes beside it; the library counts the whole input side in * `inputTokens`, the cache counts being subsets of it. */ export declare function usageOf(tokens: any): MessageUsage; //# sourceMappingURL=turn.d.ts.map