@mastra/core
Version:
Mastra is a framework for building AI-powered applications and agents with a modern TypeScript stack.
587 lines • 22.1 kB
TypeScript
/**
* Workspace Class
*
* A Workspace combines a Filesystem and a Sandbox to provide agents
* with a complete environment for storing files and executing code.
*
* Users pass provider instances directly to the Workspace constructor.
*
* @example
* ```typescript
* import { Workspace } from '@mastra/core';
* import { LocalFilesystem } from '@mastra/workspace-fs-local';
* import { AgentFS } from '@mastra/workspace-fs-agentfs';
* import { ComputeSDKSandbox } from '@mastra/workspace-sandbox-computesdk';
*
* // Simple workspace with local filesystem
* const workspace = new Workspace({
* filesystem: new LocalFilesystem({ basePath: './workspace' }),
* });
*
* // Full workspace with AgentFS and cloud sandbox
* const fullWorkspace = new Workspace({
* filesystem: new AgentFS({ path: './agent.db' }),
* sandbox: new ComputeSDKSandbox({ provider: 'e2b' }),
* });
*
* await fullWorkspace.init();
* await fullWorkspace.filesystem?.writeFile('/code/app.py', 'print("Hello!")');
* const result = await fullWorkspace.sandbox?.executeCommand?.('python3', ['app.py'], { cwd: '/code' });
* ```
*/
import type { MastraBrowser } from '../browser/index.js';
import type { IMastraLogger } from '../logger/index.js';
import { RequestContext } from '../request-context/index.js';
import type { MastraVector } from '../vector/index.js';
import { CompositeFilesystem } from './filesystem/index.js';
import type { WorkspaceFilesystem, FilesystemInfo } from './filesystem/index.js';
import { LSPManager } from './lsp/index.js';
import type { LSPConfig } from './lsp/types.js';
import type { WorkspaceSandbox, OnMountHook } from './sandbox/index.js';
import type { BM25Config, Embedder, SearchOptions, SearchResult } from './search/index.js';
import type { WorkspaceSkills, SkillsResolver, SkillSource } from './skills/index.js';
import type { WorkspaceToolsConfig } from './tools/index.js';
import type { WorkspaceStatus } from './types.js';
/**
* A function that resolves a WorkspaceFilesystem dynamically based on request context.
* Called on each tool invocation, allowing different filesystems per request.
*/
export type WorkspaceFilesystemResolver = (context: {
requestContext: RequestContext;
}) => WorkspaceFilesystem | Promise<WorkspaceFilesystem>;
/**
* Configuration for creating a Workspace.
* Users pass provider instances directly.
*
* Generic type parameters allow the workspace to preserve the concrete types
* of filesystem and sandbox providers, so accessors return the exact type
* you passed in.
*/
export interface WorkspaceConfig<TFilesystem extends WorkspaceFilesystem | undefined = WorkspaceFilesystem | undefined, TSandbox extends WorkspaceSandbox | undefined = WorkspaceSandbox | undefined, TMounts extends Record<string, WorkspaceFilesystem> | undefined = undefined> {
/** Unique identifier (auto-generated if not provided) */
id?: string;
/** Human-readable name */
name?: string;
/**
* Filesystem provider instance, or a resolver function for dynamic per-request filesystems.
*
* Static: Pass a LocalFilesystem, AgentFS, or any WorkspaceFilesystem instance.
* Dynamic: Pass a function `({ requestContext }) => WorkspaceFilesystem` to resolve
* a different filesystem per request. The resolver is called at tool execution time.
*
* Extend MastraFilesystem for automatic logger integration (static instances only).
*/
filesystem?: TFilesystem | WorkspaceFilesystemResolver;
/**
* Sandbox provider instance.
* Use ComputeSDKSandbox to access E2B, Modal, Docker, etc.
* Extend MastraSandbox for automatic logger integration.
*/
sandbox?: TSandbox;
/**
* Mount multiple filesystems at different paths.
* Creates a CompositeFilesystem that routes operations based on path.
*
* When a sandbox is configured, filesystems are automatically mounted
* into the sandbox at their respective paths during init().
*
* Use the `onMount` hook to skip or customize mounting for specific filesystems.
*
* The concrete mount types are preserved — use `workspace.filesystem.mounts.get()`
* for typed access to individual mounts.
*
* @example
* ```typescript
* const workspace = new Workspace({
* sandbox: new E2BSandbox({ timeout: 60000 }),
* mounts: {
* '/data': new S3Filesystem({ bucket: 'my-data', ... }),
* '/skills': new S3Filesystem({ bucket: 'skills', readOnly: true, ... }),
* },
* });
*
* await workspace.init();
* workspace.filesystem // CompositeFilesystem<{ '/data': S3Filesystem, '/skills': S3Filesystem }>
* workspace.filesystem.mounts.get('/data') // S3Filesystem
* ```
*/
mounts?: TMounts;
/**
* Hook called before mounting each filesystem into the sandbox.
*
* Return values:
* - `false` - Skip mount entirely (don't mount this filesystem)
* - `{ success: true }` - Hook handled the mount successfully
* - `{ success: false, error?: string }` - Hook attempted mount but failed
* - `undefined` / no return - Use provider's default mount behavior
*
* This is useful for:
* - Skipping specific filesystems (e.g., local filesystems in remote sandbox)
* - Custom mount implementations
* - Syncing files instead of FUSE mounting
*
* Note: If your hook handles the mount, you're responsible for the entire
* implementation. The sandbox provider won't do any additional tracking.
*
* @example Skip local filesystems
* ```typescript
* const workspace = new Workspace({
* sandbox: new E2BSandbox(),
* mounts: {
* '/data': new S3Filesystem({ bucket: 'data', ... }),
* '/local': new LocalFilesystem({ basePath: './data' }),
* },
* onMount: ({ filesystem }) => {
* if (filesystem.provider === 'local') return false;
* },
* });
* ```
*
* @example Custom mount implementation
* ```typescript
* onMount: async ({ filesystem, mountPath, config, sandbox }) => {
* if (config?.type === 's3') {
* await sandbox.executeCommand?.('my-s3-mount', [mountPath]);
* return { success: true };
* }
* }
* ```
*/
onMount?: OnMountHook;
/**
* Browser provider for web automation.
*
* Must be a `MastraBrowser` instance with `providerType: 'cli'` (e.g., `BrowserViewer`).
* SDK providers (`AgentBrowser`, `StagehandBrowser`) are not supported here —
* use `Agent.browser` for SDK providers.
*
* The browser is launched via Playwright and exposes a CDP URL that CLI tools
* (`agent-browser`, `browser-use`, `browse-cli`) can connect to.
*
* @example
* ```typescript
* import { BrowserViewer } from '@mastra/browser-viewer';
*
* const workspace = new Workspace({
* sandbox: new LocalSandbox({ cwd: './workspace' }),
* browser: new BrowserViewer({
* cli: 'agent-browser',
* headless: false,
* }),
* });
* ```
*/
browser?: MastraBrowser;
/**
* Vector store for semantic search.
* When provided along with embedder, enables vector and hybrid search.
*/
vectorStore?: MastraVector;
/**
* Embedder function for generating vectors.
* Required when vectorStore is provided.
*/
embedder?: Embedder;
/**
* Enable BM25 keyword search.
* Pass true for defaults, or a BM25Config object for custom parameters.
*/
bm25?: boolean | BM25Config;
/**
* Custom index name for the vector store.
* If not provided, defaults to a sanitized version of `${id}_search`.
*
* Must be a valid SQL identifier for SQL-based stores (PgVector, LibSQL):
* - Start with a letter or underscore
* - Contain only letters, numbers, or underscores
* - Maximum 63 characters
*
* @example 'my_workspace_vectors'
*/
searchIndexName?: string;
/**
* Paths to auto-index on init().
* Files in these directories will be indexed for search.
* @example ['docs', 'support']
*/
autoIndexPaths?: string[];
/**
* Paths where skills are located.
* Workspace will discover SKILL.md files in these directories.
*
* Can be a static array of paths or a function that returns paths
* dynamically based on request context (e.g., user tier, tenant).
*
* @example Static paths
* ```typescript
* skills: ['skills', 'node_modules/@myorg/skills']
* ```
*
* @example Dynamic paths
* ```typescript
* skills: (ctx) => {
* const tier = ctx.requestContext?.get('userTier');
* return tier === 'premium'
* ? ['skills/basic', 'skills/premium']
* : ['skills/basic'];
* }
* ```
*/
skills?: SkillsResolver;
/**
* Custom SkillSource to use for skill discovery.
* When provided, this source is used instead of the workspace filesystem or LocalSkillSource.
*
* Use `VersionedSkillSource` to read skills from the content-addressable blob store,
* serving a specific published version without touching the live filesystem.
*
* @example
* ```typescript
* import { VersionedSkillSource } from '@mastra/core/workspace';
*
* const workspace = new Workspace({
* skills: ['skills'],
* skillSource: new VersionedSkillSource(tree, blobStore, versionCreatedAt),
* });
* ```
*/
skillSource?: SkillSource;
/**
* Check SKILL.md file mtime in addition to directory mtime for staleness detection.
*
* When enabled, allows hot-reload detection of in-place SKILL.md edits
* (e.g., fixing a validation error or updating a skill description).
*
* Trade-off: This doubles the stat() calls per skill during staleness checks.
* Recommended for local development only. Not recommended for cloud storage
* backends (S3, etc.) where stat() calls have higher latency.
*
* @default false
*/
checkSkillFileMtime?: boolean;
/**
* Enable LSP diagnostics for edit tools.
*
* When enabled, edit tools (edit_file, write_file, ast_edit) will append
* type errors, warnings, and other diagnostics from language servers after edits.
*
* LSP requires a sandbox with a process manager (`sandbox.processes`) to spawn
* language server processes. It works with any sandbox backend (local, E2B, etc.).
*
* Requires optional peer dependencies: `vscode-jsonrpc`, `vscode-languageserver-protocol`,
* and the relevant language server (e.g. `typescript-language-server` for TypeScript).
*
* - `true` — Enable with defaults
* - `LSPConfig` object — Enable with custom timeouts/settings
*
* @default undefined (disabled)
*/
lsp?: boolean | LSPConfig;
/**
* Per-tool configuration for workspace tools.
* Controls which tools are enabled and their safety settings.
*
* This replaces the provider-level `requireApproval` and `requireReadBeforeWrite`
* settings, allowing more granular control per tool.
*
* @example
* ```typescript
* tools: {
* mastra_workspace_read_file: {
* enabled: true,
* requireApproval: false,
* },
* mastra_workspace_write_file: {
* enabled: true,
* requireApproval: true,
* requireReadBeforeWrite: true,
* },
* mastra_workspace_execute_command: {
* enabled: true,
* requireApproval: true,
* },
* }
* ```
*/
tools?: WorkspaceToolsConfig;
/** Auto-sync between fs and sandbox (default: false) */
autoSync?: boolean;
/** Timeout for individual operations in milliseconds */
operationTimeout?: number;
}
export type { WorkspaceStatus } from './types.js';
/**
* A Workspace with any combination of filesystem, sandbox, and mounts.
* Use this when you need to accept any Workspace regardless of its generic parameters.
*/
export type AnyWorkspace = Workspace<WorkspaceFilesystem | undefined, WorkspaceSandbox | undefined, any>;
/** A workspace entry in the Mastra registry, enriched with source metadata. */
export interface RegisteredWorkspace {
workspace: Workspace;
source: 'mastra' | 'agent';
agentId?: string;
agentName?: string;
}
/**
* Information about how filesystem and sandbox paths relate.
* Used by agents to understand how to access workspace files from sandbox code.
*/
export interface PathContext {
/** Filesystem details (if available) */
filesystem?: {
provider: string;
/** Absolute base path on disk (for local filesystems) */
basePath?: string;
};
/** Sandbox details (if available) */
sandbox?: {
provider: string;
/** Working directory for command execution */
workingDirectory?: string;
};
/**
* Human-readable instructions for how to access filesystem files from sandbox code.
* Combined from filesystem and sandbox provider instructions.
*/
instructions: string;
}
export interface WorkspaceInfo {
id: string;
name: string;
status: WorkspaceStatus;
createdAt: Date;
lastAccessedAt: Date;
/** Filesystem info (if available) */
filesystem?: FilesystemInfo & {
totalFiles?: number;
totalSize?: number;
};
/** Sandbox info (if available) */
sandbox?: {
provider: string;
status: string;
resources?: {
memoryMB?: number;
memoryUsedMB?: number;
cpuCores?: number;
cpuPercent?: number;
diskMB?: number;
diskUsedMB?: number;
};
};
}
/**
* Workspace provides agents with filesystem and execution capabilities.
*
* At minimum, a workspace has either a filesystem or a sandbox (or both).
* Users pass instantiated provider objects to the constructor.
*/
export declare class Workspace<TFilesystem extends WorkspaceFilesystem | undefined = WorkspaceFilesystem | undefined, TSandbox extends WorkspaceSandbox | undefined = WorkspaceSandbox | undefined, TMounts extends Record<string, WorkspaceFilesystem> | undefined = undefined> {
readonly id: string;
readonly name: string;
readonly createdAt: Date;
lastAccessedAt: Date;
private _status;
private readonly _fs?;
private readonly _filesystemResolver?;
private readonly _sandbox?;
private readonly _browser?;
private readonly _config;
private readonly _searchEngine?;
private _skills?;
private _lsp?;
private _logger?;
constructor(config: WorkspaceConfig<TFilesystem, TSandbox, TMounts>);
private generateId;
private hasSkillsConfig;
get status(): WorkspaceStatus;
/**
* The filesystem provider (if configured).
*
* Returns the concrete type you passed to the constructor.
* When `mounts` is used instead of `filesystem`, returns `CompositeFilesystem`
* parameterized with the concrete mount types.
*/
get filesystem(): [TMounts] extends [Record<string, WorkspaceFilesystem>] ? CompositeFilesystem<TMounts> : TFilesystem;
/**
* The sandbox provider (if configured).
*
* Returns the concrete type you passed to the constructor.
*/
get sandbox(): TSandbox;
/**
* The browser provider (if configured).
*
* Returns the MastraBrowser instance (must be a CLI provider like BrowserViewer).
*/
get browser(): MastraBrowser | undefined;
/**
* Get the per-tool configuration for this workspace.
* Returns undefined if no tools config was provided.
*/
getToolsConfig(): WorkspaceToolsConfig | undefined;
/**
* The LSP manager (if configured, initialized, and a process manager is available).
* Returns undefined if LSP is not configured, deps are missing, or sandbox has no process manager.
*/
get lsp(): LSPManager | undefined;
/**
* Update the per-tool configuration for this workspace.
* Takes effect on the next `createWorkspaceTools()` call.
*
* @example
* ```typescript
* // Disable write tools for read-only mode
* workspace.setToolsConfig({
* mastra_workspace_write_file: { enabled: false },
* mastra_workspace_edit_file: { enabled: false },
* });
*
* // Re-enable all tools
* workspace.setToolsConfig(undefined);
* ```
*/
setToolsConfig(config: WorkspaceToolsConfig | undefined): void;
/**
* Returns true if a filesystem is configured, either as a static instance or a resolver function.
*/
hasFilesystemConfig(): boolean;
/**
* Resolve the filesystem for a given request context.
* When a resolver function is configured, calls it with the provided requestContext.
* When a static filesystem is configured, returns it directly.
* Returns undefined if no filesystem is configured.
*/
resolveFilesystem({ requestContext, }: {
requestContext: RequestContext;
}): Promise<WorkspaceFilesystem | undefined>;
/**
* Access skills stored in this workspace.
* Skills are SKILL.md files discovered from the configured skillPaths.
*
* Returns undefined if no skillPaths are configured.
*
* @example
* ```typescript
* const skills = await workspace.skills?.list();
* const skill = await workspace.skills?.get('skills/brand-guidelines');
* const results = await workspace.skills?.search('brand colors');
* ```
*/
get skills(): WorkspaceSkills | undefined;
/**
* Check if BM25 keyword search is available.
*/
get canBM25(): boolean;
/**
* Check if vector semantic search is available.
*/
get canVector(): boolean;
/**
* Check if hybrid search is available.
*/
get canHybrid(): boolean;
/**
* Index content for search.
* The path becomes the document ID in search results.
*
* @param path - File path (used as document ID)
* @param content - Text content to index
* @param options - Index options (metadata, type hints)
* @throws {SearchNotAvailableError} if search is not configured
*/
index(path: string, content: string, options?: {
type?: 'text' | 'image' | 'file';
mimeType?: string;
metadata?: Record<string, unknown>;
startLineOffset?: number;
}): Promise<void>;
/**
* Search indexed content.
*
* @param query - Search query string
* @param options - Search options (topK, mode, filters)
* @returns Array of search results
* @throws {SearchNotAvailableError} if search is not configured
*/
search(query: string, options?: SearchOptions): Promise<SearchResult[]>;
/**
* Rebuild the search index from filesystem paths.
* Used internally for auto-indexing on init.
*
* Paths can be plain directories, single files, or glob patterns.
* Uses resolvePathPattern for unified resolution: file matches are
* indexed directly, directory matches are recursed.
*/
private rebuildSearchIndex;
/**
* Load file contents for search indexing in parallel (bounded by {@link FS_READ_CONCURRENCY}).
* Paths that cannot be read as UTF-8 text are omitted (same behavior as {@link indexFileForSearch}).
*/
private batchReadFiles;
/**
* Batch-read paths and {@link SearchEngine.indexMany}
*
* @returns paths that were indexed successfully.
* @remarks Falls back to one-at-a-time indexing on failure of {@link SearchEngine.indexMany}
*/
private indexFilesForSearch;
/**
* Index a single file for search. Skips files that can't be read as text.
* Large files are automatically split into chunks to stay within embedding
* model token limits.
*
* @returns `filePath` when indexed, or `undefined` if read/index failed.
*/
private indexFileForSearch;
private getAllFiles;
/**
* Initialize the workspace.
* Starts the sandbox, initializes the filesystem, and auto-mounts filesystems.
*/
init(): Promise<void>;
/**
* Destroy the workspace and clean up all resources.
*/
destroy(): Promise<void>;
/**
* Get workspace information.
* @param options.includeFileCount - Whether to count total files (can be slow for large workspaces)
*/
getInfo(options?: {
includeFileCount?: boolean;
requestContext?: RequestContext;
}): Promise<WorkspaceInfo>;
/**
* Get human-readable instructions describing the workspace environment.
*
* When both a sandbox with mounts and a filesystem exist, each mount path
* is classified as sandbox-accessible (state === 'mounted') or
* workspace-only (pending / mounting / error / unsupported). When there's
* no sandbox or no mounts, falls back to provider-level instructions.
*
* @param opts - Optional options including request context for per-request customisation
* @returns Combined instructions string (may be empty)
*/
getInstructions(opts?: {
requestContext?: RequestContext;
}): string;
/**
* Get information about how filesystem and sandbox paths relate.
* Useful for understanding how to access workspace files from sandbox code.
*
* @deprecated Use {@link getInstructions} instead. `getInstructions()` is
* mount-state-aware and feeds into the system message via
* `WorkspaceInstructionsProcessor`.
*
* @returns PathContext with paths and instructions from providers
*/
getPathContext(): PathContext;
/**
* Set the logger for this workspace and propagate to providers.
* Called by Mastra when the logger is set.
* @internal
*/
__setLogger(logger: IMastraLogger): void;
}
//# sourceMappingURL=workspace.d.ts.map