UNPKG

@mastra/core

Version:

Mastra is a framework for building AI-powered applications and agents with a modern TypeScript stack.

537 lines 19.5 kB
import type { AgentSignalContents, AgentSignalInput } from '../agent/signals.js'; import type { MastraBrowser } from '../browser/browser.js'; import { Mastra } from '../mastra/index.js'; import type { MastraMemory } from '../memory/memory.js'; import type { TracingContext, TracingOptions } from '../observability/index.js'; import { RequestContext } from '../request-context/index.js'; import type { ObservationalMemoryRecord } from '../storage/types.js'; import { Workspace } from '../workspace/workspace.js'; import type { TaskItemSnapshot } from './tools.js'; import type { AvailableModel, HeartbeatHandler, HarnessConfig, HarnessDisplayState, HarnessDisplayStateListener, HarnessDisplayStateSubscriptionOptions, HarnessEventListener, HarnessMessage, HarnessMode, HarnessQuestionAnswer, HarnessSession, HarnessThread, ModelAuthStatus, PermissionPolicy, PermissionRules, TokenUsage, ToolCategory } from './types.js'; /** * The Harness orchestrates multiple agent modes, shared state, memory, and storage. * It's the core abstraction that a TUI (or other UI) controls. * * @example * ```ts * const harness = new Harness({ * id: "my-coding-agent", * storage: new LibSQLStore({ url: "file:./data.db" }), * stateSchema: z.object({ * currentModelId: z.string().optional(), * }), * modes: [ * { id: "plan", name: "Plan", default: true, agent: planAgent }, * { id: "build", name: "Build", agent: buildAgent }, * ], * }) * * harness.subscribe((event) => { * if (event.type === "message_update") renderMessage(event.message) * }) * * await harness.init() * await harness.sendMessage({ content: "Hello!" }) * ``` */ export declare class Harness<TState = {}> { #private; readonly id: string; private config; private stateSchema; private state; private currentModeId; private currentThreadId; private resourceId; private defaultResourceId; private listeners; private displayStateSchedulers; private abortController; private abortRequested; private currentRunId; private currentTraceId; private currentOperationId; private agentThreadSubscription; private agentThreadSubscriptionKey; private followUpQueue; private pendingApprovalResolve; private pendingApprovalToolName; private pendingSuspensionRunId; private pendingSuspensionToolCallId; private pendingQuestions; private pendingPlanApprovals; private workspace; private workspaceFn; private workspaceInitialized; private browser; private browserFn; private heartbeatTimers; private tokenUsage; private sessionGrantedCategories; private sessionGrantedTools; private displayState; private stateUpdateQueue; constructor(config: HarnessConfig<TState>); /** * Access the internal Mastra instance. * Available after `init()` when storage is configured. * Useful for scorer registration, observability access, and eval tooling. */ getMastra(): Mastra | undefined; /** * Sets or updates the harness-level browser and propagates it to static mode agents. */ setBrowser(browser: MastraBrowser | undefined): void; /** * Initialize the harness — loads storage and workspace. * Must be called before using the harness. */ init(): Promise<void>; /** * Select the most recent thread, or create one if none exist. */ selectOrCreateThread(): Promise<HarnessThread>; private getMemoryStorage; /** * Get current harness state (read-only snapshot). */ getState(): Readonly<TState>; private applyStateUpdates; /** * Update harness state. Validates against schema if provided. * Emits state_changed event. */ setState(updates: Partial<TState>): Promise<void>; private updateState; private getSchemaDefaults; listModes(): HarnessMode<TState>[]; getCurrentModeId(): string; getCurrentMode(): HarnessMode<TState>; /** * Switch to a different mode. * Aborts any in-progress generation and switches to the mode's default model. */ switchMode({ modeId }: { modeId: string; }): Promise<void>; /** * Load the stored model ID for a specific mode. * Falls back to: thread metadata -> mode's defaultModelId -> current model. */ private loadModeModelId; private propagateRuntimeServicesToAgent; /** * Get the agent for the current mode. */ private getCurrentAgent; /** * Get a short display name from the current model ID. */ getModelName(): string; /** * Get the full model ID (e.g., "anthropic/claude-sonnet-4"). */ getFullModelId(): string; /** * Switch to a different model at runtime. */ switchModel({ modelId, scope, modeId, }: { modelId: string; scope?: 'global' | 'thread'; modeId?: string; }): Promise<void>; getCurrentModelId(): string; hasModelSelected(): boolean; /** * Check if the current model's provider has authentication configured. * Uses the provider registry's `apiKeyEnvVar` and the optional `modelAuthChecker` hook. */ getCurrentModelAuthStatus(): Promise<ModelAuthStatus>; /** * Get all available models from the provider registry with auth status. * Uses the optional `modelAuthChecker`, `modelUseCountProvider`, and * `customModelCatalogProvider` hooks. */ listAvailableModels(): Promise<AvailableModel[]>; private getProviderApiKeyEnvVar; getCurrentThreadId(): string | null; getResourceId(): string; getResolvedMemory(): Promise<MastraMemory | null>; setResourceId({ resourceId }: { resourceId: string; }): void; getDefaultResourceId(): string; getKnownResourceIds(): Promise<string[]>; createThread({ title }?: { title?: string; }): Promise<HarnessThread>; /** * Returns a memory accessor with thread and message management methods. */ get memory(): { createThread: ({ title }?: { title?: string; }) => Promise<HarnessThread>; switchThread: ({ threadId }: { threadId: string; }) => Promise<void>; listThreads: (options?: { allResources?: boolean; /** * Include forked subagent fork threads. Defaults to false: forks are * transient clones used by the runtime and should not show up in user-facing * thread lists / pickers / startup flows. Set to true for admin / debug * tooling that needs to see every thread. */ includeForkedSubagents?: boolean; }) => Promise<HarnessThread[]>; renameThread: ({ title }: { title: string; }) => Promise<void>; deleteThread: ({ threadId }: { threadId: string; }) => Promise<void>; }; private deleteThread; renameThread({ title }: { title: string; }): Promise<void>; cloneThread({ sourceThreadId, title, resourceId, }?: { sourceThreadId?: string; title?: string; resourceId?: string; }): Promise<HarnessThread>; switchThread({ threadId }: { threadId: string; }): Promise<void>; listThreads(options?: { allResources?: boolean; /** * Include forked subagent fork threads. Defaults to false: forks are * transient clones used by the runtime and should not show up in user-facing * thread lists / pickers / startup flows. Set to true for admin / debug * tooling that needs to see every thread. */ includeForkedSubagents?: boolean; }): Promise<HarnessThread[]>; setThreadSetting({ key, value }: { key: string; value: unknown; }): Promise<void>; private deleteThreadSetting; private loadThreadMetadata; /** * Load observational memory progress for the current thread. * Reads the OM record and recent messages to reconstruct status, * then emits an `om_status` event for the UI. */ loadOMProgress(): Promise<void>; getObservationalMemoryRecord(): Promise<ObservationalMemoryRecord | null>; /** * Returns the observer model ID from state, falling back to omConfig defaults. */ getObserverModelId(): string | undefined; /** * Returns the reflector model ID from state, falling back to omConfig defaults. */ getReflectorModelId(): string | undefined; /** * Returns the observation threshold from state, falling back to omConfig defaults. */ getObservationThreshold(): number | undefined; /** * Returns the reflection threshold from state, falling back to omConfig defaults. */ getReflectionThreshold(): number | undefined; /** * Resolves the observer model ID to a language model instance via `resolveModel`. */ getResolvedObserverModel(): import("../memory").MastraLanguageModel | undefined; /** * Resolves the reflector model ID to a language model instance via `resolveModel`. */ getResolvedReflectorModel(): import("../memory").MastraLanguageModel | undefined; /** * Switch the Observer model. */ switchObserverModel({ modelId }: { modelId: string; }): Promise<void>; /** * Switch the Reflector model. */ switchReflectorModel({ modelId }: { modelId: string; }): Promise<void>; getSubagentModelId({ agentType }?: { agentType?: string; }): string | null; setSubagentModelId({ modelId, agentType }: { modelId: string; agentType?: string; }): Promise<void>; grantSessionCategory({ category }: { category: ToolCategory; }): void; grantSessionTool({ toolName }: { toolName: string; }): void; getSessionGrants(): { categories: ToolCategory[]; tools: string[]; }; getToolCategory({ toolName }: { toolName: string; }): ToolCategory | null; setPermissionForCategory({ category, policy }: { category: ToolCategory; policy: PermissionPolicy; }): void; setPermissionForTool({ toolName, policy }: { toolName: string; policy: PermissionPolicy; }): void; getPermissionRules(): PermissionRules; /** * Resolve whether a tool call should be auto-approved, denied, or asked. * Resolution chain: per-tool deny → yolo → per-tool policy → session tool grant → * session category grant → category policy → "ask" */ private resolveToolApproval; private cleanupAgentThreadSubscription; private getAgentThreadSubscriptionKey; private ensureAgentThreadSubscription; private ensureCurrentAgentThreadSubscription; private drainFollowUpQueue; private isActiveAgentThreadSubscription; private finishSubscribedStreamRun; private handleSubscribedStreamError; private processSubscribedThreadStream; /** * Send a signal to the current agent/thread. */ sendSignal(input: AgentSignalInput | { content: AgentSignalContents; tracingContext?: TracingContext; tracingOptions?: TracingOptions; requestContext?: RequestContext; }): { id: string; type: AgentSignalInput['type']; accepted: Promise<{ accepted: true; runId: string; }>; }; /** * Send a message to the current agent. * Streams the response and emits events. */ sendMessage({ content, files, tracingContext, tracingOptions, requestContext: requestContextInput, }: { content: string; files?: Array<{ data: string; mediaType: string; filename?: string; }>; tracingContext?: TracingContext; tracingOptions?: TracingOptions; requestContext?: RequestContext; }): Promise<void>; listMessages(options?: { limit?: number; }): Promise<HarnessMessage[]>; saveSystemReminderMessage({ message, reminderType, role, metadata, }: { message: string; reminderType: string; role?: 'user' | 'assistant' | 'system'; metadata?: Record<string, unknown>; }): Promise<HarnessMessage | null>; listMessagesForThread({ threadId, limit }: { threadId: string; limit?: number; }): Promise<HarnessMessage[]>; getFirstUserMessageForThread({ threadId }: { threadId: string; }): Promise<HarnessMessage | null>; getFirstUserMessagesForThreads({ threadIds }: { threadIds: string[]; }): Promise<Map<string, HarnessMessage>>; private convertToHarnessMessage; /** * Process a stream response (shared between sendMessage and tool approval). */ private createStreamState; private abortForOmFailure; private processStream; private processStreamChunk; private finishStreamState; /** * Abort the current operation. */ abort(): void; /** * Steer the agent mid-stream: aborts current run and sends a new message. */ steer({ content, requestContext }: { content: string; requestContext?: RequestContext; }): Promise<void>; /** * Queue a follow-up message to be processed after the current operation completes. */ followUp({ content, requestContext }: { content: string; requestContext?: RequestContext; }): Promise<void>; getFollowUpCount(): number; isRunning(): boolean; getCurrentRunId(): string | null; isCurrentThreadStreamActive(): boolean; /** * Resolve once the current thread's stream is fully idle. * * After `abort()` is called the run's status can still be `'running'` for a * few microtasks while the underlying model stream finalizes. Callers that * need to send a fresh signal after an abort (e.g. plan approval → mode * switch → trigger reminder) should await this before calling `sendSignal` * to avoid the new signal being queued onto the dying run, which would then * be drained with the previous run's already-aborted abortSignal. */ private waitForCurrentThreadStreamIdle; getCurrentTraceId(): string | null; private getSubagentDisplayName; /** * Returns a read-only snapshot of the canonical display state. * UIs should use this to render instead of building up state from raw events. */ getDisplayState(): Readonly<HarnessDisplayState>; /** * Restore task display state after a UI replays persisted task tool history. * This updates the Harness-owned display snapshot without emitting a live * `task_updated` event, since no task tool just ran. */ restoreDisplayTasks(tasks: TaskItemSnapshot[]): void; /** * Reset display state fields that are scoped to a thread. * Called on thread switch/creation. */ private resetThreadDisplayState; /** * Respond to a pending tool approval from the UI. * "always_allow_category" grants the tool's category for the rest of the session, then approves. */ respondToToolApproval({ decision, requestContext, }: { decision: 'approve' | 'decline' | 'always_allow_category'; requestContext?: RequestContext; }): void; /** * Respond to a pending tool suspension from the UI. * Provides resume data so the suspended tool can continue execution. */ respondToToolSuspension({ resumeData, requestContext, }: { resumeData: any; requestContext?: RequestContext; }): Promise<void>; /** * Register a pending question resolver. * Called by agent tools (e.g., ask_user) to pause execution until the UI responds. */ registerQuestion({ questionId, resolve, }: { questionId: string; resolve: (answer: HarnessQuestionAnswer) => void; }): void; /** * Resolve a pending question with the user's answer. * Called by the UI when the user responds to a question dialog. */ respondToQuestion({ questionId, answer }: { questionId: string; answer: HarnessQuestionAnswer; }): void; /** * Register a pending plan approval resolver. * Called by agent tools (e.g., submit_plan) to pause execution until approval. */ registerPlanApproval({ planId, resolve, }: { planId: string; resolve: (result: { action: 'approved' | 'rejected'; feedback?: string; }) => void; }): void; /** * Respond to a pending plan approval. * On approval: resolves the suspended plan tool, then switches to the default mode. * On rejection: resolves with feedback (stays in current mode). */ respondToPlanApproval({ planId, response, }: { planId: string; response: { action: 'approved' | 'rejected'; feedback?: string; }; }): Promise<void>; private handleToolApprove; private handleToolDecline; private handleToolResume; /** * Subscribe to harness events. Returns an unsubscribe function. */ subscribe(listener: HarnessEventListener): () => void; /** * Subscribe to coalesced display state snapshots. * * Use this for UI rendering paths that only need the latest display state. * Raw event consumers should continue to use `subscribe()`. */ subscribeDisplayState(listener: HarnessDisplayStateListener, options?: HarnessDisplayStateSubscriptionOptions): () => void; private emit; private dispatchDisplayStateChanged; private dispatchToListeners; /** * Apply a display state update based on an incoming event. * This is the centralized state machine that keeps HarnessDisplayState in sync * with every event the Harness emits. */ private applyDisplayStateUpdate; /** * Build the toolsets object that includes built-in harness tools (ask_user, submit_plan, * and optionally subagent) plus any user-configured tools. * Used by sendMessage, handleToolApprove, and handleToolDecline. */ private buildToolsets; /** * Build request context for agent execution. * Tools can access harness state via requestContext.get('harness'). */ private buildRequestContext; /** * Resolve memory from config — handles both static instances and dynamic factory functions. */ private resolveMemory; getTokenUsage(): TokenUsage; private persistTokenUsage; getWorkspace(): Workspace | undefined; /** * Eagerly resolve the workspace. For dynamic workspaces (factory function), * this triggers resolution and caches the result so getWorkspace() returns it. * Useful for code paths outside the request flow (e.g. slash commands). */ resolveWorkspace({ requestContext, }?: { requestContext?: RequestContext; }): Promise<Workspace | undefined>; hasWorkspace(): boolean; isWorkspaceReady(): boolean; destroyWorkspace(): Promise<void>; private startHeartbeats; registerHeartbeat(handler: HeartbeatHandler): void; removeHeartbeat({ id }: { id: string; }): Promise<void>; stopHeartbeats(): Promise<void>; destroy(): Promise<void>; getSession(): Promise<HarnessSession>; private generateId; } //# sourceMappingURL=harness.d.ts.map