import { z } from 'zod'; import { toolToSchema } from './tool.js'; import type { ServerToolBuilder } from './tool.js'; import { QueuedPromptBuilder } from './queue-job.js'; import type { SubAgentPauseKind, SubAgentRunStore } from './sub-agent-run-store.js'; import type { PendingHandoff } from './handoffs-driver.js'; import type { AgentPromptOptions, AiMessage, AiMiddleware, AgentResponse, AgentStep, AgentStreamResponse, AnyTool, CacheableConfig, ConversationalSpec, ConversationStore, SubAgentUpdate, FinishReason, HasMiddleware, HasTools, MiddlewareContext, PrepareStepResult, RemembersSpec, StopCondition, StreamChunk, ToolCall, TokenUsage, UserMemory } from './types.js'; /** Stop after N steps */ export declare function stepCountIs(n: number): StopCondition; /** Stop when a specific tool is called in the latest step */ export declare function hasToolCall(toolName: string): StopCondition; /** Options for {@link Agent.resumeAsTool}. */ export interface SubAgentResumeOptions { /** Shared run store the snapshot lives in. */ runStore: SubAgentRunStore; /** The sub-agent instance to resume. */ agent: Agent; /** Approved ids for an `approval` pause. */ approvedToolCallIds?: string[]; /** Rejected ids for an `approval` pause. */ rejectedToolCallIds?: string[]; /** * Opt-in live progress during the resume. When set, the resumed inner loop * runs via `stream()` instead of `prompt()` and each chunk is projected into * a {@link SubAgentUpdate} forwarded to {@link onUpdate}. `true` uses * {@link defaultSubAgentProjector} (emits `tool_call` / `agent_pending_approval`); * a function is your own projector. Mirrors {@link Agent.asTool}'s `streaming` * on the initial-dispatch path. Unset → the legacy non-streaming `prompt()` * resume (no behavior change). The pause/completion partition is identical * either way — this only adds a progress channel. */ streaming?: AsToolStreamingOption; /** * Sink for projected updates. Only fires when {@link streaming} is set, once * per non-null projection, in stream order. Awaited, so a slow sink applies * backpressure to the resume. */ onUpdate?: (update: SubAgentUpdate) => void | Promise; } /** * One entry in a {@link Agent.resumeManyAsTool} batch — a single paused * sub-agent to resume. Mirrors the positional args of the singular * {@link Agent.resumeAsTool}, plus an optional host `key` echoed back on the * matching outcome so callers can correlate results without relying on array * order. */ export interface SubAgentResumeRequest { /** The paused run's id (the `subRunId` from its pause chunk/snapshot). */ subRunId: string; /** The sub-agent instance to resume (each item may be a different agent). */ agent: Agent; /** Client tool-results for a `client_tool` pause (one per pending id). */ clientToolResults?: ReadonlyArray<{ toolCallId: string; result: unknown; }>; /** Approved ids for an `approval` pause. */ approvedToolCallIds?: string[]; /** Rejected ids for an `approval` pause. */ rejectedToolCallIds?: string[]; /** Opaque correlation key echoed back on this item's outcome. */ key?: string; } /** Outcome for a single item in a {@link Agent.resumeManyAsTool} batch. */ export type SubAgentResumeOutcome = { key?: string; originalSubRunId: string; kind: 'completed'; response: AgentResponse; } | { key?: string; originalSubRunId: string; kind: 'paused'; subRunId: string; pauseKind: SubAgentPauseKind; pendingToolCallIds: string[]; toolCall?: ToolCall; isClientTool?: boolean; } | { key?: string; originalSubRunId: string; kind: 'error'; error: Error; }; export interface SubAgentResumeManyOptions { /** Shared run store all the snapshots live in. */ runStore: SubAgentRunStore; /** * What to do when one item fails (expired/forged `subRunId`, duplicate * result id, inner error): * - `'capture'` (default) — record it as a `{ kind: 'error' }` outcome and * let the rest of the batch resume; the aggregated round-trip still * returns. * - `'throw'` — reject the whole call on the first failure, matching the * singular `resumeAsTool` strictness. */ onError?: 'capture' | 'throw'; /** * - `'parallel'` (default) — resume all snapshots concurrently. Snapshots * are independent and `consume()` is per-id atomic, so this is safe and * fastest. * - `'serial'` — resume one at a time in array order, for deterministic * side-effect ordering when sub-agents touch shared state. */ concurrency?: 'parallel' | 'serial'; /** * Shared live-progress projector applied to every resumed item — same option * as {@link SubAgentResumeOptions.streaming}. Unset → every item resumes * non-streaming (legacy behavior). Set → each item streams and its projected * updates flow to {@link onUpdate} tagged with the originating request. */ streaming?: AsToolStreamingOption; /** * Sink for projected updates across the whole batch. Only fires when * {@link streaming} is set. Each call carries the originating item's `key` * (when supplied) and `originalSubRunId`, so a host can correlate a chunk * back to its request and fan it out (e.g. to a per-sub-agent SSE channel). */ onUpdate?: (update: SubAgentUpdate, ctx: { key?: string; originalSubRunId: string; }) => void | Promise; } /** Aggregated result of a {@link Agent.resumeManyAsTool} batch. */ export interface SubAgentResumeManyResult { /** Every item's outcome, in input order. */ results: SubAgentResumeOutcome[]; /** The items that paused again (need another client round-trip). */ paused: Extract[]; /** The items that ran to completion. */ completed: Extract[]; /** The items that failed (only populated under `onError: 'capture'`). */ errors: Extract[]; /** * All pending tool-call ids across every paused item, flattened — the * single set the host gathers client results / approvals for before the * next `resumeManyAsTool`. Empty when nothing paused. */ pendingToolCallIds: string[]; /** * `true` when nothing is still paused and no item errored — i.e. there is * no further round-trip to do. Loop `resumeManyAsTool` until this is `true`. */ allCompleted: boolean; } export declare abstract class Agent { /** System instructions for this agent */ abstract instructions(): string; /** Model string (e.g. 'anthropic/claude-sonnet-4-5'). Defaults to registry default. */ model(): string | undefined; /** Failover provider/model strings */ failover(): string[]; /** Maximum iterations for the tool loop (default: 20) */ maxSteps(): number; /** Per-step control — override model, tools, messages per iteration */ prepareStep?(_ctx: { stepNumber: number; steps: AgentStep[]; messages: AiMessage[]; }): PrepareStepResult | Promise; /** Stop conditions — combine with array (OR logic) */ stopWhen(): StopCondition | StopCondition[]; /** Temperature (0-1) */ temperature(): number | undefined; /** Max tokens for response */ maxTokens(): number | undefined; /** * Declarative prompt-cache configuration. * * Override on a subclass to mark stable parts of the prompt as cacheable * — provider adapters translate to native primitives (Anthropic * `cache_control`, OpenAI `prompt_cache_key`, Google `cachedContent`) * so cache hits can save 50–90% on input tokens for long system prompts, * tool definitions, or stable conversation context. * * Returning `undefined` (the default) means no caching. Per-call override * via `agent.prompt(input, { cache: false })` disables caching for that * call; passing a {@link CacheableConfig} for `cache` replaces the agent * default for that call. * * @example * class SupportAgent extends Agent { * instructions() { return LONG_SYSTEM_PROMPT } * tools() { return [tool1, tool2, tool3] } * cacheable() { * return { instructions: true, tools: true } * } * } */ cacheable(): CacheableConfig | undefined; /** * Opt into auto-persisted conversation behavior. Override on a subclass * to declare *which* user owns the thread and (optionally) which * specific thread, and the framework will load history before each * `prompt()`/`stream()` call and append the new turn after it — without * any caller having to remember `forUser()` / `continue()`. * * Returning `false` (the default) disables auto-persist; the agent runs * stateless. Returning a {@link ConversationalSpec} opts in: * * @example * class ChatAgent extends Agent { * conversational() { * return { user: Auth.user()?.id } // null user → falsy → opt-out * } * } * * await new ChatAgent().prompt('Hi') // auto-loads + auto-saves * * **Precedence (high → low):** * 1. Explicit `agent.forUser(id).prompt()` / `agent.continue(id).prompt()` * 2. Per-call `prompt(input, { conversation: false | {...} })` * 3. This method's return value * * Async returns are supported — useful when the user identity is fetched * from an async DI binding. */ conversational(): false | ConversationalSpec | Promise; /** * Opt this agent class into per-user memory beyond conversation history * (#A4). Returns a {@link RemembersSpec} naming the user whose memory * the agent reads/writes, and how injection / extraction should behave. * Returning `false` (the default) leaves the agent memory-stateless. * * Phase 1 wires the declaration + the per-call precedence chain so * apps and downstream phases (auto-inject middleware in Phase 2, * auto-extract middleware in Phase 3) can read a consistent spec. * Calling this method directly today produces no runtime behavior * unless application code reads it via `resolveRemembersSpec()`. * * **Precedence (high → low):** * 1. Per-call `prompt(input, { memory: false | {...} })` * 2. This method's return value * * Async returns are supported — useful when the user identity is fetched * from an async DI binding. * * @example * class SupportAgent extends Agent { * remembers() { return { user: ctx.user.id, inject: 'auto', tags: ['support'] } } * } */ remembers(): false | RemembersSpec | Promise; /** * Default for `AgentPromptOptions.parallelTools`. When `true` (default), * multiple tool calls within a single step run their `execute()` functions * concurrently. Override on a subclass to flip the default for an agent * whose tools share non-idempotent state. Per-call options still win. */ parallelTools(): boolean; /** Run the agent with a prompt (non-streaming) */ prompt(input: string, options?: AgentPromptOptions): Promise; /** Run the agent with a prompt (streaming) */ stream(input: string, options?: AgentPromptOptions): AgentStreamResponse; /** Queue the prompt for background execution */ queue(input: string, options?: AgentPromptOptions): QueuedPromptBuilder; /** Set the user scope for conversation persistence */ forUser(userId: string): ConversableAgent; /** * Continue an existing conversation. Chain it after `forUser()` when the * thread has an owner — resuming one as another user is refused (#984). */ continue(conversationId: string): ConversableAgent; /** * Wrap this agent as a tool another agent can call (the "subagents" * pattern). The returned tool is fully-formed — pass it directly into the * parent agent's `tools()` array. When the parent calls it, this agent * runs its own loop end-to-end (its own model, tools, middleware) and * returns a single result. * * Defaults are tuned for the zero-config case: * - `inputSchema` defaults to `{ prompt: string }` and the agent is * invoked with `input.prompt`. * - The parent model only sees `response.text` on its next step * (override with `modelOutput`); the UI still receives the full * `AgentResponse` via the `tool-result` chunk. * * @example Zero-config * const research = researchAgent.asTool({ * name: 'research', * description: 'Research a topic in depth.', * }) * * @example Custom schema + prompt mapper * const research = researchAgent.asTool({ * name: 'research', * description: 'Research a topic in depth.', * inputSchema: z.object({ topic: z.string(), depth: z.enum(['quick', 'deep']) }), * prompt: ({ topic, depth }) => `Research ${topic} (${depth}).`, * }) */ asTool(options: { name: string; description: string; inputSchema: TInput; prompt: (input: z.infer) => string; modelOutput?: (response: AgentResponse) => string | Promise; streaming?: AsToolStreamingOption; suspendable?: AsToolSuspendableOption; }): ServerToolBuilder, AgentResponse>; asTool(options: { name: string; description: string; modelOutput?: (response: AgentResponse) => string | Promise; streaming?: AsToolStreamingOption; suspendable?: AsToolSuspendableOption; }): ServerToolBuilder<{ prompt: string; }, AgentResponse>; /** * Resume a sub-agent run that previously paused with either * `pauseForClientTools` (client-tool pause) or `pauseForApproval` * (approval pause), typically from {@link Agent.asTool} with * `suspendable: { runStore }` set. The snapshot's `pauseKind` * (default `'client_tool'`) selects the resume contract: * * - **`client_tool`** — `clientToolResults` must carry one entry per * id in the snapshot's `pendingToolCallIds`. Results are appended * to the inner-agent message history and the loop re-runs. * - **`approval`** — `approvedToolCallIds` and/or * `rejectedToolCallIds` must reference the single pending id. * `clientToolResults` must be empty; the loop re-runs with the * approval decision injected via `AgentPromptOptions`. * * Returns either a `'completed'` result (the inner agent finished), * a `'paused'` continuation pointing at a fresh `subRunId` for the * next round-trip, or stays `'paused'` if the inner loop hits another * gate. The resume can pause on a different kind than it started on * (e.g. an approval pause that, once approved, hits a client-tool * pause on the next step). * * @example Client-tool resume * const r = await Agent.resumeAsTool(subRunId, browserResults, { runStore, agent: subAgent }) * * @example Approval resume * const r = await Agent.resumeAsTool(subRunId, [], { * runStore, agent: subAgent, * approvedToolCallIds: ['inner-call-id'], * }) */ static resumeAsTool(subRunId: string, clientToolResults: ReadonlyArray<{ toolCallId: string; result: unknown; }>, options: SubAgentResumeOptions): Promise<{ kind: 'completed'; response: AgentResponse; } | { kind: 'paused'; subRunId: string; pauseKind: SubAgentPauseKind; pendingToolCallIds: string[]; toolCall?: ToolCall; isClientTool?: boolean; }>; /** * Resume MANY paused sub-agents in one call and aggregate their pending * tool calls into a single client round-trip. * * When an orchestrator dispatches several sub-agents in one parent turn * and more than one pauses on a client tool (or approval gate), the host * would otherwise loop over {@link Agent.resumeAsTool} by hand and stitch * the pending sets back together. This does that: each request resumes its * own `(subRunId, agent)` snapshot, and the result carries the combined * `completed` / `paused` / `errors` partition plus the flattened * `pendingToolCallIds` the host collects the next batch of results for. * * Re-entrant: feed the next round of `clientToolResults` / approvals back * in as a fresh batch keyed off each paused item's NEW `subRunId` until * `allCompleted` is `true`. * * @example * let batch = await Agent.resumeManyAsTool( * paused.map(p => ({ subRunId: p.subRunId, agent: p.agent, clientToolResults: results[p.subRunId] })), * { runStore }, * ) * // batch.pendingToolCallIds → gather the next round from the browser, repeat. */ static resumeManyAsTool(requests: ReadonlyArray, options: SubAgentResumeManyOptions): Promise; } /** * Projects an inner-agent {@link StreamChunk} into a {@link SubAgentUpdate} the * host can render, or `null` to suppress it. Used by both {@link Agent.asTool} * (`streaming`) and the streaming resume path ({@link SubAgentResumeOptions.streaming}). * * On the resume paths the projector also receives a `ctx` 2nd arg carrying the * originating sub-run's `originalSubRunId` (and the host `key` when batched via * {@link Agent.resumeManyAsTool}), so a side-effect projector can fan a raw * chunk out to the correct per-sub-agent channel and return `null`: * * ```ts * streaming: (chunk, ctx) => { pumpToChannel(ctx!.originalSubRunId, chunk); return null } * ``` * * `ctx` is optional — {@link Agent.asTool}'s initial-dispatch path omits it, and * existing projectors that ignore the arg are unaffected. */ export type ChunkProjector = (chunk: StreamChunk, ctx?: { originalSubRunId: string; key?: string; }) => SubAgentUpdate | null; /** * Live-progress option shared by {@link Agent.asTool} and the streaming resume * surface: `true` uses {@link defaultSubAgentProjector}; a function is your own * {@link ChunkProjector}. */ export type AsToolStreamingOption = boolean | ChunkProjector; type AsToolSuspendableOption = { runStore: SubAgentRunStore; }; /** * Wraps an Agent to add conversation memory. * Created via `agent.forUser(id)` or `agent.continue(id)`. */ export declare class ConversableAgent { private readonly agent; private _userId; private _conversationId; constructor(agent: Agent); forUser(userId: string): this; continue(conversationId: string): this; prompt(input: string, options?: AgentPromptOptions): Promise; stream(input: string, options?: AgentPromptOptions): AgentStreamResponse; /** * Translate the wrapper's explicit-form state (`forUser` / `continue`) * into a {@link ConversationalSpec}. The explicit chain bypasses the * agent's `conversational()` declaration entirely — `forUser` always * wins over class defaults. * * A bare `continue()` still yields an empty user rather than throwing: it * is a legal call for threads that have no owner, and one whose owner * check refuses it for every thread that does (#984). */ private toSpec; } /** * Create an anonymous agent inline. * * @example * const response = await agent('You are helpful.').prompt('Hello') * * @example * const response = await agent({ * instructions: 'You are a search assistant.', * tools: [searchTool], * model: 'anthropic/claude-sonnet-4-5', * }).prompt('Find users named John') */ export declare function agent(instructionsOrOptions: string | { instructions: string; tools?: AnyTool[] | undefined; model?: string | undefined; middleware?: AiMiddleware[] | undefined; }): Agent & HasTools & HasMiddleware; /** Set the global conversation store (called by service provider or manually) */ export declare function setConversationStore(store: ConversationStore): void; /** * Set the global {@link UserMemory} (called by `AiProvider` from * `AiConfig.memory`, or manually for tests / standalone setups). * Phase 2/3 middleware reads it via `resolveUserMemory()` — * imported by the persistence layer the same way * `resolveConversationStore` is wired today. */ export declare function setUserMemory(memory: UserMemory): void; export declare function resolveUserMemory(): UserMemory | undefined; /** * Mutable state shared between the non-streaming and streaming agent loops. * Helpers (`runFailover`, `emitObserverFailed`, `emitObserverCompleted`, * `buildAgentResponse`) read and write this struct so the same orchestration * logic serves both `prompt()` and `stream()` callers. */ export interface LoopContext { readonly agent: Agent; readonly input: string; readonly options: AgentPromptOptions | undefined; readonly modelString: string; readonly providerName: string; readonly tools: AnyTool[]; readonly toolMap: Map; readonly toolSchemas: ReturnType[]; readonly middlewares: AiMiddleware[]; readonly loopStart: number; readonly ctx: MiddlewareContext & { readonly _aborted: boolean; readonly _abortReason: string; }; readonly messages: AiMessage[]; readonly steps: AgentStep[]; readonly totalUsage: TokenUsage; readonly pendingClientToolCalls: ToolCall[]; pendingApprovalToolCall: { toolCall: ToolCall; isClientTool: boolean; } | undefined; loopFinishReason: FinishReason | undefined; stopForClientTools: boolean; stopForApproval: boolean; resumedToolMessages: AiMessage[]; failoverAttempts: number; /** * Set by the tool phase when the model called a {@link handoff} tool. * Triggers the parent loop to break and the handoff wrapper to construct * the child agent and continue with the carried message history. */ pendingHandoff?: PendingHandoff; stopForHandoff: boolean; } export type { PendingHandoff } from './handoffs-driver.js'; export type { InvalidToolArgumentsError } from './tool-helpers.js'; //# sourceMappingURL=agent.d.ts.map