import { ActionProcedureDescriptor } from '@voltro/protocol'; import { ColumnDefinition } from '@voltro/database'; import { Context } from 'effect'; import { DataStore } from '@voltro/database'; import { defaultSettingsMiddleware } from 'ai'; import { Effect } from 'effect'; import { EmbeddingModelV4 } from '@ai-sdk/provider'; import { Experimental_RealtimeModelV4 } from '@ai-sdk/provider'; import { Experimental_RealtimeModelV4SessionConfig } from '@ai-sdk/provider'; import { Experimental_VideoModelV4 } from '@ai-sdk/provider'; import { Experimental_VideoModelV4File } from '@ai-sdk/provider'; import { extractReasoningMiddleware } from 'ai'; import { GatewayLanguageModelEntry } from '@ai-sdk/gateway'; import { Guards } from '@voltro/protocol'; import { ImageModelV4 } from '@ai-sdk/provider'; import { LanguageModelMiddleware } from 'ai'; import { LanguageModelV4 } from '@ai-sdk/provider'; import { LanguageModelV4Source } from '@ai-sdk/provider'; import { ModelMessage } from 'ai'; import { PluginMediaPersistCapability } from '@voltro/protocol'; import { PluginMediaPersistMetadata } from '@voltro/protocol'; import { QueryDescriptor } from '@voltro/database'; import { QueryProcedureDescriptor } from '@voltro/protocol'; import { Runtime } from 'effect'; import { Schema } from 'effect'; import { SchemaTable } from '@voltro/database'; import { SemanticCacheShape } from '@voltro/cache'; import { simulateStreamingMiddleware } from 'ai'; import { SpeechModelV4 } from '@ai-sdk/provider'; import { Stream } from 'effect'; import { Subject } from '@voltro/protocol'; import { Table } from '@voltro/database'; import { ToolSet } from 'ai'; import { TranscriptionModelV4 } from '@ai-sdk/provider'; import { VoidIfEmpty } from 'effect/Types'; import { WebSocketGatewayRoute } from '@voltro/protocol'; import { YieldableError } from 'effect/Cause'; /** Count of in-flight streams (diagnostics / leak detection). */ export declare const activeStreamCount: () => number; declare const AGENT_DESCRIPTOR_BRAND: "@voltro/ai/AgentDescriptor"; /** Per-turn message table — the `source` of the reactive `.messages` * query, and what `runAssistant` delta-patches as it streams. */ export declare const AGENT_MESSAGES_TABLE = "_voltro_agent_messages"; /** Thread metadata table (one row per conversation). */ export declare const AGENT_THREADS_TABLE = "_voltro_agent_threads"; /** * WHO MAY TALK TO an agent — the same three-state decision a mutation makes, * applied to BOTH synthesized routes (`.send` and `.messages`). * * The two routes used to carry a hard-coded `openAccess`, honestly worded: * "serves any session this app authenticates; defineAgent declares no per-agent * guard vocabulary today". That sentence made every agent a free model-spend * surface for anyone who could open the socket — anonymous included, since the * routes never looked at the subject — and it made the boot gate's carve-out * ("synthesized routes are not the app's to guard") true only because there * was nothing on the descriptor to read. There is now, so the gate judges an * agent like a workflow: one that declares neither refuses the boot under * `security.defaultDeny`. */ export declare interface AgentAccessOptions { /** A caller must hold a scope (`{ scope: 'support:chat' }`), or a * relationship to the thread (`{ action, resourceType, resource: (i) => * i.threadId }`). Checked on every `send` and on every `messages` delivery. */ readonly guards?: Guards; /** Anyone who can open the socket may talk to it — and WHY. A reason, not a * boolean. Note what it does NOT buy: an anonymous caller owns no thread, so * thread privacy (below) is only enforced for authenticated subjects. */ readonly openAccess?: string; } /** Stamp an agent-initiated call's actor with `via: 'agent'` so audit + trace * + provenance (08) can distinguish "user X" from "user X via agent". The * actor id stays the calling subject's — the agent never escalates identity. */ export declare const agentActor: (subjectId: string | null) => { readonly id: string | null; readonly via: "agent"; }; export declare interface AgentDescriptor { readonly _brand: typeof AGENT_DESCRIPTOR_BRAND; /** Wire id → rpc tags `.send` / `.messages`. */ readonly name: string; /** Input schema (default `{ prompt: string }`). The send route's wire input * extends this with `{ threadId, order }`. */ readonly input?: S; /** WHO MAY TALK TO IT — see {@link AgentAccessOptions}. */ readonly guards?: Guards; readonly openAccess?: string; } /** Error taxonomy. `retryable` on the event drives client/agent retry. */ export declare const AgentErrorCode: Schema.Literal<["provider", "tool", "timeout", "cancelled", "decode", "internal"]>; export declare type AgentErrorCode = typeof AgentErrorCode.Type; export declare const AgentEvent: Schema.Union<[Schema.Struct<{ _tag: Schema.Literal<["token"]>; text: typeof Schema.String; }>, Schema.Struct<{ _tag: Schema.Literal<["reasoning"]>; text: typeof Schema.String; }>, Schema.Struct<{ _tag: Schema.Literal<["toolCall"]>; id: typeof Schema.String; name: typeof Schema.String; input: typeof Schema.Unknown; }>, Schema.Struct<{ _tag: Schema.Literal<["toolResult"]>; id: typeof Schema.String; name: typeof Schema.String; output: Schema.optional; error: Schema.optional; }>, Schema.Struct<{ _tag: Schema.Literal<["source"]>; sourceType: Schema.Literal<["url", "document"]>; id: typeof Schema.String; url: Schema.optional; title: Schema.optional; mediaType: Schema.optional; filename: Schema.optional; /** Provider-owned citation attribution; not normalized or interpreted. */ providerMetadata: Schema.optional; }>, Schema.Struct<{ _tag: Schema.Literal<["file"]>; mediaType: typeof Schema.String; data: typeof Schema.String; /** Metadata for this file, not the whole model step. */ providerMetadata: Schema.optional; }>, Schema.Struct<{ _tag: Schema.Literal<["stepMetadata"]>; step: Schema.filter; providerMetadata: typeof Schema.Unknown; }>, Schema.Struct<{ _tag: Schema.Literal<["message"]>; role: typeof Schema.String; content: typeof Schema.String; }>, Schema.Struct<{ _tag: Schema.Literal<["error"]>; code: Schema.Literal<["provider", "tool", "timeout", "cancelled", "decode", "internal"]>; message: typeof Schema.String; retryable: typeof Schema.Boolean; }>, Schema.Struct<{ _tag: Schema.Literal<["done"]>; finishReason: typeof Schema.String; usage: Schema.Struct<{ inputTokens: Schema.optional; outputTokens: Schema.optional; totalTokens: Schema.optional; }>; }>]>; export declare type AgentEvent = typeof AgentEvent.Type; export declare interface AgentExecutor { readonly _brand: typeof EXECUTOR_BRAND; readonly descriptor: AgentDescriptor; readonly config: AgentExecutorConfig>; } export declare interface AgentExecutorConfig { /** Static prompt, or a function of the decoded input. */ readonly system?: SystemPrompt; /** Tools the model may call. Same shape `runAssistant` takes. */ readonly tools?: Record; /** Per-agent provider override, INCLUDING its own key: * `{ name, model, apiKey? }`. Each field falls back to env when omitted — * `AI_PROVIDER` / `AI_MODEL` and the provider's standard key var * (`OPENAI_API_KEY` / `ANTHROPIC_API_KEY` / `AI_GATEWAY_API_KEY`). The key * stays server-side (this file never reaches the browser). * * May ALSO be a function of the decoded input — for a UI model picker, * cost/size routing, plan-tier models, etc. (mirrors `system`). Return * `undefined` to fall back to env for that call. Runs server-side. * * SECURITY: the input is client-controlled. NEVER feed it straight into the * config (`(i) => ({ model: i.model })`) — a caller could pick an * expensive/arbitrary model. Constrain the picker with a `Schema.Literal` * in the descriptor input + map to server-owned configs; read keys from * `process.env`, never from the input: * ```ts * // descriptor: input: Schema.Struct({ prompt: Schema.String, tier: Schema.Literal('fast','smart') }) * model: (input) => input.tier === 'smart' * ? { name: 'openai', model: 'gpt-5.5' } * : { name: 'openai', model: 'gpt-4o-mini' }, * ``` */ readonly model?: ProviderConfig | ((input: Input) => ProviderConfig | undefined); /** Max LLM↔tool round-trips before the loop stops. */ readonly maxSteps?: number; /** Provider-specific options forwarded to the SDK — most importantly REASONING * EFFORT. A routing/help agent on a reasoning model (`gpt-5.x`) should pin it * low so a one-line answer doesn't cost the default high-effort 10s+: * providerOptions: { openai: { reasoningEffort: 'low' } } * See `AiProviderOptions`. */ readonly providerOptions?: AiProviderOptions; } /** The decoded input an agent's executor sees, derived from its schema. */ export declare type AgentInput = Schema.Schema.Type; /** The agent's input schema (default `{ prompt: string }`). */ export declare const agentInputSchema: (desc: AnyAgentDescriptor) => Schema.Schema.Any; export declare interface AgentMessage { readonly id: string; readonly threadId: string; readonly tenantId: string | null; readonly role: AgentMessageRole; readonly content: string; readonly streaming: boolean; readonly order: number; readonly stepOrder: number; readonly parts: ReadonlyArray; readonly createdAt: Date; } export declare type AgentMessageRole = 'user' | 'assistant' | 'system' | 'tool'; export declare const agentMessageSchema: Schema.Struct<{ id: typeof Schema.String; threadId: typeof Schema.String; role: typeof Schema.String; content: typeof Schema.String; streaming: typeof Schema.Boolean; order: typeof Schema.Number; stepOrder: typeof Schema.Number; parts: Schema.Array$>; }>; export declare const agentMessagesTable: Table>, true>; /** * The two browser-safe route descriptors synthesized for an agent. The SAME * source the runtime synthesis (`agentSynthesis.ts`) and codegen both consume, * so "what the server serves" and "what the client is typed for" can't drift. */ export declare const agentRouteDescriptors: (desc: AnyAgentDescriptor) => { send: ActionProcedureDescriptor<`${string}.send`, Schema.Schema.Any, Schema.Struct<{ text: typeof Schema.String; }>, typeof AgentThreadAccessDenied>; messages: QueryProcedureDescriptor<`${string}.messages`, Schema.Struct<{ threadId: typeof Schema.String; }>, Schema.Array$>; }>>, typeof AgentThreadAccessDenied>; }; /** The one field both synthesized routes share on the wire — what a * resource-scoped agent guard may read its id from. */ export declare interface AgentRouteInput { readonly threadId: string; } export declare interface AgentThread { readonly id: string; readonly tenantId: string | null; readonly subjectId: string | null; readonly title: string | null; readonly createdAt: Date; readonly updatedAt: Date; } /** * A thread belongs to the subject that opened it. Raised by `.send` and * `.messages` when an authenticated caller addresses a thread another * subject created — the isolation the old `openAccess` sentence said it could * not claim. In the routes' error union, so it reaches the client typed. */ export declare class AgentThreadAccessDenied extends AgentThreadAccessDenied_base { get message(): string; } declare const AgentThreadAccessDenied_base: Schema.TaggedErrorClass; } & { threadId: typeof Schema.String; }>; export declare const agentThreadsTable: Table>, true>; export declare const AI_BUDGET_RESERVATIONS_TABLE = "_voltro_ai_budget_reservations"; export declare const AI_BUDGET_TABLE = "_voltro_ai_budget"; export declare const AI_INFERENCES_TABLE = "_voltro_ai_inferences"; export declare const AI_USAGE_TABLE = "_voltro_ai_usage"; export declare type AiBudgetError = AiBudgetExceeded | AiBudgetInvalidInput | AiBudgetReservationConflict; /** A refused reservation, not evidence of a provider charge. */ export declare class AiBudgetExceeded extends AiBudgetExceeded_base { } declare const AiBudgetExceeded_base: Schema.TaggedErrorClass; } & { tenantId: Schema.NullOr; limitUsd: typeof Schema.Number; spentUsd: typeof Schema.Number; attemptedUsd: typeof Schema.Number; }>; /** Invalid caller configuration, without echoing the supplied value. */ export declare class AiBudgetInvalidInput extends AiBudgetInvalidInput_base { } declare const AiBudgetInvalidInput_base: Schema.TaggedErrorClass; } & { field: Schema.Literal<["limitUsd", "addUsd", "since", "reservationId", "tenantId"]>; message: typeof Schema.String; }>; export declare interface AiBudgetReservation { readonly reservationId: string; readonly tenantId: string | null; readonly amountMicroUsd: string; readonly status: 'reserved' | 'replayed'; } /** A durable reservation identity cannot be rebound or spent after release. */ export declare class AiBudgetReservationConflict extends AiBudgetReservationConflict_base { } declare const AiBudgetReservationConflict_base: Schema.TaggedErrorClass; } & { reason: Schema.Literal<["identity-bound", "already-released"]>; message: typeof Schema.String; }>; /** Receipt and release tombstone. A receipt and its counter change commit together. */ export declare const aiBudgetReservationsTable: Table>, true>; /** Exact micro-USD counter, shared by a tenant's window. */ export declare const aiBudgetTable: Table>, true>; /** Attributes stamped on the span + used as metric labels. Derived from the * resolved provider config; carries NO prompt/response content or secrets. */ export declare interface AiCallAttributes { readonly provider: string; readonly model: string; /** 'generateText' | 'generateObject' | 'generateObjectWithTools' | 'streamText' | custom. */ readonly operation: string; } /** Derive span/metric attributes from a resolved provider config + operation. */ export declare const aiCallAttributes: (config: ProviderConfig, operation: string) => AiCallAttributes; export declare type AiCallEvidence = typeof AiCallEvidenceSchema.Type; /** Allowlisted consumption evidence, never a provider response or SDK error. * Completeness describes the three token counters, not billing completeness. */ export declare const AiCallEvidenceSchema: Schema.Struct<{ provider: typeof Schema.String; model: typeof Schema.String; usage: Schema.Struct<{ inputTokens: Schema.optional>>>; outputTokens: Schema.optional>>>; totalTokens: Schema.optional>>>; completeness: Schema.Literal<["complete", "partial", "unknown"]>; }>; cost: Schema.Union<[Schema.Struct<{ status: Schema.Literal<["actual"]>; source: Schema.Literal<["gateway"]>; usd: Schema.filter>; }>, Schema.Struct<{ status: Schema.Literal<["unknown"]>; source: Schema.Literal<["unreported"]>; }>]>; }>; /** One logged AI call. `usage` is the SDK's token-usage object (present for * generate; a stream logs the call at start, before usage is known). */ export declare interface AiCallLog { readonly phase: 'generate' | 'stream'; readonly model: string; readonly usage?: unknown; } /** * Typed AI failure. `reason: 'generation'` — the provider call failed * (network / API / quota). `reason: 'decode'` — the model returned output * that didn't satisfy the requested schema. Handlers can * `Effect.catchTag('AiError', …)` and, for 'generation', retry. */ export declare class AiError extends AiError_base { } declare const AiError_base: Schema.TaggedErrorClass; } & { reason: Schema.Literal<["generation", "decode"]>; message: typeof Schema.String; evidence: Schema.optional>>>; outputTokens: Schema.optional>>>; totalTokens: Schema.optional>>>; completeness: Schema.Literal<["complete", "partial", "unknown"]>; }>; cost: Schema.Union<[Schema.Struct<{ status: Schema.Literal<["actual"]>; source: Schema.Literal<["gateway"]>; usd: Schema.filter>; }>, Schema.Struct<{ status: Schema.Literal<["unknown"]>; source: Schema.Literal<["unreported"]>; }>]>; }>>; }>; /** * The one way a CAUGHT provider failure becomes an `AiError`. * * Every call site used to write `new AiError({ message: String(e.message) })`, * which kept the top message and dropped `cause`. For a network failure that * leaves `fetch failed` with nothing under it — no `ECONNREFUSED`, no TLS code — * so the one fact that says what to fix was gone. Two sites were worse and * dropped the provider's message too (`structured provider call failed`). * * Two things carry the cause now, deliberately split by audience: * - the CODES of the cause chain (`ECONNREFUSED`, `CERT_HAS_EXPIRED`, …) join * the message. Codes only: a nested message can carry an internal host or * address, and `message` is a schema field — it is encoded, and it reaches a * browser whenever a procedure declares `AiError` in its error union; * - the raw failure stays on the instance as `cause`, NOT enumerable, so a * server log or `error.cause` has the whole chain and the encoder never * sees it. */ export declare const aiGenerationError: (cause: unknown, options?: { readonly prefix?: string; readonly evidence?: AiCallEvidence; }) => AiError; /** * One offloaded model call. * * The row IS the wait. A run parked on `awaitSignalSuspending` holds nothing; * everything needed to perform the call, resume the right run, and explain the * outcome afterwards is here. */ export declare const aiInferencesTable: SchemaTable; /** Provider-specific knobs forwarded verbatim to the Vercel AI SDK's * `providerOptions` on generateText / streamText / generateObject. The outer * key is the provider id, the inner record its options. The headline use is * REASONING EFFORT on reasoning models — `gpt-5.x` defaults to a high effort * that costs many seconds per call even for a one-line answer, so a routing/ * help agent should pin it low: * * providerOptions: { openai: { reasoningEffort: 'low' } } // or 'minimal' * providerOptions: { anthropic: { thinking: { type: 'disabled' } } } * * Shape matches the SDK's `ProviderOptions` (Record>). The value is the standard JSON value (mutable object + array, * so it stays assignable to the SDK's `SharedV2ProviderOptions` under * `exactOptionalPropertyTypes`) — NOT `unknown`, which the SDK rejects. */ declare type AiJsonValue = null | boolean | number | string | { [key: string]: AiJsonValue; } | AiJsonValue[]; /** A language-model middleware (the AI SDK's `LanguageModelMiddleware`): may * implement any of `transformParams` / `wrapGenerate` / `wrapStream`. */ export declare type AiMiddleware = LanguageModelMiddleware; export declare type AiProviderOptions = Record>; export declare class AiService extends AiService_base { } declare const AiService_base: Context.TagClass; export declare interface AiServiceImpl { readonly generateText: (options: GenerateTextOptions) => Effect.Effect; readonly generateObject: (options: GenerateObjectOptions) => Effect.Effect, AiError>; } /** * Sum recorded spend (USD) — optionally scoped to a tenant and/or a * `since` window. Reads the ledger table; pair with a reactive query * (`source: '_voltro_ai_usage'`) for a live spend meter. */ export declare const aiSpendUsd: (store: DataStore, opts?: { readonly tenantId?: string | null; readonly since?: Date; /** Scope the sum to ONE prompt version. This is the "did revision 4 cost * more than revision 3" query, and it is the reason the digest is on the * ledger row rather than only on the step row. */ readonly promptDigest?: string; }) => Effect.Effect; /** Token tally as returned by `generateText` / `generateObject` / `streamText`. */ export declare interface AiUsage { readonly inputTokens: number | undefined; readonly outputTokens: number | undefined; readonly totalTokens?: number | undefined; } /** The usage ledger could not be written — the call it describes is not booked. */ export declare class AiUsageLedgerError extends AiUsageLedgerError_base<{ readonly receiptKey: string; readonly cause: unknown; }> { get message(): string; } declare const AiUsageLedgerError_base: new = {}>(args: VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => YieldableError & { readonly _tag: "AiUsageLedgerError"; } & Readonly; /** * Price a usage tally and write a ledger row. Returns the computed * `CostBreakdown` so the caller can surface the cost without a re-query. */ /** What `recordAiUsage` did with the receipt: wrote it, or found it already written. */ export declare type AiUsageReceipt = 'recorded' | 'replayed'; /** * A receipt was recorded again with DIFFERENT values than the row already * written under it. * * A replay is the same paid call recorded twice, so its values are the same by * definition. Different values under one receipt mean one of them is wrong — a * second call billed under the first one's identity, or a caller whose receipt * key is not the identity it thinks it is. Before this existed the second * recording answered `replayed` and handed back the values it was GIVEN, so the * caller saw a charge that was never booked and no sign of the disagreement. */ export declare class AiUsageReceiptConflict extends AiUsageReceiptConflict_base<{ readonly receiptKey: string; readonly recorded: AiUsageReceiptFacts; readonly attempted: AiUsageReceiptFacts; }> { get message(): string; } declare const AiUsageReceiptConflict_base: new = {}>(args: VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => YieldableError & { readonly _tag: "AiUsageReceiptConflict"; } & Readonly; /** The facts of one usage row that a replay must agree with. */ export declare interface AiUsageReceiptFacts { readonly tenantId: string | null; readonly provider: string; readonly model: string; readonly operation: string; readonly inputTokens: number; readonly outputTokens: number; readonly costMicroUsd: number; } export declare interface AiUsageRecordInput { readonly tenantId?: string | null; readonly provider: ProviderName | string; readonly model: string; readonly operation: string; readonly agent?: string | null; readonly usage: AiUsage | MeteredUsage; /** Override the price table (negotiated/volume pricing or unknown model). */ readonly price?: ModelPrice; /** The ACTUAL per-call cost reported by the provider/gateway (USD). When set, * it's persisted verbatim (costSource='gateway') — the fix for gateway-routed * models the static table prices at zero. Extract it from the AI-SDK result's * `providerMetadata.gateway.cost` (see `gatewayCostUsd`). */ readonly actualCostUsd?: number; /** Which prompt VERSION produced this call — `definePrompt(...).render(...)` * returns one, and `aiStep` passes it through automatically. Stamped onto the * ledger row so spend can be attributed to a prompt revision. */ readonly prompt?: PromptStamp; /** Override the timestamp (tests; default `new Date()`). */ readonly calledAt?: Date; /** The request this call belongs to (a queued inference id, a message id, a * workflow step's request id) and which attempt of it. Together they name * the receipt, so a replayed recording writes one row. */ readonly requestId?: string; readonly attempt?: number; /** The receipt's idempotency key outright, when the caller has one that is * not `requestId#attempt`. */ readonly receiptKey?: string; } /** * One row per priced AI call. Registered into the auto-migrate set * alongside the agent tables when an app ships any `*.agent.tsx`; a * non-agent app that wants plain-call cost tracking imports * `aiUsageTable` into its `database/index.ts` barrel. It is reactive so * a live spend meter can subscribe to it. */ export declare const aiUsageTable: Table>, true>; export declare type AnthropicModel = 'claude-opus-4-8' | 'claude-sonnet-4-6' | 'claude-haiku-4-5-20251001' | (string & {}); /** A descriptor of any input shape — for the type-erased runtime helpers. */ export declare type AnyAgentDescriptor = AgentDescriptor; /** An executor of any input shape — for the type-erased runtime helpers. */ export declare type AnyAgentExecutor = AgentExecutor; /** A heterogeneous tool — the shape `buildSdkTools` / `runAssistant` consume. * `defineTool` keeps full input typing on its `execute`/`handler` body, then * erases to this shape so a `Record` accepts tools of * differing input/output types (Effect's `Schema` encoded type is invariant, * so a heterogeneous collection can't preserve per-tool generics). */ export declare interface AnyTool { readonly name: string; readonly description: string; readonly input: Schema.Schema.Any; /** See `ToolDef.inputJsonSchema` — a foreign parameter schema shown to the * model in place of a rendered `input`. */ readonly inputJsonSchema?: Record; readonly output?: Schema.Schema.Any; readonly execute?: (input: never) => Effect.Effect; readonly handler?: ToolHandler; readonly timeoutMs?: number; } /** Append a message + bump the thread's `updatedAt` (best-effort). */ export declare const appendMessage: (store: DataStore, input: { readonly threadId: string; readonly role: AgentMessageRole; readonly content: string; readonly tenantId?: string | null; readonly order?: number; readonly stepOrder?: number; readonly parts?: ReadonlyArray; readonly streaming?: boolean; }) => Effect.Effect; /** * Insert the assistant row that the run will stream into. Starts empty + * `streaming:true` so the reactive feed renders it as the live bubble. Returns * the new row id (the store generates it). */ export declare const appendStreamingMessage: (store: DataStore, input: { readonly threadId: string; readonly tenantId?: string | null; readonly order: number; }) => Effect.Effect; /** * Wrap `model` with the global stack plus any per-call `middleware`, in that * order (global first). Returns `model` untouched when there's nothing to * apply — no wrapper, no behavior change. Used by `resolveModel`. */ export declare const applyMiddleware: (model: LanguageModelV4, middleware?: AiMiddleware | ReadonlyArray) => LanguageModelV4; /** * The EXECUTION admission for one descriptor, as data. * * Same shape and same ORDER as `mcpToolDecision` in `mcpTools.ts`, on purpose: * an app tool and an external MCP tool are admitted by two functions that agree * about what a decision is, so one confirm-UI, one inventory type and one * refusal message cover both. The asymmetry between them is only in step 1 — * an app descriptor carries an `exposeAsTool` act, so an absent allowlist is * permissive here and refused there. * * It exists because a SECOND caller appeared. `appTools` used to inline the * three filters, which was fine while it was the only executor; the moment a * transport (the inspect agent-tool surface, reached over MCP) needed the same * verdict, an inlined chain would have become a second policy path — and a * second policy path is how a ceiling gets bypassed. So the verdict is a * function, `appTools` calls it, and the transport calls the same one. * * Order, asserted in the tests: * 1. `passesPolicy` (deny beats allow) on the descriptor NAME, * 2. exposability — an `exposeAsTool` object WITH a description, * 3. the writes gate — a mutation/action needs `includeWrites`, * 4. the CONFIRM gate — `confirm` must be backed by `requiresApproval:`. * * ── Why step 4 exists (`confirm` was reported, not enforced) ───────────────── * * `confirm` shipped as metadata: `synthesizeToolSpecs` reported it, this * function admitted the tool anyway, and the file said out loud that enforcement * was "the agent loop / UI"'s job. That is the shape this repo keeps paying for * — a control that reads as enforcement in every inventory and enforces nothing * in any caller that forgets to look. * * There is now somewhere for it to MEAN something. A descriptor that declares * `requiresApproval:` parks the call as a durable approval in the shared serve * pipeline — the same gate a human's call goes through — so the second human is * the app's own approval queue rather than a prompt in a harness we do not * control. A confirm tool WITH that policy is therefore admitted: executing it * cannot skip the human, because the handler behind it will not run without one. * * A confirm tool WITHOUT it is refused. The alternative is to mount it and hope * the caller asks, and the caller is a model. */ export declare type AppToolDecision = { readonly admitted: true; readonly spec: SynthesizedTool; } | { readonly admitted: false; readonly reason: string; }; export declare const appToolDecision: (descriptor: AppToolDescriptor, policy: AppToolPolicy) => AppToolDecision; /** The descriptor fields tool synthesis reads. */ export declare interface AppToolDescriptor { readonly kind: 'query' | 'mutation' | 'action' | string; readonly name: string; readonly input: Schema.Schema.Any; readonly output?: Schema.Schema.Any; readonly exposeAsTool?: ExposeAsToolLike; /** * Presence of the descriptor's `requiresApproval:` policy — read as a BOOLEAN * here and nothing more, so `@voltro/ai` needs no `@voltro/protocol` shape for * it (structural, like `exposeAsTool` above). * * It is what turns `confirm` from a report into an enforcement: see * `appToolDecision`. */ readonly requiresApproval?: unknown; } /** A descriptor paired with its ctx-bound executor (the real handler). */ export declare interface AppToolEntry { readonly descriptor: AppToolDescriptor; readonly invoke: (input: unknown) => Promise | unknown; } export declare interface AppToolPolicy { /** Allowlist — tags (with `*` glob) or a RegExp. Omitted = all exposed. */ readonly allow?: ReadonlyArray | RegExp; /** Denylist — wins over allow. For destructive/high-blast tags. */ readonly deny?: ReadonlyArray | RegExp; /** Default confirm posture for writes when the descriptor doesn't say. * Default true (writes confirm). */ readonly requireConfirmForWrites?: boolean; /** Include WRITE tools (mutation/action) in the executable toolset. * Default false — writes are opt-in + confirm (the safety default). Reads * are always included. */ readonly includeWrites?: boolean; } /** * The EXECUTABLE toolset for the agent loop. Read tools are always included; * WRITE tools only when `policy.includeWrites` is true (writes are opt-in — * the safety default). Each tool's handler runs the descriptor's bound * `invoke` (the real handler under the caller's subject). * * A `confirm` tool is admitted only when the descriptor is APPROVAL-BACKED * (`requiresApproval:`), and then the human step is enforced by the real handler * — the tool call returns the typed `ApprovalRequired` refusal, a human decides * in the app, and the identical call succeeds once. This module still gates no * human prompt of its own, and no longer needs to: it decides which tools can * exist, and the pipeline decides whether the call runs. */ export declare const appTools: (entries: ReadonlyArray, policy?: AppToolPolicy) => ReadonlyArray; export declare interface AssertionFailure { readonly assertion: EvalAssertion; readonly detail: string; } /** Render the capability map into a system prompt that enumerates EXACTLY the * tables + columns the model may use — the soft in-grammar steer that pairs * with the hard validator. */ export declare const buildCopilotSystemPrompt: (schema: CopilotSchema) => string; /** Adapt our tools to the AI SDK's ToolSet (keyed by tool name). When a * `ctx` is supplied it is threaded into `*.tool.tsx` executor bodies; inline * `execute` tools ignore it. */ export declare const buildSdkTools: (tools: Record, ctx?: ToolContext) => ToolSet; /** The names the built-in generators resolve without a registration. */ export declare const BUILTIN_MEDIA_PROVIDERS: ReadonlyArray; /** The names `generateSpeech` resolves without a registration. */ export declare const BUILTIN_SPEECH_PROVIDERS: ReadonlyArray; /** * Cancel an in-flight stream by id. Aborts its controller (the SDK call stops + * the stream emits a terminal `cancelled` error). Returns `true` if a live * stream was found and aborted, `false` if the id is unknown (already finished * or never started) — so a double-click "Stop" is a harmless no-op. */ export declare const cancelStream: (streamId: string) => boolean; /** * Can this runtime run this model? * * The finer form, for the common case of filtering a catalog. It answers about * the MODALITY — whether the framework has a primitive at all — and not about * whether a particular model is healthy, in the caller's region, or affordable. * Those are different questions with different answers and different owners. */ export declare const canExecute: (model: Pick) => boolean; /** * Union the three overlapping fields into one answer per capability. * * Each source is half-right on its own: `tags` is editorial and lags, and * `supported_parameters` / `modalities` are mechanical and sometimes omit what * the model plainly does. Taking either alone produces false negatives — and a * false "cannot" is worse than a false "can" here, because it removes a model * from a picker with no way for the user to discover why. */ export declare const capabilitiesOf: (raw: RawGatewayModel) => ModelCapabilities; /** Clear the global middleware stack. */ export declare const clearAiMiddleware: () => void; /** Test seam: forget every registration. */ export declare const clearMediaTaskProviders: () => void; /** Test seam: forget every registration. */ export declare const clearSpeechProviders: () => void; export declare interface ConsumeOptions { /** Replay events with `seq` strictly greater than this (default 0 = from the * start). A reconnecting client passes the last `seq` it rendered. */ readonly fromSeq?: number; /** Tail poll interval in ms while the producer is still running (default 50). */ readonly pollMs?: number; /** End the consumer if no new event arrives for this long AND the producer * hasn't finished — a safety valve against a crashed producer that never * marked done. Default: undefined (wait indefinitely). */ readonly idleTimeoutMs?: number; } /** * Consume a resumable stream: replay persisted events past `fromSeq`, then tail * live ones until the producer marks it done. This is what a streaming rpc * returns to each client — a fresh subscription (or a reconnect) just calls it * again with the right `fromSeq`. */ export declare const consumeResumableStream: (streamId: string, store: ResumableStreamStore, opts?: ConsumeOptions) => Stream.Stream; export declare const contextWindowOf: (raw: RawGatewayModel) => number | undefined; /** Hard cap on rows — a copilot query never returns an unbounded set. */ export declare const COPILOT_MAX_LIMIT = 200; export declare interface CopilotFilter { readonly column: string; readonly op: CopilotOp; readonly value: unknown; } export declare type CopilotOp = 'eq' | 'neq' | 'gt' | 'gte' | 'lt' | 'lte' | 'contains' | 'in'; /** The model's proposed query — a constrained shape, never free SQL. */ export declare interface CopilotProposal { readonly table: string; /** Projection; omit/empty = all columns. */ readonly columns?: ReadonlyArray; readonly filters?: ReadonlyArray; readonly orderBy?: { readonly column: string; readonly direction?: 'asc' | 'desc'; }; readonly limit?: number; /** Optional aggregate (validated; the descriptor build for aggregates is a * refinement — see `aggregate` on the result). */ readonly aggregate?: { readonly fn: 'count' | 'sum' | 'avg' | 'min' | 'max'; readonly column?: string; }; } /** The effect/Schema the model's structured output is constrained to (pass as * `generateObject({ schema: CopilotProposalSchema })`). Mirrors * {@link CopilotProposal}. */ export declare const CopilotProposalSchema: Schema.Struct<{ table: typeof Schema.String; columns: Schema.optional>; filters: Schema.optional; value: typeof Schema.Unknown; }>>>; orderBy: Schema.optional>; }>>; limit: Schema.optional; aggregate: Schema.optional; column: Schema.optional; }>>; }>; /** Why a proposal was refused. Typed so a copilot tool/action can surface it on * its `error:` channel and the client can show "I can't answer that". */ export declare class CopilotRejected extends CopilotRejected_base { } declare const CopilotRejected_base: Schema.TaggedErrorClass; } & { reason: Schema.Literal<["unknown-table", "unknown-column", "bad-op", "bad-aggregate", "empty"]>; detail: typeof Schema.String; }>; export declare interface CopilotSchema { readonly tables: ReadonlyArray; } /** The manifest slice the validator needs (mirrors the capability map's * `tables`). Decoupled — pass the live manifest's tables. */ export declare interface CopilotSchemaColumn { readonly name: string; readonly type?: string; } export declare interface CopilotSchemaTable { readonly name: string; readonly columns: ReadonlyArray; } export declare type CopilotValidation = { readonly ok: true; readonly descriptor: QueryDescriptor; readonly table: string; readonly aggregate?: CopilotProposal['aggregate']; } | { readonly ok: false; readonly rejection: CopilotRejected; }; /** Cosine similarity between two vectors (re-export of the AI SDK helper). * Useful for app-side reranking / thresholding without a DB round-trip. */ export declare const cosineSimilarity: (a: ReadonlyArray, b: ReadonlyArray) => number; export declare interface CostBreakdown { readonly inputTokens: number; readonly outputTokens: number; readonly inputCostUsd: number; readonly outputCostUsd: number; readonly totalCostUsd: number; /** Where `totalCostUsd` came from: `gateway` = the provider/gateway reported * the ACTUAL per-call cost (authoritative); `estimated` = derived from the * static price table (or zero for an unknown model). Analytics can flag * estimated rows + a model that priced at zero. */ readonly costSource: 'gateway' | 'estimated' | 'unpriced'; /** What `inputTokens`/`outputTokens` are counts OF. */ readonly unit: UsageUnit; } /** Open a new thread (the store generates the id). */ export declare const createThread: (store: DataStore, input?: { readonly tenantId?: string | null; readonly subjectId?: string | null; readonly title?: string | null; }) => Effect.Effect; export declare interface DataCopilotDeps { /** Turn the NL question (+ the grammar system prompt) into a structured * proposal. Wire to `@voltro/ai`'s `generateObject` with * `CopilotProposalSchema`; injected so the orchestrator is unit-testable * without a live model. */ readonly propose: (args: { readonly system: string; readonly prompt: string; }) => Promise; } export declare const dataPolicyOf: (raw: RawGatewayModel) => ModelDataPolicy; /** * Durable, multi-node resumable-stream store backed by the framework's own * `DataStore` — works on every dialect the framework supports. The producer * election uses `insertIgnore` (INSERT … ON CONFLICT DO NOTHING) on the UNIQUE * `streamId`, comparing the returned row's id to ours: we inserted ⇒ we won. * That is the same at-most-once arbiter `_voltro_idempotency` uses. * * The two tables (`streamEventsTable`, `streamStateTable`) are created for you: * the framework's table assembly adds them for any app that DEPENDS on * `@voltro/ai`, so `voltro dev` / `voltro db apply` / `voltro migrate` all plan * them. Nothing to add anywhere. * * This sentence used to say the opposite — "add them to the database barrel (or * they ride along with a `*.agent.tsx` app)" — and both halves were wrong. * Discovery is FILE-based (`*.entity.ts`), so a re-export from a barrel changes * no plan; and this store is an ordinary call reachable from an action, so an * agent file is not the gate either. The tables were simply missing from the * assembly. What that cost is worth stating, because it is the failure shape of * every "the app adds it" contract: `voltro db plan` answered *schema is up to * date* while two tables the feature cannot run without were absent, and the * first user's turn died inside the store instead of at boot. * * Building this store BOUNDS them (see `registerStreamLogRetention`); * `gcResumableStreams` remains for a tighter, done-aware collection an app can * run on its own schedule. */ export declare const dataStoreResumableStreamStore: (store: DataStore) => ResumableStreamStore; export declare const decideInferenceFailure: (input: { readonly attempts: number; readonly maxAttempts: number; readonly baseDelayMs: number; readonly maxDelayMs: number; /** Milliseconds the provider itself asked us to wait, when it said so. */ readonly retryAfterMs?: number | undefined; }) => InferenceFailureAction; /** Attempts before a queued call is reported as failed to its waiting run. */ export declare const DEFAULT_INFERENCE_ATTEMPTS = 4; /** How many queued calls one dispatcher performs concurrently. * * This is the number offloading exists to make explicit: inline, the ceiling * is "however many runs happen to be waiting". Here it is declared, and it is * the knob to turn when a provider starts rate-limiting. */ export declare const DEFAULT_INFERENCE_CONCURRENCY = 4; /** How long a dispatcher's claim survives with nothing completing it. * * Generous relative to a model call, for the asymmetry the admission lease * documents: reclaiming early performs a paid call twice, reclaiming late * delays one call. */ export declare const DEFAULT_INFERENCE_LEASE_MS: number; /** How often the dispatcher looks for work. Fast, because it sits directly in * the latency of every offloaded call — a suspend/resume round trip already * costs one engine wake, and there is no reason to add a second of polling on * top of it. */ export declare const DEFAULT_INFERENCE_POLL_MS = 250; /** Default window on `_voltro_ai_inferences`: 30 days since COMPLETION. * * Env `VOLTRO_AI_INFERENCES_TTL_HOURS`. Shorter than the usage ledger's year * because this table stores the PROMPT verbatim (the dispatcher has to, to * perform the call) — so it is the one AI table where "keep it forever by * default" is a data-protection decision as well as a size one. */ export declare const DEFAULT_INFERENCE_RETENTION_HOURS: number; declare const DEFAULT_INPUT: Schema.Struct<{ prompt: typeof Schema.String; }>; /** The default judge threshold when a judge is configured without one. */ export declare const DEFAULT_JUDGE_THRESHOLD = 0.7; /** Default window on `_voltro_prompts`: a year since last use. Long, because a * registry row is tiny and its value is answering a question about a run you * are only now investigating. Env `VOLTRO_AI_PROMPTS_TTL_HOURS`. */ export declare const DEFAULT_PROMPT_RETENTION_HOURS: number; /** Default window on the resumable-stream log: 7 days since the row was * written. Env `VOLTRO_STREAM_LOG_TTL_HOURS`. * * It is measured in DAYS against a thing whose useful life is MINUTES on * purpose. A resumable stream exists so a browser that lost its socket can * reconnect and replay; nothing reads a week-old log. The width is there so the * bound can never be the reason a reconnect came back short, which makes it a * bound on the DISK rather than a decision about the feature. */ export declare const DEFAULT_STREAM_LOG_RETENTION_HOURS: number; /** Default input schema type when `input` is omitted (`{ prompt: string }`). */ export declare type DefaultAgentInputSchema = typeof DEFAULT_INPUT; /** * Default service implementation backed by `providerFromEnv`. Apps that * want runtime provider switching wire their own AiServiceImpl into a * Layer and provide it instead. */ export declare const defaultAiService: AiServiceImpl; export { defaultSettingsMiddleware } /** * Declare an agent's browser-safe wire contract: its `name` and `input` * schema. The decoded input type is inferred from the schema (like * `defineQuery`). Behaviour (system prompt, tools, model + key, maxSteps) lives * in the paired `*.agent.server.tsx` via `defineAgentExecutor`. * * ```ts * // support.agent.tsx (browser-safe descriptor) * export const support = defineAgent({ * name: 'support', * input: Schema.Struct({ prompt: Schema.String, locale: Schema.optional(Schema.String) }), * }) * ``` */ export declare const defineAgent: (config: { readonly name: string; readonly input?: S; } & AgentAccessOptions) => AgentDescriptor; /** * Bind a server executor to its descriptor. Default-export the result from * `.agent.server.tsx`: * * ```ts * import { defineAgentExecutor } from '@voltro/ai' * import { support } from './support.agent' * import { searchDocs } from '../tools/searchDocs.tool' * * export default defineAgentExecutor(support, { * system: (input) => `…locale=${input.locale}`, * tools: { searchDocs }, * model: { name: 'openai', model: 'gpt-5.5', apiKey: process.env.SUPPORT_OPENAI_KEY }, * maxSteps: 6, * }) * ``` */ export declare const defineAgentExecutor: (descriptor: AgentDescriptor, config: AgentExecutorConfig>) => AgentExecutor; export declare interface DefinedPrompt extends PromptDefinition { /** Content digest of `template` + `system`. The version. */ readonly digest: string; /** Placeholder names found in the template, in first-appearance order. */ readonly variables: ReadonlyArray; /** Substitute the variables. Fails when one is missing — a `{{ticket}}` left * unsubstituted is a prompt that quietly asks the model about the literal * string `{{ticket}}`, which is worse than an error. */ readonly render: (vars?: Record) => Effect.Effect; } /** * Declare an eval suite. Validated at module load — an empty case list, a * duplicate case id, or a nonsensical threshold throws loudly so a broken eval * fails discovery, never at replay time. Call it as the default export of a * `*.eval.ts` file; `voltro eval` discovers + runs it. */ export declare const defineEval: (input: DefineEvalInput) => EvalDefinition; export declare interface DefineEvalInput { readonly name: string; readonly cases: ReadonlyArray; /** Assertions applied to EVERY case. */ readonly assert?: ReadonlyArray; readonly judge?: EvalJudgeConfig; /** Tool names the replay may call (resolved by the runner against the app's * registered `*.tool.tsx`). Empty = a bare generate. */ readonly tools?: ReadonlyArray; } /** * Declare a versioned prompt. * * ```ts * export const triage = definePrompt({ * id: 'support.triage', * system: 'You triage support tickets. Answer only with the category.', * template: 'Ticket:\n{{body}}\n\nCategories: {{categories}}', * label: 'v3-shorter-system', * }) * ``` * * The digest is computed here, from the file — so it exists before any database * does, and a test, a CLI, or an eval can identify a prompt version without a * connection. Registration (`recordPromptVersion`) only adds the human-facing * revision number and the retrievable copy. */ export declare const definePrompt: (definition: PromptDefinition) => DefinedPrompt; /** * Declare an authenticated, server-owned voice/text relay as a *.ws.ts route. * This uses the gateway listener, not the physical RPC subscription socket. * Credentials, provider configuration and raw provider events stay server-side. * No reconnection or replay is attempted: a new connection is a new paid session. */ export declare const defineRealtimeSession: (options: RealtimeSessionOptions) => WebSocketGatewayRoute; export declare const defineTool: (def: { readonly name: string; readonly description: string; readonly input: Schema.Schema; readonly inputJsonSchema?: Record; readonly output?: Schema.Schema.Any; readonly execute?: (input: A) => Effect.Effect; readonly handler?: ToolHandler; readonly timeoutMs?: number; }) => AnyTool; /** * Describe an external tool to the model. * * The provenance prefix is short on purpose — it is paid per tool, per turn — * but it is not decoration. A model that has been told which text is * third-party has something to weigh an instruction in that text against; a * model handed the same sentence with no attribution has nothing. */ export declare const describeExternalTool: (namespace: string, tool: McpToolInfo) => string; /** * One dispatch tick. Never throws. * * A tick that threw would stop every offloaded call in the deployment until * somebody noticed, and every run parked on one would sit there. Failures are * collected and reported. */ export declare const dispatchInferences: (deps: InferenceDispatchDeps, options?: InferenceDispatchOptions) => Promise; /** * Embed a single string to a vector. Returns the embedding through Effect's * success channel; provider failures surface as a typed * `AiError({ reason: 'generation' })`. Defaults to the env-configured * provider (mock unless `AI_EMBED_PROVIDER` is set). * * ```ts * const vec = yield* embed('how do I deploy?') * ``` */ export declare const embed: (text: string, config?: EmbeddingProviderConfig) => Effect.Effect, AiError>; export declare interface EmbeddingProviderConfig { readonly name: EmbeddingProviderName; /** Model id. For 'openai': e.g. 'text-embedding-3-small'. Ignored by the * mock provider (which derives its output from `mockDim`). */ readonly model: string; /** Mock-only: the deterministic output-vector length. Default 8. */ readonly mockDim?: number; /** Native SDK options, e.g. `{ openai: { dimensions: 512 } }` or * `{ voyage: { inputType: 'query', outputDimension: 512 } }`. * Model support is provider-specific; these are passed to every SDK batch. */ readonly providerOptions?: AiProviderOptions; /** Validate the returned vector length. This does not configure the model: * request its dimension through providerOptions (or mockDim for mock). */ readonly expectedDimensions?: number; } /** * Read the embedding-provider config from env: * AI_EMBED_PROVIDER=mock|openai|voyage|cohere (default: mock) * AI_EMBED_MODEL= (default per provider) * AI_EMBED_MOCK_DIM= (mock-only, default 8) */ export declare const embeddingProviderFromEnv: (env?: Record) => EmbeddingProviderConfig; export declare type EmbeddingProviderName = 'mock' | 'openai' | 'voyage' | 'cohere'; /** * Embed many strings in one batched call. Order-preserving: result[i] is the * embedding of texts[i]. Cheaper than N `embed()` calls — the SDK batches up * to the provider's per-call limit. */ export declare const embedMany: (texts: ReadonlyArray, config?: EmbeddingProviderConfig) => Effect.Effect>, AiError>; /** * The ownership rule, in one place for both synthesized routes. * * `threadId` is caller-supplied, so the first message on an id is what opens * the thread: the row is created then, owned by the caller. Every later call * on that id compares owners — a thread with an owner refuses every OTHER * authenticated subject with `AgentThreadAccessDenied`; a thread with none * (opened anonymously, or before ownership existed) admits anyone, because a * row that records no owner cannot honestly claim one. * * Returns the thread. Never a silent `null`: a refusal is the typed error. */ export declare const ensureThreadOwned: (store: DataStore, input: { readonly threadId: string; readonly subjectId: string | null; readonly tenantId?: string | null; }) => Effect.Effect; /** * Derive a USD cost from a token `usage` tally + a model price. An * unknown model (or the `mock` provider) resolves to zero cost rather * than throwing, so cost accounting never breaks a call. */ export declare const estimateCostUsd: (usage: AiUsage | MeteredUsage, opts: { readonly model: string; readonly price?: ModelPrice; readonly actualCostUsd?: number; }) => CostBreakdown; /** * A HARD assertion over the replayed output — a pure predicate, no model in the * loop. These are the deterministic half of the gate: an LLM judge decides "is * this answer good", an assertion decides "does it literally contain the order id * / never leak the system prompt / come back non-empty within the latency bound". */ export declare type EvalAssertion = { readonly kind: 'contains'; readonly value: string; readonly caseInsensitive?: boolean; } | { readonly kind: 'notContains'; readonly value: string; readonly caseInsensitive?: boolean; } | { readonly kind: 'matches'; readonly pattern: string; readonly flags?: string; } | { readonly kind: 'equals'; readonly value: string; readonly trim?: boolean; } | { readonly kind: 'maxLatencyMs'; readonly value: number; } | { readonly kind: 'nonEmpty'; }; export declare interface EvalCase { /** Stable id — names the case in the report + the branch. */ readonly id: string; readonly input: EvalInput; /** The output the ORIGINAL recorded run produced — the baseline the judge * compares the replay against. Optional: a case can gate on assertions alone. */ readonly golden?: string; /** Assertions for THIS case (run in addition to the eval-wide `assert`). */ readonly assert?: ReadonlyArray; } export declare interface EvalCaseResult { readonly id: string; readonly pass: boolean; readonly assertionFailures: ReadonlyArray; /** Present when the eval declared a judge. */ readonly judge?: JudgeVerdict; readonly output: EvalReplayOutput; } export declare interface EvalDefinition extends DefineEvalInput { readonly __eval: true; } /** The captured inputs of a recorded run — what the replay re-sends. `messages` * carries the full prior conversation for a multi-turn recorded run; `prompt` is * the single user turn. */ export declare interface EvalInput { readonly prompt: string; readonly system?: string; readonly messages?: ReadonlyArray; } /** Grade one replay. Injected so the pure runner never needs a provider. */ export declare type EvalJudge = (args: { readonly case: EvalCase; readonly output: EvalReplayOutput; readonly config: EvalJudgeConfig; }) => Promise; /** LLM-judge configuration. The judge scores the replay against the rubric (and, * when present, the golden baseline); a score below `threshold` fails the case. */ export declare interface EvalJudgeConfig { /** What "good" means for these cases — the grading rubric handed to the judge. */ readonly rubric: string; /** Minimum score (0..1) to pass. Default 0.7. */ readonly threshold?: number; } /** Re-run a single case. Injected by the caller — the CLI wires the app's * provider (against a branched store); a test passes a deterministic fake. */ export declare type EvalReplay = (args: { readonly case: EvalCase; readonly def: EvalDefinition; }) => Promise; /** What one replay produced. `latencyMs` feeds the `maxLatencyMs` assertion. */ export declare interface EvalReplayOutput { readonly text: string; readonly latencyMs: number; } export declare interface EvalReport { readonly name: string; readonly ok: boolean; readonly passed: number; readonly failed: number; readonly cases: ReadonlyArray; /** The isolated data branch this suite's replays target — reused branching * machinery (`branchNamespaceName`). Set by the CLI when `--branch` is used. */ readonly branch?: { readonly id: string; }; } /** Run every assertion against one output; return only the failures. Pure. */ export declare const evaluateAssertions: (out: EvalReplayOutput, assertions: ReadonlyArray) => ReadonlyArray; /** * The modalities this build can execute. * * A model whose modality is not in here must not be offered: selecting it * produces a control that cannot run. When a primitive ships, the set grows and * an app that asked this question needs no change — which is the point. */ export declare const executableModalities: () => ReadonlySet; declare const EXECUTOR_BRAND: "@voltro/ai/AgentExecutor"; /** The `exposeAsTool` annotation shape (mirrors @voltro/protocol's, structural * so @voltro/ai needs no protocol dep). `true` alone can't expose — a tool * needs a description, so only the object form (with `description`) exposes. */ export declare type ExposeAsToolLike = boolean | { readonly description: string; readonly confirm?: boolean; readonly maxPerRun?: number; }; export { extractReasoningMiddleware } /** * Flatten an MCP `content` array into the string the model sees, bounded. * * Non-text content is DESCRIBED, not inlined: a server returning a 6 MB base64 * image would otherwise spend a context window (and a lot of money) on * something the text channel cannot use anyway. */ export declare const flattenMcpContent: (result: Record, maxBytes: number) => string; /** One raw entry → the shape callers read. Exported for the parser's tests. */ export declare const fromRaw: (raw: RawGatewayModel) => GatewayModelInfo; /** * Pull the ACTUAL per-call cost (USD) out of an AI-SDK result's provider * metadata, when the Vercel AI Gateway reported it. The gateway attaches * `providerMetadata.gateway.cost` (a USD number or numeric string) to * generate/stream results; returns `undefined` when absent or invalid so the * caller falls back to the price table. Empty, negative and non-finite values * are not evidence of a charge. An explicit zero remains authoritative. */ export declare const gatewayCostUsd: (providerMetadata: unknown) => number | undefined; /** Creator namespaces the Vercel AI Gateway routes to. Open union — the named * arms are autocomplete, any other creator string still works. */ export declare type GatewayCreator = 'openai' | 'anthropic' | 'google' | (string & {}); /** The gateway addresses models as `creator/model` ids. The template-literal * type enforces that shape at COMPILE time — what `providerFromEnv` otherwise * only catches as a runtime throw (`{ name: 'gateway', model: 'gpt-5.5' }` is * now a type error, not a boot crash). */ export declare type GatewayModel = `${GatewayCreator}/${string}`; export declare interface GatewayModelInfo { /** `creator/model` id used to address the model (e.g. `openai/gpt-5.5`). */ readonly id: string; readonly name: string; readonly description?: string; readonly modality: ModelModality; /** Pricing normalised to USD per 1,000,000 tokens (matches `cost.ModelPrice`). */ readonly pricing?: { readonly inputPer1M: number; readonly outputPer1M: number; readonly cachedInputPer1M?: number; }; /** What the model can do, unioned from `tags`, `supported_parameters` and * `modalities` — the three fields that each half-say it and regularly * disagree. */ readonly capabilities?: ModelCapabilities; /** What it accepts and produces, verbatim from the response. */ readonly modalities?: { readonly input: ReadonlyArray; readonly output: ReadonlyArray; }; readonly contextWindow?: number; readonly maxOutputTokens?: number; /** ISO 8601. The wire carries UNIX seconds; see `gatewayCatalog.ts` for why * that is converted here rather than passed along. */ readonly releasedAt?: string; readonly knowledgeCutoff?: string; /** * Whether the provider retains or trains on what is sent. * * It decides whether a model may see customer text, and it was the clearest * casualty of reading the provider type: a field the framework does not pass * through is a decision nobody makes. */ readonly dataPolicy?: ModelDataPolicy; /** * The entry exactly as it came over the wire. * * The normalised fields above are the shared answer to the questions every * app on the gateway asks. `raw` is for the ones only one app asks — a chips * row built from `tags`, a pricing display that shows the vendor's own * structure, `zdr` and `no_training` kept apart rather than folded into * `dataPolicy`. * * Without it, an app with its own display fetches `/v1/models` a SECOND time * and maintains a SECOND parser of the same response — and then has to decide * again, at every new field the gateway adds, whether to wait for us. That is * the decision this field removes, which is why it is one field and not three * more normalised ones. * * Absent on the injected-provider path for the same reason `capabilities` is: * that seam never saw a response, and inventing an empty object would turn * "no wire data" into the claim "the wire carried nothing". */ readonly raw?: RawGatewayModel; } /** * Drop finished resumable streams whose state row predates `olderThan` — * deletes the state row + all its event rows. Run it on a schedule (a cron / * sweep) so the log doesn't grow without bound. Returns the count of streams * collected. Only DONE streams are eligible (an in-flight one is never GC'd). */ export declare const gcResumableStreams: (store: DataStore, opts: { readonly olderThan: Date; }) => Effect.Effect; export declare interface GeneratedAudio { readonly data: Uint8Array | string; readonly mediaType: string; } /** One generated image. `data` is the raw bytes (the SDK also exposes base64; * the union keeps a base64 string valid for callers that pass one through). */ export declare interface GeneratedImage { readonly data: Uint8Array | string; readonly mediaType: string; /** Metadata belonging to this image, not the entire generation. */ readonly providerMetadata?: unknown; } /** Preserve provider-call boundaries: grounding for one batch is not another's. */ export declare interface GeneratedImageCall { readonly images: ReadonlyArray; readonly usage?: MediaUsage; readonly providerMetadata?: unknown; } /** The produced video. Providers return EITHER a `url` (the common case — video * files are large) OR inline `data` bytes; exactly one is populated. */ export declare interface GeneratedVideo { readonly data?: Uint8Array; readonly url?: string; readonly mediaType: string; } /** * Generate one or more images from a text prompt. Returns the decoded image * bytes + media type, a usage tally, and provider metadata. Provider failures * surface as `AiError({ reason: 'generation' })`. */ export declare const generateImage: (options: GenerateImageOptions) => Effect.Effect; export declare interface GenerateImageOptions { /** What to draw. */ readonly prompt: string; /** Image-editing references and optional mask: URLs, data URLs, base64 or bytes. */ readonly inputs?: { readonly images: ReadonlyArray; readonly mask?: string | Uint8Array; }; readonly seed?: number; /** Interrupt the call and signal provider I/O. Fiber interruption does the same. */ readonly abortSignal?: AbortSignal; /** Override the provider/model for a single call (defaults to `providerFromEnv()`). * `anthropic` has no image model; use `openai` or `gateway`. */ readonly provider?: ProviderConfig; /** Number of images to generate. Default 1 (provider-dependent). */ readonly n?: number; /** Exact pixel size, `{width}x{height}` (mutually exclusive with `aspectRatio` * per provider). */ readonly size?: `${number}x${number}`; /** Aspect ratio, `{width}:{height}`. */ readonly aspectRatio?: `${number}:${number}`; /** Provider-specific options forwarded to the SDK. */ readonly providerOptions?: AiProviderOptions; /** Retries of a failed provider call (the SDK's default is 2). A charged call * that fails with a retryable status is paid again on every retry — set 0 * where the caller retries on its own terms. */ readonly maxRetries?: number; } export declare interface GenerateImageResult { readonly images: ReadonlyArray; /** Original per-call metadata, including grounding. No response headers or bodies. */ readonly calls: ReadonlyArray; readonly usage?: MediaUsage; /** Raw provider/gateway metadata — feed to `gatewayCostUsd(...)` for the * gateway's real per-call cost (see {@link GenerateTextResult.providerMetadata}). */ readonly providerMetadata?: unknown; } export declare const generateObject: (options: GenerateObjectOptions) => Effect.Effect, AiError>; /** * Structured output validated against its already-rendered JSON Schema. * * Extracted from `generateObject` rather than written beside it: an offloaded * inference is performed by a dispatcher that has the stored JSON Schema and no * access to the caller's Effect Schema (a closure cannot be journaled), and the * awaiting workflow decodes on resume. Two implementations of "ask the model for * JSON matching this shape" would have been two sets of provider-option * handling, fallback behaviour and span attributes. */ export declare const generateObjectJson: (options: GenerateObjectJsonOptions) => Effect.Effect, AiError>; export declare interface GenerateObjectJsonOptions { /** A JSON Schema, already rendered. */ readonly jsonSchema: Record; readonly prompt: string; readonly system?: string; readonly provider?: ProviderConfig; readonly fallbacks?: ProviderFallbacks; readonly maxTokens?: number; readonly temperature?: number; readonly providerOptions?: AiProviderOptions; } export declare interface GenerateObjectOptions { /** Effect Schema the model output must satisfy. Converted to JSON * Schema for the SDK, then decoded back through this schema so the * result carries branded types + refinements (not just JSON shape). */ readonly schema: Schema.Schema; readonly prompt: string; readonly system?: string; readonly provider?: ProviderConfig; /** Ordered provider/model fallback chain — see {@link ProviderFallbacks}. A * `decode` failure does NOT fall through (another provider won't fix a schema * mismatch); only a `generation` failure does. */ readonly fallbacks?: ProviderFallbacks; readonly maxTokens?: number; readonly temperature?: number; /** Provider-specific options forwarded to the SDK (e.g. reasoning effort). * See `AiProviderOptions`. */ readonly providerOptions?: AiProviderOptions; } export declare interface GenerateObjectResult { readonly object: A; /** Allowlisted usage/cost evidence from the provider that actually answered. */ readonly evidence: AiCallEvidence; readonly usage: { readonly inputTokens: number | undefined; readonly outputTokens: number | undefined; readonly totalTokens: number | undefined; }; /** Raw provider/gateway metadata (see {@link GenerateTextResult.providerMetadata}): * feed to `gatewayCostUsd` for the gateway's real per-call cost. */ readonly providerMetadata?: unknown; } /** * Run an adaptive tool-calling loop AND constrain the final answer to an * Effect Schema — the batch-agent analogue of `generateObject` that also gets * tool access (the Convex agents' "call Jira tools on demand, then emit the * structured summary" pattern). * * Mechanism: the AI SDK's `generateText` drives the LLM↔tool loop * (`stopWhen: stepCountIs(maxSteps)`); `experimental_output: Output.object` * makes the terminal step emit a JSON object constrained to the schema's * JSON Schema. The SDK's parsed `experimental_output` is then re-decoded * through the SAME Effect Schema, so brands/refinements hold and malformed * output surfaces as `AiError({ reason: 'decode' })`. * * Tool bodies inherit the caller's services: the ambient Effect runtime is * captured (`Effect.runtime`) and threaded into the tool context, so a tool * may `yield* JiraService` and reach the layer provided at the * action/workflow boundary. */ export declare const generateObjectWithTools: (options: GenerateObjectWithToolsOptions) => Effect.Effect, AiError>; export declare interface GenerateObjectWithToolsOptions { /** Effect Schema the FINAL object must satisfy. The model runs the tool * loop, then emits one object matching this schema; it is decoded back * through the schema so refinements/brands hold. */ readonly schema: Schema.Schema; readonly prompt: string; readonly system?: string; readonly provider?: ProviderConfig; /** Abort the turn — the provider call and every tool, through one signal. */ readonly abortSignal?: AbortSignal; /** Ordered provider/model fallback chain — see {@link ProviderFallbacks}. */ readonly fallbacks?: ProviderFallbacks; readonly maxTokens?: number; readonly temperature?: number; /** Provider-specific options forwarded to the SDK (e.g. reasoning effort). * See `AiProviderOptions`. */ readonly providerOptions?: AiProviderOptions; /** Tools the model may call while reasoning toward the final object. The * SDK drives the call→execute→feed-back loop up to `maxSteps`. Tool bodies * reach the caller's services through the ambient runtime (see below). */ readonly tools: Record; /** Max LLM↔tool round-trips before the model must produce the object. * Default 8. */ readonly maxSteps?: number; /** Request context threaded into `*.tool.tsx` executor bodies. The caller's * ambient Effect runtime is captured automatically and merged in, so tool * bodies may `yield*` services (`JiraService`, the store layer, …). */ readonly toolContext?: ToolContext; } export declare const generateSpeech: (options: GenerateSpeechOptions) => Effect.Effect; export declare interface GenerateSpeechOptions { /** Text to speak. */ readonly text: string; /** Interrupt native or registered providers and signal their I/O. */ readonly abortSignal?: AbortSignal; /** Override the provider/model (defaults to `providerFromEnv()`). `anthropic` * has no speech model; use `openai` or `gateway`. */ readonly provider?: ProviderConfig | RegisteredSpeechConfig; /** Provider voice id. */ readonly voice?: string; /** Desired audio container, e.g. `'mp3'` / `'wav'`. */ readonly outputFormat?: 'mp3' | 'wav' | (string & {}); /** Free-text delivery instructions, e.g. "speak slowly". */ readonly instructions?: string; /** Speaking rate multiplier. */ readonly speed?: number; /** ISO 639-1 language code (or `'auto'`). */ readonly language?: string; /** Provider-specific options forwarded to the SDK. */ readonly providerOptions?: AiProviderOptions; } export declare interface GenerateSpeechResult { readonly audio: GeneratedAudio; /** * What the call consumed. * * The speech model SPEC reports none, so a gateway-routed call still leaves * this undefined. A REGISTERED provider is the other case, and it is not a * property of the model any more: an app's own adapter knows the billed * quantity, because its vendor invoices on it — characters for ElevenLabs, * Cartesia and OpenAI, seconds for others. * * So it takes a `MeteredUsage` too, and that is what makes speech spend * reachable: without a shape that can say "4200 characters", the number the * adapter already has has nowhere to go, and `recordAiUsage` never sees the * call at all. Not un-priced — absent from the ledger. */ readonly usage?: MediaUsage | MeteredUsage; readonly providerMetadata?: unknown; } /** * Generate text from a prompt or conversation. Returns text, sources and usage. */ export declare const generateText: (options: GenerateTextOptions) => Effect.Effect; export declare interface GenerateTextOptions { /** Tools the model may call on the way to its answer — the same tool-loop * `generateObjectWithTools` runs, for a TEXT result. With tools, `usage` is * the SUM over every step, so a usage row records the whole loop. */ readonly tools?: Record; /** Max LLM↔tool round-trips before the model must answer. Default 8. */ readonly maxSteps?: number; /** Request context threaded into the tool executors. */ readonly toolContext?: ToolContext; /** Abort the turn: the provider call is interrupted AND every tool sees the * same signal (`ctx.abortSignal`), so a tool call the model emitted just * before the abort does not run after it. */ readonly abortSignal?: AbortSignal; /** The user prompt. The SDK auto-wraps it as the single user message. */ readonly prompt?: string; /** Complete conversation, including the latest user message. Supply instead of prompt. */ readonly messages?: ReadonlyArray; /** Optional system message. Steers the model independently of the prompt. */ readonly system?: string; /** Override the provider/model for a single call. */ readonly provider?: ProviderConfig; /** Ordered provider/model fallback chain — see {@link ProviderFallbacks}. */ readonly fallbacks?: ProviderFallbacks; /** Soft ceiling on output tokens. Provider-dependent. */ readonly maxTokens?: number; /** Model sampling temperature; explicit zero is preserved. Provider support varies. */ readonly temperature?: number; /** Provider-specific options forwarded to the SDK (e.g. reasoning effort). * See `AiProviderOptions`. */ readonly providerOptions?: AiProviderOptions; } export declare interface GenerateTextResult { readonly text: string; readonly sources: ReadonlyArray; readonly usage: { readonly inputTokens: number | undefined; readonly outputTokens: number | undefined; readonly totalTokens: number | undefined; }; /** Raw provider/gateway metadata from the SDK result. The Vercel AI Gateway * reports the ACTUAL per-call cost here (`gateway.cost`); pass this to * `gatewayCostUsd(...)` → `recordAiUsage({ actualCostUsd })` to record the * real charge for a gateway-routed model the static price table can't price. * `undefined` for direct providers / the mock. */ readonly providerMetadata?: unknown; } /** * Generate a video from a text prompt (optionally conditioned on reference * images/videos). Returns the URL the provider yields OR inline bytes, plus * provider metadata; provider failures surface as `AiError`. * * Unlike image/speech, this calls the resolved `VideoModelV4` DIRECTLY rather * than the SDK's `experimental_generateVideo`. That helper force-DOWNLOADS a * URL-typed video into bytes (a potentially multi-GB fetch), discarding the * URL; calling the model spec directly lets us return whichever form the * provider produced (`{ url }` or `{ data }`), matching {@link GeneratedVideo}. * No SDK-less gateway fallback is needed — the installed `ai` ships a video * primitive and the gateway provider exposes a native `.video()` model. */ export declare const generateVideo: (options: GenerateVideoOptions) => Effect.Effect; /** A URL shorthand or an explicitly typed file/URL for provider routing. */ export declare type GenerateVideoInput = string | Experimental_VideoModelV4File; export declare interface GenerateVideoInputs { /** Reference / starting-frame image URL (image-to-video). */ readonly image?: GenerateVideoInput; /** Reference image/video URLs (reference-to-video). */ readonly references?: ReadonlyArray; readonly frameImages?: ReadonlyArray<{ readonly frameType: 'first_frame' | 'last_frame'; readonly image: GenerateVideoInput; }>; } export declare interface GenerateVideoOptions { /** What to animate. */ readonly prompt: string; /** Interrupt the call and signal provider I/O. Fiber interruption does the same. */ readonly abortSignal?: AbortSignal; /** Override the provider/model (defaults to `providerFromEnv()`). Only the * Vercel AI Gateway (`gateway`) ships a video model among the wired * providers; `openai` / `anthropic` raise an `AiError`. */ readonly provider?: ProviderConfig; readonly params?: GenerateVideoParams; readonly inputs?: GenerateVideoInputs; /** Maximum decoded inline video bytes. Default 64 MiB; URL results are not downloaded. */ readonly maxInlineBytes?: number; /** Provider-specific options forwarded to the model. */ readonly providerOptions?: AiProviderOptions; } export declare interface GenerateVideoParams { /** `{width}:{height}`, or `adaptive` where the model derives it from the reference input. */ readonly aspectRatio?: `${number}:${number}` | 'adaptive'; readonly durationSeconds?: number; readonly resolution?: `${number}x${number}`; readonly fps?: number; readonly seed?: number; readonly generateAudio?: boolean; } export declare interface GenerateVideoResult { readonly video: GeneratedVideo; /** The video model spec reports no usage; present for surface parity. */ readonly usage?: MediaUsage; readonly providerMetadata?: unknown; } /** The currently-installed global middleware stack. */ export declare const getAiMiddleware: () => ReadonlyArray; /** * Fetch the live model catalog from the Vercel AI Gateway. Uses the default * `gateway` (reads `AI_GATEWAY_API_KEY`) unless an explicit `apiKey`/`baseURL` * is given. Optionally filter by modality (the picker usually wants `language`). * `gatewayProvider` is injectable for tests. */ export declare const getAvailableModels: (opts?: { readonly apiKey?: string; readonly baseURL?: string; readonly modality?: ModelModality; readonly gatewayProvider?: { getAvailableModels: () => Promise<{ models: GatewayLanguageModelEntry[]; }>; }; }) => Promise>; /** Load a thread's messages in chronological order (for reload / resume). */ export declare const getMessages: (store: DataStore, threadId: string) => Effect.Effect>; /** The currently-installed override (or null). */ export declare const getProviderOverride: () => ProviderConfig | null; export declare const getThread: (store: DataStore, threadId: string) => Effect.Effect; /** FNV-1a-seeded deterministic float vector in [-1, 1], length `dim`. */ export declare const hashToVector: (text: string, dim: number) => number[]; /** * Convert a persisted thread (`agent_messages` rows) into the model-message * list that gives an assistant turn its CONVERSATION MEMORY. Without this the * model only ever sees the latest prompt + system — it can't remember earlier * turns. Keeps user/assistant text turns (skips the live streaming row, empty * rows, and system/tool rows — `system` is passed separately), ordered by the * thread's monotonic `order`/`stepOrder`. */ export declare const historyToModelMessages: (history: ReadonlyArray) => ModelMessage[]; /** * MCP over Streamable HTTP. * * SSRF note, stated rather than silently assumed: `url` is app configuration. * Nothing here validates that it points outside your own network, because a * legitimate deployment mounts an MCP server on a private address. If the URL * can ever come from user or model input in your app, gate it there — this * layer treats it as trusted and everything the URL RETURNS as untrusted. */ export declare const httpMcpTransport: (options: HttpMcpTransportOptions) => McpTransport; export declare interface HttpMcpTransportOptions { /** The server's Streamable-HTTP endpoint. App configuration — NEVER derived * from anything a model produced. */ readonly url: string; /** Namespace, used only for error attribution. */ readonly server: string; /** * Extra request headers — this is where an `Authorization` goes. * * Read it from the environment at the call site (`process.env.GITHUB_MCP_TOKEN`). * The framework ships no credential value for any server, and there is * deliberately no "default token" field here to make that accidental. */ readonly headers?: Record; readonly bounds?: Partial; /** Injectable for tests. Defaults to the global `fetch`. */ readonly fetchImpl?: typeof fetch; } export declare interface InferenceDispatchDeps { readonly store: DataStore; /** * Resolve a transcription `storageRef` to bytes. * * Injected here for the same reason `transcribe` injects it: this package must * not learn about buckets, and the read carries the app's tenancy and signing. * Required only when an offloaded transcription used a ref — a queued call * with a `url` or inline bytes needs nothing. */ readonly resolveTranscriptionRef?: (ref: string) => Promise<{ bytes: Uint8Array; mediaType: string; }>; /** * Persist a media artifact a provider produced — the storage plugin's * `mediaPersist` capability, handed over by the host. Required for a `media` * job; a queued media job with no persistence is failed with the reason, * never resumed with bytes in the payload. */ readonly persistMedia?: PluginMediaPersistCapability; /** * Resume the parked run with the call's outcome. * * MUST be idempotent — completing an already-resolved durable deferred is a * no-op, which is what makes the perform → resume → mark ordering safe. */ readonly resume: (input: { readonly executionId: string; readonly workflowName: string; readonly signalName: string; readonly payload: unknown; }) => Promise; /** Runs an `@voltro/ai` Effect. Injected so this module needs no runtime. */ readonly run: (effect: Effect.Effect) => Promise; readonly now?: () => number; readonly replicaId?: string; readonly log?: { readonly info: (message: string, meta?: Record) => void; readonly warn: (message: string, meta?: Record) => void; }; } export declare interface InferenceDispatchOptions { readonly batch?: number; readonly concurrency?: number; readonly leaseMs?: number; readonly maxAttempts?: number; readonly baseDelayMs?: number; readonly maxDelayMs?: number; /** Between polls of a task-provider media job. Default {@link DEFAULT_MEDIA_TASK_POLL_MS}. */ readonly taskPollMs?: number; /** A task still pending this long after submission is failed. Default {@link DEFAULT_MEDIA_TASK_TIMEOUT_MS}. */ readonly taskTimeoutMs?: number; } export declare interface InferenceDispatchResult { /** `false` when nothing was claimed AND nothing was waiting — so an empty * tick and a tick that could not read are never the same report. */ readonly examined: boolean; readonly claimed: number; readonly succeeded: number; readonly failed: number; readonly retried: number; readonly reclaimed: number; /** Task-provider media jobs asked once and found still pending — released * back to the queue for a later claim, without a lease held across the job. */ readonly polled: number; readonly failures: ReadonlyArray<{ readonly id: string; readonly detail: string; }>; } /** * What the dispatcher should do with a call that just failed. * * PURE, and separated for the usual reason: the interesting cases here are * "when do we give up" and "does the waiting run learn about it", and neither * needs a store, a clock or a provider to decide. */ export declare type InferenceFailureAction = { readonly kind: 'retry'; readonly attempts: number; readonly delayMs: number; } | { readonly kind: 'give-up'; readonly attempts: number; }; /** The signal name a parked run awaits for one queued call. Derived from the * row id, so the run and the dispatcher agree without sharing anything. */ export declare const inferenceSignalName: (inferenceId: string) => string; /** Discovery predicate for `*.agent.tsx` exports. */ export declare const isAgentDescriptor: (x: unknown) => x is AnyAgentDescriptor; /** Discovery predicate for `*.agent.server.tsx` default exports. */ export declare const isAgentExecutor: (x: unknown) => x is AnyAgentExecutor; export declare const isEvalDefinition: (value: unknown) => value is EvalDefinition; /** * Is this external tool a WRITE, for the purpose of the `includeWrites` gate * and the confirm default? * * The default answer is YES for everything. That is not pessimism for its own * sake: the classification exists to decide whether an agent may take an action * with side effects, and the only party who can answer that for an external * server is the app that chose to mount it. So the app answers, with `readOnly`. * `trustToolHints` delegates the answer back to the server, explicitly, in one * place a reviewer can find. */ export declare const isExternalWrite: (tool: McpToolInfo, namespace: string, policy: McpToolPolicy) => boolean; export declare const isMeteredUsage: (u: AiUsage | MeteredUsage) => u is MeteredUsage; /** Is this a rendered prompt or a bare string? The discriminator `aiStep` uses * so `prompt:` can accept either without a wrapper type at every call site. */ export declare const isRenderedPrompt: (value: unknown) => value is RenderedPrompt; /** Whether a stream with this id is currently registered (in flight). */ export declare const isStreamActive: (streamId: string) => boolean; /** Is `x` a `defineTool(...)` result? */ export declare const isTool: (x: unknown) => x is AnyTool; export declare const isUsableMcpToolName: (name: unknown, maxChars: number) => name is string; export declare interface JudgeVerdict { readonly pass: boolean; /** 0..1. Below `judge.threshold` fails the case even when `pass` is true. */ readonly score: number; readonly reason: string; } export declare const knowledgeCutoffOf: (raw: RawGatewayModel) => string | undefined; /** * The real LLM judge: asks the model for a structured `{ pass, score, reason }` * verdict via `generateObject` (schema-constrained, so a malformed grade surfaces * as a typed error rather than a parse crash). A judge failure is turned into a * failing verdict rather than throwing, so one flaky grade fails ONE case instead * of aborting the whole eval — the report stays a complete deploy-gate signal. */ export declare const llmJudge: EvalJudge; /** * The synthesized `.messages` body — load the thread's messages, for the * subject that owns the thread. Same rule as `send`: an owned thread refuses * every other authenticated subject; an unknown or unowned thread reads as * its rows (empty, for an id nobody has written to). Re-checked on every * reactive delivery, since the query re-runs its executor. */ export declare const loadAgentMessages: (store: DataStore, threadId: string, subjectId?: string | null) => Effect.Effect, AgentThreadAccessDenied>; /** * A middleware that logs every generate/stream call's model (and token usage * for generate). Dependency-free demonstration of the seam and a useful dev * default. Pass `log` to route into `@voltro/logger` or a metrics sink instead * of `console`. * * ```ts * setAiMiddleware(loggingMiddleware()) * ``` */ export declare const loggingMiddleware: (opts?: { readonly log?: (event: AiCallLog) => void; }) => AiMiddleware; /** * Build ONE external tool. Exported so the gate can be tested at the sharpest * point: hand this a tool the policy denies and it still refuses at call time, * without touching the transport. */ export declare const makeMcpTool: (server: McpServer, tool: McpToolInfo, policy: McpToolPolicy, bounds: McpBounds) => AnyTool; export declare const maxOutputTokensOf: (raw: RawGatewayModel) => number | undefined; export declare const MCP_BOUNDS_DEFAULTS: McpBounds; /** Identity this client reports in `initialize`. No version literal here — the * caller passes its own, so a published build reports the lockstep release. */ export declare const MCP_CLIENT_INFO: { readonly name: string; readonly version: string; }; /** The MCP revision this client requests. Mirror of `@voltro/mcp`'s. */ export declare const MCP_CLIENT_PROTOCOL_VERSION = "2025-06-18"; /** * The ceilings applied to everything an external server sends. * * Resolution order is explicit option → environment variable → default, so an * operator can tighten a deployment without a code change and an app can pin a * value the operator cannot loosen. */ export declare interface McpBounds { /** How many of a server's tools may be mounted, AFTER the policy gate. * A server advertising 4 000 tools would otherwise buy the whole context * window. Env `VOLTRO_MCP_MAX_TOOLS`. */ readonly maxTools: number; /** Ceiling on one tool's description — the prompt-injection surface. * Env `VOLTRO_MCP_MAX_DESCRIPTION_CHARS`. */ readonly maxDescriptionChars: number; /** Ceiling on a tool's advertised input JSON Schema, serialized. A schema * over this is DROPPED rather than truncated — a half-schema would make the * model call the tool wrongly. Env `VOLTRO_MCP_MAX_SCHEMA_BYTES`. */ readonly maxSchemaBytes: number; /** How deep + how wide the input schema may be before it is pruned. Guards * the recursive sanitizer itself against a schema built to blow the stack. */ readonly maxSchemaDepth: number; readonly maxSchemaNodes: number; /** Ceiling on one tool-call RESULT as handed back to the model. Truncated * with a visible marker. Env `VOLTRO_MCP_MAX_RESULT_BYTES`. */ readonly maxResultBytes: number; /** Hard ceiling on any single HTTP/stdio response body, applied while * reading — an unbounded stream is cancelled, not buffered. * Env `VOLTRO_MCP_MAX_RESPONSE_BYTES`. */ readonly maxResponseBytes: number; /** Per-JSON-RPC-request deadline. Env `VOLTRO_MCP_TIMEOUT_MS`. */ readonly requestTimeoutMs: number; /** Ceiling on a tool NAME. Longer names are dropped, not truncated — * a truncated name would not be the name we allowed. */ readonly maxToolNameChars: number; } /** * `tools/call`. Returns the flattened, bounded text the model receives. * * `isError: true` is surfaced as text prefixed with `Error:` rather than as a * failed Effect: a tool that failed IS information the model should get and act * on, and that is exactly how `buildSdkTools` treats an app tool's rejection. */ export declare const mcpCallTool: (transport: McpTransport, server: string, toolName: string, args: Record, bounds: McpBounds) => Effect.Effect; /** * Typed MCP client failure. * * `transport` — the socket/process/HTTP layer failed or timed out. * `protocol` — we reached the server and it answered with something that is * not a usable MCP response (a JSON-RPC error, a malformed body). * `bounds` — the server answered, and the answer exceeded a stated bound. * Kept SEPARATE from `protocol` on purpose: "the server is broken" * and "the server sent 40 MB" are different operational events and * the second one is the one you want to alert on. * `policy` — the app's own allow/deny gate refused. Never the server's fault. */ export declare class McpError extends McpError_base { } declare const McpError_base: Schema.TaggedErrorClass; } & { /** The app-chosen namespace of the server, never a server-supplied string. */ server: typeof Schema.String; reason: Schema.Literal<["transport", "protocol", "bounds", "policy"]>; message: typeof Schema.String; }>; /** The MCP handshake: `initialize`, then the `notifications/initialized` the * spec requires before any other request. */ export declare const mcpInitialize: (transport: McpTransport, server: string, clientInfo?: { readonly name: string; readonly version: string; }) => Effect.Effect<{ readonly protocolVersion: string; readonly serverName: string; }, McpError>; export declare interface McpJsonRpcRequest { readonly jsonrpc: '2.0'; readonly id?: string | number | null; readonly method: string; readonly params?: Record; } export declare interface McpJsonRpcResponse { readonly jsonrpc: '2.0'; readonly id: string | number | null; readonly result?: unknown; readonly error?: { readonly code: number; readonly message: string; }; } /** * `tools/list`, bounded. * * Everything a server can inflate is capped HERE rather than at the call site: * a tool whose name is unusable or whose schema is oversized is DROPPED (with * its name reported), a description is sanitized + truncated, and the list is * cut to `maxTools` after sorting so the cut is deterministic across calls * rather than "whichever ones the server put first this time". */ export declare const mcpListTools: (transport: McpTransport, server: string, bounds: McpBounds) => Effect.Effect<{ readonly tools: ReadonlyArray; readonly dropped: ReadonlyArray<{ readonly name: string; readonly reason: string; }>; }, McpError>; /** An external server, as the app names and reaches it. */ export declare interface McpServer { /** * The app's own name for this server. Prefixes every tool tag * (`github.create_issue`), so it is what an allow/deny rule matches on. * App-chosen — never the server's self-reported name, which is untrusted. */ readonly namespace: string; readonly transport: McpTransport; } export declare type McpToolDecision = { readonly admitted: true; readonly spec: SynthesizedTool; } | { readonly admitted: false; readonly reason: string; }; /** * THE gate. One pure function, called at mount AND re-called inside every tool * body before a request leaves the process, so a toolset that was tampered with * after construction still cannot reach the server. * * Order matters and is asserted in the tests: default-deny, then the shared * `passesPolicy` (deny beats allow), then the writes gate. */ export declare const mcpToolDecision: (tool: McpToolInfo, namespace: string, policy: McpToolPolicy) => McpToolDecision; /** One tool as an external server advertises it, already bounded + sanitized. */ export declare interface McpToolInfo { readonly name: string; readonly description: string; readonly inputSchema: Record; /** Advertised execution contract. This client does not negotiate MCP tasks. */ readonly execution?: { readonly taskSupport: 'forbidden' | 'optional' | 'required'; }; /** * The server's own hints about the tool. ADVISORY ONLY — see * `mcpTools.ts`; the server is the untrusted party, so its claim that a tool * is read-only cannot be what decides whether the tool is allowed to run. */ readonly annotations?: { readonly readOnlyHint?: boolean; readonly destructiveHint?: boolean; readonly idempotentHint?: boolean; }; } /** * The mount policy. Extends `AppToolPolicy` rather than paralleling it: an app * that already expresses its agent policy in one shape expresses this one in * the same shape, and the two lists can literally be the same object. */ export declare interface McpToolPolicy extends AppToolPolicy { /** * Tools the APP declares to be reads (tags, `*` globs, or a RegExp), matched * against the namespaced tag. This is the app's judgement, and it is the only * one that counts by default — see {@link isExternalWrite}. */ readonly readOnly?: ReadonlyArray | RegExp; /** * Accept the server's `annotations.readOnlyHint` as evidence a tool is a read. * * Default FALSE, and the default is the security decision. For an app tool the * read/write split comes from the descriptor KIND, which somebody in this * repository wrote. For an external tool it would come from the untrusted * party, who can flip it between two `tools/list` calls — so trusting it would * let a server talk its way past `includeWrites: false`. */ readonly trustToolHints?: boolean; /** Ceilings on everything the server sends. See {@link McpBounds}. */ readonly bounds?: Partial; } /** The `Record` an agent loop wants, without the inventory. */ export declare const mcpTools: (server: McpServer, policy: McpToolPolicy) => Effect.Effect, McpError>; /** A mounted server's toolset. */ export declare interface McpToolset { readonly namespace: string; /** Keyed by the namespaced tag — the shape the agent loop consumes. */ readonly tools: Record; /** The same inventory type `synthesizeToolSpecs` returns, for a confirm-UI. */ readonly specs: ReadonlyArray; /** Everything the server advertised that did NOT mount, and why. Surfaced * rather than logged: a tool silently missing from an agent's set is a * support ticket that starts "the agent just says it can't do that". */ readonly dropped: ReadonlyArray<{ readonly name: string; readonly reason: string; }>; /** The bounds actually in force (after env + defaults resolved). */ readonly bounds: McpBounds; readonly close: () => Effect.Effect; } /** * Connect to an external MCP server, list its tools, and mount the ones this * app's policy admits. * * ```ts * const github = yield* mcpToolset( * { namespace: 'github', transport: httpMcpTransport({ * server: 'github', url: process.env.GITHUB_MCP_URL!, * headers: { authorization: `Bearer ${process.env.GITHUB_MCP_TOKEN!}` }, * }) }, * { allow: ['github.get_*', 'github.search_*'], readOnly: ['github.get_*', 'github.search_*'] }, * ) * yield* generateObjectWithTools({ prompt, tools: github.tools, schema: Result }) * ``` */ export declare const mcpToolset: (server: McpServer, policy: McpToolPolicy) => Effect.Effect; /** `.` — the tag an allow/deny rule matches. */ export declare const mcpToolTag: (namespace: string, toolName: string) => string; /** * One JSON-RPC round trip against an external server. The seam every test uses * — a test supplies an in-memory transport and never opens a socket, which is * what makes the untrusted-input bounds assertable on their own. */ export declare interface McpTransport { /** Send a request; resolve with its response. */ readonly send: (request: McpJsonRpcRequest) => Effect.Effect; /** Send a notification (no id, no response expected). */ readonly notify: (request: McpJsonRpcRequest) => Effect.Effect; /** Release the socket / child process. Idempotent. */ readonly close: () => Effect.Effect; } /** One artifact a finished task produced — a URL the dispatcher re-hosts, or * bytes (raw or base64) it stores. Same shape the built-in generators hand * over, so both paths persist identically. */ export declare interface MediaTaskArtifact { readonly url?: string; readonly data?: Uint8Array | string; readonly mediaType: string; } /** * The provider's handle for one submitted task. * * Stored on the queue row VERBATIM as JSON and handed back to `poll`, so it * may carry whatever the provider needs to find the task again — a request * id, the endpoint it was submitted to, a region. It must survive * `JSON.stringify`: a handle that does not is refused before it is stored, * because a handle that came back different would poll a task that does not * exist. */ export declare type MediaTaskHandle = Readonly>; export declare interface MediaTaskProvider { /** Submit the job; return the provider's handle as soon as it has one. */ readonly submit: (request: MediaTaskSubmitRequest) => Effect.Effect; /** Ask the provider about one handle. Called once per dispatcher claim. */ readonly poll: (handle: MediaTaskHandle, request: MediaTaskSubmitRequest) => Effect.Effect; /** * The provider de-duplicates on `idempotencyKey`: a second `submit` with the * same key returns the SAME task's handle rather than starting a second job. * * This closes the one window the handle cannot: a worker lost between the * provider accepting the job and the handle reaching the row. Without this * flag such a row is resumed with `AiOffloadError.outcome === 'unknown'` — * the job may have run and been charged for, and nothing can find it. With * it, the reclaiming worker submits again under the same key, receives the * same handle, and polls it. * * Say it only when the provider guarantees it. A `true` over a provider that * ignores the key turns the lost-worker case into a second paid job — the * exact outcome the `unknown` report exists to avoid. */ readonly idempotentSubmit?: boolean; } /** The task provider registered under `name`, or `undefined`. */ export declare const mediaTaskProvider: (name: string) => MediaTaskProvider | undefined; export declare type MediaTaskStatus = { readonly status: 'pending'; /** How long to wait before asking again, when the provider says. Bounded * below by the dispatcher's own poll interval. */ readonly retryAfterMs?: number; } | { readonly status: 'succeeded'; readonly artifacts: ReadonlyArray; /** What the task consumed, in the provider's unit — seconds of video, * characters of text, or tokens. Recorded in the usage ledger under * the job's row and attempt. */ readonly usage?: MediaUsage | MeteredUsage; readonly providerMetadata?: unknown; } | { readonly status: 'failed'; /** The provider's own message. It is what the parked run is told. */ readonly message: string; /** * Whether a fresh submission could succeed. Default `false`: a task the * provider ran and failed is a paid job, and re-submitting it is a second * charge, not a retry. Say `true` for a transient failure the provider * itself names as such (capacity, an upstream outage) — then the job is * re-submitted under the next attempt, with the dispatcher's backoff. */ readonly retryable?: boolean; }; /** * What the dispatcher hands `submit`. * * `idempotencyKey` is derived from the queue row and the attempt — the same * two things the artifacts are stored under — so it is stable across a lost * worker and different for a deliberate re-submission. Forward it to the * provider when the provider takes one: see `MediaTaskProvider.idempotentSubmit` * for what that buys. */ export declare interface MediaTaskSubmitRequest { readonly modality: 'image' | 'video' | 'speech'; readonly prompt: string; readonly model?: string; /** The job's `params`, as declared on `mediaStep`. */ readonly params: Readonly>; /** URL inputs, as declared on `mediaStep`. Never bytes. */ readonly inputs: Readonly>; readonly providerOptions?: AiProviderOptions; readonly tenantId: string | null; /** `#`. */ readonly idempotencyKey: string; /** Aborted on Effect interruption; pass to provider I/O. */ readonly abortSignal?: AbortSignal; } /** Token tally, mirroring `GenerateTextResult['usage']`. Only `generateImage` * reports one (the speech + video model specs carry no usage), which is why * it's optional on every media result. */ export declare interface MediaUsage { readonly inputTokens: number | undefined; readonly outputTokens: number | undefined; readonly totalTokens: number | undefined; } /** * Single-process resumable-stream store. Holds each stream's log in memory — * perfect for dev, tests, and single-node self-host. A multi-node deployment * needs `dataStoreResumableStreamStore` (or a Redis backend) so every node * shares the log and the producer election. */ export declare const memoryResumableStreamStore: () => ResumableStreamStore; export declare type MessagePart = { readonly type: 'text'; readonly text: string; } | { readonly type: 'tool'; readonly toolCallId: string; readonly toolName: string; /** 'input-available' once the model emitted args; 'output-available' once the tool ran. */ readonly state: 'input-available' | 'output-available' | 'output-error'; readonly input?: unknown; readonly output?: unknown; readonly errorText?: string; } /** The model's reasoning ("thinking"), separate from the answer text — a UI * renders it in a collapsible pane. Accumulated like `text`. */ | { readonly type: 'reasoning'; readonly text: string; } /** A cited source (RAG / web-search grounding). `url` variant carries `url`; * `document` variant carries `mediaType`/`filename`. Rendered as a citation * chip / footnote. */ | StoredEventPart<'source'> /** An inline file the model produced (usually an image): IANA `mediaType` + * base64 `data`. A UI renders `image/*` inline. */ | StoredEventPart<'file'> /** Provider-owned attribution for each model step, not answer text. */ | StoredEventPart<'stepMetadata'>; /** * A tally in a unit that is not tokens. * * Deliberately NOT `AiUsage` with a unit tacked on. Writing a character count * into a field called `inputTokens` puts a lie in a ledger row that someone * reads later, and a dashboard summing that column across units is wrong in a * way nothing reports. The token shape is left exactly as it was — it is what * the AI SDK returns, and text callers change nothing. */ export declare interface MeteredUsage { readonly unit: Exclude; /** What went in — characters of text, say. */ readonly input?: number | undefined; /** What came out — seconds of audio, say. */ readonly output?: number | undefined; } /** A scripted inline file the mock model emits on a turn (usually an image). * `data` is base64 (a short ascii string works for assertions). */ export declare interface MockFile { readonly mediaType: string; readonly data: string; } /** A consumable sequence of model turns, plus a recorder for asserting on * what the model was asked. Each `doGenerate`/`doStream` advances the * cursor by one turn; running past the end repeats the last turn's text * (or empties) so a loop that over-steps doesn't hang. */ export declare class MockScript { private readonly turns; private cursor; readonly calls: Array<{ readonly prompt: unknown; readonly tools: ReadonlyArray; }>; constructor(turns: ReadonlyArray); record(prompt: unknown, tools: ReadonlyArray): void; next(): MockTurn; reset(): void; } /** A scripted citation the mock model emits on a turn (RAG grounding). */ export declare interface MockSource { readonly sourceType?: 'url' | 'document'; readonly id?: string; readonly url?: string; readonly title?: string; readonly mediaType?: string; readonly filename?: string; } /** A scripted tool call the mock model emits on a turn. */ export declare interface MockToolCall { readonly name: string; readonly input: unknown; } /** One model turn. A turn can carry text (streamed word-by-word), scripted * tool calls (which trigger the SDK's tool loop), cited sources, inline files, * or an error. A turn with tool calls + no text drives one LLM↔tool * round-trip. */ export declare interface MockTurn { readonly text?: string; /** Reasoning ("thinking") the mock streams before the answer text. */ readonly reasoning?: string; readonly toolCalls?: ReadonlyArray; readonly sources?: ReadonlyArray; readonly files?: ReadonlyArray; readonly error?: { readonly message: string; }; } export declare const modalitiesOf: (raw: RawGatewayModel) => { readonly input: ReadonlyArray; readonly output: ReadonlyArray; }; /** * The primitive that makes each modality runnable — the reason the entry is * here, written down so a reader can check it rather than trust it. */ export declare const MODALITY_PRIMITIVE: Readonly>>; /** `model_type` on the wire; the same union `ModelModality` names. */ export declare const modalityOf: (raw: RawGatewayModel) => ModelModality; /** * Built-in published list prices (USD per 1M tokens) for the major providers. * * ⚠️ POINT-IN-TIME DEFAULTS — these are public LIST prices as of **January * 2026** and WILL drift as providers re-price. They are a sane default for the * budget guard + cost dashboard, NOT a contract. Override them without editing * the framework three ways: * - per call: `estimateCostUsd(usage, { model, price })` / `recordAiUsage({ price })` * - app-wide: `setModelPricing({ 'my-model': { inputPer1M, outputPer1M } })` * (merged OVER these defaults — a user entry wins) * - authoritative: for a GATEWAY-routed model, pass the gateway's REPORTED * per-call cost via `actualCostUsd: gatewayCostUsd(meta)` * (`costSource: 'gateway'`), which beats any static rate here. * * The `mock` provider + any model absent from the merged map price at zero * (never throws — cost accounting must not break a call). * * Covers the framework's first-class DIRECT providers — `anthropic` (`claude-*`) * and `openai` (`gpt-*`) — plus the common GATEWAY creators reachable by their * bare model id (`google` Gemini, and the same OpenAI/Anthropic ids the gateway * routes with a `creator/` prefix, matched below by their bare id after the `/`). */ export declare const MODEL_PRICING_DEFAULTS: Record; /** What a model can do, unioned from the three fields that each half-say it. */ export declare interface ModelCapabilities { readonly vision: boolean; readonly reasoning: boolean; readonly toolUse: boolean; readonly structuredOutput: boolean; readonly webSearch: boolean; readonly caching: boolean; readonly imageOut: boolean; readonly audioOut: boolean; readonly videoOut: boolean; readonly audioIn: boolean; readonly videoIn: boolean; readonly fileInput: boolean; } export declare interface ModelDataPolicy { /** Zero data retention. */ readonly zeroRetention: PolicyCoverage; /** The provider does not train on what is sent. */ readonly noTraining: PolicyCoverage; } export declare type ModelModality = 'language' | 'embedding' | 'image' | 'video' | 'realtime' | 'reranking' | 'speech' | 'transcription' | 'evaluation' | 'unknown'; export declare interface ModelPrice { /** USD per 1,000,000 input (prompt) tokens. */ readonly inputPer1M: number; /** USD per 1,000,000 output (completion) tokens. */ readonly outputPer1M: number; } /** The current merged price map (defaults + installed overrides). Overrides win. */ export declare const modelPricing: () => Record; /** Both shapes, flattened to the two counts + the unit they are counts of. */ export declare const normaliseUsage: (usage: AiUsage | MeteredUsage) => { readonly unit: UsageUnit; readonly input: number; readonly output: number; }; /** * Wrap a `streamText` `Stream` with the same `voltro.ai.call` span + * metrics. The stream never fails — usage arrives on the terminal `done` event, * error status on a terminal `error` event — so the metrics are recorded from a * `Stream.tap` on the terminal event, and the whole stream is spanned via * `Stream.withSpan`. A `cancelled` error counts as an error (non-`ok`) call. */ export declare const observeStream: (attributes: AiCallAttributes, stream: Stream.Stream) => Stream.Stream; /** * The DETERMINISTIC row id for an offloaded call. * * Keyed on the execution + step, not on a generated id, so a replay that * reaches the enqueue before the first attempt's journal entry is durable * cannot queue — and pay for — the same call twice. `insertIgnore` on this id * is what makes the enqueue idempotent. * * Hashed for LENGTH, exactly like `pendingSlotId`: `id()` is VARCHAR(64) on * MySQL/MariaDB and a step name is app-chosen text. */ export declare const offloadedInferenceId: (executionId: string, stepName: string) => string; export declare type OpenAIModel = 'gpt-5.5' | 'gpt-4o' | 'gpt-4o-mini' | (string & {}); /** The JSON Schema a `generateObject` output schema renders to. Exported * because an OFFLOADED inference has to store it: a JavaScript Schema object * cannot be journaled, and its JSON rendering can. */ export declare const outputJsonSchema: (schema: Schema.Schema.Any, fn: string) => Effect.Effect, AiError>; /** * Pull the first JSON-RPC message out of a Streamable-HTTP response body. * A server may answer a POST with `application/json` (one object) or with * `text/event-stream` (SSE frames, the response interleaved with whatever else * the server wants to push). Both are spec-legal; we take the first frame whose * `id` matches and ignore the rest, because a client that keeps reading after * its answer is a client an unbounded server can hold open forever. */ export declare const parseHttpMcpBody: (body: string, contentType: string, id: string | number | null) => McpJsonRpcResponse | null; /** Does this tag pass the allow/deny policy? deny wins; allow (when set) gates. */ export declare const passesPolicy: (tag: string, policy: AppToolPolicy) => boolean; /** * Patch the streaming row's `parts` (the live snapshot). Call-site throttles * this to ~100ms so the subscription doesn't fan out one update per token. * Mirrors `content` to the concatenated text so the durable row stays readable * even if a deployment ignores `parts`. */ export declare const patchStreamingMessage: (store: DataStore, id: string, parts: ReadonlyArray) => Effect.Effect; /** How much of a provider's retention promise applies. */ export declare type PolicyCoverage = 'all' | 'some' | 'none'; /** * The effective {@link ModelPrice} for a model id, or `undefined` if unknown. * Looks up the merged map (overrides over defaults); a gateway `creator/model` * id also matches on its bare `model` segment, so `openai/gpt-4o` prices like * `gpt-4o` when the caller didn't pass a `price` / `actualCostUsd`. */ export declare const priceForModel: (model: string) => ModelPrice | undefined; export declare interface PromptDefinition { /** * Stable identity across revisions. Dotted, like a descriptor tag * (`support.triage`) — the same vocabulary the rest of the framework uses for * "the name of a thing an app owns". */ readonly id: string; /** * The template, with `{{name}}` placeholders. A STRING, not a function, and * that is the design: a function has no stable content to hash, so a * function-built prompt could be versioned only by hashing its output — which * would make every distinct customer message a new "version". Compose in the * variables instead. */ readonly template: string; /** Optional system message. Part of the digest — changing it IS a new * version, because it changes the model's behaviour as surely as the * template does. */ readonly system?: string; /** Optional human label for this revision (`'shorter-system'`). Metadata: * deliberately NOT part of the digest, so relabelling does not fork a * version. */ readonly label?: string; } /** The digest form: a prefix, not the whole hash. This is a correlation handle, * and a full sha256 in a dashboard cell is a wall of hex nobody reads. * * Lives here rather than in `inferStep.ts` (where it started) because it is now * the identity of a versioned artefact, and `inferStep` imports * `@voltro/workflow` — a prompt registry has no business needing the workflow * engine to compute a hash. `inferStep` re-exports it. */ export declare const promptDigest: (prompt: string) => string; export declare class PromptRenderError extends PromptRenderError_base { } declare const PromptRenderError_base: Schema.TaggedErrorClass; } & { promptId: typeof Schema.String; missing: Schema.Array$; message: typeof Schema.String; }>; export declare const PROMPTS_TABLE = "_voltro_prompts"; /** * One row per (prompt id · content digest) — i.e. one row per VERSION. * * `promptId` + `digest` are `.maxLength(191)` because they carry a composite * UNIQUE: an unbounded text column under a UNIQUE is backed on MariaDB by a * hash long-unique index whose hidden column wedges binlog CDC (see * `@voltro/database`'s maintainer note). 191 is the utf8mb4 index-prefix limit. * * `lastUsedAt` is what the retention sweep prunes on, and the table is * self-healing under it: `recordPromptVersion` re-inserts on the next use, so a * version that ages out is a version nothing has run in a year. */ export declare const promptsTable: Table>, true>; /** A prompt's identity, as it is stamped onto a step row and a usage row. */ export declare interface PromptStamp { /** The author-chosen stable id, e.g. `support.triage`. */ readonly promptId: string; /** Content digest of the template (+ system). CHANGES when the template does * — that is the version. */ readonly digest: string; /** The monotonic revision `recordPromptVersion` assigned, once it has been * registered. Absent before first registration; the digest is the identity * either way. */ readonly revision?: number; /** Optional human label the author gave this revision. */ readonly label?: string; } /** Look one version up by its digest — the reverse of the stamp on a step row * or a usage row, and therefore the answer to "which prompt produced this * run". */ export declare const promptVersionByDigest: (store: DataStore, digest: string) => Effect.Effect; /** * The version digest of a (template, system) pair. * * LENGTH-PREFIXED, not separator-joined. A separator is only injective when it * cannot occur in the parts, and every printable candidate can occur in prose: * join on a space and template `'a b'` + no system collides with template `'a'` * + system `'b'`, so two genuinely different prompts read as ONE version and the * second one's provenance silently points at the first. A length prefix has no * such case and needs no exotic character. * * (The first version of this used a raw NUL as the separator, which is the * repo-wide rule this file now also demonstrates the reason for: the file * became BINARY to grep, and every text search over it silently returned * nothing — indistinguishable from a clean file.) */ export declare const promptVersionDigest: (template: string, system: string | undefined) => string; /** A registered version, as read back. */ export declare interface PromptVersionRow { readonly promptId: string; readonly digest: string; readonly revision: number; readonly label: string | null; readonly template: string | null; readonly system: string | null; readonly firstSeenAt: Date | null; readonly lastUsedAt: Date | null; } /** Every registered version of one prompt, newest revision first. */ export declare const promptVersionsFor: (store: DataStore, promptId: string) => Effect.Effect>; /** * A resolved provider choice — a DISCRIMINATED UNION on `name`. Each provider * only carries the fields that apply to it: the mock-only `mockText` / `script` * cannot appear on a real provider, and a real provider always carries a * `model` typed to that provider's ids. `apiKey` / `baseURL` switch * `resolveModel` to a per-config provider instance (per-agent key / BYOK); * omit them to fall back to the provider's standard env var (OPENAI_API_KEY / * ANTHROPIC_API_KEY / AI_GATEWAY_API_KEY). */ export declare type ProviderConfig = { readonly name: 'mock'; /** Optional for mock — defaults to `'mock'`. */ readonly model?: string; /** The exact text the mock returns (instead of echoing the prompt). * Lets tests/demos drive `generateObject` with canned JSON. */ readonly mockText?: string; /** A multi-turn script. Takes precedence over `mockText` — each * `doGenerate`/`doStream` consumes the next turn, so the SDK's tool * loop runs deterministically. */ readonly script?: MockScript; /** Provider metadata the mock attaches to its `doGenerate` result — lets a * test exercise the gateway-cost path (`gatewayCostUsd`) deterministically * by faking `{ gateway: { cost } }`. Also surfaced by the mock speech + * video media models. */ readonly providerMetadata?: AiProviderOptions; /** Canned output for the media models (`resolveImageModel` / * `resolveSpeechModel` / `resolveVideoModel`). Every field is optional and * falls back to a deterministic default, so `useMockAi` yields valid media * without configuration; set a field to assert on exact bytes / URL. */ readonly mockMedia?: { /** base64 image payload `generateImage` decodes to bytes (default: a * 1×1 PNG header, so media-type detection resolves to `image/png`). */ readonly imageBase64?: string; /** base64 audio payload `generateSpeech` decodes to bytes. */ readonly audioBase64?: string; /** URL the mock video model yields (the default branch — most video * providers return a URL rather than inline bytes). */ readonly videoUrl?: string; /** base64 the mock video model yields INSTEAD of a URL — exercises the * bytes branch of `generateVideo`. Takes precedence over `videoUrl`. */ readonly videoBase64?: string; /** The text the mock transcription model returns. Lets a test assert on * an exact transcript without a provider. */ readonly transcript?: string; }; } | { readonly name: 'anthropic'; readonly model: AnthropicModel; readonly apiKey?: string; readonly baseURL?: string; } | { readonly name: 'openai'; readonly model: OpenAIModel; readonly apiKey?: string; readonly baseURL?: string; } | { readonly name: 'gateway'; /** A `creator/model` id, e.g. 'openai/gpt-5.5'. */ readonly model: GatewayModel; readonly apiKey?: string; readonly baseURL?: string; }; /** * An ordered provider/model fallback chain. When a call FAILS on the primary * provider (`provider ?? providerFromEnv()`) — after that provider's OWN * SDK-level retries — the call re-runs against `fallbacks[0]`, then * `fallbacks[1]`, … until one succeeds. Exhausting every option surfaces the * LAST provider's typed error (an `AiError({ reason: 'generation' })`). Only a * `generation` failure (the provider call itself) falls through — a `decode` * failure (the model answered but the output didn't satisfy the schema) is a * prompt/schema problem another provider won't fix, so it surfaces immediately. * An empty / absent chain leaves behavior unchanged. */ export declare type ProviderFallbacks = ReadonlyArray; export declare const providerFromEnv: (env?: Record) => ProviderConfig; /** * The env var a provider's SDK reads its key from — `undefined` for `mock`, * which needs none. ONE map, exported, so the boot gate that checks a key is * present cannot drift from the provider that reads it. */ export declare const providerKeyEnvVar: (name: string) => string | undefined; export declare type ProviderName = 'mock' | 'anthropic' | 'openai' | 'gateway'; /** The minimal store surface {@link recordReads} needs — any object with a * `query(descriptor)` that returns rows. Structurally a subset of the * framework `DataStore`, so no `@voltro/runtime` import is required. */ export declare interface QueryableStore { query(descriptor: { readonly table: string; } & Record): Promise>>; } /** A queued call in domain shape. */ export declare interface QueuedInference { readonly id: string; readonly tenantId: string | null; readonly executionId: string; readonly workflowName: string; readonly runId: string | null; readonly signalName: string; readonly stepName: string; readonly kind: 'text' | 'object' | 'transcription' | 'media'; readonly provider: string; readonly model: string; readonly prompt: string; readonly system: string | null; /** Prompt provenance carried from the parked run — see the table. */ readonly promptId: string | null; readonly promptDigest: string | null; readonly promptRevision: number | null; readonly maxTokens: number | null; readonly providerOptions: unknown; readonly jsonSchema: Record | null; /** The source + options for a `transcription` call. */ readonly transcription: Record | null; /** The media job for a `media` call. */ readonly media: QueuedMediaJob | null; /** The stored provider handle of a task-provider media job, or `null`. */ readonly taskHandle: Record | null; readonly taskSubmittedAt: Date | null; readonly attempts: number; readonly fence: number; } /** What a `media` row carries. Inputs are URLs; bytes never enter the queue. */ export declare interface QueuedMediaJob { readonly modality: 'image' | 'video' | 'speech'; readonly params: Record; readonly inputs: Record; /** Owner, visibility and tags for the stored artifacts — see `PluginMediaPersistMetadata`. */ readonly persist?: PluginMediaPersistMetadata; } /** What a media job resumes its run with: hosted artifacts, never bytes. */ export declare interface QueuedMediaResult { readonly artifacts: ReadonlyArray<{ readonly url: string; readonly refId: string | null; readonly mediaType: string; }>; readonly usage: unknown; readonly providerMetadata: unknown; } /** One entry of the raw `/v1/models` response, every field optional — the * gateway adds fields, and a parser that requires one is a parser that breaks * on a Tuesday. */ export declare interface RawGatewayModel { readonly id?: unknown; readonly name?: unknown; readonly description?: unknown; readonly model_type?: unknown; readonly tags?: unknown; readonly supported_parameters?: unknown; readonly modalities?: unknown; readonly context_window?: unknown; readonly max_tokens?: unknown; readonly released?: unknown; readonly knowledge?: unknown; readonly zdr?: unknown; readonly no_training?: unknown; readonly pricing?: unknown; } /** * The catalog, read from `/v1/models` directly. * * `getAvailableModels` asks `@ai-sdk/gateway`'s provider, whose entry type * carries six of the response's twenty-one fields — so the other fifteen never * arrive, and an app that needs them fetches the endpoint a second time and * writes its own parser. This reads the response. * * The provider path is kept and still used when a `gatewayProvider` is injected * (tests) or a fetch fails: a catalog with fewer fields is worth more than no * catalog, and the fields that were always there stay there. A degraded read is * VISIBLE rather than silent — the entries simply carry no `capabilities`, which * is the difference between "not known" and "no". */ export declare const readGatewayCatalog: (opts?: { readonly apiKey?: string; readonly baseURL?: string; readonly modality?: ModelModality; readonly fetchImpl?: typeof fetch; }) => Promise>; /** A store wrapper that records the dependency set of everything read through * it — the provenance the semantic cache tags entries with. */ export declare interface ReadRecorder { /** Pass THIS where the real store would go; every `query()` it serves records * the table + each returned row's id. All other methods pass through. */ readonly store: S; /** The accumulated dependency tags (deduped): `table:` for each table * queried + `row::` for each row returned that carries an `id`. Feed * this to a call's `deps`. */ deps(): ReadonlyArray; /** Drop everything recorded so far (reuse the recorder for a new answer). */ reset(): void; } /** Browser-safe input allowlist. Session configuration and provider credentials * belong to the server, never to events received from the browser. */ export declare const RealtimeClientEvent: Schema.Union<[Schema.Struct<{ type: Schema.Literal<["input-audio-append"]>; audio: Schema.filter; }>, Schema.Struct<{ type: Schema.Literal<["input-audio-commit", "input-audio-clear", "response-create", "response-cancel"]>; }>, Schema.Struct<{ type: Schema.Literal<["conversation-item-create"]>; item: Schema.Struct<{ type: Schema.Literal<["text-message"]>; role: Schema.Literal<["user"]>; text: typeof Schema.String; }>; }>, Schema.Struct<{ type: Schema.Literal<["conversation-item-truncate"]>; itemId: typeof Schema.String; contentIndex: Schema.filter>>; audioEndMs: Schema.filter>; }>]>; export declare type RealtimeClientEvent = typeof RealtimeClientEvent.Type; /** Safe, normalized server events. No raw provider payload, client secret, * arbitrary conversation item or provider error message crosses this surface. */ export declare const RealtimeServerEvent: Schema.Union<[Schema.Struct<{ type: Schema.Literal<["ready"]>; }>, Schema.Struct<{ type: Schema.Literal<["session-created"]>; sessionId: Schema.optional; }>, Schema.Struct<{ type: Schema.Literal<["session-updated"]>; }>, Schema.Struct<{ type: Schema.Literal<["speech-started", "speech-stopped"]>; itemId: Schema.optional; }>, Schema.Struct<{ type: Schema.Literal<["audio-committed"]>; itemId: Schema.optional; previousItemId: Schema.optional; }>, Schema.Struct<{ itemId: typeof Schema.String; type: Schema.Literal<["conversation-item-added"]>; }>, Schema.Struct<{ transcript: typeof Schema.String; itemId: typeof Schema.String; type: Schema.Literal<["input-transcription-completed"]>; }>, Schema.Struct<{ type: Schema.Literal<["response-created"]>; responseId: typeof Schema.String; }>, Schema.Struct<{ type: Schema.Literal<["response-done"]>; responseId: typeof Schema.String; status: typeof Schema.String; }>, Schema.Struct<{ itemId: typeof Schema.String; responseId: typeof Schema.String; type: Schema.Literal<["output-item-added", "output-item-done", "content-part-added", "content-part-done", "audio-done"]>; }>, Schema.Struct<{ delta: Schema.filter; itemId: typeof Schema.String; responseId: typeof Schema.String; type: Schema.Literal<["audio-delta"]>; }>, Schema.Struct<{ delta: typeof Schema.String; itemId: typeof Schema.String; responseId: typeof Schema.String; type: Schema.Literal<["text-delta", "audio-transcript-delta"]>; }>, Schema.Struct<{ text: Schema.optional; itemId: typeof Schema.String; responseId: typeof Schema.String; type: Schema.Literal<["text-done"]>; }>, Schema.Struct<{ transcript: Schema.optional; itemId: typeof Schema.String; responseId: typeof Schema.String; type: Schema.Literal<["audio-transcript-done"]>; }>]>; export declare type RealtimeServerEvent = typeof RealtimeServerEvent.Type; export declare interface RealtimeSessionOptions { readonly path: `/${string}`; readonly model: Experimental_RealtimeModelV4; /** Runs before minting/opening. Authentication alone does not authorize AI spend. */ readonly authorize: (subject: Subject, model: { readonly provider: string; readonly modelId: string; }) => Effect.Effect; /** Server-owned configuration. Tool execution is not supported by this relay. */ readonly sessionConfig: Omit; readonly limits?: { readonly maxMessageBytes?: number; readonly maxPendingBytes?: number; readonly maxPendingMessages?: number; readonly setupTimeoutMs?: number; readonly maxDurationMs?: number; }; } export declare const recordAiUsage: (store: DataStore, record: AiUsageRecordInput) => Effect.Effect; /** * `recordAiUsage` for a call that has ALREADY been paid for, where failing the * caller would lose a finished answer over a ledger row. Never fails — and never * silent: a ledger failure or a receipt conflict is logged at error level with * everything needed to reconcile it by hand. * * The framework's three callers each swallowed the failure outright * (`.catch(() => {})`, `catchAllCause(() => Effect.void)` twice). "Do not fail * the call" was right; "leave no trace" was not — an unbooked charge that * nothing reports is found only when the invoice disagrees with the ledger. */ export declare const recordAiUsageBestEffort: (store: DataStore, record: AiUsageRecordInput) => Effect.Effect; /** * Register a prompt version, idempotently, and return it. * * First sight of a `(promptId, digest)` pair inserts it with the next revision * number for that `promptId`; every later sight only touches `lastUsedAt`. The * insert is an `insertIgnore` over the composite UNIQUE, so two replicas * racing the same first sight produce one row. * * The revision NUMBER is read-then-write and therefore not strictly serialised: * two different versions of one prompt first seen in the same millisecond on * two replicas can both land on revision 4. That is accepted deliberately — the * digest is the identity and it is exact; the revision is a label for humans, * and paying for a serialisable counter to make a label prettier is the wrong * trade. Order by `firstSeenAt` when you need the true sequence. */ export declare const recordPromptVersion: (store: DataStore, prompt: PromptDefinition & { readonly digest?: string; }) => Effect.Effect; /** * Wrap a store so the rows it serves become a dependency set — automatic * provenance capture with no runtime edit. Every `query()` records * `tableDep(table)` and, for each returned row with an `id`, `rowDep(table, * id)`. Read your data through `rec.store`, build the prompt from the rows, * then pass `rec.deps()` as the call's dependency set. * * The wrapper is a `Proxy`, so the returned `store` has the SAME type + full * method surface as the input — only `query` is intercepted. */ export declare const recordReads: (store: S) => ReadRecorder; /** * Redis-backed resumable-stream store — the production "resumable via * Redis-Replay" path. The append-only log is a Redis LIST per `streamId` * (`RPUSH` to append, `LRANGE afterSeq -1` to read the tail past a cursor — the * list index lines up with the 1-based `seq` because exactly ONE producer * appends, elected by `claimProducer` = `SET … NX`). Multi-node correct + the * fastest backend; TTL (default 1h) evicts finished/abandoned streams without a * sweep. A drop-in for `dataStoreResumableStreamStore` against the same * `ResumableStreamStore` interface. */ export declare const redisResumableStreamStore: (redis: ResumableRedis, opts?: { readonly keyPrefix?: string; readonly ttlSeconds?: number; }) => ResumableStreamStore; /** * Re-list and re-gate a mounted server. * * This is the ONLY way the tool set changes. Nothing reacts to a * `notifications/tools/list_changed`, and that is deliberate: a set that can be * rewritten by the party it protects against is not a set, and the cost of the * alternative is a stale tool until the app decides otherwise. */ export declare const refreshMcpToolset: (server: McpServer, policy: McpToolPolicy) => Effect.Effect; /** * The provider config `mediaStep` accepts for a registered task provider — * named, not enumerated, exactly like `RegisteredSpeechConfig`. */ export declare interface RegisteredMediaTaskConfig { readonly name: string; readonly model?: string; } /** Every registered name. */ export declare const registeredMediaTaskProviders: () => ReadonlyArray; /** * Synthesize speech audio from text. Returns the audio bytes + media type and * provider metadata; provider failures surface as `AiError`. */ /** * A provider registered with `registerSpeechProvider` — named, not enumerated. * * The gateway serves nine speech models; a real voice catalog runs to hundreds * behind vendors it does not carry (Cartesia, ElevenLabs, Google Cloud TTS). An * app with its own adapter had nowhere to plug it in, so it called its own * synthesizer and skipped this primitive entirely — losing the shared error type * and the shared cost path with it. */ export declare interface RegisteredSpeechConfig { readonly name: string; readonly model?: string; readonly voice?: string; } /** Every registered name — what a picker asks for. */ export declare const registeredSpeechProviders: () => ReadonlyArray; /** * Register an external-task media provider under a name. * * The name then works in `mediaStep({ provider: { name, model } })`: the * dispatcher submits through the provider, keeps the handle on the queue row, * polls it once per claim, and resumes the run with the hosted artifacts. * Taking over a built-in name needs `{ replaceBuiltin: true }`. */ export declare const registerMediaTaskProvider: (name: string, provider: MediaTaskProvider, options?: RegisterMediaTaskProviderOptions) => void; export declare interface RegisterMediaTaskProviderOptions { /** * Take over a BUILT-IN name (`openai`, `gateway`, `anthropic`, `mock`). * * A registration under a built-in name routes every `mediaStep` that names * it through the task contract instead of the generator. Refused unless the * intent is in the call, for the reason the speech registry gives: a silent * shadow makes the same config mean different things depending on which * module loaded first. */ readonly replaceBuiltin?: boolean; } /** * Register a speech provider under a name. * * The name then works in `generateSpeech({ provider: { name, model, voice } })`. * Taking over a built-in name needs `{ replaceBuiltin: true }` — see the option. */ export declare const registerSpeechProvider: (name: string, provider: SpeechProvider, options?: RegisterSpeechProviderOptions) => void; export declare interface RegisterSpeechProviderOptions { /** * Take over a BUILT-IN name (`openai`, `gateway`, `anthropic`, `mock`). * * Refusing these outright was wrong, and the case that showed it is one the * refusal could not see: a provider name is not always an alias the app * chooses. `openai` is the value the GATEWAY puts in its model ids, so it is * what an app's stored catalog rows carry and what its picker submits. Told * to pick another name, such an app cannot — it would have to rewrite * persisted rows and live sessions — so it keeps its own dispatch instead, * and the primitive stays unused for every provider, not just the colliding * one. * * What the refusal was actually protecting is worth keeping: a SILENT shadow, * where the same config means different things depending on which module * loaded first. This flag keeps that property by putting the intent in the * call. An override is then a decision someone wrote down, not an accident of * import order. */ readonly replaceBuiltin?: boolean; } /** * Register (or reuse) the `AbortController` for `streamId`. `streamText` calls * this when given a `streamId`; it threads the returned controller's `signal` * into the SDK call. Re-registering an id that is still in flight returns the * SAME controller (so a pre-registered-then-aborted id is honoured) — keep * `streamId` unique per logical stream. */ export declare const registerStream: (streamId: string) => AbortController; /** Release the STORED amount once. Supply the owning tenant, never an amount. * A missing/expired receipt cannot be reconstructed and returns not-found. */ export declare const releaseAiBudget: (store: DataStore, opts: { readonly reservationId: string; readonly tenantId: string | null; }) => Effect.Effect<"released" | "already-released" | "not-found", AiBudgetInvalidInput>; export declare const releasedAtOf: (raw: RawGatewayModel) => string | undefined; /** What `.render(vars)` produces — a stamp plus the text to send. */ export declare interface RenderedPrompt extends PromptStamp { readonly prompt: string; readonly system?: string; /** The artefact this was rendered from. Carried along so a deployment that * receives only the rendered value (`aiStep`) can REGISTER the version * without the app having to remember a separate boot-time call. It is the * template — code, not data — so carrying it costs nothing a repository * checkout does not already cost. */ readonly definition: PromptDefinition; } /** Whether `name` is a registration standing in front of a built-in. */ export declare const replacesBuiltinSpeechProvider: (name: string) => boolean; /** Reserve before provider I/O. Reuse reservationId only for the same logical * spend, tenant, amount and window. Zero checks for strictly positive headroom * and returns null; it never creates a free reservation. * Call outside another transaction; the receipt and counter need one commit. * A provider failure does not automatically release possibly billed spend. */ export declare const requireAiBudget: (store: DataStore, opts: { readonly tenantId?: string | null; readonly limitUsd: number; readonly addUsd?: number; readonly since?: Date; readonly reservationId?: string; }) => Effect.Effect; /** * Rerank `documents` by relevance to `query`, highest score first. Returns * each surviving document with its relevance score; truncated to `topN` * when set. * * ```ts * const top = yield* rerank({ query: 'deploy', documents: rows, getText: (r) => r.body, topN: 5 }) * ``` */ export declare const rerank: (options: RerankOptions) => Effect.Effect>, AiError>; export declare interface RerankOptions { readonly query: string; readonly documents: ReadonlyArray; /** Extract the text to score from each document. Default: identity for * `string[]`; required for non-string document shapes. */ readonly getText?: (doc: T) => string; /** Rerank model id. e.g. 'rerank-english-v3.0' (cohere) / 'rerank-2' (voyage). */ readonly model?: string; /** * Where to rerank. Default `'mock'` (lexical overlap; deterministic). * * A `ProviderConfig` — the shape every other primitive in this package takes * — routes through the gateway, so one `AI_GATEWAY_API_KEY` reaches * `cohere/rerank-v4-pro`, `voyage/rerank-2.5` and the rest of the catalog. * This was the one primitive that could not do that: it wanted the vendors' * own keys and had no way to express the gateway at all. * * The string shorthands stay for the direct-key route. They are NOT folded * into `ProviderConfig`: that union is shared with `generateText` and friends, * and adding `cohere` there would let somebody name a provider that has no * language model — a config error the type currently catches. */ readonly provider?: 'mock' | 'cohere' | 'voyage' | ProviderConfig; /** Return only the top-N after reranking. Default: all. */ readonly topN?: number; } export declare interface RerankResult { readonly document: T; readonly score: number; } /** Resolve the provider config for a given decoded input. Supports a static * config OR a `(input) => ProviderConfig | undefined` selector (model picker / * cost routing). `undefined` → fall back to env (`providerFromEnv`). */ export declare const resolveAgentModel: (exec: AnyAgentExecutor, input: unknown) => ProviderConfig | undefined; /** * Resolve an {@link EmbeddingProviderConfig} to an AI-SDK * `EmbeddingModelV4`. The mock provider is synchronous; real providers are * resolved lazily (server-only dynamic import) so the provider package is * never bundled for the browser and is an optional, user-installed * dependency. A missing package surfaces a clear, actionable error. */ export declare const resolveEmbeddingModel: (config: EmbeddingProviderConfig) => Promise; export declare const resolveImageModel: (config: ProviderConfig) => ImageModelV4; /** Explicit option → env → default, per field. */ export declare const resolveMcpBounds: (overrides: Partial | undefined, env?: Record) => McpBounds; export declare const resolveModel: (config: ProviderConfig, middleware?: AiMiddleware | ReadonlyArray) => LanguageModelV4; /** Resolve supported server-owned provider configuration to the SDK codec. */ export declare const resolveRealtimeModel: (config: ProviderConfig) => Experimental_RealtimeModelV4; export declare const resolveSpeechModel: (config: ProviderConfig) => SpeechModelV4; /** Resolve the system prompt for a given decoded input. */ export declare const resolveSystem: (exec: AnyAgentExecutor, input: unknown) => string | undefined; /** * Wire a `*.tool.tsx` module's DEFAULT-EXPORT handler onto its `defineTool` * export(s). The documented tool shape is a named descriptor with NO body plus * a separate `export default (input, ctx) => …` handler: * * export const searchDocs = defineTool({ name, description, input }) * export default async ({ query }, ctx) => ctx.store.select('docs')… * * Nothing else connects the two halves — an agent's `tools: { searchDocs }` * imports the BODY-LESS descriptor, so without this pass every such tool fails * at call time ("neither execute nor handler"). This attaches the default * export as the descriptor's `handler` (mutated in place — the agent imports * the SAME module instance, so its tool reference is fixed too). No-op when the * tool already has an inline `execute`/`handler`, or the module has no default * function. Returns the number of tools wired. Idempotent. */ export declare const resolveToolModuleHandlers: (mod: Record) => number; /** * The transcription model for a provider config. * * Mirrors `resolveSpeechModel` deliberately — the two are the same seam in * opposite directions, and the gateway exposes both (`.transcription(id)`), so * one key reaches all nine transcription models in the catalog. */ export declare const resolveTranscriptionModel: (config: ProviderConfig) => TranscriptionModelV4; export declare const resolveVideoModel: (config: ProviderConfig) => Experimental_VideoModelV4; /** * The minimal Redis surface a resumable-stream store needs — a per-event LIST * (`RPUSH`/`LRANGE`) plus two flag keys (claim + done). Adapt your client * (ioredis / node-redis / a `@voltro/cache` RESP handle) to these five methods; * `@voltro/ai` takes NO Redis dependency, so the client is always injected. * `ttlSeconds`, when passed, should map to `SET … EX` / `EXPIRE` so finished * (or abandoned) streams self-evict — there is no GC sweep for the Redis path. */ export declare interface ResumableRedis { /** `SET key value NX [EX ttl]` — return true iff the key did NOT exist (we set it). */ readonly setNx: (key: string, value: string, ttlSeconds?: number) => Promise; /** `RPUSH key value`. */ readonly rpush: (key: string, value: string) => Promise; /** `LRANGE key start stop` (0-based, inclusive; negative = from the end, -1 = last). */ readonly lrange: (key: string, start: number, stop: number) => Promise>; /** `SET key value [EX ttl]`. */ readonly set: (key: string, value: string, ttlSeconds?: number) => Promise; /** `EXISTS key` → boolean. */ readonly exists: (key: string) => Promise; /** `EXPIRE key seconds` — refresh a key's TTL. Optional (no-op if absent). */ readonly expire?: (key: string, seconds: number) => Promise; } /** * The one call a streaming rpc makes per subscription. Elects a producer for * `streamId` (forking it as a daemon so it survives THIS deployment's lifetime), * then returns a deployment stream that replays past `fromSeq` and tails to the * end. First connection produces + consumes; a reconnect (producer already * elected) just consumes from its cursor. */ export declare const resumableStream: (opts: ResumableStreamOptions) => Stream.Stream; export declare interface ResumableStreamOptions { readonly streamId: string; readonly store: ResumableStreamStore; /** The underlying event source — typically `() => streamText({...})`. Run at * most once per `streamId` (by the elected producer). */ readonly source: () => Stream.Stream; /** Replay events past this `seq` before tailing (a reconnect cursor). */ readonly fromSeq?: number; readonly pollMs?: number; readonly idleTimeoutMs?: number; } /** * The persistence port a resumable stream drives. Append-only log + a * done-flag + a one-winner producer election, all keyed by `streamId`. * Implement it against any ordered store (a Redis list, a log table, …); * two implementations ship below. */ export declare interface ResumableStreamStore { /** * Atomically elect the producer for `streamId`. Returns `true` for the ONE * caller that should run the model, `false` for everyone else (they consume). * Must be race-free across processes — the durable backend uses an * insert-if-not-exists, exactly like `_voltro_idempotency`'s claim. */ readonly claimProducer: (streamId: string) => Effect.Effect; /** Append one event at its `seq`. Producer-only. */ readonly append: (streamId: string, ev: SeqEvent) => Effect.Effect; /** Persisted events with `seq > afterSeq`, ascending. */ readonly read: (streamId: string, afterSeq: number) => Effect.Effect>; /** Mark the stream complete — the producer reached a terminal event. */ readonly markDone: (streamId: string) => Effect.Effect; /** Whether the producer has finished (all events are persisted). */ readonly isDone: (streamId: string) => Effect.Effect; } /** Convenience: resume a `streamText` run by `streamId`. The producer runs * `streamText(options)`; reconnect by passing `fromSeq`. */ export declare const resumableStreamText: (args: { readonly streamId: string; readonly store: ResumableStreamStore; readonly options: StreamTextOptions; readonly fromSeq?: number; readonly pollMs?: number; readonly idleTimeoutMs?: number; }) => Stream.Stream; /** * Search, rerank, truncate — with the two numbers named. * * ```ts * const docs = yield* retrieveReranked({ * search: (limit) => Effect.promise(() => searchDocs(q, limit)), * query: q, * getText: (d) => d.body, * provider: { name: 'gateway', model: 'cohere/rerank-v4-pro' }, * }) * ``` * * Returns the documents, not `{ document, score }` — the score is the second * stage's working, and a caller that wants it calls `rerank` directly. Handing * back a wrapper would make every consumer unwrap it. */ export declare const retrieveReranked: (options: RetrieveRerankedOptions) => Effect.Effect, E | AiError>; export declare interface RetrieveRerankedOptions { /** * The app's own first stage, given a limit. * * A function rather than a value, so the helper decides how DEEP to go: the * candidate count is the parameter being traded against accuracy, and a * caller that had already run the search would have chosen it. */ readonly search: (limit: number) => Effect.Effect, E>; readonly query: string; readonly getText: (doc: T) => string; /** How many the first stage fetches. Default 50 — cheap and shallow. */ readonly candidates?: number; /** How many survive the rerank. Default 8. */ readonly topN?: number; readonly provider?: RerankOptions['provider']; readonly model?: string; } /** * The synthesized `.send` body. Appends the user turn at `order`, then * drives a streaming assistant turn at `order + 1` via `runAssistant` (which * owns the loop + delta-persistence). Returns the assembled assistant text. */ export declare const runAgentTurn: (exec: AnyAgentExecutor, store: DataStore, args: { readonly threadId: string; readonly input: unknown; readonly tenantId?: string | null; /** The caller — `null` for an anonymous subject. Opens the thread on its * first message and is checked against the thread's owner on every later * one (`ensureThreadOwned`). Omitted ⇒ treated as anonymous. */ readonly subjectId?: string | null; readonly order: number; readonly toolContext?: ToolContext; readonly throttleMs?: number; readonly promptOf?: (input: unknown) => string; }) => Effect.Effect; /** * Drive a streaming assistant turn end-to-end against the persisted row. * * Inserts the streaming row, consumes `streamText`, accumulates token deltas * into a single text part, records each tool call + result as a tool part, * throttle-patches the row as deltas arrive, and flips `streaming:false` on the * terminal `done`/`error` event. Returns the final assembled text. */ export declare const runAssistant: (store: DataStore, input: RunAssistantInput) => Effect.Effect; export declare interface RunAssistantInput { readonly threadId: string; readonly tenantId?: string | null; readonly prompt: string; /** Full prior conversation for multi-turn memory. When present it is sent to * the model INSTEAD of `prompt` (must already end with the latest user * turn). The synthesized agent send builds this from the thread history. */ readonly messages?: ReadonlyArray; readonly system?: string; readonly tools?: Record; /** Provider override; defaults to `providerFromEnv()`. */ readonly provider?: ProviderConfig; /** Max LLM↔tool round-trips before the loop stops. Forwarded to streamText. */ readonly maxSteps?: number; /** Request context threaded into `*.tool.tsx` executor bodies. */ readonly toolContext?: ToolContext; /** Provider-specific options forwarded to the SDK (e.g. reasoning effort). * See `AiProviderOptions`. */ readonly providerOptions?: AiProviderOptions; /** The order the streaming assistant row was inserted at. */ readonly order: number; /** Throttle window for the live `parts` patch. Default 100ms. */ readonly throttleMs?: number; /** The agent this turn belongs to — stamped onto the usage ledger row so * spend is attributable per agent. The synthesized send passes its name. */ readonly agent?: string; /** * Record the turn's token usage into `_voltro_ai_usage` (default `true`). * A tool loop is a model call like any other, and this was the one path * that spent without recording: `aiStep` records, and the agent turn — which * owns a store by construction — wrote nothing, so an app whose model sites * are mostly tool loops could show zero spend. `false` for a caller that * records elsewhere. */ readonly recordUsage?: boolean; } /** * End-to-end copilot step: build the grammar prompt, ask the model for a * constrained proposal, then VALIDATE it against the schema. Returns a * validated read-only descriptor or a typed {@link CopilotRejected}. Run the * descriptor AS the subject for tenant/scope isolation. * * Real wiring of `propose`: * ```ts * propose: ({ system, prompt }) => * Effect.runPromise( * generateObject({ system, prompt, schema: CopilotProposalSchema }) * .pipe(Effect.map((r) => r.object)), * ) * ``` */ export declare const runDataCopilot: (question: string, schema: CopilotSchema, deps: DataCopilotDeps) => Promise; /** * Replay + score every case in an eval. The orchestrator is pure over its * injected `replay` / `judge`, so it runs identically under a real provider and * under a deterministic test fake. `ok` is the deploy-gate signal: false iff any * case failed. */ export declare const runEval: (def: EvalDefinition, deps: RunEvalDeps) => Promise; export declare interface RunEvalDeps { readonly replay: EvalReplay; /** Omitted → assertions-only gating (no LLM in the loop). */ readonly judge?: EvalJudge; readonly branchId?: string; } /** * Make one server-supplied string safe to place in a model's context: strip * invisibles, normalise runaway whitespace, and cap the length with a marker * the model can see. * * The cap is the honest part. It does NOT stop a description from CONTAINING * an instruction — nothing does, short of not showing the model the description * at all, which would make the tool unusable. It stops the description from * being a whole second system prompt, and it stops the invisible encodings that * make an injection unreviewable by a human reading the same string. */ export declare const sanitizeExternalText: (raw: unknown, maxChars: number) => string; /** * Recursively bound + clean a server's input JSON Schema. * * Depth and node budget are enforced as we walk (a subtree past either is * dropped, not truncated mid-object), and every `description` / `title` string * anywhere in the tree is sanitized — those are model-visible too, and a * per-property description is the least-inspected place to hide an instruction. * Returns `null` when the schema is unusable, in which case the tool is dropped. */ export declare const sanitizeInputSchema: (schema: unknown, bounds: McpBounds) => Record | null; /** * Combine assertion failures + the judge verdict into one pass/fail. Pure. A case * passes iff no assertion failed AND (no judge, or the judge's score clears the * threshold). Assertions are deterministic and always decisive; the judge only * ever ADDS a reason to fail, never rescues an assertion failure. */ export declare const scoreCase: (c: EvalCase, out: EvalReplayOutput, evalWideAssertions: ReadonlyArray, judgeConfig: EvalJudgeConfig | undefined, judgeVerdict: JudgeVerdict | undefined) => EvalCaseResult; /** Injectable collaborators for a semantic-cached call. */ export declare interface SemanticCacheDeps { /** The semantic cache (build with `makeSemanticCache(store)` from * `@voltro/cache` over a memory / RESP `CacheStore`). */ readonly cache: SemanticCacheShape; /** Embed a prompt to a vector. Defaults to `@voltro/ai`'s `embed` (the * env-configured provider). Injectable for tests + provider choice. */ readonly embed?: (text: string) => Effect.Effect, AiError>; } /** Per-call cache policy. */ export declare interface SemanticCachePolicy { /** Minimum cosine similarity ([-1, 1]) for a hit. Default 0.95 — high, so * only genuine near-duplicates share an answer. */ readonly threshold?: number; /** TTL floor in ms — the answer expires after this even if nothing it * depends on changes. Omit for "live until a dependency changes". */ readonly ttlMs?: number; /** The dependency set: the source rows/tables this answer read, as tags * (`rowDep` / `tableDep`, or {@link recordReads}). A change to any of them * evicts the entry. Empty ⇒ cached but TTL-only (never dependency-evicted) * — correct only for an answer that reads no data. */ readonly deps: ReadonlyArray; } /** * `generateObject`, semantically cached + dependency-evicted. Same contract as * {@link semanticGenerateText} for the structured surface. * * Note on serialization: the DECODED object is stored. With the memory backend * it round-trips by reference; with a RESP backend it is JSON — so a schema * whose decoded form is not JSON-safe (class instances, non-plain branded * carriers) will not survive a cross-process hit. For those, cache the text * form or key on a JSON-safe projection. */ export declare const semanticGenerateObject: (options: GenerateObjectOptions, policy: SemanticCachePolicy, deps: SemanticCacheDeps) => Effect.Effect, AiError>; /** * `generateText`, semantically cached + dependency-evicted. * * On a near-duplicate prompt (embedding within `threshold`) returns the cached * text with `cached: true` and zero usage. On a miss it generates, records the * answer under its dependency set, and returns `cached: false`. * * ```ts * const rec = recordReads(ctx.store) * const docs = yield* Effect.promise(() => rec.store.query(docsQuery)) * const res = yield* semanticGenerateText( * { prompt: buildPrompt(docs) }, * { deps: rec.deps() }, * { cache }, * ) * // res.cached === true on the next near-identical prompt — until a doc changes. * ``` */ export declare const semanticGenerateText: (options: GenerateTextOptions, policy: SemanticCachePolicy, deps: SemanticCacheDeps) => Effect.Effect, AiError>; /** The outcome of a semantic-cached generation. */ export declare interface SemanticResult { readonly value: A; /** `true` when served from cache (zero tokens spent). */ readonly cached: boolean; /** Underlying generation usage — all-zero when `cached`. */ readonly usage: SemanticUsage; } /** Usage tally shape shared by both generate results; all-zero on a cache hit. */ export declare interface SemanticUsage { readonly inputTokens: number | undefined; readonly outputTokens: number | undefined; readonly totalTokens: number | undefined; } /** * One persisted event of a RESUMABLE stream: an `AgentEvent` plus its monotonic * 1-based position. `seq` is the resume cursor — a client that has rendered up * to `seq=N` reconnects with `fromSeq: N` and gets `N+1…` replayed, then live. * * This is the WIRE element for a resumable `defineStream` (`element: SeqEvent`). * It lives here — next to `AgentEvent`, with ZERO server imports — so a * browser-loaded stream descriptor can reference the Schema without dragging * the server-only resumable-stream store graph (`@voltro/database`) into the * client bundle. */ export declare const SeqEvent: Schema.Struct<{ seq: typeof Schema.Number; event: Schema.Union<[Schema.Struct<{ _tag: Schema.Literal<["token"]>; text: typeof Schema.String; }>, Schema.Struct<{ _tag: Schema.Literal<["reasoning"]>; text: typeof Schema.String; }>, Schema.Struct<{ _tag: Schema.Literal<["toolCall"]>; id: typeof Schema.String; name: typeof Schema.String; input: typeof Schema.Unknown; }>, Schema.Struct<{ _tag: Schema.Literal<["toolResult"]>; id: typeof Schema.String; name: typeof Schema.String; output: Schema.optional; error: Schema.optional; }>, Schema.Struct<{ _tag: Schema.Literal<["source"]>; sourceType: Schema.Literal<["url", "document"]>; id: typeof Schema.String; url: Schema.optional; title: Schema.optional; mediaType: Schema.optional; filename: Schema.optional; /** Provider-owned citation attribution; not normalized or interpreted. */ providerMetadata: Schema.optional; }>, Schema.Struct<{ _tag: Schema.Literal<["file"]>; mediaType: typeof Schema.String; data: typeof Schema.String; /** Metadata for this file, not the whole model step. */ providerMetadata: Schema.optional; }>, Schema.Struct<{ _tag: Schema.Literal<["stepMetadata"]>; step: Schema.filter; providerMetadata: typeof Schema.Unknown; }>, Schema.Struct<{ _tag: Schema.Literal<["message"]>; role: typeof Schema.String; content: typeof Schema.String; }>, Schema.Struct<{ _tag: Schema.Literal<["error"]>; code: Schema.Literal<["provider", "tool", "timeout", "cancelled", "decode", "internal"]>; message: typeof Schema.String; retryable: typeof Schema.Boolean; }>, Schema.Struct<{ _tag: Schema.Literal<["done"]>; finishReason: typeof Schema.String; usage: Schema.Struct<{ inputTokens: Schema.optional; outputTokens: Schema.optional; totalTokens: Schema.optional; }>; }>]>; }>; export declare type SeqEvent = typeof SeqEvent.Type; /** * Install the global middleware stack — every subsequent `resolveModel` wraps * its model with it. Returns the PREVIOUS stack so a caller (a test, a scoped * boot) can restore it. Call with no args to clear. */ export declare const setAiMiddleware: (...middleware: ReadonlyArray) => ReadonlyArray; /** * Install app-wide model-price overrides, merged OVER the built-in * point-in-time defaults (a user entry for a model id wins). Wire the ai * config's `pricing` map through this at boot to encode current/negotiated * rates without editing the framework. Returns the previous overrides so a * test can restore them. Pass `{}` to clear. */ export declare const setModelPricing: (pricing: Record) => Record; /** Install a provider override that every `providerFromEnv()` call honours * regardless of env. `useMockAi` uses this; returns the previous override * so it can be restored. */ export declare const setProviderOverride: (config: ProviderConfig | null) => ProviderConfig | null; export { simulateStreamingMiddleware } export declare interface SpeechProvider { /** The voices this provider offers, for a picker. Optional: a provider with a * fixed voice set the app already knows needs no listing. */ readonly listVoices?: () => Effect.Effect, AiError>; readonly synthesize: (request: SpeechSynthesisRequest) => Effect.Effect; } /** The provider registered under `name`, or `undefined`. */ export declare const speechProvider: (name: string) => SpeechProvider | undefined; /** What `generateSpeech` hands a registered provider. Mirrors its own options * minus the parts the framework resolves. */ export declare interface SpeechSynthesisRequest { readonly text: string; /** Aborted on external cancellation or Effect interruption; pass to provider I/O. */ readonly abortSignal?: AbortSignal; readonly model?: string; readonly voice?: string; readonly outputFormat?: string; readonly instructions?: string; readonly speed?: number; readonly language?: string; readonly providerOptions?: AiProviderOptions; } /** * Run the underlying stream ONCE and persist every event. Assigns a monotonic * `seq` and persists a terminal event BEFORE marking done. Defects, a throwing * source factory, missing terminals and producer interruption become sanitized * error events. If persistence itself fails, the Effect fails; it must not * advertise a completed log. A process kill cannot run this finalization. */ export declare const startResumableProducer: (streamId: string, store: ResumableStreamStore, source: () => Stream.Stream) => Effect.Effect; /** * MCP over stdio — the transport most published servers ship (`npx * some-mcp-server`). * * The child does NOT inherit `process.env`. That is a deliberate difference * from `child_process.spawn`'s default: an MCP server is a program you are * handing your model's tool surface to, and the default should not also hand * it every credential in the process. Spread what it needs explicitly. */ export declare const stdioMcpTransport: (options: StdioMcpTransportOptions) => McpTransport; export declare interface StdioMcpTransportOptions { /** Executable to spawn. App configuration, never model output. */ readonly command: string; readonly args?: ReadonlyArray; /** Namespace, for error attribution. */ readonly server: string; /** Environment for the child. Pass credentials here, from YOUR env — the * child does NOT inherit the parent environment unless you spread it in, * so a server cannot read a secret you did not hand it. */ readonly env?: Record; readonly cwd?: string; readonly bounds?: Partial; } /** * One structured part of an assistant message. Either streamed assistant text * or a record of a tool call + its result. Stored in `agent_messages.parts` * and rendered by the frontend (`toUIMessages`). */ declare type StoredEventPart = Omit, '_tag'> & { readonly type: Tag; }; export declare const STREAM_EVENTS_TABLE = "_voltro_stream_events"; export declare const STREAM_STATE_TABLE = "_voltro_stream_state"; /** The append-only event log. UNIQUE (streamId, seq) defends against a stray * double-producer writing duplicate positions. */ export declare const streamEventsTable: Table>, true>; export declare interface StreamRetryOptions { /** Total attempts, including the first (default 3 → 1 try + 2 retries). */ readonly maxAttempts?: number; /** Base backoff between attempts in ms (default 250); grows exponentially. */ readonly backoffMs?: number; /** Cap on a single backoff in ms (default 5000). */ readonly maxBackoffMs?: number; } /** Framework-owned, serialisable subset of the AI SDK's stream smoothing. * Smoothing is applied before `AgentEvent` conversion and resumable-stream * journaling, so live delivery and replay see identical chunk boundaries. */ export declare interface StreamSmoothingOptions { /** Semantic chunk boundary. Defaults to word-by-word. */ readonly chunking?: 'word' | 'line'; /** Delay between chunks in milliseconds. Defaults to 10; `null` rechunks * without adding an artificial wait. */ readonly delayMs?: number | null; } /** The per-stream state + producer-election row. UNIQUE `streamId` is the * atomic claim arbiter (insert-if-not-exists). */ export declare const streamStateTable: Table>, true>; export declare const streamText: (options: StreamTextOptions) => Stream.Stream; export declare interface StreamTextOptions { /** The latest user turn, sent as a single user message. Ignored when * `messages` is supplied (then the full conversation is sent instead). */ readonly prompt: string; /** Full prior conversation (multi-turn memory). When present it is sent to * the model INSTEAD of `prompt` — it should already include the latest user * turn as its last entry. The agent send path builds this from the thread. */ readonly messages?: ReadonlyArray; readonly system?: string; readonly provider?: ProviderConfig; /** Ordered provider/model fallback chain. When a stream ERRORS with a * retryable `provider` error BEFORE any content has streamed, the run * re-attempts against the next config in this list (then the next, …). Once * any content event (token / tool / …) has flowed the answer is committed and * a later error surfaces as-is — re-running elsewhere would duplicate tokens. * A `cancelled` error (deliberate abort) never falls through. Applies via * `streamTextWithFallback` / `streamTextWithRetry`; a plain `streamText` uses * only the primary provider. */ readonly fallbacks?: ReadonlyArray; readonly maxTokens?: number; readonly temperature?: number; /** Tools the model may call. The SDK dispatches them + loops. */ readonly tools?: Record; /** Max LLM↔tool round-trips before the loop stops. Default 8. */ readonly maxSteps?: number; /** Request context threaded into `*.tool.tsx` executor bodies. */ readonly toolContext?: ToolContext; /** Provider-specific options forwarded to the SDK (e.g. reasoning effort). * See `AiProviderOptions`. */ readonly providerOptions?: AiProviderOptions; /** Cancel-by-streamId (#12). When set, the run registers an `AbortController` * addressable by `cancelStream(streamId)` — a "Stop" button calls that out of * band to abort the run. The id is dropped from the registry when the stream * ends. Keep it unique per logical stream. */ readonly streamId?: string; /** An external abort signal (composed with `streamId`'s controller, if any). * Aborting it ends the run with a terminal `cancelled` error. */ readonly signal?: AbortSignal; /** SDK-level transient retry (#12). Forwarded as the AI SDK's `maxRetries` — * retries the provider HTTP call on a transient failure BEFORE any bytes * stream, so it never duplicates emitted tokens. For re-attempting a whole * stream that errored before producing content, use `streamTextWithRetry`. */ readonly maxRetries?: number; /** Opt-in semantic smoothing for text and reasoning deltas. The transform runs * before `AgentEvent` mapping (and therefore before resumable journaling). * Omit or set `false` to preserve provider-native chunking and latency. */ readonly smooth?: false | StreamSmoothingOptions; } /** * `streamText` with an ordered provider/model FALLBACK chain (F10). When a run * ERRORS with a retryable `provider` error BEFORE any content has streamed, it * re-attempts against the next config in `options.fallbacks` (then the next, …). * The moment any content event (token / reasoning / tool / source / file) flows, * the answer is committed and a later error surfaces as-is — re-running against * another provider would duplicate tokens. A `cancelled` error (deliberate * abort) is never a fallback trigger. Exhausting every option surfaces the LAST * provider's terminal error. * * This complements `streamTextWithRetry` (which re-attempts the SAME provider on * a transient blip): each fallback config gets its own SDK-level `maxRetries` * BEFORE the chain moves on. Compose both by passing `fallbacks` AND wrapping in * `streamTextWithRetry` per config — or just use `streamTextWithRetry`, which * runs the fallback chain internally. With no `fallbacks` this is a single * `streamText`. */ export declare const streamTextWithFallback: (options: StreamTextOptions) => Stream.Stream; /** * `streamText` with automatic re-attempts on a transient failure — but ONLY * while nothing has streamed yet. The moment any content event (token / * reasoning / tool / source / file) flows, the answer is committed and a later * error is surfaced as-is (re-running would duplicate tokens). A `cancelled` * error (deliberate abort) is never retryable, so a Stop is honoured. * * This complements `streamText({ maxRetries })` (the SDK's HTTP-level retry, * which handles a transient failure BEFORE the first byte): use `maxRetries` * for connection blips, this wrapper to recover from an immediate provider * error event (e.g. a 503 surfaced as the first stream chunk). Backoff is * exponential between attempts. * * FALLBACK-AWARE (F10): when `options.fallbacks` is set, each provider in the * chain (primary first) gets its OWN `maxAttempts` retries; when a provider * exhausts its retries WITHOUT streaming content, the chain moves to the next * provider before finally surfacing the last provider's terminal error. So this * one wrapper gives you "retry each provider N times, then fall to the next". */ export declare const streamTextWithRetry: (options: StreamTextOptions, retry?: StreamRetryOptions) => Stream.Stream; /** Live partial transcription, not container decoding or a durable workflow journal. * No automatic retry: replaying audio can duplicate transcript segments and charges. * Raw provider chunks, headers and private metadata are never emitted. */ export declare const streamTranscribe: (options: StreamTranscribeOptions) => Stream.Stream; export declare interface StreamTranscribeOptions { /** Single-use raw audio source. Scope interruption cancels it and the provider. */ readonly audio: ReadableStream; readonly inputAudioFormat: TranscriptionAudioFormat; readonly provider?: ProviderConfig; readonly providerOptions?: AiProviderOptions; readonly signal?: AbortSignal; } /** A synthesized tool's metadata (the discovery/inventory view). */ export declare interface SynthesizedTool { readonly name: string; readonly kind: string; readonly description: string; /** mutation/action → true. Drives the confirm default + the writes gate. */ readonly write: boolean; /** Require human confirmation before this call executes. */ readonly confirm: boolean; /** * Whether that confirmation is ENFORCED by the app — i.e. the descriptor also * declares `requiresApproval:`, so the real handler parks the call as a * durable approval instead of running it. * * The inventory reports both flags because they answer different questions: a * confirm-UI asks `confirm`, and "why is this tool not executable" is answered * only by the pair. */ readonly approvalBacked: boolean; readonly maxPerRun?: number; } /** * The discovery/inventory view: every descriptor that is exposable AND passes * the policy, as `SynthesizedTool` metadata (no executable body). Use for the * capability surface / a confirm-UI. Sorted by name. * * Deliberately does NOT apply the `includeWrites` gate — that is an EXECUTION * decision (`appToolDecision`), and an inventory that hid write tools would * make a confirm-UI unable to explain why one is missing. */ export declare const synthesizeToolSpecs: (entries: ReadonlyArray, policy?: AppToolPolicy) => ReadonlyArray; /** A `system` prompt: a static string OR a per-request function of the * decoded input (templates locale / plan / persona into the prompt). */ export declare type SystemPrompt = string | ((input: Input) => string); /** Project a raw gateway entry → our clean, UI-friendly + cost-aligned shape. */ export declare const toModelInfo: (e: GatewayLanguageModelEntry) => GatewayModelInfo; /** The slice of the request context a `*.tool.tsx` handler receives. Kept * structurally typed here (rather than importing `AppContext` from * `@voltro/runtime`) so `@voltro/ai` carries no runtime dependency — the * CLI discovery path passes the real `AppContext`, which is a superset. * * `runtime` is the caller's ambient Effect `Runtime`. When present, tool * bodies are run through it (`Runtime.runPromise(runtime)(eff)`) instead of * bare `Effect.runPromise`, so a body may `yield*` any service the caller's * runtime carries — `JiraService`, the data store layer, `HttpClient`, … — * rather than being limited to `R = never`. The agent loop * (`streamText`/`runAssistant`) captures `yield* Effect.runtime()` and threads * it here; without a runtime, behaviour is unchanged. */ export declare interface ToolContext { readonly store: unknown; readonly request?: { readonly subject?: unknown; }; readonly runtime?: Runtime.Runtime; /** SDK tool-call identity for this execution, not a durable application idempotency key. */ readonly invocationId?: string; /** Cancellation from either the caller's turn or SDK invocation; Promise handlers must pass it to their I/O. */ readonly abortSignal?: AbortSignal; } export declare interface ToolDef { readonly name: string; readonly description: string; readonly input: Schema.Schema; /** * A ready-made JSON Schema for the tool's parameters, used INSTEAD of * rendering `input`. * * For a tool whose shape is defined elsewhere — an external MCP server's * `inputSchema` — there is no faithful Effect Schema to render from, and a * lossy re-expression would reject arguments the far side would have * accepted. So the far side's schema is what the model sees, while `input` * stays the decode gate on the way in. Leave unset for a normal tool; a * rendered `input` is strictly better when you own the shape. */ readonly inputJsonSchema?: Record; /** Optional result schema. When set, `buildSdkTools` decodes the body's * output through it before handing the value back to the model. */ readonly output?: Schema.Schema.Any; /** Inline body: returns an Effect. May declare service deps on the R * channel — they are provided by the caller's runtime * (`ctx.runtime`) at execution time. */ readonly execute?: (input: A) => Effect.Effect; /** File-convention body: receives `ctx`, returns Effect or Promise. */ readonly handler?: ToolHandler; /** Per-call deadline; the tool fails with a timeout error past it. */ readonly timeoutMs?: number; } /** A context-receiving tool body — the `*.tool.tsx` default-export form. * May return an Effect (with service deps the caller's runtime provides) or * a Promise; `buildSdkTools` normalises both. */ export declare type ToolHandler = (input: A, ctx: ToolContext) => Effect.Effect | Promise; export declare const toQueuedInference: (row: Record) => QueuedInference; /** * Transcribe audio to text. * * ```ts * const { text, segments } = yield* transcribe({ * source: { storageRef: recording.ref }, * resolveRef: (ref) => ctx.storage.read(ref), * timestamps: 'segment', * }) * ``` */ export declare const transcribe: (options: TranscribeOptions) => Effect.Effect; export declare interface TranscribeOptions { readonly source: TranscriptionSource; /** Override the provider/model. `anthropic` has no transcription model. */ readonly provider?: ProviderConfig; /** ISO 639-1. Given rather than detected — detection costs a pass and is * wrong on short or accented audio. */ readonly language?: string; /** Vocabulary, proper nouns, spellings the model would otherwise guess. */ readonly prompt?: string; readonly timestamps?: TranscriptionTimestamps; /** Separate speakers. Not every provider can; one that cannot returns * segments without `speaker` rather than failing. */ readonly diarize?: boolean; readonly translateToEnglish?: boolean; /** * Resolve a `storageRef` to bytes. * * Injected, and required only when the source IS a ref — so an app that * passes a url or bytes wires nothing, and `@voltro/ai` never imports storage. */ readonly resolveRef?: (ref: string) => Promise<{ bytes: Uint8Array; mediaType: string; }>; readonly providerOptions?: AiProviderOptions; } /** Raw audio only. Container files must be decoded before entering this stream. */ export declare interface TranscriptionAudioFormat { readonly type: 'audio/pcm' | 'audio/pcmu' | 'audio/pcma'; /** Required for PCM; other formats may declare their provider-supported rate. */ readonly rate?: number; } export declare interface TranscriptionResult { readonly text: string; readonly language?: string; readonly durationSec?: number; readonly segments?: ReadonlyArray; /** * Usage, for the SAME ledger every other primitive reports to. * * Transcription is the one modality billed by DURATION rather than by tokens, * which is exactly why it must not be the one modality missing from the * budget: a spend limit that silently excludes the long-running thing is not * a spend limit. */ readonly usage?: MediaUsage; readonly providerMetadata?: unknown; } export declare interface TranscriptionSegment { readonly start: number; readonly end: number; readonly text: string; /** Present only when `diarize` was asked for AND the provider supports it. */ readonly speaker?: string; } /** * Where the audio is. * * `storageRef` is the one to reach for: the file is already in the app's * storage, and resolving it there keeps tenancy, signing and lifetime with the * component that owns them. The resolver is injected rather than imported so * this module stays free of a storage dependency — `@voltro/ai` must not learn * about buckets to transcribe a file. */ export declare type TranscriptionSource = /** Resolved by the app's own storage — the case to reach for. */ { readonly storageRef: string; } /** Fetched by this function. Convenient, but the bytes do pass through this * process: the SDK takes bytes or base64 and never a location. */ | { readonly url: string; } /** Already in hand. The one that costs memory by construction. */ | { readonly bytes: Uint8Array; readonly mediaType: string; }; /** Browser-safe error contract for a declared transcription stream. */ export declare class TranscriptionStreamError extends TranscriptionStreamError_base { } declare const TranscriptionStreamError_base: Schema.TaggedErrorClass; } & { reason: Schema.Literal<["input", "unsupported", "provider", "cancelled"]>; message: typeof Schema.String; }>; /** Partial text is replaceable, delta text is append-only, and final text seals a segment. */ export declare const TranscriptionStreamEvent: Schema.Union<[Schema.Struct<{ delta: typeof Schema.String; id: Schema.optional; type: Schema.Literal<["transcript-delta"]>; }>, Schema.Struct<{ text: typeof Schema.String; startSecond: Schema.optional; durationInSeconds: Schema.optional; channelIndex: Schema.optional; id: Schema.optional; type: Schema.Literal<["transcript-partial"]>; }>, Schema.Struct<{ text: typeof Schema.String; startSecond: Schema.optional; endSecond: Schema.optional; channelIndex: Schema.optional; id: Schema.optional; type: Schema.Literal<["transcript-final"]>; }>, Schema.Struct<{ type: Schema.Literal<["finish"]>; text: typeof Schema.String; segments: Schema.Array$>; language: Schema.optional; durationSec: Schema.optional; }>]>; export declare type TranscriptionStreamEvent = typeof TranscriptionStreamEvent.Type; /** How much timestamp detail to ask for. Providers charge for it. */ export declare type TranscriptionTimestamps = 'none' | 'segment' | 'word'; /** * Drop a stream's controller. `streamText` calls this in its finaliser when the * stream ends — success, error, cancel, or consumer interrupt. Idempotent. */ export declare const unregisterStream: (streamId: string) => void; /** * What a call was billed in. * * Text is tokens. Speech is not: every speech vendor meters CHARACTERS of input * or SECONDS of audio, and none of them meters tokens. A ledger that only knows * tokens therefore cannot hold a speech call at all — which is why speech spend * was invisible rather than merely un-priced. */ export declare type UsageUnit = 'tokens' | 'characters' | 'seconds'; /** * Validate a proposal against the schema and (on success) build a read-only * `QueryDescriptor`. EVERY table/column the proposal names must exist in the * manifest — otherwise a typed `CopilotRejected`. The descriptor is always a * SELECT (read-only); run it as the subject for tenant/scope isolation. */ export declare const validateCopilotProposal: (proposal: CopilotProposal, schema: CopilotSchema) => CopilotValidation; /** One voice a registered provider offers. */ export declare interface VoiceInfo { readonly id: string; readonly name: string; readonly language?: string; /** Free-form, provider-shaped: gender, accent, age, whatever it publishes. * Not normalised — every vendor's taxonomy is different, and flattening them * would lose the half a picker actually shows. */ readonly labels?: Readonly>; } /** * Wrap an `Effect`-returning generate call with a `voltro.ai.call` span + * per-call metrics (count, error count, duration histogram, token + cost * counters), labelled by provider/model/operation. A typed failure increments * the error counter + marks the span; success records the usage tally the * result exposes via `usageOf`. Pure metric updates + one span — no content or * secret ever touches a label. */ export declare const withAiCallSpan: (attributes: AiCallAttributes, usageOf: (a: A) => { readonly inputTokens?: number | undefined; readonly outputTokens?: number | undefined; }, effect: Effect.Effect) => Effect.Effect; export { }