@adonis-agora/agent
Version:
Governed, durable-backed AI agent (chat + tool-calling + governance) for AdonisJS — part of the Agora ecosystem.
91 lines • 4.81 kB
TypeScript
import type { StandardSchemaV1 } from '@standard-schema/spec';
import type { AgentUiComponent } from '../stream-events.js';
import type { MessageUsage, ModelMessage, ToolCallRequest, ToolDefinition } from '../types.js';
import type { SinkWriter } from './token-stream-sink.js';
export interface ModelTurnArgs {
system: string;
messages: ModelMessage[];
tools: ToolDefinition[];
/** The model writes streamed text deltas here as it generates them. */
sink: SinkWriter;
abortSignal?: AbortSignal;
/**
* Constrain this call's reply to a schema. Set only by the loop's structured-output formatting
* pass, which is non-streamed and carries `tools: []` — most providers cannot serve a response
* format and a tool set in the same request.
*
* An adapter that can constrain generation should, and report what it parsed as
* {@link ModelTurnResult.object}. One that cannot may ignore it entirely: the loop validates the
* reply either way, reading the JSON out of the text when no `object` comes back.
*/
outputSchema?: StandardSchemaV1;
/**
* The model the caller picked for this turn — a `ModelCatalogEntry` id the server already checked
* against the catalog (a per-send `model`, else the thread's pinned one). Absent → the provider's
* own default. A provider serving a single model may ignore it.
*/
model?: string;
}
/** The outcome of ONE assistant turn. The loop — not the model — drives tool execution. */
export interface ModelTurnResult {
text: string;
toolCalls: ToolCallRequest[];
usage: MessageUsage;
/**
* The model actually used this turn (e.g. `anthropic.claude-...`), recorded with usage for
* cost accounting. When set it wins over the module's configured `modelId`, so the accounting
* label can't silently drift from the runtime. Omit if the provider can't report one.
*/
modelId?: string;
/**
* The ACTUAL USD cost of this turn, when the provider knows it — a gateway (Vercel AI Gateway
* `providerMetadata.gateway.cost`, OpenRouter `providerMetadata.openrouter.usage.cost`) reports real spend; a direct provider
* (Anthropic/OpenAI/Bedrock) reports only tokens and leaves this undefined. When set, the
* governance read-model uses it verbatim; otherwise it estimates from tokens × the pricing table.
*/
costUsd?: number;
/**
* What the provider parsed out of a reply it constrained to {@link ModelTurnArgs.outputSchema}.
* The loop VALIDATES it regardless — "the provider says it matched" is not the same claim as "it
* matches", and a provider that ignored the schema has to fail where the failure is repairable.
* Omit when the adapter did not constrain generation.
*/
object?: unknown;
/**
* The model's thinking this turn, as streamed. Optional: the loop derives it from the `reasoning`
* frames the provider wrote to the sink (see `observeTurnFrames`), so a provider only sets it to
* override that — e.g. one that receives reasoning it does not stream.
*/
reasoning?: string;
/** Time spent thinking this turn, in ms. Derived like {@link reasoning} when omitted. */
reasoningMs?: number;
/** Components pushed during this turn (`ui` frames). Derived like {@link reasoning} when omitted. */
ui?: AgentUiComponent[];
}
/**
* Thin wrapper over the actual LLM. The concrete impl (e.g. Vercel AI SDK `streamText`
* over Bedrock/Anthropic) lives in the host app or an adapter; core stays provider-free.
*
* Contract: `runTurn` performs exactly one model turn, streaming deltas to `args.sink`,
* and returns the assembled text + requested tool calls + usage. It MUST NOT execute
* tools — the agent loop runs each as a (durable) step for replay-safety.
*/
export interface ModelProvider {
runTurn(args: ModelTurnArgs): Promise<ModelTurnResult>;
/**
* The models this provider can run, for boot-time pricing: the provider seeds a missing price row
* from models.dev and warns about a model that would record no cost at all. Optional — a provider
* without it is simply not checked. `aiSdkModel` / `aiSdkModels` implement it.
*/
describeModels?(): DescribedModel[];
}
/** One model a {@link ModelProvider} runs, as boot-time pricing sees it. */
export interface DescribedModel {
/** The id the model is called with (`deepseek/deepseek-v4.1-flash`, `gpt-4o-mini`). */
modelId: string;
/** Its provider family as models.dev names it (`openrouter`, `openai`, `anthropic`, `vercel`). */
provider?: string;
/** The provider reports each call's real USD cost (a gateway), so no price row is needed. */
reportsCost: boolean;
}
//# sourceMappingURL=model-provider.d.ts.map