@mastra/core
Version:
329 lines • 15 kB
TypeScript
import { Agent } from '../agent/index.js';
import type { MastraBrowser } from '../browser/browser.js';
import { Mastra } from '../mastra/index.js';
import { RequestContext } from '../request-context/index.js';
import type { ObservationalMemoryRecord } from '../storage/types.js';
import { Workspace } from '../workspace/workspace.js';
import { Session } from './session.js';
import type { AvailableModel, IntervalHandler, AgentControllerConfig, AgentControllerMode, ModelAuthStatus, ToolCategory } from './types.js';
/**
* Returns Anthropic `providerOptions` that enable a server-side fallback to
* {@link FABLE_FALLBACK_MODEL} when the active model is `claude-fable-5`, and
* `undefined` otherwise.
*
* fable-5 can have a turn blocked server-side by its safety classifiers. With
* a fallback configured, Anthropic transparently retries the blocked turn on
* the fallback model and returns that model's answer instead of refusing. If
* the whole chain refuses, the run still ends on a `content-filter` finish
* reason, which is handled as a terminal error.
*
* The match is suffix-based so it covers `anthropic/claude-fable-5`, a bare
* `claude-fable-5`, and any pack/provider-prefixed form.
*/
export declare function buildFableFallbackProviderOptions(modelId: string): {
anthropic: {
fallbacks: {
model: string;
}[];
};
} | undefined;
/**
* Build a user-facing notice when a turn was served by an Anthropic
* server-side fallback model instead of the primary model.
*
* When the primary model's safety classifiers decline a turn and a fallback
* chain is configured (see {@link buildFableFallbackProviderOptions}), the API
* transparently retries on the fallback model and reports this via
* `fallback_message` entries in `providerMetadata.anthropic.iterations`.
* Without a notice the user has no way to tell that the response did not come
* from the model they selected.
*/
/**
* The AgentController orchestrates multiple agent modes, shared state, memory, and storage.
* It's the core abstraction that a TUI (or other UI) controls.
*
* @example
* ```ts
* const controller = new AgentController({
* 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 },
* ],
* })
*
* controller.subscribe((event) => {
* if (event.type === "message_update") renderMessage(event.message)
* })
*
* await controller.init()
* await controller.sendMessage({ content: "Hello!" })
* ```
*/
export declare class AgentController<TState = {}> {
#private;
readonly id: string;
private config;
private workspaceInitialized;
private initPromise;
private browser;
private workspace;
private intervalTimers;
private availableModelsCache;
private availableModelsCacheTime;
constructor(config: AgentControllerConfig<TState>);
/**
* Create a new, fully-wired {@link Session} and bring it online: it starts in
* the default mode with the seeded model, is connected to the AgentController's shared
* machinery (agent, storage/lock, config catalog), and has a current thread
* (the most recent thread for `resourceId`, or a freshly created one).
*
* The AgentController owns no session of its own — every consumer creates its own
* session and drives all work through it (`session.sendMessage`,
* `session.mode.switch`, `session.thread.*`, `session.subscribe`, ...). In a
* server / multiplayer setting, each request / thread / user gets its own
* session, isolated from every other: independent event bus, mode, model,
* state, and current thread.
*
* Call {@link init} once before creating sessions so shared storage and
* workspace are ready.
*
* @param id - Stable session identifier (mirrors `SessionRecord.id`). Defaults to the controller `id`.
* @param ownerId - Stable session owner (mirrors `SessionRecord.ownerId`). Defaults to the controller `id`.
* @param resourceId - Memory resource to bind this session to. Defaults to the controller `resourceId` or `id`.
*/
createSession({ resourceId, ownerId, id, tags, workspace, browser, requestContext, }?: {
resourceId?: string;
id?: string;
ownerId?: string;
/**
* Arbitrary string tags that scope this session. Each tag is seeded into the
* session's state and used to filter initial thread selection: a thread is a
* resume candidate only when its metadata matches every provided tag. This
* lets worktrees sharing a resourceId each resume their own thread (via a
* `projectPath` tag) and leaves room for future scoping dimensions without
* changing the API. Falls back to `initialState` when omitted.
*/
tags?: Record<string, string>;
workspace?: Workspace;
browser?: MastraBrowser;
requestContext?: RequestContext;
}): Promise<Session<TState>>;
/**
* Resolve a live session by resourceId, if one was created for it via
* {@link createSession}. Returns `undefined` when no session owns the
* resource. Used by notification delivery to run woken signals as the session
* that owns the target thread, rather than an arbitrary session.
*/
getSessionByResource(resourceId: string): Promise<Session<TState> | undefined>;
/**
* Access the Mastra instance backing this AgentController.
*
* Returns the parent Mastra when this AgentController is registered on one (see
* {@link __registerMastra}); otherwise the internal Mastra created during
* `init()` when storage is configured.
*
* Useful for scorer registration, observability access, and eval tooling.
*/
getMastra(): Mastra | undefined;
/**
* Whether a workspace is configured on this AgentController (static instance, dynamic
* factory, or config object). Sessions without an explicit workspace override
* fall back to this.
*/
hasWorkspace(): boolean;
/**
* Whether the AgentController-level static workspace has been initialized. Dynamic
* factory workspaces are resolved and initialized per-session during
* `createSession`, so this returns `false` for factory configs until a
* session is created.
*/
isWorkspaceReady(): boolean;
/**
* The AgentController-level workspace, if it is a static instance. Dynamic factory
* workspaces are not resolved here — use {@link resolveWorkspace} to resolve
* a factory against a session's request context.
*/
getWorkspace(): Workspace | undefined;
/**
* Eagerly resolve the workspace. For dynamic workspaces (factory function),
* this triggers resolution against the given session's request context and
* caches the result so {@link getWorkspace} returns it. Useful for code paths
* outside the request flow (e.g. slash commands).
*/
resolveWorkspace({ session, requestContext, }: {
session: Session<TState>;
requestContext?: RequestContext;
}): Promise<Workspace | undefined>;
/**
* Register this AgentController on a parent Mastra. Called by Mastra during
* construction when a harness is passed in its config. Once registered, the
* AgentController uses the parent Mastra (its storage, agents, gateways, and
* observability) instead of building its own internal one during `init()`.
*
* @internal
*/
__registerMastra(mastra: Mastra): void;
private resolveConfiguredMemory;
/**
* Sets or updates the harness-level browser and propagates it to mode agents.
*/
setBrowser(browser: MastraBrowser | undefined): void;
/**
* Initialize the harness — loads storage and workspace.
* Must be called before using the harness. Idempotent: repeated calls
* return the same in-flight/completed initialization instead of rebuilding
* the internal Mastra instance (which would orphan registered agents).
*/
init(): Promise<void>;
private runInit;
private getMemoryStorage;
/**
* The shared-host storage gateway the Session's thread domain reads/writes
* through. The Session owns the thread-domain logic; this adapter just maps
* raw storage rows to AgentController types — it does not call back into Session.
*/
private createThreadDataStore;
/** Persist a thread row to memory storage (gateway primitive for the Session thread domain). */
private persistThreadRow;
/** Delete a thread row from memory storage (gateway primitive for the Session thread domain). */
private deleteThreadRow;
/** Clone a thread (and messages) via the host's memory (gateway primitive for the Session thread domain). */
private cloneThreadRow;
private readThreadMetadataValue;
private writeThreadMetadataValue;
private removeThreadMetadataValue;
private queryThreadById;
private queryThreads;
private queryThreadMessages;
private queryFirstUserMessages;
listModes(): AgentControllerMode[];
private propagateRuntimeServicesToAgent;
private getAgentForMode;
/**
* Resolve the combined instructions for the current mode: harness-level
* instructions + mode-specific instructions. Passed at call time via
* `buildAgentMessageStreamOptions` so the agent's own instructions are
* never mutated.
*/
private resolveCurrentModeInstructions;
/**
* Convert AgentInstructions (string | string[] | system message objects) to
* a plain string for combining with mode instructions.
*/
private instructionsToString;
/**
* Get the agent for the current mode.
*/
/**
* Resolve the Agent backing the current mode, with runtime services (storage,
* pubsub, telemetry) propagated. Public so consumers like MastraCode's
* GoalManager can drive the agent's native objective methods
* (`setObjective`/`getObjective`/`clearObjective`/`updateObjectiveOptions`),
* which read/write the durable `threadState` `'goal'` slot.
*/
getCurrentAgent(session: Session<TState>): Agent;
/**
* Check if the current model's provider has authentication configured.
* Delegates to the {@link GatewayManager} auth chain (the same resolution
* the model router uses at run time). Returns `hasAuth: true` only when no
* model is selected; gateway-chain failures return `hasAuth: false` so the
* auth-status endpoint stays stable instead of erroring.
*/
getCurrentModelAuthStatus(session: Session<TState>): Promise<ModelAuthStatus>;
/**
* Get available models from the app-provided catalog hook with use counts applied.
*/
listAvailableModels(): Promise<AvailableModel[]>;
invalidateAvailableModelsCache(): void;
/**
* Point the session at a different memory resourceId. The resourceId itself
* lives on the session (`session.identity`); the AgentController orchestrates the
* surrounding teardown — dropping the current thread subscription and clearing
* the active thread — since those are AgentController-owned.
*/
setResourceId(session: Session<TState>, { resourceId }: {
resourceId: string;
}): Promise<void>;
getKnownResourceIds(session: Session<TState>): Promise<string[]>;
/**
* 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(session: Session<TState>): Promise<void>;
getObservationalMemoryRecord(session: Session<TState>): Promise<ObservationalMemoryRecord | null>;
getToolCategory({ toolName }: {
toolName: string;
}): ToolCategory | null;
/**
* Resolve the `activeTools` allowlist for the current mode's run.
*
* Returns `undefined` when the mode has no `availableTools` configured
* (no restriction — all tools visible). When the mode declares
* `availableTools`, returns that list filtered to remove tools whose
* permission category is denied.
*
* Per-tool `deny` is already handled by `buildToolsets` (denied tools are
* deleted from the toolsets), so those tools won't exist at execution time
* regardless of whether they appear in the allowlist.
*
* The returned list uses the same exposed tool names the execution pipeline
* checks against (e.g. `view`, `write_file`, `ask_user`), which matches the
* names workspace tools are renamed to via `TOOL_NAME_OVERRIDES`.
*/
private resolveModeActiveTools;
private buildAgentMessageStreamOptions;
private formatToolProgressOutput;
/**
* Options that every harness-driven agent run must carry — the initial stream
* AND every `resumeStream`. Centralized so the two paths can't drift: a
* missing `maxSteps` on resume silently caps the resumed run at the agent's
* small default and ends it mid-task (see {@link HARNESS_MAX_STEPS}).
*/
private buildSharedRunOptions;
/**
* Persist a system-reminder message for a thread (host-owned storage). Throws
* when no storage is configured — the Session guards the no-thread case before
* calling. Returns the saved message converted to {@link AgentControllerMessage}.
*/
private saveSystemReminder;
/**
* Resolve the mode the session transitions to when a plan is approved: the
* current mode's `transitionsTo`, else the configured default mode. The mode
* catalog is AgentController config, so this is host-owned. Returns `undefined` when
* no default mode is configured.
*/
private resolveTransitionModeId;
private convertToControllerMessage;
private getSubagentDisplayName;
/**
* 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 controller state via requestContext.get('controller').
*/
private buildRequestContext;
/**
* Resolve memory from config — handles both static instances and dynamic factory functions.
*/
private resolveMemory;
private persistTokenUsage;
private startIntervals;
registerInterval(handler: IntervalHandler): void;
removeInterval({ id }: {
id: string;
}): Promise<void>;
stopIntervals(): Promise<void>;
destroy(): Promise<void>;
private generateId;
}
//# sourceMappingURL=agent-controller.d.ts.map