UNPKG

@mastra/core

Version:
645 lines 28.9 kB
import type { MastraServerCache } from '../../cache/base.js'; import type { PubSub } from '../../events/pubsub.js'; import type { Mastra } from '../../mastra/index.js'; import type { FullOutput, MastraModelOutput } from '../../stream/base/output.js'; import type { ChunkType, MastraOnFinishCallback } from '../../stream/types.js'; import { Agent } from '../agent.js'; import type { AgentExecutionOptions } from '../agent.types.js'; import type { MessageListInput } from '../message-list/index.js'; import type { ToolsInput } from '../types.js'; import { ExtendedRunRegistry } from './run-registry.js'; import type { AgentStepFinishEventData, AgentSuspendedEventData, DurableAgenticWorkflowInput } from './types.js'; import { createDurableAgenticWorkflow } from './workflows/index.js'; /** * Options for DurableAgent.stream() */ export interface DurableAgentStreamOptions<OUTPUT = undefined> { /** Custom instructions that override the agent's default instructions for this execution */ instructions?: AgentExecutionOptions<OUTPUT>['instructions']; /** Additional context messages to provide to the agent */ context?: AgentExecutionOptions<OUTPUT>['context']; /** Memory configuration for conversation persistence and retrieval */ memory?: AgentExecutionOptions<OUTPUT>['memory']; /** Unique identifier for this execution run */ runId?: string; /** Request Context containing dynamic configuration and state */ requestContext?: AgentExecutionOptions<OUTPUT>['requestContext']; /** Maximum number of steps to run */ maxSteps?: number; /** * Conditions for stopping execution (e.g., step count, token limit). * * The predicate is non-serializable, so it's parked on the in-process run * registry and evaluated by the durable loop on every iteration. Cross-process * durable engines (e.g. Inngest after a worker restart) cannot recover the * closure and degrade to `maxSteps` only. */ stopWhen?: AgentExecutionOptions<OUTPUT>['stopWhen']; /** Additional tool sets that can be used for this execution */ toolsets?: AgentExecutionOptions<OUTPUT>['toolsets']; /** Client-side tools available during execution */ clientTools?: AgentExecutionOptions<OUTPUT>['clientTools']; /** Tool selection strategy */ toolChoice?: AgentExecutionOptions<OUTPUT>['toolChoice']; /** Tool names enabled for this execution */ activeTools?: AgentExecutionOptions<OUTPUT>['activeTools']; /** Model-specific settings like temperature */ modelSettings?: AgentExecutionOptions<OUTPUT>['modelSettings']; /** Require approval for tool calls. Boolean (gate all / none) or a per-call function policy. */ requireToolApproval?: AgentExecutionOptions<OUTPUT>['requireToolApproval']; /** Automatically resume suspended tools */ autoResumeSuspendedTools?: boolean; /** Maximum number of tool calls to execute concurrently */ toolCallConcurrency?: number; /** Whether to include raw chunks in the stream output */ includeRawChunks?: boolean; /** Maximum processor retries */ maxProcessorRetries?: number; /** Structured output configuration */ structuredOutput?: AgentExecutionOptions<OUTPUT>['structuredOutput']; /** Version overrides for sub-agent delegation */ versions?: AgentExecutionOptions<OUTPUT>['versions']; /** Callback when chunk is received */ onChunk?: (chunk: ChunkType<OUTPUT>) => void | Promise<void>; /** Callback when step finishes */ onStepFinish?: (result: AgentStepFinishEventData) => void | Promise<void>; /** Callback when execution finishes — receives rich step data (text, steps, toolResults) */ onFinish?: MastraOnFinishCallback<OUTPUT>; /** Callback on error */ onError?: ({ error }: { error: Error | string; }) => void | Promise<void>; /** Callback when workflow suspends (e.g., for tool approval) */ onSuspended?: (data: AgentSuspendedEventData) => void | Promise<void>; /** Callback when execution is aborted via abortSignal */ onAbort?: AgentExecutionOptions<OUTPUT>['onAbort']; /** Callback fired after each agentic-loop iteration */ onIterationComplete?: AgentExecutionOptions<OUTPUT>['onIterationComplete']; /** Additional system message appended after context but before user messages. */ system?: AgentExecutionOptions<OUTPUT>['system']; /** When true, background tasks are disabled for this run. */ disableBackgroundTasks?: AgentExecutionOptions<OUTPUT>['disableBackgroundTasks']; /** Tracing options forwarded to the agent/model spans. */ tracingOptions?: AgentExecutionOptions<OUTPUT>['tracingOptions']; /** Per-call actor signal forwarded to FGA checks and tool execution. */ actor?: AgentExecutionOptions<OUTPUT>['actor']; /** * Per-invocation tool payload transform policy. The closure rides on the * in-process run registry; only the JSON-safe `targets` shadow is serialized * for cross-process engines. */ transform?: AgentExecutionOptions<OUTPUT>['transform']; /** * Per-step preparation hook. Closure-only: stored on the in-process run * registry and invoked as a `PrepareStepProcessor` at the start of every * iteration. Cross-process resumes lose the hook. */ prepareStep?: AgentExecutionOptions<OUTPUT>['prepareStep']; /** * Per-call `isTaskComplete` policy. Scorer instances and `onComplete` are * closure-only and live on the in-process run registry; the JSON-safe * primitives (`strategy`, `timeout`, `parallel`, `suppressFeedback`, * `scorerNames`) are serialized for cross-process observability. */ isTaskComplete?: AgentExecutionOptions<OUTPUT>['isTaskComplete']; /** * Sub-agent delegation hooks (`onDelegationStart`, `onDelegationComplete`, * `messageFilter`, etc.). The callbacks are forwarded into `convertTools` * at prepare time and burned into the sub-agent `CoreTool` wrappers on the * in-process run registry. Cross-process resumes lose the callbacks (only * `includeSubAgentToolResultsInModelContext` would be JSON-safe), so a * fresh worker degrades to default delegation behaviour. */ delegation?: AgentExecutionOptions<OUTPUT>['delegation']; /** * When set, `stream()` delegates to the idle-loop wrapper that keeps the * outer stream open across background-task continuations — the same * behaviour as the now-deprecated `streamUntilIdle()`. * * Pass `true` for default idle timeout (5 min), or `{ maxIdleMs }` to * customise. * * @example * ```typescript * const { output, cleanup } = await durableAgent.stream('Research topic', { * untilIdle: true, * memory: { thread: 't1', resource: 'u1' }, * }); * ``` */ untilIdle?: boolean | { maxIdleMs?: number; }; /** When true, the in-loop background task check step skips waiting (streamUntilIdle sets this) */ _skipBgTaskWait?: boolean; /** * External abort signal. The durable agent always installs its own internal * `AbortController` for the run; when this signal is provided, its `abort` * event is forwarded to the internal controller so either source can cancel * the run. * * Cross-process resumes (e.g. Inngest after a worker restart) cannot * recover the signal — call `resume(runId, ..., { abortSignal })` with a * fresh signal on each segment if you need abortability post-resume. */ abortSignal?: AbortSignal; } /** * Result from DurableAgent.stream() */ export interface DurableAgentStreamResult<OUTPUT = undefined> { /** The streaming output */ output: MastraModelOutput<OUTPUT>; /** The full stream - delegates to output.fullStream for server compatibility */ readonly fullStream: ReadableStream<any>; /** The unique run ID for this execution */ runId: string; /** Thread ID if using memory */ threadId?: string; /** Resource ID if using memory */ resourceId?: string; /** Cleanup function to call when done (unsubscribes from pubsub) */ cleanup: () => void; /** * Abort the run. Flips the internal `AbortController` for this run, which * surfaces as an `AbortError` inside the durable LLM-execution step and * is bridged to the user's `onAbort` callback via the run's pubsub topic. * * Safe to call after the run has already finished — it's a no-op in that * case. */ abort: (reason?: unknown) => void; } /** * Configuration for DurableAgent - wraps an existing Agent with durable execution */ export interface DurableAgentConfig<TAgentId extends string = string, TTools extends ToolsInput = ToolsInput, TOutput = undefined> { /** * The Agent to wrap with durable execution capabilities. * All agent methods (getModel, listTools, etc.) delegate to this agent. */ agent: Agent<TAgentId, TTools, TOutput>; /** * Optional ID override. Defaults to agent.id. */ id?: TAgentId; /** * Optional name override. Defaults to agent.name. */ name?: string; /** * PubSub instance for streaming events. * Optional - if not provided, defaults to EventEmitterPubSub. */ pubsub?: PubSub; /** * Cache instance for storing stream events. * Enables resumable streams - clients can disconnect and reconnect * without missing events. * * - If not provided: Inherits from Mastra instance, or uses InMemoryServerCache * - If provided: Uses the provided cache backend (e.g., Redis) * - If set to `false`: Disables caching (streams are not resumable) */ cache?: MastraServerCache | false; /** * Maximum steps for the agentic loop. * Defaults to the workflow default if not specified. */ maxSteps?: number; /** * Timeout in milliseconds before automatic cleanup of registry entries * after a stream finishes or errors. This provides a grace period for * late observers to access the stream. * * Defaults to 30000 (30 seconds). * Set to 0 to disable auto-cleanup (manual cleanup() required). */ cleanupTimeoutMs?: number; } /** * DurableAgent wraps an existing Agent with durable execution capabilities. * * Key features: * 1. Resumable streams - clients can disconnect and reconnect without missing events * 2. Serializable workflow inputs - works with durable execution engines * 3. PubSub-based streaming - events flow through pubsub for distribution * * DurableAgent extends Agent, delegating most methods to the wrapped agent. * It overrides stream() to use durable execution with the agentic workflow. * * Subclasses (EventedAgent, InngestAgent) override executeWorkflow() to * customize how the workflow is executed. * * @example * ```typescript * import { Agent } from '@mastra/core/agent'; * import { DurableAgent } from '@mastra/core/agent/durable'; * * const agent = new Agent({ * id: 'my-agent', * instructions: 'You are a helpful assistant', * model: openai('gpt-4'), * }); * * const durableAgent = new DurableAgent({ agent }); * * const { output, runId, cleanup } = await durableAgent.stream('Hello!'); * const text = await output.text; * cleanup(); * ``` */ export declare class DurableAgent<TAgentId extends string = string, TTools extends ToolsInput = ToolsInput, TOutput = undefined> extends Agent<TAgentId, TTools, TOutput> { #private; /** * Create a new DurableAgent that wraps an existing Agent */ constructor(config: DurableAgentConfig<TAgentId, TTools, TOutput>); /** * Get the resolved cache instance. * Lazily initialized to allow inheriting from Mastra. */ get cache(): MastraServerCache | null; /** * Get the PubSub instance. * Returns CachingPubSub if caching is enabled, otherwise the inner pubsub. */ get pubsub(): PubSub; /** * Get the wrapped agent instance. */ get agent(): Agent<TAgentId, TTools, TOutput>; /** * Get the run registry (for testing and advanced usage) */ get runRegistry(): ExtendedRunRegistry; /** * Get the max steps configured for this agent */ get maxSteps(): number | undefined; /** * Get the cleanup timeout in milliseconds. * Returns 0 if auto-cleanup is disabled. */ get cleanupTimeoutMs(): number; getModel(options?: any): import("../../_types/@internal_ai-sdk-v4/dist/index.d.ts").LanguageModelV1 | import("..").MastraLanguageModel | Promise<import("../../_types/@internal_ai-sdk-v4/dist/index.d.ts").LanguageModelV1 | import("..").MastraLanguageModel>; getInstructions(options?: any): import("../../llm").SystemMessage | Promise<import("../../llm").SystemMessage>; getDefaultOptions(options?: any): AgentExecutionOptions<TOutput> | Promise<AgentExecutionOptions<TOutput>>; listTools(options?: any): TTools | Promise<TTools>; getMemory(): Promise<import("../../memory").MastraMemory | undefined>; getVoice(): Promise<import("../../_types/@internal_voice/dist/index.d.ts").MastraVoice<unknown, unknown, unknown, import("../../_types/@internal_voice/dist/index.d.ts").ToolsInput, import("../../_types/@internal_voice/dist/index.d.ts").VoiceEventMap, unknown>>; __getEditorConfig(): import("..").AgentEditorConfig | undefined; __getOverridableFields(): { instructions: import("../../types").DynamicArgument<import("../../llm").SystemMessage, unknown>; model: { id: string; model: import("../../types").DynamicArgument<import("../../llm").MastraModelConfig>; maxRetries: number; enabled: boolean; modelSettings?: import("../../types").DynamicArgument<import("..").ModelFallbackSettings>; providerOptions?: import("../../types").DynamicArgument<import("../../llm/model/provider-options").ProviderOptions>; headers?: import("../../types").DynamicArgument<Record<string, string>>; }[] | import("../../types").DynamicArgument<import("../../llm").MastraModelConfig | import("..").ModelWithRetries[], unknown>; tools: import("../../types").DynamicArgument<TTools, unknown>; workspace: import("../../types").DynamicArgument<import("../../workspace").AnyWorkspace | undefined, unknown>; }; __updateInstructions(instructions: Parameters<Agent<TAgentId, TTools, TOutput>['__updateInstructions']>[0]): void; __updateModel(config: Parameters<Agent<TAgentId, TTools, TOutput>['__updateModel']>[0]): void; __setTools(tools: Parameters<Agent<TAgentId, TTools, TOutput>['__setTools']>[0]): void; /** * Create a per-request clone for applying stored editor overrides. * * The base `Agent.__fork()` builds a bare `new Agent(...)`, which for a * DurableAgent would drop the wrapped agent and every delegating override * (tools, model, memory, voice, durable streaming) — the served fork ends up a * plain `Agent` with no tools. Instead, fork the wrapped agent (so overrides * applied to this fork don't mutate the singleton) and re-wrap it in the same * durable subclass, preserving pubsub/cache/run configuration. * * @internal */ __fork(): Agent<TAgentId, TTools, TOutput>; /** * Get the PubSub instance for use by subclasses. * @internal */ protected get pubsubInternal(): PubSub; /** * Get the run registry for use by subclasses. * @internal */ protected get runRegistryInternal(): ExtendedRunRegistry; /** * Execute the durable workflow. * * Subclasses override this method to customize how the workflow is executed: * - DurableAgent (this): Runs the workflow directly via createRun + start * - EventedAgent: Uses run.startAsync() for fire-and-forget execution * - InngestAgent: Uses inngest.send() to trigger Inngest function * * @param runId - The unique run ID * @param workflowInput - The serialized workflow input * @internal */ protected executeWorkflow(runId: string, workflowInput: DurableAgenticWorkflowInput): Promise<void>; /** * Create the durable workflow for this agent. * * Subclasses can override this method to use a different workflow implementation: * - DurableAgent (this): Uses createDurableAgenticWorkflow() * - InngestAgent: Uses createInngestDurableAgenticWorkflow() * * @internal */ protected createWorkflow(): ReturnType<typeof createDurableAgenticWorkflow>; /** * Emit an error event to pubsub. * * @param runId - The run ID * @param error - The error to emit * @internal */ protected emitError(runId: string, error: Error): Promise<void>; /** * Stream a response from the agent using durable execution. */ stream(messages: MessageListInput, options?: DurableAgentStreamOptions<TOutput>): Promise<DurableAgentStreamResult<TOutput>>; /** * Resume a suspended workflow execution. */ resume(runId: string, resumeData: unknown, options?: { onChunk?: (chunk: ChunkType<TOutput>) => void | Promise<void>; onStepFinish?: (result: AgentStepFinishEventData) => void | Promise<void>; onFinish?: MastraOnFinishCallback<TOutput>; onError?: ({ error }: { error: Error | string; }) => void | Promise<void>; onSuspended?: (data: AgentSuspendedEventData) => void | Promise<void>; /** * Optional abort signal scoped to the resumed segment. Forwarded onto a * fresh internal controller installed on the run's registry entry, so * `result.abort()` and the external signal can both cancel the resumed * iterations. */ abortSignal?: AbortSignal; /** * When set, keep the resumed segment open after the workflow's initial * resume turn finishes and continue streaming follow-up turns until the * agent goes idle (no in-flight background tasks for the same memory * scope). Same semantics as `stream({ untilIdle })`. Pass an object to * tune `maxIdleMs`. */ untilIdle?: boolean | { maxIdleMs?: number; }; }): Promise<DurableAgentStreamResult<TOutput>>; /** * Override the inherited `resumeStream()` so that callers using the base * `Agent` API (including `approveToolCall` / `declineToolCall`) are routed * through the durable `resume()` path instead of the regular Agent's * snapshot-based resume. * * Returns just the `MastraModelOutput` (matching the base Agent's return * type) while internally delegating to `this.resume()`. */ resumeStream(resumeData: any, streamOptions?: any): Promise<MastraModelOutput<TOutput>>; /** * Override the inherited `approveToolCall()` to route through the durable * `resume()` path. */ approveToolCall(options: { runId: string; toolCallId?: string; } & Record<string, any>): Promise<MastraModelOutput<any>>; /** * Override the inherited `declineToolCall()` to route through the durable * `resume()` path. */ declineToolCall(options: { runId: string; toolCallId?: string; } & Record<string, any>): Promise<MastraModelOutput<any>>; /** * Generate a complete response from the agent using durable execution. * * Drains the underlying durable stream to completion and returns the same * {@link FullOutput} shape as non-durable `Agent.generate`. The underlying * workflow is identical to `stream()` — it just collects the final result * for callers that don't want to consume chunks themselves. * * This method intentionally re-implements the `stream()` setup rather than * delegating to `this.stream(...)` so that `prepareForDurableExecution` (and * downstream `convertTools`) receives `methodType: 'generate'`. Tool * factories that vary their `CoreTool` output based on the calling method * (e.g. `clientTools` vs server-side tools) rely on this signal — calling * `stream()` here would silently pass `methodType: 'stream'`. * * If the run suspends (e.g. tool approval or `suspend()` from a tool), the * returned output's `finishReason` will be `'suspended'` and * `suspendPayload` will be populated. Use {@link DurableAgent.resumeGenerate} * to continue. * * Note on suspend persistence: for the base `DurableAgent`, the workflow * engine's `run.start()` only resolves after the suspend snapshot is * persisted, so awaiting `workflowExecution` on suspend is sufficient for * a subsequent `resumeGenerate()` to find the snapshot. Subclasses like * `EventedAgent` use a fire-and-forget `run.startAsync()` and therefore * cannot rely on this await for snapshot durability — see the * `EventedAgent` docs for the recommended pattern. */ generate(messages: MessageListInput, options?: DurableAgentStreamOptions<TOutput>): Promise<FullOutput<TOutput>>; /** * Resume a suspended durable run and drain it to a single * {@link FullOutput}. Mirrors {@link Agent.resumeGenerate} on top of * {@link DurableAgent.resume}. * * Unlike `generate()`, this delegates to `resume()` because resume reads * its tools from the existing run-registry entry rather than running * `prepareForDurableExecution` again — there is no `methodType` to thread * through. The same `EventedAgent` caveat about fire-and-forget snapshot * persistence noted on `generate()` applies if the resumed turn suspends. */ resumeGenerate(runId: string, resumeData: unknown, options?: Parameters<DurableAgent<TAgentId, TTools, TOutput>['resume']>[2]): Promise<FullOutput<TOutput>>; /** * Observe an existing stream. * Use this to reconnect to a stream after a network disconnection. * * **Warning:** The returned `cleanup()` function destroys the run's registry * entries and cached PubSub events. Only call it when you are done with the * run entirely. If the workflow is suspended and you intend to resume later, * do not call cleanup — let the auto-cleanup timer handle it after * FINISH/ERROR. Auto-cleanup does not fire on SUSPENDED events. */ observe(runId: string, options?: { offset?: number; onChunk?: (chunk: ChunkType<TOutput>) => void | Promise<void>; onStepFinish?: (result: AgentStepFinishEventData) => void | Promise<void>; onFinish?: MastraOnFinishCallback<TOutput>; onError?: ({ error }: { error: Error | string; }) => void | Promise<void>; onSuspended?: (data: AgentSuspendedEventData) => void | Promise<void>; }): Promise<Omit<DurableAgentStreamResult<TOutput>, 'runId'> & { runId: string; }>; /** * Get the workflow instance for direct execution. * Lazily creates the workflow and registers Mastra on it (needed for * getAgentById in execution steps). */ getWorkflow(): import("../../workflows").Workflow<import("../../workflows").DefaultEngineType, import("../../workflows").Step<string, unknown, unknown, unknown, unknown, unknown, any, unknown>[], "durable-agentic-loop", unknown, { __workflowKind: "durable-agent"; runId: string; agentId: string; messageListState: any; toolsMetadata: any[]; modelConfig: { provider: string; modelId: string; specificationVersion?: string | undefined; settings?: Record<string, any> | undefined; providerOptions?: Record<string, any> | undefined; }; options: any; state: any; messageId: string; agentName?: string | undefined; modelList?: { id: string; config: { provider: string; modelId: string; specificationVersion?: string | undefined; originalConfig?: string | Record<string, any> | undefined; providerOptions?: Record<string, any> | undefined; }; maxRetries: number; enabled: boolean; }[] | undefined; agentSpanData?: any; modelSpanData?: any; requestContextEntries?: Record<string, any> | undefined; }, { messageListState: any; messageId: string; stepResult: any; output: { usage: any; steps: any[]; text?: string | undefined; }; state: any; }, { messageListState: any; messageId: string; stepResult: any; output: { usage: any; steps: any[]; text?: string | undefined; }; state: any; }, unknown>; /** * @deprecated Use `stream(messages, { untilIdle: true })` instead. * * Stream until all background tasks complete and the agent is idle. * Mirrors the regular Agent's streamUntilIdle but adapted for durable execution. */ streamUntilIdle<OUTPUT = TOutput>(messages: MessageListInput, streamOptions?: DurableAgentStreamOptions<OUTPUT> & { maxIdleMs?: number; }): Promise<DurableAgentStreamResult<OUTPUT>>; /** * Prepare for durable execution without starting it. */ prepare(messages: MessageListInput, options?: AgentExecutionOptions<TOutput>): Promise<{ runId: string; messageId: string; workflowInput: DurableAgenticWorkflowInput; registryEntry: import("./types").RunRegistryEntry; threadId: string | undefined; resourceId: string | undefined; }>; /** * Get the durable workflows required by this agent. * Called by Mastra during agent registration. * @internal */ getDurableWorkflows(): import("../../workflows").Workflow<import("../../workflows").DefaultEngineType, import("../../workflows").Step<string, unknown, unknown, unknown, unknown, unknown, any, unknown>[], "durable-agentic-loop", unknown, { __workflowKind: "durable-agent"; runId: string; agentId: string; messageListState: any; toolsMetadata: any[]; modelConfig: { provider: string; modelId: string; specificationVersion?: string | undefined; settings?: Record<string, any> | undefined; providerOptions?: Record<string, any> | undefined; }; options: any; state: any; messageId: string; agentName?: string | undefined; modelList?: { id: string; config: { provider: string; modelId: string; specificationVersion?: string | undefined; originalConfig?: string | Record<string, any> | undefined; providerOptions?: Record<string, any> | undefined; }; maxRetries: number; enabled: boolean; }[] | undefined; agentSpanData?: any; modelSpanData?: any; requestContextEntries?: Record<string, any> | undefined; }, { messageListState: any; messageId: string; stepResult: any; output: { usage: any; steps: any[]; text?: string | undefined; }; state: any; }, { messageListState: any; messageId: string; stepResult: any; output: { usage: any; steps: any[]; text?: string | undefined; }; state: any; }, unknown>[]; /** * Delegate scorer listing to the wrapped agent so that callers querying the * durable wrapper still see the underlying agent's scorers. */ listScorers(opts?: Parameters<Agent<TAgentId, TTools, TOutput>['listScorers']>[0]): ReturnType<Agent<TAgentId, TTools, TOutput>['listScorers']>; /** * Set the Mastra instance. * Called by the durable agent registration path in addAgent(). * Delegates to __registerMastra so the pubsub wiring and agent * registration happen regardless of which entry point is called first. * @internal */ __setMastra(mastra: Mastra): void; /** * Register the Mastra instance. * Called by Mastra during agent registration (normal Agent path). * * Also wires mastra.pubsub as the inner pubsub (if the user didn't provide * a custom one), so that the OBSERVE_AGENT_STREAM_ROUTE handler can subscribe * to the same PubSub instance that this agent publishes to. * @internal */ __registerMastra(mastra: Mastra): void; } //# sourceMappingURL=durable-agent.d.ts.map