deepagents 1.8.0 → 1.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +55 -3
- package/dist/index.cjs +294 -50
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +50 -6
- package/dist/index.d.ts +50 -6
- package/dist/index.js +294 -50
- package/dist/index.js.map +1 -1
- package/package.json +5 -2
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":["path","z","StateSchema","ReducedValue","ToolMessage","Command","Command","ToolMessage","HumanMessage","z","SystemMessage","AIMessage","ToolMessage","RemoveMessage","REMOVE_ALL_MESSAGES","ReducedValue","z","StateSchema","z","SystemMessage","z","StateSchema","ReducedValue","validateSkillName","z","HumanMessage","ToolMessage","AIMessage","SystemMessage","ContextOverflowError","Command","fsSync","path","fs","#timeout","#maxOutputBytes","#sandboxId","#env","#initialized","fs","path","cp","SystemMessage","Runnable","path","fs","os","z","fs","MAX_SKILL_FILE_SIZE","MAX_SKILL_NAME_LENGTH","MAX_SKILL_DESCRIPTION_LENGTH","fs","path"],"sources":["../src/backends/protocol.ts","../src/backends/utils.ts","../src/backends/state.ts","../src/middleware/fs.ts","../src/middleware/subagents.ts","../src/middleware/patch_tool_calls.ts","../src/values.ts","../src/middleware/memory.ts","../src/middleware/skills.ts","../src/middleware/utils.ts","../src/middleware/summarization.ts","../src/backends/store.ts","../src/backends/filesystem.ts","../src/backends/composite.ts","../src/backends/local-shell.ts","../src/backends/sandbox.ts","../src/agent.ts","../src/config.ts","../src/middleware/agent-memory.ts","../src/skills/loader.ts"],"sourcesContent":["/**\n * Protocol definition for pluggable memory backends.\n *\n * This module defines the BackendProtocol that all backend implementations\n * must follow. Backends can store files in different locations (state, filesystem,\n * database, etc.) and provide a uniform interface for file operations.\n */\n\nimport type { BaseStore } from \"@langchain/langgraph-checkpoint\";\n\nexport type MaybePromise<T> = T | Promise<T>;\n\n/**\n * Structured file listing info.\n *\n * Minimal contract used across backends. Only \"path\" is required.\n * Other fields are best-effort and may be absent depending on backend.\n */\nexport interface FileInfo {\n /** File path */\n path: string;\n /** Whether this is a directory */\n is_dir?: boolean;\n /** File size in bytes (approximate) */\n size?: number;\n /** ISO 8601 timestamp of last modification */\n modified_at?: string;\n}\n\n/**\n * Structured grep match entry.\n */\nexport interface GrepMatch {\n /** File path where match was found */\n path: string;\n /** Line number (1-indexed) */\n line: number;\n /** The matching line text */\n text: string;\n}\n\n/**\n * File data structure used by backends.\n *\n * All file data is represented as objects with this structure:\n */\nexport interface FileData {\n /** Lines of text content */\n content: string[];\n /** ISO format timestamp of creation */\n created_at: string;\n /** ISO format timestamp of last modification */\n modified_at: string;\n}\n\n/**\n * Result from backend write operations.\n *\n * Checkpoint backends populate filesUpdate with {file_path: file_data} for LangGraph state.\n * External backends set filesUpdate to null (already persisted to disk/S3/database/etc).\n */\nexport interface WriteResult {\n /** Error message on failure, undefined on success */\n error?: string;\n /** File path of written file, undefined on failure */\n path?: string;\n /**\n * State update dict for checkpoint backends, null for external storage.\n * Checkpoint backends populate this with {file_path: file_data} for LangGraph state.\n * External backends set null (already persisted to disk/S3/database/etc).\n */\n filesUpdate?: Record<string, FileData> | null;\n /** Metadata for the write operation, attached to the ToolMessage */\n metadata?: Record<string, unknown>;\n}\n\n/**\n * Result from backend edit operations.\n *\n * Checkpoint backends populate filesUpdate with {file_path: file_data} for LangGraph state.\n * External backends set filesUpdate to null (already persisted to disk/S3/database/etc).\n */\nexport interface EditResult {\n /** Error message on failure, undefined on success */\n error?: string;\n /** File path of edited file, undefined on failure */\n path?: string;\n /**\n * State update dict for checkpoint backends, null for external storage.\n * Checkpoint backends populate this with {file_path: file_data} for LangGraph state.\n * External backends set null (already persisted to disk/S3/database/etc).\n */\n filesUpdate?: Record<string, FileData> | null;\n /** Number of replacements made, undefined on failure */\n occurrences?: number;\n /** Metadata for the edit operation, attached to the ToolMessage */\n metadata?: Record<string, unknown>;\n}\n\n/**\n * Result of code execution.\n * Simplified schema optimized for LLM consumption.\n */\nexport interface ExecuteResponse {\n /** Combined stdout and stderr output of the executed command */\n output: string;\n /** The process exit code. 0 indicates success, non-zero indicates failure */\n exitCode: number | null;\n /** Whether the output was truncated due to backend limitations */\n truncated: boolean;\n}\n\n/**\n * Standardized error codes for file upload/download operations.\n */\nexport type FileOperationError =\n | \"file_not_found\"\n | \"permission_denied\"\n | \"is_directory\"\n | \"invalid_path\";\n\n/**\n * Result of a single file download operation.\n */\nexport interface FileDownloadResponse {\n /** The file path that was requested */\n path: string;\n /** File contents as Uint8Array on success, null on failure */\n content: Uint8Array | null;\n /** Standardized error code on failure, null on success */\n error: FileOperationError | null;\n}\n\n/**\n * Result of a single file upload operation.\n */\nexport interface FileUploadResponse {\n /** The file path that was requested */\n path: string;\n /** Standardized error code on failure, null on success */\n error: FileOperationError | null;\n}\n\n/**\n * Protocol for pluggable memory backends (single, unified).\n *\n * Backends can store files in different locations (state, filesystem, database, etc.)\n * and provide a uniform interface for file operations.\n *\n * All file data is represented as objects with the FileData structure.\n *\n * Methods can return either direct values or Promises, allowing both\n * synchronous and asynchronous implementations.\n */\nexport interface BackendProtocol {\n /**\n * Structured listing with file metadata.\n *\n * Lists files and directories in the specified directory (non-recursive).\n * Directories have a trailing / in their path and is_dir=true.\n *\n * @param path - Absolute path to directory\n * @returns List of FileInfo objects for files and directories directly in the directory\n */\n lsInfo(path: string): MaybePromise<FileInfo[]>;\n\n /**\n * Read file content with line numbers or an error string.\n *\n * @param filePath - Absolute file path\n * @param offset - Line offset to start reading from (0-indexed), default 0\n * @param limit - Maximum number of lines to read, default 500\n * @returns Formatted file content with line numbers, or error message\n */\n read(filePath: string, offset?: number, limit?: number): MaybePromise<string>;\n\n /**\n * Read file content as raw FileData.\n *\n * @param filePath - Absolute file path\n * @returns Raw file content as FileData\n */\n readRaw(filePath: string): MaybePromise<FileData>;\n\n /**\n * Structured search results or error string for invalid input.\n *\n * Searches file contents for a regex pattern.\n *\n * @param pattern - Regex pattern to search for\n * @param path - Base path to search from (default: null)\n * @param glob - Optional glob pattern to filter files (e.g., \"*.py\")\n * @returns List of GrepMatch objects or error string for invalid regex\n */\n grepRaw(\n pattern: string,\n path?: string | null,\n glob?: string | null,\n ): MaybePromise<GrepMatch[] | string>;\n\n /**\n * Structured glob matching returning FileInfo objects.\n *\n * @param pattern - Glob pattern (e.g., `*.py`, `**\\/*.ts`)\n * @param path - Base path to search from (default: \"/\")\n * @returns List of FileInfo objects matching the pattern\n */\n globInfo(pattern: string, path?: string): MaybePromise<FileInfo[]>;\n\n /**\n * Create a new file.\n *\n * @param filePath - Absolute file path\n * @param content - File content as string\n * @returns WriteResult with error populated on failure\n */\n write(filePath: string, content: string): MaybePromise<WriteResult>;\n\n /**\n * Edit a file by replacing string occurrences.\n *\n * @param filePath - Absolute file path\n * @param oldString - String to find and replace\n * @param newString - Replacement string\n * @param replaceAll - If true, replace all occurrences (default: false)\n * @returns EditResult with error, path, filesUpdate, and occurrences\n */\n edit(\n filePath: string,\n oldString: string,\n newString: string,\n replaceAll?: boolean,\n ): MaybePromise<EditResult>;\n\n /**\n * Upload multiple files.\n * Optional - backends that don't support file upload can omit this.\n *\n * @param files - List of [path, content] tuples to upload\n * @returns List of FileUploadResponse objects, one per input file\n */\n uploadFiles?(\n files: Array<[string, Uint8Array]>,\n ): MaybePromise<FileUploadResponse[]>;\n\n /**\n * Download multiple files.\n * Optional - backends that don't support file download can omit this.\n *\n * @param paths - List of file paths to download\n * @returns List of FileDownloadResponse objects, one per input path\n */\n downloadFiles?(paths: string[]): MaybePromise<FileDownloadResponse[]>;\n}\n\n/**\n * Protocol for sandboxed backends with isolated runtime.\n * Sandboxed backends run in isolated environments (e.g., containers)\n * and communicate via defined interfaces.\n */\nexport interface SandboxBackendProtocol extends BackendProtocol {\n /**\n * Execute a command in the sandbox.\n *\n * @param command - Full shell command string to execute\n * @returns ExecuteResponse with combined output, exit code, and truncation flag\n */\n execute(command: string): MaybePromise<ExecuteResponse>;\n\n /** Unique identifier for the sandbox backend instance */\n readonly id: string;\n}\n\n/**\n * Type guard to check if a backend supports execution.\n *\n * @param backend - Backend instance to check\n * @returns True if the backend implements SandboxBackendProtocol\n */\nexport function isSandboxBackend(\n backend: BackendProtocol,\n): backend is SandboxBackendProtocol {\n return (\n typeof (backend as SandboxBackendProtocol).execute === \"function\" &&\n typeof (backend as SandboxBackendProtocol).id === \"string\"\n );\n}\n\n/**\n * Metadata for a single sandbox instance.\n *\n * This lightweight structure is returned from list operations and provides\n * basic information about a sandbox without requiring a full connection.\n *\n * @typeParam MetadataT - Type of the metadata field. Providers can define\n * their own interface for type-safe metadata access.\n *\n * @example\n * ```typescript\n * // Using default metadata type\n * const info: SandboxInfo = {\n * sandboxId: \"sb_abc123\",\n * metadata: { status: \"running\", createdAt: \"2024-01-15T10:30:00Z\" },\n * };\n *\n * // Using typed metadata\n * interface MyMetadata {\n * status: \"running\" | \"stopped\";\n * createdAt: string;\n * }\n * const typedInfo: SandboxInfo<MyMetadata> = {\n * sandboxId: \"sb_abc123\",\n * metadata: { status: \"running\", createdAt: \"2024-01-15T10:30:00Z\" },\n * };\n * ```\n */\nexport interface SandboxInfo<MetadataT = Record<string, unknown>> {\n /** Unique identifier for the sandbox instance */\n sandboxId: string;\n /** Optional provider-specific metadata (e.g., creation time, status, template) */\n metadata?: MetadataT;\n}\n\n/**\n * Paginated response from a sandbox list operation.\n *\n * This structure supports cursor-based pagination for efficiently browsing\n * large collections of sandboxes.\n *\n * @typeParam MetadataT - Type of the metadata field in SandboxInfo items.\n *\n * @example\n * ```typescript\n * const response: SandboxListResponse = {\n * items: [\n * { sandboxId: \"sb_001\", metadata: { status: \"running\" } },\n * { sandboxId: \"sb_002\", metadata: { status: \"stopped\" } },\n * ],\n * cursor: \"eyJvZmZzZXQiOjEwMH0=\",\n * };\n *\n * // Fetch next page\n * const nextResponse = await provider.list({ cursor: response.cursor });\n * ```\n */\nexport interface SandboxListResponse<MetadataT = Record<string, unknown>> {\n /** List of sandbox metadata objects for the current page */\n items: SandboxInfo<MetadataT>[];\n /**\n * Opaque continuation token for retrieving the next page.\n * null indicates no more pages available.\n */\n cursor: string | null;\n}\n\n/**\n * Options for listing sandboxes.\n */\nexport interface SandboxListOptions {\n /**\n * Continuation token from a previous list() call.\n * Pass undefined to start from the beginning.\n */\n cursor?: string;\n}\n\n/**\n * Options for getting or creating a sandbox.\n */\nexport interface SandboxGetOrCreateOptions {\n /**\n * Unique identifier of an existing sandbox to retrieve.\n * If undefined, creates a new sandbox instance.\n * If provided but the sandbox doesn't exist, an error will be thrown.\n */\n sandboxId?: string;\n}\n\n/**\n * Options for deleting a sandbox.\n */\nexport interface SandboxDeleteOptions {\n /** Unique identifier of the sandbox to delete */\n sandboxId: string;\n}\n\n/**\n * Common error codes shared across all sandbox provider implementations.\n *\n * These represent the core error conditions that any sandbox provider may encounter.\n * Provider-specific error codes should extend this type with additional codes.\n *\n * @example\n * ```typescript\n * // Provider-specific error code type extending the common codes:\n * type MySandboxErrorCode = SandboxErrorCode | \"CUSTOM_ERROR\";\n * ```\n */\nexport type SandboxErrorCode =\n /** Sandbox has not been initialized - call initialize() first */\n | \"NOT_INITIALIZED\"\n /** Sandbox is already initialized - cannot initialize twice */\n | \"ALREADY_INITIALIZED\"\n /** Command execution timed out */\n | \"COMMAND_TIMEOUT\"\n /** Command execution failed */\n | \"COMMAND_FAILED\"\n /** File operation (read/write) failed */\n | \"FILE_OPERATION_FAILED\";\n\nconst SANDBOX_ERROR_SYMBOL = Symbol.for(\"sandbox.error\");\n\n/**\n * Custom error class for sandbox operations.\n *\n * @param message - Human-readable error description\n * @param code - Structured error code for programmatic handling\n * @returns SandboxError with message and code\n *\n * @example\n * ```typescript\n * try {\n * await sandbox.execute(\"some command\");\n * } catch (error) {\n * if (error instanceof SandboxError) {\n * switch (error.code) {\n * case \"NOT_INITIALIZED\":\n * await sandbox.initialize();\n * break;\n * case \"COMMAND_TIMEOUT\":\n * console.error(\"Command took too long\");\n * break;\n * default:\n * throw error;\n * }\n * }\n * }\n * ```\n */\nexport class SandboxError extends Error {\n /** Symbol for identifying sandbox error instances */\n [SANDBOX_ERROR_SYMBOL] = true as const;\n\n /** Error name for instanceof checks and logging */\n override readonly name: string = \"SandboxError\";\n\n /**\n * Creates a new SandboxError.\n *\n * @param message - Human-readable error description\n * @param code - Structured error code for programmatic handling\n */\n constructor(\n message: string,\n public readonly code: string,\n public readonly cause?: Error,\n ) {\n super(message);\n Object.setPrototypeOf(this, SandboxError.prototype);\n }\n\n static isInstance(error: unknown): error is SandboxError {\n return (\n typeof error === \"object\" &&\n error !== null &&\n (error as Record<symbol, unknown>)[SANDBOX_ERROR_SYMBOL] === true\n );\n }\n}\n\n/**\n * State and store container for backend initialization.\n *\n * This provides a clean interface for what backends need to access:\n * - state: Current agent state (with files, messages, etc.)\n * - store: Optional persistent store for cross-conversation data\n *\n * Different contexts build this differently:\n * - Tools: Extract state via getCurrentTaskInput(config)\n * - Middleware: Use request.state directly\n */\nexport interface StateAndStore {\n /** Current agent state with files, messages, etc. */\n state: unknown;\n /** Optional BaseStore for persistent cross-conversation storage */\n store?: BaseStore;\n /** Optional assistant ID for per-assistant isolation in store */\n assistantId?: string;\n}\n\n/**\n * Factory function type for creating backend instances.\n *\n * Backends receive StateAndStore which contains the current state\n * and optional store, extracted from the execution context.\n *\n * @example\n * ```typescript\n * // Using in middleware\n * const middleware = createFilesystemMiddleware({\n * backend: (stateAndStore) => new StateBackend(stateAndStore)\n * });\n * ```\n */\nexport type BackendFactory = (stateAndStore: StateAndStore) => BackendProtocol;\n","/**\n * Shared utility functions for memory backend implementations.\n *\n * This module contains both user-facing string formatters and structured\n * helpers used by backends and the composite router. Structured helpers\n * enable composition without fragile string parsing.\n */\n\nimport micromatch from \"micromatch\";\nimport { basename } from \"path\";\nimport type { FileData, GrepMatch } from \"./protocol.js\";\n\n// Constants\nexport const EMPTY_CONTENT_WARNING =\n \"System reminder: File exists but has empty contents\";\nexport const MAX_LINE_LENGTH = 10000;\nexport const LINE_NUMBER_WIDTH = 6;\nexport const TOOL_RESULT_TOKEN_LIMIT = 20000; // Same threshold as eviction\nexport const TRUNCATION_GUIDANCE =\n \"... [results truncated, try being more specific with your parameters]\";\n\n/**\n * Sanitize tool_call_id to prevent path traversal and separator issues.\n *\n * Replaces dangerous characters (., /, \\) with underscores.\n */\nexport function sanitizeToolCallId(toolCallId: string): string {\n return toolCallId.replace(/\\./g, \"_\").replace(/\\//g, \"_\").replace(/\\\\/g, \"_\");\n}\n\n/**\n * Format file content with line numbers (cat -n style).\n *\n * Chunks lines longer than MAX_LINE_LENGTH with continuation markers (e.g., 5.1, 5.2).\n *\n * @param content - File content as string or list of lines\n * @param startLine - Starting line number (default: 1)\n * @returns Formatted content with line numbers and continuation markers\n */\nexport function formatContentWithLineNumbers(\n content: string | string[],\n startLine: number = 1,\n): string {\n let lines: string[];\n if (typeof content === \"string\") {\n lines = content.split(\"\\n\");\n if (lines.length > 0 && lines[lines.length - 1] === \"\") {\n lines = lines.slice(0, -1);\n }\n } else {\n lines = content;\n }\n\n const resultLines: string[] = [];\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i];\n const lineNum = i + startLine;\n\n if (line.length <= MAX_LINE_LENGTH) {\n resultLines.push(\n `${lineNum.toString().padStart(LINE_NUMBER_WIDTH)}\\t${line}`,\n );\n } else {\n // Split long line into chunks with continuation markers\n const numChunks = Math.ceil(line.length / MAX_LINE_LENGTH);\n for (let chunkIdx = 0; chunkIdx < numChunks; chunkIdx++) {\n const start = chunkIdx * MAX_LINE_LENGTH;\n const end = Math.min(start + MAX_LINE_LENGTH, line.length);\n const chunk = line.substring(start, end);\n if (chunkIdx === 0) {\n // First chunk: use normal line number\n resultLines.push(\n `${lineNum.toString().padStart(LINE_NUMBER_WIDTH)}\\t${chunk}`,\n );\n } else {\n // Continuation chunks: use decimal notation (e.g., 5.1, 5.2)\n const continuationMarker = `${lineNum}.${chunkIdx}`;\n resultLines.push(\n `${continuationMarker.padStart(LINE_NUMBER_WIDTH)}\\t${chunk}`,\n );\n }\n }\n }\n }\n\n return resultLines.join(\"\\n\");\n}\n\n/**\n * Check if content is empty and return warning message.\n *\n * @param content - Content to check\n * @returns Warning message if empty, null otherwise\n */\nexport function checkEmptyContent(content: string): string | null {\n if (!content || content.trim() === \"\") {\n return EMPTY_CONTENT_WARNING;\n }\n return null;\n}\n\n/**\n * Convert FileData to plain string content.\n *\n * @param fileData - FileData object with 'content' key\n * @returns Content as string with lines joined by newlines\n */\nexport function fileDataToString(fileData: FileData): string {\n return fileData.content.join(\"\\n\");\n}\n\n/**\n * Create a FileData object with timestamps.\n *\n * @param content - File content as string\n * @param createdAt - Optional creation timestamp (ISO format)\n * @returns FileData object with content and timestamps\n */\nexport function createFileData(content: string, createdAt?: string): FileData {\n const lines = typeof content === \"string\" ? content.split(\"\\n\") : content;\n const now = new Date().toISOString();\n\n return {\n content: lines,\n created_at: createdAt || now,\n modified_at: now,\n };\n}\n\n/**\n * Update FileData with new content, preserving creation timestamp.\n *\n * @param fileData - Existing FileData object\n * @param content - New content as string\n * @returns Updated FileData object\n */\nexport function updateFileData(fileData: FileData, content: string): FileData {\n const lines = typeof content === \"string\" ? content.split(\"\\n\") : content;\n const now = new Date().toISOString();\n\n return {\n content: lines,\n created_at: fileData.created_at,\n modified_at: now,\n };\n}\n\n/**\n * Format file data for read response with line numbers.\n *\n * @param fileData - FileData object\n * @param offset - Line offset (0-indexed)\n * @param limit - Maximum number of lines\n * @returns Formatted content or error message\n */\nexport function formatReadResponse(\n fileData: FileData,\n offset: number,\n limit: number,\n): string {\n const content = fileDataToString(fileData);\n const emptyMsg = checkEmptyContent(content);\n if (emptyMsg) {\n return emptyMsg;\n }\n\n const lines = content.split(\"\\n\");\n const startIdx = offset;\n const endIdx = Math.min(startIdx + limit, lines.length);\n\n if (startIdx >= lines.length) {\n return `Error: Line offset ${offset} exceeds file length (${lines.length} lines)`;\n }\n\n const selectedLines = lines.slice(startIdx, endIdx);\n return formatContentWithLineNumbers(selectedLines, startIdx + 1);\n}\n\n/**\n * Perform string replacement with occurrence validation.\n *\n * @param content - Original content\n * @param oldString - String to replace\n * @param newString - Replacement string\n * @param replaceAll - Whether to replace all occurrences\n * @returns Tuple of [new_content, occurrences] on success, or error message string\n *\n * Special case: When both content and oldString are empty, this sets the initial\n * content to newString. This allows editing empty files by treating empty oldString\n * as \"set initial content\" rather than \"replace nothing\".\n */\nexport function performStringReplacement(\n content: string,\n oldString: string,\n newString: string,\n replaceAll: boolean,\n): [string, number] | string {\n // Special case: empty file with empty oldString sets initial content\n if (content === \"\" && oldString === \"\") {\n return [newString, 0];\n }\n\n // Validate that oldString is not empty (for non-empty files)\n if (oldString === \"\") {\n return \"Error: oldString cannot be empty when file has content\";\n }\n\n // Use split to count occurrences (simpler than regex)\n const occurrences = content.split(oldString).length - 1;\n\n if (occurrences === 0) {\n return `Error: String not found in file: '${oldString}'`;\n }\n\n if (occurrences > 1 && !replaceAll) {\n return `Error: String '${oldString}' has multiple occurrences (appears ${occurrences} times) in file. Use replace_all=True to replace all instances, or provide a more specific string with surrounding context.`;\n }\n\n // Python's str.replace() replaces ALL occurrences\n // Use split/join for consistent behavior\n const newContent = content.split(oldString).join(newString);\n\n return [newContent, occurrences];\n}\n\n/**\n * Truncate list or string result if it exceeds token limit (rough estimate: 4 chars/token).\n */\nexport function truncateIfTooLong(\n result: string[] | string,\n): string[] | string {\n if (Array.isArray(result)) {\n const totalChars = result.reduce((sum, item) => sum + item.length, 0);\n if (totalChars > TOOL_RESULT_TOKEN_LIMIT * 4) {\n const truncateAt = Math.floor(\n (result.length * TOOL_RESULT_TOKEN_LIMIT * 4) / totalChars,\n );\n return [...result.slice(0, truncateAt), TRUNCATION_GUIDANCE];\n }\n return result;\n }\n // string\n if (result.length > TOOL_RESULT_TOKEN_LIMIT * 4) {\n return (\n result.substring(0, TOOL_RESULT_TOKEN_LIMIT * 4) +\n \"\\n\" +\n TRUNCATION_GUIDANCE\n );\n }\n return result;\n}\n\n/**\n * Validate and normalize a directory path.\n *\n * Ensures paths are safe to use by preventing directory traversal attacks\n * and enforcing consistent formatting. All paths are normalized to use\n * forward slashes and start with a leading slash.\n *\n * This function is designed for virtual filesystem paths and rejects\n * Windows absolute paths (e.g., C:/..., F:/...) to maintain consistency\n * and prevent path format ambiguity.\n *\n * @param path - Path to validate\n * @returns Normalized path starting with / and ending with /\n * @throws Error if path is invalid\n *\n * @example\n * ```typescript\n * validatePath(\"foo/bar\") // Returns: \"/foo/bar/\"\n * validatePath(\"/./foo//bar\") // Returns: \"/foo/bar/\"\n * validatePath(\"../etc/passwd\") // Throws: Path traversal not allowed\n * validatePath(\"C:\\\\Users\\\\file\") // Throws: Windows absolute paths not supported\n * ```\n */\nexport function validatePath(path: string | null | undefined): string {\n const pathStr = path || \"/\";\n if (!pathStr || pathStr.trim() === \"\") {\n throw new Error(\"Path cannot be empty\");\n }\n\n let normalized = pathStr.startsWith(\"/\") ? pathStr : \"/\" + pathStr;\n\n if (!normalized.endsWith(\"/\")) {\n normalized += \"/\";\n }\n\n return normalized;\n}\n\n/**\n * Validate and normalize a file path for security.\n *\n * Ensures paths are safe to use by preventing directory traversal attacks\n * and enforcing consistent formatting. All paths are normalized to use\n * forward slashes and start with a leading slash.\n *\n * This function is designed for virtual filesystem paths and rejects\n * Windows absolute paths (e.g., C:/..., F:/...) to maintain consistency\n * and prevent path format ambiguity.\n *\n * @param path - The path to validate and normalize.\n * @param allowedPrefixes - Optional list of allowed path prefixes. If provided,\n * the normalized path must start with one of these prefixes.\n * @returns Normalized canonical path starting with `/` and using forward slashes.\n * @throws Error if path contains traversal sequences (`..` or `~`), is a Windows\n * absolute path (e.g., C:/...), or does not start with an allowed prefix\n * when `allowedPrefixes` is specified.\n *\n * @example\n * ```typescript\n * validateFilePath(\"foo/bar\") // Returns: \"/foo/bar\"\n * validateFilePath(\"/./foo//bar\") // Returns: \"/foo/bar\"\n * validateFilePath(\"../etc/passwd\") // Throws: Path traversal not allowed\n * validateFilePath(\"C:\\\\Users\\\\file.txt\") // Throws: Windows absolute paths not supported\n * validateFilePath(\"/data/file.txt\", [\"/data/\"]) // Returns: \"/data/file.txt\"\n * validateFilePath(\"/etc/file.txt\", [\"/data/\"]) // Throws: Path must start with...\n * ```\n */\nexport function validateFilePath(\n path: string,\n allowedPrefixes?: string[],\n): string {\n // Check for path traversal\n if (path.includes(\"..\") || path.startsWith(\"~\")) {\n throw new Error(`Path traversal not allowed: ${path}`);\n }\n\n // Reject Windows absolute paths (e.g., C:\\..., D:/...)\n // This maintains consistency in virtual filesystem paths\n if (/^[a-zA-Z]:/.test(path)) {\n throw new Error(\n `Windows absolute paths are not supported: ${path}. Please use virtual paths starting with / (e.g., /workspace/file.txt)`,\n );\n }\n\n // Normalize path separators and remove redundant slashes\n let normalized = path.replace(/\\\\/g, \"/\");\n\n // Remove redundant path components (./foo becomes foo, foo//bar becomes foo/bar)\n const parts: string[] = [];\n for (const part of normalized.split(\"/\")) {\n if (part === \".\" || part === \"\") {\n continue;\n }\n parts.push(part);\n }\n normalized = \"/\" + parts.join(\"/\");\n\n // Check allowed prefixes if provided\n if (\n allowedPrefixes &&\n !allowedPrefixes.some((prefix) => normalized.startsWith(prefix))\n ) {\n throw new Error(\n `Path must start with one of ${JSON.stringify(allowedPrefixes)}: ${path}`,\n );\n }\n\n return normalized;\n}\n\n/**\n * Search files dict for paths matching glob pattern.\n *\n * @param files - Dictionary of file paths to FileData\n * @param pattern - Glob pattern (e.g., `*.py`, `**\\/*.ts`)\n * @param path - Base path to search from\n * @returns Newline-separated file paths, sorted by modification time (most recent first).\n * Returns \"No files found\" if no matches.\n *\n * @example\n * ```typescript\n * const files = {\"/src/main.py\": FileData(...), \"/test.py\": FileData(...)};\n * globSearchFiles(files, \"*.py\", \"/\");\n * // Returns: \"/test.py\\n/src/main.py\" (sorted by modified_at)\n * ```\n */\nexport function globSearchFiles(\n files: Record<string, FileData>,\n pattern: string,\n path: string = \"/\",\n): string {\n let normalizedPath: string;\n try {\n normalizedPath = validatePath(path);\n } catch {\n return \"No files found\";\n }\n\n const filtered = Object.fromEntries(\n Object.entries(files).filter(([fp]) => fp.startsWith(normalizedPath)),\n );\n\n // Respect standard glob semantics:\n // - Patterns without path separators (e.g., \"*.py\") match only in the current\n // directory (non-recursive) relative to `path`.\n // - Use \"**\" explicitly for recursive matching.\n const effectivePattern = pattern;\n\n const matches: Array<[string, string]> = [];\n for (const [filePath, fileData] of Object.entries(filtered)) {\n let relative = filePath.substring(normalizedPath.length);\n if (relative.startsWith(\"/\")) {\n relative = relative.substring(1);\n }\n if (!relative) {\n const parts = filePath.split(\"/\");\n relative = parts[parts.length - 1] || \"\";\n }\n\n if (\n micromatch.isMatch(relative, effectivePattern, {\n dot: true,\n nobrace: false,\n })\n ) {\n matches.push([filePath, fileData.modified_at]);\n }\n }\n\n matches.sort((a, b) => b[1].localeCompare(a[1])); // Sort by modified_at descending\n\n if (matches.length === 0) {\n return \"No files found\";\n }\n\n return matches.map(([fp]) => fp).join(\"\\n\");\n}\n\n/**\n * Format grep search results based on output mode.\n *\n * @param results - Dictionary mapping file paths to list of [line_num, line_content] tuples\n * @param outputMode - Output format - \"files_with_matches\", \"content\", or \"count\"\n * @returns Formatted string output\n */\nexport function formatGrepResults(\n results: Record<string, Array<[number, string]>>,\n outputMode: \"files_with_matches\" | \"content\" | \"count\",\n): string {\n if (outputMode === \"files_with_matches\") {\n return Object.keys(results).sort().join(\"\\n\");\n }\n if (outputMode === \"count\") {\n const lines: string[] = [];\n for (const filePath of Object.keys(results).sort()) {\n const count = results[filePath].length;\n lines.push(`${filePath}: ${count}`);\n }\n return lines.join(\"\\n\");\n }\n // content mode\n const lines: string[] = [];\n for (const filePath of Object.keys(results).sort()) {\n lines.push(`${filePath}:`);\n for (const [lineNum, line] of results[filePath]) {\n lines.push(` ${lineNum}: ${line}`);\n }\n }\n return lines.join(\"\\n\");\n}\n\n/**\n * Search file contents for literal text pattern.\n *\n * Performs literal text search.\n *\n * @param files - Dictionary of file paths to FileData\n * @param pattern - Literal text to search for\n * @param path - Base path to search from\n * @param glob - Optional glob pattern to filter files (e.g., \"*.py\")\n * @param outputMode - Output format - \"files_with_matches\", \"content\", or \"count\"\n * @returns Formatted search results. Returns \"No matches found\" if no results.\n *\n * @example\n * ```typescript\n * const files = {\"/file.py\": FileData({content: [\"import os\", \"print('hi')\"], ...})};\n * grepSearchFiles(files, \"import\", \"/\");\n * // Returns: \"/file.py\" (with output_mode=\"files_with_matches\")\n * ```\n */\nexport function grepSearchFiles(\n files: Record<string, FileData>,\n pattern: string,\n path: string | null = null,\n glob: string | null = null,\n outputMode: \"files_with_matches\" | \"content\" | \"count\" = \"files_with_matches\",\n): string {\n let normalizedPath: string;\n try {\n normalizedPath = validatePath(path);\n } catch {\n return \"No matches found\";\n }\n\n let filtered = Object.fromEntries(\n Object.entries(files).filter(([fp]) => fp.startsWith(normalizedPath)),\n );\n\n if (glob) {\n filtered = Object.fromEntries(\n Object.entries(filtered).filter(([fp]) =>\n micromatch.isMatch(basename(fp), glob, { dot: true, nobrace: false }),\n ),\n );\n }\n\n const results: Record<string, Array<[number, string]>> = {};\n for (const [filePath, fileData] of Object.entries(filtered)) {\n for (let i = 0; i < fileData.content.length; i++) {\n const line = fileData.content[i];\n const lineNum = i + 1;\n // Simple substring search for literal matching\n if (line.includes(pattern)) {\n if (!results[filePath]) {\n results[filePath] = [];\n }\n results[filePath].push([lineNum, line]);\n }\n }\n }\n\n if (Object.keys(results).length === 0) {\n return \"No matches found\";\n }\n return formatGrepResults(results, outputMode);\n}\n\n/**\n * Return structured grep matches from an in-memory files mapping.\n *\n * Performs literal text search (not regex).\n *\n * Returns a list of GrepMatch on success, or a string for invalid inputs.\n * We deliberately do not raise here to keep backends non-throwing in tool\n * contexts and preserve user-facing error messages.\n */\nexport function grepMatchesFromFiles(\n files: Record<string, FileData>,\n pattern: string,\n path: string | null = null,\n glob: string | null = null,\n): GrepMatch[] | string {\n let normalizedPath: string;\n try {\n normalizedPath = validatePath(path);\n } catch {\n return [];\n }\n\n let filtered = Object.fromEntries(\n Object.entries(files).filter(([fp]) => fp.startsWith(normalizedPath)),\n );\n\n if (glob) {\n filtered = Object.fromEntries(\n Object.entries(filtered).filter(([fp]) =>\n micromatch.isMatch(basename(fp), glob, { dot: true, nobrace: false }),\n ),\n );\n }\n\n const matches: GrepMatch[] = [];\n for (const [filePath, fileData] of Object.entries(filtered)) {\n for (let i = 0; i < fileData.content.length; i++) {\n const line = fileData.content[i];\n const lineNum = i + 1;\n // Simple substring search for literal matching\n if (line.includes(pattern)) {\n matches.push({ path: filePath, line: lineNum, text: line });\n }\n }\n }\n\n return matches;\n}\n\n/**\n * Group structured matches into the legacy dict form used by formatters.\n */\nexport function buildGrepResultsDict(\n matches: GrepMatch[],\n): Record<string, Array<[number, string]>> {\n const grouped: Record<string, Array<[number, string]>> = {};\n for (const m of matches) {\n if (!grouped[m.path]) {\n grouped[m.path] = [];\n }\n grouped[m.path].push([m.line, m.text]);\n }\n return grouped;\n}\n\n/**\n * Format structured grep matches using existing formatting logic.\n */\nexport function formatGrepMatches(\n matches: GrepMatch[],\n outputMode: \"files_with_matches\" | \"content\" | \"count\",\n): string {\n if (matches.length === 0) {\n return \"No matches found\";\n }\n return formatGrepResults(buildGrepResultsDict(matches), outputMode);\n}\n","/**\n * StateBackend: Store files in LangGraph agent state (ephemeral).\n */\n\nimport type {\n BackendProtocol,\n EditResult,\n FileData,\n FileDownloadResponse,\n FileInfo,\n FileUploadResponse,\n GrepMatch,\n StateAndStore,\n WriteResult,\n} from \"./protocol.js\";\nimport {\n createFileData,\n fileDataToString,\n formatReadResponse,\n globSearchFiles,\n grepMatchesFromFiles,\n performStringReplacement,\n updateFileData,\n} from \"./utils.js\";\n\n/**\n * Backend that stores files in agent state (ephemeral).\n *\n * Uses LangGraph's state management and checkpointing. Files persist within\n * a conversation thread but not across threads. State is automatically\n * checkpointed after each agent step.\n *\n * Special handling: Since LangGraph state must be updated via Command objects\n * (not direct mutation), operations return filesUpdate in WriteResult/EditResult\n * for the middleware to apply via Command.\n */\nexport class StateBackend implements BackendProtocol {\n private stateAndStore: StateAndStore;\n\n constructor(stateAndStore: StateAndStore) {\n this.stateAndStore = stateAndStore;\n }\n\n /**\n * Get files from current state.\n */\n private getFiles(): Record<string, FileData> {\n return (\n ((this.stateAndStore.state as any).files as Record<string, FileData>) ||\n {}\n );\n }\n\n /**\n * List files and directories in the specified directory (non-recursive).\n *\n * @param path - Absolute path to directory\n * @returns List of FileInfo objects for files and directories directly in the directory.\n * Directories have a trailing / in their path and is_dir=true.\n */\n lsInfo(path: string): FileInfo[] {\n const files = this.getFiles();\n const infos: FileInfo[] = [];\n const subdirs = new Set<string>();\n\n // Normalize path to have trailing slash for proper prefix matching\n const normalizedPath = path.endsWith(\"/\") ? path : path + \"/\";\n\n for (const [k, fd] of Object.entries(files)) {\n // Check if file is in the specified directory or a subdirectory\n if (!k.startsWith(normalizedPath)) {\n continue;\n }\n\n // Get the relative path after the directory\n const relative = k.substring(normalizedPath.length);\n\n // If relative path contains '/', it's in a subdirectory\n if (relative.includes(\"/\")) {\n // Extract the immediate subdirectory name\n const subdirName = relative.split(\"/\")[0];\n subdirs.add(normalizedPath + subdirName + \"/\");\n continue;\n }\n\n // This is a file directly in the current directory\n const size = fd.content.join(\"\\n\").length;\n infos.push({\n path: k,\n is_dir: false,\n size: size,\n modified_at: fd.modified_at,\n });\n }\n\n // Add directories to the results\n for (const subdir of Array.from(subdirs).sort()) {\n infos.push({\n path: subdir,\n is_dir: true,\n size: 0,\n modified_at: \"\",\n });\n }\n\n infos.sort((a, b) => a.path.localeCompare(b.path));\n return infos;\n }\n\n /**\n * Read file content with line numbers.\n *\n * @param filePath - Absolute file path\n * @param offset - Line offset to start reading from (0-indexed)\n * @param limit - Maximum number of lines to read\n * @returns Formatted file content with line numbers, or error message\n */\n read(filePath: string, offset: number = 0, limit: number = 500): string {\n const files = this.getFiles();\n const fileData = files[filePath];\n\n if (!fileData) {\n return `Error: File '${filePath}' not found`;\n }\n\n return formatReadResponse(fileData, offset, limit);\n }\n\n /**\n * Read file content as raw FileData.\n *\n * @param filePath - Absolute file path\n * @returns Raw file content as FileData\n */\n readRaw(filePath: string): FileData {\n const files = this.getFiles();\n const fileData = files[filePath];\n\n if (!fileData) throw new Error(`File '${filePath}' not found`);\n return fileData;\n }\n\n /**\n * Create a new file with content.\n * Returns WriteResult with filesUpdate to update LangGraph state.\n */\n write(filePath: string, content: string): WriteResult {\n const files = this.getFiles();\n\n if (filePath in files) {\n return {\n error: `Cannot write to ${filePath} because it already exists. Read and then make an edit, or write to a new path.`,\n };\n }\n\n const newFileData = createFileData(content);\n return {\n path: filePath,\n filesUpdate: { [filePath]: newFileData },\n };\n }\n\n /**\n * Edit a file by replacing string occurrences.\n * Returns EditResult with filesUpdate and occurrences.\n */\n edit(\n filePath: string,\n oldString: string,\n newString: string,\n replaceAll: boolean = false,\n ): EditResult {\n const files = this.getFiles();\n const fileData = files[filePath];\n\n if (!fileData) {\n return { error: `Error: File '${filePath}' not found` };\n }\n\n const content = fileDataToString(fileData);\n const result = performStringReplacement(\n content,\n oldString,\n newString,\n replaceAll,\n );\n\n if (typeof result === \"string\") {\n return { error: result };\n }\n\n const [newContent, occurrences] = result;\n const newFileData = updateFileData(fileData, newContent);\n return {\n path: filePath,\n filesUpdate: { [filePath]: newFileData },\n occurrences: occurrences,\n };\n }\n\n /**\n * Structured search results or error string for invalid input.\n */\n grepRaw(\n pattern: string,\n path: string = \"/\",\n glob: string | null = null,\n ): GrepMatch[] | string {\n const files = this.getFiles();\n return grepMatchesFromFiles(files, pattern, path, glob);\n }\n\n /**\n * Structured glob matching returning FileInfo objects.\n */\n globInfo(pattern: string, path: string = \"/\"): FileInfo[] {\n const files = this.getFiles();\n const result = globSearchFiles(files, pattern, path);\n\n if (result === \"No files found\") {\n return [];\n }\n\n const paths = result.split(\"\\n\");\n const infos: FileInfo[] = [];\n for (const p of paths) {\n const fd = files[p];\n const size = fd ? fd.content.join(\"\\n\").length : 0;\n infos.push({\n path: p,\n is_dir: false,\n size: size,\n modified_at: fd?.modified_at || \"\",\n });\n }\n return infos;\n }\n\n /**\n * Upload multiple files.\n *\n * Note: Since LangGraph state must be updated via Command objects,\n * the caller must apply filesUpdate via Command after calling this method.\n *\n * @param files - List of [path, content] tuples to upload\n * @returns List of FileUploadResponse objects, one per input file\n */\n uploadFiles(\n files: Array<[string, Uint8Array]>,\n ): FileUploadResponse[] & { filesUpdate?: Record<string, FileData> } {\n const responses: FileUploadResponse[] = [];\n const updates: Record<string, FileData> = {};\n\n for (const [path, content] of files) {\n try {\n const contentStr = new TextDecoder().decode(content);\n const fileData = createFileData(contentStr);\n updates[path] = fileData;\n responses.push({ path, error: null });\n } catch {\n responses.push({ path, error: \"invalid_path\" });\n }\n }\n\n // Attach filesUpdate for the caller to apply via Command\n const result = responses as FileUploadResponse[] & {\n filesUpdate?: Record<string, FileData>;\n };\n result.filesUpdate = updates;\n return result;\n }\n\n /**\n * Download multiple files.\n *\n * @param paths - List of file paths to download\n * @returns List of FileDownloadResponse objects, one per input path\n */\n downloadFiles(paths: string[]): FileDownloadResponse[] {\n const files = this.getFiles();\n const responses: FileDownloadResponse[] = [];\n\n for (const path of paths) {\n const fileData = files[path];\n if (!fileData) {\n responses.push({ path, content: null, error: \"file_not_found\" });\n continue;\n }\n\n const contentStr = fileDataToString(fileData);\n const content = new TextEncoder().encode(contentStr);\n responses.push({ path, content, error: null });\n }\n\n return responses;\n }\n}\n","/**\n * Middleware for providing filesystem tools to an agent.\n *\n * Provides ls, read_file, write_file, edit_file, glob, and grep tools with support for:\n * - Pluggable backends (StateBackend, StoreBackend, FilesystemBackend, CompositeBackend)\n * - Tool result eviction for large outputs\n */\n\nimport {\n createMiddleware,\n tool,\n ToolMessage,\n type AgentMiddleware as _AgentMiddleware,\n} from \"langchain\";\nimport {\n Command,\n isCommand,\n getCurrentTaskInput,\n StateSchema,\n ReducedValue,\n} from \"@langchain/langgraph\";\nimport { z } from \"zod/v4\";\nimport type {\n BackendProtocol,\n BackendFactory,\n FileData,\n StateAndStore,\n} from \"../backends/protocol.js\";\nimport { isSandboxBackend } from \"../backends/protocol.js\";\nimport { StateBackend } from \"../backends/state.js\";\nimport {\n sanitizeToolCallId,\n formatContentWithLineNumbers,\n} from \"../backends/utils.js\";\n\n/**\n * Tools that should be excluded from the large result eviction logic.\n *\n * This array contains tools that should NOT have their results evicted to the filesystem\n * when they exceed token limits. Tools are excluded for different reasons:\n *\n * 1. Tools with built-in truncation (ls, glob, grep):\n * These tools truncate their own output when it becomes too large. When these tools\n * produce truncated output due to many matches, it typically indicates the query\n * needs refinement rather than full result preservation. In such cases, the truncated\n * matches are potentially more like noise and the LLM should be prompted to narrow\n * its search criteria instead.\n *\n * 2. Tools with problematic truncation behavior (read_file):\n * read_file is tricky to handle as the failure mode here is single long lines\n * (e.g., imagine a jsonl file with very long payloads on each line). If we try to\n * truncate the result of read_file, the agent may then attempt to re-read the\n * truncated file using read_file again, which won't help.\n *\n * 3. Tools that never exceed limits (edit_file, write_file):\n * These tools return minimal confirmation messages and are never expected to produce\n * output large enough to exceed token limits, so checking them would be unnecessary.\n */\nexport const TOOLS_EXCLUDED_FROM_EVICTION = [\n \"ls\",\n \"glob\",\n \"grep\",\n \"read_file\",\n \"edit_file\",\n \"write_file\",\n] as const;\n\n/**\n * Approximate number of characters per token for truncation calculations.\n * Using 4 chars per token as a conservative approximation (actual ratio varies by content)\n * This errs on the high side to avoid premature eviction of content that might fit.\n */\nexport const NUM_CHARS_PER_TOKEN = 4;\n\n/**\n * Default values for read_file tool pagination (in lines).\n */\nexport const DEFAULT_READ_LINE_OFFSET = 0;\nexport const DEFAULT_READ_LINE_LIMIT = 100;\n\n/**\n * Template for truncation message in read_file.\n * {file_path} will be filled in at runtime.\n */\nconst READ_FILE_TRUNCATION_MSG = `\n\n[Output was truncated due to size limits. The file content is very large. Consider reformatting the file to make it easier to navigate. For example, if this is JSON, use execute(command='jq . {file_path}') to pretty-print it with line breaks. For other formats, you can use appropriate formatting tools to split long lines.]`;\n\n/**\n * Message template for evicted tool results.\n */\nconst TOO_LARGE_TOOL_MSG = `Tool result too large, the result of this tool call {tool_call_id} was saved in the filesystem at this path: {file_path}\nYou can read the result from the filesystem by using the read_file tool, but make sure to only read part of the result at a time.\nYou can do this by specifying an offset and limit in the read_file tool call.\nFor example, to read the first 100 lines, you can use the read_file tool with offset=0 and limit=100.\n\nHere is a preview showing the head and tail of the result (lines of the form\n... [N lines truncated] ...\nindicate omitted lines in the middle of the content):\n\n{content_sample}`;\n\n/**\n * Create a preview of content showing head and tail with truncation marker.\n *\n * @param contentStr - The full content string to preview.\n * @param headLines - Number of lines to show from the start (default: 5).\n * @param tailLines - Number of lines to show from the end (default: 5).\n * @returns Formatted preview string with line numbers.\n */\nexport function createContentPreview(\n contentStr: string,\n headLines: number = 5,\n tailLines: number = 5,\n): string {\n const lines = contentStr.split(\"\\n\");\n\n if (lines.length <= headLines + tailLines) {\n // If file is small enough, show all lines\n const previewLines = lines.map((line) => line.substring(0, 1000));\n return formatContentWithLineNumbers(previewLines, 1);\n }\n\n // Show head and tail with truncation marker\n const head = lines.slice(0, headLines).map((line) => line.substring(0, 1000));\n const tail = lines.slice(-tailLines).map((line) => line.substring(0, 1000));\n\n const headSample = formatContentWithLineNumbers(head, 1);\n const truncationNotice = `\\n... [${lines.length - headLines - tailLines} lines truncated] ...\\n`;\n const tailSample = formatContentWithLineNumbers(\n tail,\n lines.length - tailLines + 1,\n );\n\n return headSample + truncationNotice + tailSample;\n}\n\n/**\n * required for type inference\n */\nimport type * as _zodTypes from \"@langchain/core/utils/types\";\nimport type * as _zodMeta from \"@langchain/langgraph/zod\";\nimport type * as _messages from \"@langchain/core/messages\";\n\n/**\n * Zod v3 schema for FileData (re-export from backends)\n */\nexport const FileDataSchema = z.object({\n content: z.array(z.string()),\n created_at: z.string(),\n modified_at: z.string(),\n});\n\n/**\n * Type for the files state record.\n */\nexport type FilesRecord = Record<string, FileData>;\n\n/**\n * Type for file updates, where null indicates deletion.\n */\nexport type FilesRecordUpdate = Record<string, FileData | null>;\n\n/**\n * Reducer for files state that merges file updates with support for deletions.\n * When a file value is null, the file is deleted from state.\n * When a file value is non-null, it is added or updated in state.\n *\n * This reducer enables concurrent updates from parallel subagents by properly\n * merging their file changes instead of requiring LastValue semantics.\n *\n * @param current - The current files record (from state)\n * @param update - The new files record (from a subagent update), with null values for deletions\n * @returns Merged files record with deletions applied\n */\nexport function fileDataReducer(\n current: FilesRecord | undefined,\n update: FilesRecordUpdate | undefined,\n): FilesRecord {\n // If no update, return current (or empty object)\n if (update === undefined) {\n return current || {};\n }\n\n // If no current, filter out null values from update\n if (current === undefined) {\n const result: FilesRecord = {};\n for (const [key, value] of Object.entries(update)) {\n if (value !== null) {\n result[key] = value;\n }\n }\n return result;\n }\n\n // Merge: apply updates and deletions\n const result = { ...current };\n for (const [key, value] of Object.entries(update)) {\n if (value === null) {\n delete result[key];\n } else {\n result[key] = value;\n }\n }\n return result;\n}\n\n/**\n * Shared filesystem state schema.\n * Defined at module level to ensure the same object identity is used across all agents,\n * preventing \"Channel already exists with different type\" errors when multiple agents\n * use createFilesystemMiddleware.\n *\n * Uses ReducedValue for files to allow concurrent updates from parallel subagents.\n */\nconst FilesystemStateSchema = new StateSchema({\n files: new ReducedValue(\n z.record(z.string(), FileDataSchema).default(() => ({})),\n {\n inputSchema: z.record(z.string(), FileDataSchema.nullable()).optional(),\n reducer: fileDataReducer,\n },\n ),\n});\n\n/**\n * Resolve backend from factory or instance.\n *\n * @param backend - Backend instance or factory function\n * @param stateAndStore - State and store container for backend initialization\n */\nfunction getBackend(\n backend: BackendProtocol | BackendFactory,\n stateAndStore: StateAndStore,\n): BackendProtocol {\n if (typeof backend === \"function\") {\n return backend(stateAndStore);\n }\n return backend;\n}\n\n// System prompts\nconst FILESYSTEM_SYSTEM_PROMPT = `## Filesystem Tools \\`ls\\`, \\`read_file\\`, \\`write_file\\`, \\`edit_file\\`, \\`glob\\`, \\`grep\\`\n\nYou have access to a filesystem which you can interact with using these tools.\nAll file paths must start with a /.\n\n- ls: list files in a directory (requires absolute path)\n- read_file: read a file from the filesystem\n- write_file: write to a file in the filesystem\n- edit_file: edit a file in the filesystem\n- glob: find files matching a pattern (e.g., \"**/*.py\")\n- grep: search for text within files`;\n\n// Tool descriptions - ported from Python for comprehensive LLM guidance\nexport const LS_TOOL_DESCRIPTION = `Lists all files in a directory.\n\nThis is useful for exploring the filesystem and finding the right file to read or edit.\nYou should almost ALWAYS use this tool before using the read_file or edit_file tools.`;\n\nexport const READ_FILE_TOOL_DESCRIPTION = `Reads a file from the filesystem.\n\nAssume this tool is able to read all files. If the User provides a path to a file assume that path is valid. It is okay to read a file that does not exist; an error will be returned.\n\nUsage:\n- By default, it reads up to 100 lines starting from the beginning of the file\n- **IMPORTANT for large files and codebase exploration**: Use pagination with offset and limit parameters to avoid context overflow\n - First scan: read_file(path, limit=100) to see file structure\n - Read more sections: read_file(path, offset=100, limit=200) for next 200 lines\n - Only omit limit (read full file) when necessary for editing\n- Specify offset and limit: read_file(path, offset=0, limit=100) reads first 100 lines\n- Results are returned using cat -n format, with line numbers starting at 1\n- Lines longer than 10,000 characters will be split into multiple lines with continuation markers (e.g., 5.1, 5.2, etc.). When you specify a limit, these continuation lines count towards the limit.\n- You have the capability to call multiple tools in a single response. It is always better to speculatively read multiple files as a batch that are potentially useful.\n- If you read a file that exists but has empty contents you will receive a system reminder warning in place of file contents.\n- You should ALWAYS make sure a file has been read before editing it.`;\n\nexport const WRITE_FILE_TOOL_DESCRIPTION = `Writes to a new file in the filesystem.\n\nUsage:\n- The write_file tool will create a new file.\n- Prefer to edit existing files (with the edit_file tool) over creating new ones when possible.`;\n\nexport const EDIT_FILE_TOOL_DESCRIPTION = `Performs exact string replacements in files.\n\nUsage:\n- You must read the file before editing. This tool will error if you attempt an edit without reading the file first.\n- When editing, preserve the exact indentation (tabs/spaces) from the read output. Never include line number prefixes in old_string or new_string.\n- ALWAYS prefer editing existing files over creating new ones.\n- Only use emojis if the user explicitly requests it.`;\n\nexport const GLOB_TOOL_DESCRIPTION = `Find files matching a glob pattern.\n\nSupports standard glob patterns: \\`*\\` (any characters), \\`**\\` (any directories), \\`?\\` (single character).\nReturns a list of absolute file paths that match the pattern.\n\nExamples:\n- \\`**/*.py\\` - Find all Python files\n- \\`*.txt\\` - Find all text files in root\n- \\`/subdir/**/*.md\\` - Find all markdown files under /subdir`;\n\nexport const GREP_TOOL_DESCRIPTION = `Search for a text pattern across files.\n\nSearches for literal text (not regex) and returns matching files or content based on output_mode.\nSpecial characters like parentheses, brackets, pipes, etc. are treated as literal characters, not regex operators.\n\nExamples:\n- Search all files: \\`grep(pattern=\"TODO\")\\`\n- Search Python files only: \\`grep(pattern=\"import\", glob=\"*.py\")\\`\n- Show matching lines: \\`grep(pattern=\"error\", output_mode=\"content\")\\`\n- Search for code with special chars: \\`grep(pattern=\"def __init__(self):\")\\``;\nexport const EXECUTE_TOOL_DESCRIPTION = `Executes a shell command in an isolated sandbox environment.\n\nUsage:\nExecutes a given command in the sandbox environment with proper handling and security measures.\nBefore executing the command, please follow these steps:\n\n1. Directory Verification:\n - If the command will create new directories or files, first use the ls tool to verify the parent directory exists and is the correct location\n - For example, before running \"mkdir foo/bar\", first use ls to check that \"foo\" exists and is the intended parent directory\n\n2. Command Execution:\n - Always quote file paths that contain spaces with double quotes (e.g., cd \"path with spaces/file.txt\")\n - Examples of proper quoting:\n - cd \"/Users/name/My Documents\" (correct)\n - cd /Users/name/My Documents (incorrect - will fail)\n - python \"/path/with spaces/script.py\" (correct)\n - python /path/with spaces/script.py (incorrect - will fail)\n - After ensuring proper quoting, execute the command\n - Capture the output of the command\n\nUsage notes:\n - Commands run in an isolated sandbox environment\n - Returns combined stdout/stderr output with exit code\n - If the output is very large, it may be truncated\n - VERY IMPORTANT: You MUST avoid using search commands like find and grep. Instead use the grep, glob tools to search. You MUST avoid read tools like cat, head, tail, and use read_file to read files.\n - When issuing multiple commands, use the ';' or '&&' operator to separate them. DO NOT use newlines (newlines are ok in quoted strings)\n - Use '&&' when commands depend on each other (e.g., \"mkdir dir && cd dir\")\n - Use ';' only when you need to run commands sequentially but don't care if earlier commands fail\n - Try to maintain your current working directory throughout the session by using absolute paths and avoiding usage of cd\n\nExamples:\n Good examples:\n - execute(command=\"pytest /foo/bar/tests\")\n - execute(command=\"python /path/to/script.py\")\n - execute(command=\"npm install && npm test\")\n\n Bad examples (avoid these):\n - execute(command=\"cd /foo/bar && pytest tests\") # Use absolute path instead\n - execute(command=\"cat file.txt\") # Use read_file tool instead\n - execute(command=\"find . -name '*.py'\") # Use glob tool instead\n - execute(command=\"grep -r 'pattern' .\") # Use grep tool instead\n\nNote: This tool is only available if the backend supports execution (SandboxBackendProtocol).\nIf execution is not supported, the tool will return an error message.`;\n\n// System prompt for execution capability\nexport const EXECUTION_SYSTEM_PROMPT = `## Execute Tool \\`execute\\`\n\nYou have access to an \\`execute\\` tool for running shell commands in a sandboxed environment.\nUse this tool to run commands, scripts, tests, builds, and other shell operations.\n\n- execute: run a shell command in the sandbox (returns output and exit code)`;\n\n/**\n * Create ls tool using backend.\n */\nfunction createLsTool(\n backend: BackendProtocol | BackendFactory,\n options: { customDescription: string | undefined },\n) {\n const { customDescription } = options;\n return tool(\n async (input, config) => {\n const stateAndStore: StateAndStore = {\n state: getCurrentTaskInput(config),\n store: (config as any).store,\n };\n const resolvedBackend = getBackend(backend, stateAndStore);\n const path = input.path || \"/\";\n const infos = await resolvedBackend.lsInfo(path);\n\n if (infos.length === 0) {\n return `No files found in ${path}`;\n }\n\n // Format output\n const lines: string[] = [];\n for (const info of infos) {\n if (info.is_dir) {\n lines.push(`${info.path} (directory)`);\n } else {\n const size = info.size ? ` (${info.size} bytes)` : \"\";\n lines.push(`${info.path}${size}`);\n }\n }\n return lines.join(\"\\n\");\n },\n {\n name: \"ls\",\n description: customDescription || LS_TOOL_DESCRIPTION,\n schema: z.object({\n path: z\n .string()\n .optional()\n .default(\"/\")\n .describe(\"Directory path to list (default: /)\"),\n }),\n },\n );\n}\n\n/**\n * Create read_file tool using backend.\n */\nfunction createReadFileTool(\n backend: BackendProtocol | BackendFactory,\n options: {\n customDescription: string | undefined;\n toolTokenLimitBeforeEvict: number | null;\n },\n) {\n const { customDescription, toolTokenLimitBeforeEvict } = options;\n return tool(\n async (input, config) => {\n const stateAndStore: StateAndStore = {\n state: getCurrentTaskInput(config),\n store: (config as any).store,\n };\n const resolvedBackend = getBackend(backend, stateAndStore);\n const {\n file_path,\n offset = DEFAULT_READ_LINE_OFFSET,\n limit = DEFAULT_READ_LINE_LIMIT,\n } = input;\n let result = await resolvedBackend.read(file_path, offset, limit);\n\n // Enforce line limit on result (in case backend returns more)\n const lines = result.split(\"\\n\");\n if (lines.length > limit) {\n result = lines.slice(0, limit).join(\"\\n\");\n }\n\n // Check if result exceeds token threshold and truncate if necessary\n if (\n toolTokenLimitBeforeEvict &&\n result.length >= NUM_CHARS_PER_TOKEN * toolTokenLimitBeforeEvict\n ) {\n // Calculate truncation message length to ensure final result stays under threshold\n const truncationMsg = READ_FILE_TRUNCATION_MSG.replace(\n \"{file_path}\",\n file_path,\n );\n const maxContentLength =\n NUM_CHARS_PER_TOKEN * toolTokenLimitBeforeEvict -\n truncationMsg.length;\n result = result.substring(0, maxContentLength) + truncationMsg;\n }\n\n return result;\n },\n {\n name: \"read_file\",\n description: customDescription || READ_FILE_TOOL_DESCRIPTION,\n schema: z.object({\n file_path: z.string().describe(\"Absolute path to the file to read\"),\n offset: z.coerce\n .number()\n .optional()\n .default(DEFAULT_READ_LINE_OFFSET)\n .describe(\"Line offset to start reading from (0-indexed)\"),\n limit: z.coerce\n .number()\n .optional()\n .default(DEFAULT_READ_LINE_LIMIT)\n .describe(\"Maximum number of lines to read\"),\n }),\n },\n );\n}\n\n/**\n * Create write_file tool using backend.\n */\nfunction createWriteFileTool(\n backend: BackendProtocol | BackendFactory,\n options: { customDescription: string | undefined },\n) {\n const { customDescription } = options;\n return tool(\n async (input, config) => {\n const stateAndStore: StateAndStore = {\n state: getCurrentTaskInput(config),\n store: (config as any).store,\n };\n const resolvedBackend = getBackend(backend, stateAndStore);\n const { file_path, content } = input;\n const result = await resolvedBackend.write(file_path, content);\n\n if (result.error) {\n return result.error;\n }\n\n // If filesUpdate is present, return Command to update state\n const message = new ToolMessage({\n content: `Successfully wrote to '${file_path}'`,\n tool_call_id: config.toolCall?.id as string,\n name: \"write_file\",\n metadata: result.metadata,\n });\n\n if (result.filesUpdate) {\n return new Command({\n update: { files: result.filesUpdate, messages: [message] },\n });\n }\n\n return message;\n },\n {\n name: \"write_file\",\n description: customDescription || WRITE_FILE_TOOL_DESCRIPTION,\n schema: z.object({\n file_path: z.string().describe(\"Absolute path to the file to write\"),\n content: z\n .string()\n .default(\"\")\n .describe(\"Content to write to the file\"),\n }),\n },\n );\n}\n\n/**\n * Create edit_file tool using backend.\n */\nfunction createEditFileTool(\n backend: BackendProtocol | BackendFactory,\n options: { customDescription: string | undefined },\n) {\n const { customDescription } = options;\n return tool(\n async (input, config) => {\n const stateAndStore: StateAndStore = {\n state: getCurrentTaskInput(config),\n store: (config as any).store,\n };\n const resolvedBackend = getBackend(backend, stateAndStore);\n const { file_path, old_string, new_string, replace_all = false } = input;\n const result = await resolvedBackend.edit(\n file_path,\n old_string,\n new_string,\n replace_all,\n );\n\n if (result.error) {\n return result.error;\n }\n\n const message = new ToolMessage({\n content: `Successfully replaced ${result.occurrences} occurrence(s) in '${file_path}'`,\n tool_call_id: config.toolCall?.id as string,\n name: \"edit_file\",\n metadata: result.metadata,\n });\n\n // If filesUpdate is present, return Command to update state\n if (result.filesUpdate) {\n return new Command({\n update: { files: result.filesUpdate, messages: [message] },\n });\n }\n\n // External storage (filesUpdate is null)\n return message;\n },\n {\n name: \"edit_file\",\n description: customDescription || EDIT_FILE_TOOL_DESCRIPTION,\n schema: z.object({\n file_path: z.string().describe(\"Absolute path to the file to edit\"),\n old_string: z\n .string()\n .describe(\"String to be replaced (must match exactly)\"),\n new_string: z.string().describe(\"String to replace with\"),\n replace_all: z\n .boolean()\n .optional()\n .default(false)\n .describe(\"Whether to replace all occurrences\"),\n }),\n },\n );\n}\n\n/**\n * Create glob tool using backend.\n */\nfunction createGlobTool(\n backend: BackendProtocol | BackendFactory,\n options: { customDescription: string | undefined },\n) {\n const { customDescription } = options;\n return tool(\n async (input, config) => {\n const stateAndStore: StateAndStore = {\n state: getCurrentTaskInput(config),\n store: (config as any).store,\n };\n const resolvedBackend = getBackend(backend, stateAndStore);\n const { pattern, path = \"/\" } = input;\n const infos = await resolvedBackend.globInfo(pattern, path);\n\n if (infos.length === 0) {\n return `No files found matching pattern '${pattern}'`;\n }\n\n return infos.map((info) => info.path).join(\"\\n\");\n },\n {\n name: \"glob\",\n description: customDescription || GLOB_TOOL_DESCRIPTION,\n schema: z.object({\n pattern: z.string().describe(\"Glob pattern (e.g., '*.py', '**/*.ts')\"),\n path: z\n .string()\n .optional()\n .default(\"/\")\n .describe(\"Base path to search from (default: /)\"),\n }),\n },\n );\n}\n\n/**\n * Create grep tool using backend.\n */\nfunction createGrepTool(\n backend: BackendProtocol | BackendFactory,\n options: { customDescription: string | undefined },\n) {\n const { customDescription } = options;\n return tool(\n async (input, config) => {\n const stateAndStore: StateAndStore = {\n state: getCurrentTaskInput(config),\n store: (config as any).store,\n };\n const resolvedBackend = getBackend(backend, stateAndStore);\n const { pattern, path = \"/\", glob = null } = input;\n const result = await resolvedBackend.grepRaw(pattern, path, glob);\n\n // If string, it's an error\n if (typeof result === \"string\") {\n return result;\n }\n\n if (result.length === 0) {\n return `No matches found for pattern '${pattern}'`;\n }\n\n // Format output: group by file\n const lines: string[] = [];\n let currentFile: string | null = null;\n for (const match of result) {\n if (match.path !== currentFile) {\n currentFile = match.path;\n lines.push(`\\n${currentFile}:`);\n }\n lines.push(` ${match.line}: ${match.text}`);\n }\n\n return lines.join(\"\\n\");\n },\n {\n name: \"grep\",\n description: customDescription || GREP_TOOL_DESCRIPTION,\n schema: z.object({\n pattern: z.string().describe(\"Regex pattern to search for\"),\n path: z\n .string()\n .optional()\n .default(\"/\")\n .describe(\"Base path to search from (default: /)\"),\n glob: z\n .string()\n .optional()\n .nullable()\n .describe(\"Optional glob pattern to filter files (e.g., '*.py')\"),\n }),\n },\n );\n}\n\n/**\n * Create execute tool using backend.\n */\nfunction createExecuteTool(\n backend: BackendProtocol | BackendFactory,\n options: { customDescription: string | undefined },\n) {\n const { customDescription } = options;\n return tool(\n async (input, config) => {\n const stateAndStore: StateAndStore = {\n state: getCurrentTaskInput(config),\n store: (config as any).store,\n };\n const resolvedBackend = getBackend(backend, stateAndStore);\n\n // Runtime check - fail gracefully if not supported\n if (!isSandboxBackend(resolvedBackend)) {\n return (\n \"Error: Execution not available. This agent's backend \" +\n \"does not support command execution (SandboxBackendProtocol). \" +\n \"To use the execute tool, provide a backend that implements SandboxBackendProtocol.\"\n );\n }\n\n const result = await resolvedBackend.execute(input.command);\n\n // Format output for LLM consumption\n const parts = [result.output];\n\n if (result.exitCode !== null) {\n const status = result.exitCode === 0 ? \"succeeded\" : \"failed\";\n parts.push(`\\n[Command ${status} with exit code ${result.exitCode}]`);\n }\n\n if (result.truncated) {\n parts.push(\"\\n[Output was truncated due to size limits]\");\n }\n\n return parts.join(\"\");\n },\n {\n name: \"execute\",\n description: customDescription || EXECUTE_TOOL_DESCRIPTION,\n schema: z.object({\n command: z.string().describe(\"The shell command to execute\"),\n }),\n },\n );\n}\n\n/**\n * Options for creating filesystem middleware.\n */\nexport interface FilesystemMiddlewareOptions {\n /** Backend instance or factory (default: StateBackend) */\n backend?: BackendProtocol | BackendFactory;\n /** Optional custom system prompt override */\n systemPrompt?: string | null;\n /** Optional custom tool descriptions override */\n customToolDescriptions?: Record<string, string> | null;\n /** Optional token limit before evicting a tool result to the filesystem (default: 20000 tokens, ~80KB) */\n toolTokenLimitBeforeEvict?: number | null;\n}\n\n/**\n * Create filesystem middleware with all tools and features.\n */\nexport function createFilesystemMiddleware(\n options: FilesystemMiddlewareOptions = {},\n) {\n const {\n backend = (stateAndStore: StateAndStore) => new StateBackend(stateAndStore),\n systemPrompt: customSystemPrompt = null,\n customToolDescriptions = null,\n toolTokenLimitBeforeEvict = 20000,\n } = options;\n\n const baseSystemPrompt = customSystemPrompt || FILESYSTEM_SYSTEM_PROMPT;\n\n // All tools including execute (execute will be filtered at runtime if backend doesn't support it)\n const allTools = [\n createLsTool(backend, {\n customDescription: customToolDescriptions?.ls,\n }),\n createReadFileTool(backend, {\n customDescription: customToolDescriptions?.read_file,\n toolTokenLimitBeforeEvict,\n }),\n createWriteFileTool(backend, {\n customDescription: customToolDescriptions?.write_file,\n }),\n createEditFileTool(backend, {\n customDescription: customToolDescriptions?.edit_file,\n }),\n createGlobTool(backend, {\n customDescription: customToolDescriptions?.glob,\n }),\n createGrepTool(backend, {\n customDescription: customToolDescriptions?.grep,\n }),\n createExecuteTool(backend, {\n customDescription: customToolDescriptions?.execute,\n }),\n ];\n\n return createMiddleware({\n name: \"FilesystemMiddleware\",\n stateSchema: FilesystemStateSchema,\n tools: allTools,\n wrapModelCall: async (request, handler) => {\n // Check if backend supports execution\n const stateAndStore: StateAndStore = {\n state: request.state || {},\n // @ts-expect-error - request.config is incorrect typed\n store: request.config?.store,\n };\n const resolvedBackend = getBackend(backend, stateAndStore);\n const supportsExecution = isSandboxBackend(resolvedBackend);\n\n // Filter tools based on backend capabilities\n let tools = request.tools;\n if (!supportsExecution) {\n tools = tools.filter((t: { name: string }) => t.name !== \"execute\");\n }\n\n // Build system prompt - add execution instructions if available\n let filesystemPrompt = baseSystemPrompt;\n if (supportsExecution) {\n filesystemPrompt = `${filesystemPrompt}\\n\\n${EXECUTION_SYSTEM_PROMPT}`;\n }\n\n // Combine with existing system message\n const newSystemMessage = request.systemMessage.concat(filesystemPrompt);\n\n return handler({ ...request, tools, systemMessage: newSystemMessage });\n },\n wrapToolCall: async (request, handler) => {\n // Return early if eviction is disabled\n if (!toolTokenLimitBeforeEvict) {\n return handler(request);\n }\n\n // Check if this tool is excluded from eviction\n const toolName = request.toolCall?.name;\n if (\n toolName &&\n TOOLS_EXCLUDED_FROM_EVICTION.includes(\n toolName as (typeof TOOLS_EXCLUDED_FROM_EVICTION)[number],\n )\n ) {\n return handler(request);\n }\n\n const result = await handler(request);\n\n async function processToolMessage(\n msg: ToolMessage,\n toolTokenLimitBeforeEvict: number,\n ) {\n if (\n typeof msg.content === \"string\" &&\n msg.content.length > toolTokenLimitBeforeEvict * NUM_CHARS_PER_TOKEN\n ) {\n // Build StateAndStore from request\n const stateAndStore: StateAndStore = {\n state: request.state || {},\n // @ts-expect-error - request.config is incorrect typed\n store: request.config?.store,\n };\n const resolvedBackend = getBackend(backend, stateAndStore);\n const sanitizedId = sanitizeToolCallId(\n request.toolCall?.id || msg.tool_call_id,\n );\n const evictPath = `/large_tool_results/${sanitizedId}`;\n\n const writeResult = await resolvedBackend.write(\n evictPath,\n msg.content,\n );\n\n if (writeResult.error) {\n return { message: msg, filesUpdate: null };\n }\n\n // Create preview showing head and tail of the result\n const contentSample = createContentPreview(msg.content);\n const replacementText = TOO_LARGE_TOOL_MSG.replace(\n \"{tool_call_id}\",\n msg.tool_call_id,\n )\n .replace(\"{file_path}\", evictPath)\n .replace(\"{content_sample}\", contentSample);\n\n const truncatedMessage = new ToolMessage({\n content: replacementText,\n tool_call_id: msg.tool_call_id,\n name: msg.name,\n });\n\n return {\n message: truncatedMessage,\n filesUpdate: writeResult.filesUpdate,\n };\n }\n return { message: msg, filesUpdate: null };\n }\n\n if (ToolMessage.isInstance(result)) {\n const processed = await processToolMessage(\n result,\n toolTokenLimitBeforeEvict,\n );\n\n if (processed.filesUpdate) {\n return new Command({\n update: {\n files: processed.filesUpdate,\n messages: [processed.message],\n },\n });\n }\n\n return processed.message;\n }\n\n if (isCommand(result)) {\n const update = result.update as any;\n if (!update?.messages) {\n return result;\n }\n\n let hasLargeResults = false;\n const accumulatedFiles: Record<string, FileData> = update.files\n ? { ...update.files }\n : {};\n const processedMessages: ToolMessage[] = [];\n\n for (const msg of update.messages) {\n if (ToolMessage.isInstance(msg)) {\n const processed = await processToolMessage(\n msg,\n toolTokenLimitBeforeEvict,\n );\n processedMessages.push(processed.message);\n\n if (processed.filesUpdate) {\n hasLargeResults = true;\n Object.assign(accumulatedFiles, processed.filesUpdate);\n }\n } else {\n processedMessages.push(msg);\n }\n }\n\n if (hasLargeResults) {\n return new Command({\n update: {\n ...update,\n messages: processedMessages,\n files: accumulatedFiles,\n },\n });\n }\n }\n\n return result;\n },\n });\n}\n","import { z } from \"zod/v4\";\nimport {\n createMiddleware,\n createAgent,\n AgentMiddleware,\n tool,\n ToolMessage,\n humanInTheLoopMiddleware,\n SystemMessage,\n type InterruptOnConfig,\n type ReactAgent,\n StructuredTool,\n} from \"langchain\";\nimport { Command, getCurrentTaskInput } from \"@langchain/langgraph\";\nimport type { LanguageModelLike } from \"@langchain/core/language_models/base\";\nimport type { Runnable } from \"@langchain/core/runnables\";\nimport { HumanMessage } from \"@langchain/core/messages\";\n\nexport type { AgentMiddleware };\n\n/**\n * Default system prompt for subagents.\n * Provides a minimal base prompt that can be extended by specific subagent configurations.\n */\nexport const DEFAULT_SUBAGENT_PROMPT =\n \"In order to complete the objective that the user asks of you, you have access to a number of standard tools.\";\n\n/**\n * State keys that are excluded when passing state to subagents and when returning\n * updates from subagents.\n *\n * When returning updates:\n * 1. The messages key is handled explicitly to ensure only the final message is included\n * 2. The todos and structuredResponse keys are excluded as they do not have a defined reducer\n * and no clear meaning for returning them from a subagent to the main agent.\n * 3. The skillsMetadata and memoryContents keys are automatically excluded from subagent output\n * to prevent parent state from leaking to child agents. Each agent loads its own skills/memory\n * independently based on its middleware configuration.\n */\nconst EXCLUDED_STATE_KEYS = [\n \"messages\",\n \"todos\",\n \"structuredResponse\",\n \"skillsMetadata\",\n \"memoryContents\",\n] as const;\n\n/**\n * Default description for the general-purpose subagent.\n * This description is shown to the model when selecting which subagent to use.\n */\nexport const DEFAULT_GENERAL_PURPOSE_DESCRIPTION =\n \"General-purpose agent for researching complex questions, searching for files and content, and executing multi-step tasks. When you are searching for a keyword or file and are not confident that you will find the right match in the first few tries use this agent to perform the search for you. This agent has access to all tools as the main agent.\";\n\n// Comprehensive task tool description from Python\nfunction getTaskToolDescription(subagentDescriptions: string[]): string {\n return `\nLaunch an ephemeral subagent to handle complex, multi-step independent tasks with isolated context windows.\n\nAvailable agent types and the tools they have access to:\n${subagentDescriptions.join(\"\\n\")}\n\nWhen using the Task tool, you must specify a subagent_type parameter to select which agent type to use.\n\n## Usage notes:\n1. Launch multiple agents concurrently whenever possible, to maximize performance; to do that, use a single message with multiple tool uses\n2. When the agent is done, it will return a single message back to you. The result returned by the agent is not visible to the user. To show the user the result, you should send a text message back to the user with a concise summary of the result.\n3. Each agent invocation is stateless. You will not be able to send additional messages to the agent, nor will the agent be able to communicate with you outside of its final report. Therefore, your prompt should contain a highly detailed task description for the agent to perform autonomously and you should specify exactly what information the agent should return back to you in its final and only message to you.\n4. The agent's outputs should generally be trusted\n5. Clearly tell the agent whether you expect it to create content, perform analysis, or just do research (search, file reads, web fetches, etc.), since it is not aware of the user's intent\n6. If the agent description mentions that it should be used proactively, then you should try your best to use it without the user having to ask for it first. Use your judgement.\n7. When only the general-purpose agent is provided, you should use it for all tasks. It is great for isolating context and token usage, and completing specific, complex tasks, as it has all the same capabilities as the main agent.\n\n### Example usage of the general-purpose agent:\n\n<example_agent_descriptions>\n\"general-purpose\": use this agent for general purpose tasks, it has access to all tools as the main agent.\n</example_agent_descriptions>\n\n<example>\nUser: \"I want to conduct research on the accomplishments of Lebron James, Michael Jordan, and Kobe Bryant, and then compare them.\"\nAssistant: *Uses the task tool in parallel to conduct isolated research on each of the three players*\nAssistant: *Synthesizes the results of the three isolated research tasks and responds to the User*\n<commentary>\nResearch is a complex, multi-step task in it of itself.\nThe research of each individual player is not dependent on the research of the other players.\nThe assistant uses the task tool to break down the complex objective into three isolated tasks.\nEach research task only needs to worry about context and tokens about one player, then returns synthesized information about each player as the Tool Result.\nThis means each research task can dive deep and spend tokens and context deeply researching each player, but the final result is synthesized information, and saves us tokens in the long run when comparing the players to each other.\n</commentary>\n</example>\n\n<example>\nUser: \"Analyze a single large code repository for security vulnerabilities and generate a report.\"\nAssistant: *Launches a single \\`task\\` subagent for the repository analysis*\nAssistant: *Receives report and integrates results into final summary*\n<commentary>\nSubagent is used to isolate a large, context-heavy task, even though there is only one. This prevents the main thread from being overloaded with details.\nIf the user then asks followup questions, we have a concise report to reference instead of the entire history of analysis and tool calls, which is good and saves us time and money.\n</commentary>\n</example>\n\n<example>\nUser: \"Schedule two meetings for me and prepare agendas for each.\"\nAssistant: *Calls the task tool in parallel to launch two \\`task\\` subagents (one per meeting) to prepare agendas*\nAssistant: *Returns final schedules and agendas*\n<commentary>\nTasks are simple individually, but subagents help silo agenda preparation.\nEach subagent only needs to worry about the agenda for one meeting.\n</commentary>\n</example>\n\n<example>\nUser: \"I want to order a pizza from Dominos, order a burger from McDonald's, and order a salad from Subway.\"\nAssistant: *Calls tools directly in parallel to order a pizza from Dominos, a burger from McDonald's, and a salad from Subway*\n<commentary>\nThe assistant did not use the task tool because the objective is super simple and clear and only requires a few trivial tool calls.\nIt is better to just complete the task directly and NOT use the \\`task\\`tool.\n</commentary>\n</example>\n\n### Example usage with custom agents:\n\n<example_agent_descriptions>\n\"content-reviewer\": use this agent after you are done creating significant content or documents\n\"greeting-responder\": use this agent when to respond to user greetings with a friendly joke\n\"research-analyst\": use this agent to conduct thorough research on complex topics\n</example_agent_description>\n\n<example>\nuser: \"Please write a function that checks if a number is prime\"\nassistant: Sure let me write a function that checks if a number is prime\nassistant: First let me use the Write tool to write a function that checks if a number is prime\nassistant: I'm going to use the Write tool to write the following code:\n<code>\nfunction isPrime(n) {\n if (n <= 1) return false\n for (let i = 2; i * i <= n; i++) {\n if (n % i === 0) return false\n }\n return true\n}\n</code>\n<commentary>\nSince significant content was created and the task was completed, now use the content-reviewer agent to review the work\n</commentary>\nassistant: Now let me use the content-reviewer agent to review the code\nassistant: Uses the Task tool to launch with the content-reviewer agent\n</example>\n\n<example>\nuser: \"Can you help me research the environmental impact of different renewable energy sources and create a comprehensive report?\"\n<commentary>\nThis is a complex research task that would benefit from using the research-analyst agent to conduct thorough analysis\n</commentary>\nassistant: I'll help you research the environmental impact of renewable energy sources. Let me use the research-analyst agent to conduct comprehensive research on this topic.\nassistant: Uses the Task tool to launch with the research-analyst agent, providing detailed instructions about what research to conduct and what format the report should take\n</example>\n\n<example>\nuser: \"Hello\"\n<commentary>\nSince the user is greeting, use the greeting-responder agent to respond with a friendly joke\n</commentary>\nassistant: \"I'm going to use the Task tool to launch with the greeting-responder agent\"\n</example>\n `.trim();\n}\n\n/**\n * System prompt section that explains how to use the task tool for spawning subagents.\n *\n * This prompt is automatically appended to the main agent's system prompt when\n * using `createSubAgentMiddleware`. It provides guidance on:\n * - When to use the task tool\n * - Subagent lifecycle (spawn → run → return → reconcile)\n * - When NOT to use the task tool\n * - Best practices for parallel task execution\n *\n * You can provide a custom `systemPrompt` to `createSubAgentMiddleware` to override\n * or extend this default.\n */\nexport const TASK_SYSTEM_PROMPT = `## \\`task\\` (subagent spawner)\n\nYou have access to a \\`task\\` tool to launch short-lived subagents that handle isolated tasks. These agents are ephemeral — they live only for the duration of the task and return a single result.\n\nWhen to use the task tool:\n- When a task is complex and multi-step, and can be fully delegated in isolation\n- When a task is independent of other tasks and can run in parallel\n- When a task requires focused reasoning or heavy token/context usage that would bloat the orchestrator thread\n- When sandboxing improves reliability (e.g. code execution, structured searches, data formatting)\n- When you only care about the output of the subagent, and not the intermediate steps (ex. performing a lot of research and then returned a synthesized report, performing a series of computations or lookups to achieve a concise, relevant answer.)\n\nSubagent lifecycle:\n1. **Spawn** → Provide clear role, instructions, and expected output\n2. **Run** → The subagent completes the task autonomously\n3. **Return** → The subagent provides a single structured result\n4. **Reconcile** → Incorporate or synthesize the result into the main thread\n\nWhen NOT to use the task tool:\n- If you need to see the intermediate reasoning or steps after the subagent has completed (the task tool hides them)\n- If the task is trivial (a few tool calls or simple lookup)\n- If delegating does not reduce token usage, complexity, or context switching\n- If splitting would add latency without benefit\n\n## Important Task Tool Usage Notes to Remember\n- Whenever possible, parallelize the work that you do. This is true for both tool_calls, and for tasks. Whenever you have independent steps to complete - make tool_calls, or kick off tasks (subagents) in parallel to accomplish them faster. This saves time for the user, which is incredibly important.\n- Remember to use the \\`task\\` tool to silo independent tasks within a multi-part objective.\n- You should use the \\`task\\` tool whenever you have a complex task that will take multiple steps, and is independent from other tasks that the agent needs to complete. These agents are highly competent and efficient.`;\n\n/**\n * Type definitions for pre-compiled agents.\n *\n * @typeParam TRunnable - The type of the runnable (ReactAgent or Runnable).\n * When using `createAgent` or `createDeepAgent`, this preserves the middleware\n * types for type inference. Uses `ReactAgent<any>` to accept agents with any\n * type configuration (including DeepAgent instances).\n */\nexport interface CompiledSubAgent<\n TRunnable extends ReactAgent<any> | Runnable = ReactAgent<any> | Runnable,\n> {\n /** The name of the agent */\n name: string;\n /** The description of the agent */\n description: string;\n /** The agent instance */\n runnable: TRunnable;\n}\n\n/**\n * Specification for a subagent that can be dynamically created.\n *\n * When using `createDeepAgent`, subagents automatically receive a default middleware\n * stack (todoListMiddleware, filesystemMiddleware, summarizationMiddleware, etc.) before\n * any custom `middleware` specified in this spec.\n *\n * Required fields:\n * - `name`: Identifier used to select this subagent in the task tool\n * - `description`: Shown to the model for subagent selection\n * - `systemPrompt`: The system prompt for the subagent\n *\n * Optional fields:\n * - `model`: Override the default model for this subagent\n * - `tools`: Override the default tools for this subagent\n * - `middleware`: Additional middleware appended after defaults\n * - `interruptOn`: Human-in-the-loop configuration for specific tools\n * - `skills`: Skill source paths for SkillsMiddleware (e.g., `[\"/skills/user/\", \"/skills/project/\"]`)\n *\n * @example\n * ```typescript\n * const researcher: SubAgent = {\n * name: \"researcher\",\n * description: \"Research assistant for complex topics\",\n * systemPrompt: \"You are a research assistant.\",\n * tools: [webSearchTool],\n * skills: [\"/skills/research/\"],\n * };\n * ```\n */\nexport interface SubAgent {\n /** Identifier used to select this subagent in the task tool */\n name: string;\n\n /** Description shown to the model for subagent selection */\n description: string;\n\n /** The system prompt to use for the agent */\n systemPrompt: string;\n\n /** The tools to use for the agent (tool instances, not names). Defaults to defaultTools */\n tools?: StructuredTool[];\n\n /** The model for the agent. Defaults to defaultModel */\n model?: LanguageModelLike | string;\n\n /** Additional middleware to append after default_middleware */\n middleware?: readonly AgentMiddleware[];\n\n /** Human-in-the-loop configuration for specific tools. Requires a checkpointer. */\n interruptOn?: Record<string, boolean | InterruptOnConfig>;\n\n /**\n * Skill source paths for SkillsMiddleware.\n *\n * List of paths to skill directories (e.g., `[\"/skills/user/\", \"/skills/project/\"]`).\n * When specified, the subagent will have its own SkillsMiddleware that loads skills\n * from these paths. This allows subagents to have different skill sets than the main agent.\n *\n * Note: Custom subagents do NOT inherit skills from the main agent by default.\n * Only the general-purpose subagent inherits the main agent's skills.\n *\n * @example\n * ```typescript\n * const researcher: SubAgent = {\n * name: \"researcher\",\n * description: \"Research assistant\",\n * systemPrompt: \"You are a researcher.\",\n * skills: [\"/skills/research/\", \"/skills/web-search/\"],\n * };\n * ```\n */\n skills?: string[];\n}\n\n/**\n * Base specification for the general-purpose subagent.\n *\n * This constant provides the default configuration for the general-purpose subagent\n * that is automatically included when `generalPurposeAgent: true` (the default).\n *\n * The general-purpose subagent:\n * - Has access to all tools from the main agent\n * - Inherits skills from the main agent (when skills are configured)\n * - Uses the same model as the main agent (by default)\n * - Is ideal for delegating complex, multi-step tasks\n *\n * You can spread this constant and override specific properties when creating\n * custom subagents that should behave similarly to the general-purpose agent:\n *\n * @example\n * ```typescript\n * import { GENERAL_PURPOSE_SUBAGENT, createDeepAgent } from \"@anthropic/deepagents\";\n *\n * // Use as-is (automatically included with generalPurposeAgent: true)\n * const agent = createDeepAgent({ model: \"claude-sonnet-4-5-20250929\" });\n *\n * // Or create a custom variant with different tools\n * const customGP: SubAgent = {\n * ...GENERAL_PURPOSE_SUBAGENT,\n * name: \"research-gp\",\n * tools: [webSearchTool, readFileTool],\n * };\n *\n * const agent = createDeepAgent({\n * model: \"claude-sonnet-4-5-20250929\",\n * subagents: [customGP],\n * // Disable the default general-purpose agent since we're providing our own\n * // (handled automatically when using createSubAgentMiddleware directly)\n * });\n * ```\n */\nexport const GENERAL_PURPOSE_SUBAGENT: Pick<\n SubAgent,\n \"name\" | \"description\" | \"systemPrompt\"\n> = {\n name: \"general-purpose\",\n description: DEFAULT_GENERAL_PURPOSE_DESCRIPTION,\n systemPrompt: DEFAULT_SUBAGENT_PROMPT,\n} as const;\n\n/**\n * Filter state to exclude certain keys when passing to subagents\n */\nfunction filterStateForSubagent(\n state: Record<string, unknown>,\n): Record<string, unknown> {\n const filtered: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(state)) {\n if (!EXCLUDED_STATE_KEYS.includes(key as never)) {\n filtered[key] = value;\n }\n }\n return filtered;\n}\n\n/**\n * Create Command with filtered state update from subagent result\n */\nfunction returnCommandWithStateUpdate(\n result: Record<string, unknown>,\n toolCallId: string,\n): Command {\n const stateUpdate = filterStateForSubagent(result);\n const messages = result.messages as Array<{ content: string }>;\n const lastMessage = messages?.[messages.length - 1];\n\n return new Command({\n update: {\n ...stateUpdate,\n messages: [\n new ToolMessage({\n content: lastMessage?.content || \"Task completed\",\n tool_call_id: toolCallId,\n name: \"task\",\n }),\n ],\n },\n });\n}\n\n/**\n * Create subagent instances from specifications\n */\nfunction getSubagents(options: {\n defaultModel: LanguageModelLike | string;\n defaultTools: StructuredTool[];\n defaultMiddleware: AgentMiddleware[] | null;\n /** Middleware specifically for the general-purpose subagent (includes skills from main agent) */\n generalPurposeMiddleware: AgentMiddleware[] | null;\n defaultInterruptOn: Record<string, boolean | InterruptOnConfig> | null;\n subagents: (SubAgent | CompiledSubAgent)[];\n generalPurposeAgent: boolean;\n}): {\n agents: Record<string, ReactAgent | Runnable>;\n descriptions: string[];\n} {\n const {\n defaultModel,\n defaultTools,\n defaultMiddleware,\n generalPurposeMiddleware: gpMiddleware,\n defaultInterruptOn,\n subagents,\n generalPurposeAgent,\n } = options;\n\n const defaultSubagentMiddleware = defaultMiddleware || [];\n // General-purpose middleware includes skills from main agent, falls back to default\n const generalPurposeMiddlewareBase =\n gpMiddleware || defaultSubagentMiddleware;\n const agents: Record<string, ReactAgent | Runnable> = {};\n const subagentDescriptions: string[] = [];\n\n // Create general-purpose agent if enabled\n if (generalPurposeAgent) {\n const generalPurposeMiddleware = [...generalPurposeMiddlewareBase];\n if (defaultInterruptOn) {\n generalPurposeMiddleware.push(\n humanInTheLoopMiddleware({ interruptOn: defaultInterruptOn }),\n );\n }\n\n const generalPurposeSubagent = createAgent({\n model: defaultModel,\n systemPrompt: DEFAULT_SUBAGENT_PROMPT,\n tools: defaultTools as any,\n middleware: generalPurposeMiddleware,\n name: \"general-purpose\",\n });\n\n agents[\"general-purpose\"] = generalPurposeSubagent;\n subagentDescriptions.push(\n `- general-purpose: ${DEFAULT_GENERAL_PURPOSE_DESCRIPTION}`,\n );\n }\n\n // Process custom subagents (use defaultMiddleware WITHOUT skills)\n for (const agentParams of subagents) {\n subagentDescriptions.push(\n `- ${agentParams.name}: ${agentParams.description}`,\n );\n\n if (\"runnable\" in agentParams) {\n agents[agentParams.name] = agentParams.runnable;\n } else {\n const middleware = agentParams.middleware\n ? [...defaultSubagentMiddleware, ...agentParams.middleware]\n : [...defaultSubagentMiddleware];\n\n const interruptOn = agentParams.interruptOn || defaultInterruptOn;\n if (interruptOn)\n middleware.push(humanInTheLoopMiddleware({ interruptOn }));\n\n agents[agentParams.name] = createAgent({\n model: agentParams.model ?? defaultModel,\n systemPrompt: agentParams.systemPrompt,\n tools: agentParams.tools ?? defaultTools,\n middleware,\n name: agentParams.name,\n });\n }\n }\n\n return { agents, descriptions: subagentDescriptions };\n}\n\n/**\n * Create the task tool for invoking subagents\n */\nfunction createTaskTool(options: {\n defaultModel: LanguageModelLike | string;\n defaultTools: StructuredTool[];\n defaultMiddleware: AgentMiddleware[] | null;\n /** Middleware specifically for the general-purpose subagent (includes skills from main agent) */\n generalPurposeMiddleware: AgentMiddleware[] | null;\n defaultInterruptOn: Record<string, boolean | InterruptOnConfig> | null;\n subagents: (SubAgent | CompiledSubAgent)[];\n generalPurposeAgent: boolean;\n taskDescription: string | null;\n}) {\n const {\n defaultModel,\n defaultTools,\n defaultMiddleware,\n generalPurposeMiddleware,\n defaultInterruptOn,\n subagents,\n generalPurposeAgent,\n taskDescription,\n } = options;\n\n const { agents: subagentGraphs, descriptions: subagentDescriptions } =\n getSubagents({\n defaultModel,\n defaultTools,\n defaultMiddleware,\n generalPurposeMiddleware,\n defaultInterruptOn,\n subagents,\n generalPurposeAgent,\n });\n\n const finalTaskDescription = taskDescription\n ? taskDescription\n : getTaskToolDescription(subagentDescriptions);\n\n return tool(\n async (\n input: { description: string; subagent_type: string },\n config,\n ): Promise<Command | string> => {\n const { description, subagent_type } = input;\n\n // Validate subagent type\n if (!(subagent_type in subagentGraphs)) {\n const allowedTypes = Object.keys(subagentGraphs)\n .map((k) => `\\`${k}\\``)\n .join(\", \");\n throw new Error(\n `Error: invoked agent of type ${subagent_type}, the only allowed types are ${allowedTypes}`,\n );\n }\n\n const subagent = subagentGraphs[subagent_type];\n\n // Get current state and filter it for subagent\n const currentState = getCurrentTaskInput<Record<string, unknown>>();\n const subagentState = filterStateForSubagent(currentState);\n subagentState.messages = [new HumanMessage({ content: description })];\n\n // Invoke the subagent\n const result = (await subagent.invoke(subagentState, config)) as Record<\n string,\n unknown\n >;\n\n // Return command with filtered state update\n if (!config.toolCall?.id) {\n throw new Error(\"Tool call ID is required for subagent invocation\");\n }\n\n return returnCommandWithStateUpdate(result, config.toolCall.id);\n },\n {\n name: \"task\",\n description: finalTaskDescription,\n schema: z.object({\n description: z\n .string()\n .describe(\"The task to execute with the selected agent\"),\n subagent_type: z\n .string()\n .describe(\n `Name of the agent to use. Available: ${Object.keys(subagentGraphs).join(\", \")}`,\n ),\n }),\n },\n );\n}\n\n/**\n * Options for creating subagent middleware\n */\nexport interface SubAgentMiddlewareOptions {\n /** The model to use for subagents */\n defaultModel: LanguageModelLike | string;\n /** The tools to use for the default general-purpose subagent */\n defaultTools?: StructuredTool[];\n /** Default middleware to apply to custom subagents (WITHOUT skills from main agent) */\n defaultMiddleware?: AgentMiddleware[] | null;\n /**\n * Middleware specifically for the general-purpose subagent (includes skills from main agent).\n * If not provided, falls back to defaultMiddleware.\n */\n generalPurposeMiddleware?: AgentMiddleware[] | null;\n /** The tool configs for the default general-purpose subagent */\n defaultInterruptOn?: Record<string, boolean | InterruptOnConfig> | null;\n /** A list of additional subagents to provide to the agent */\n subagents?: (SubAgent | CompiledSubAgent)[];\n /** Full system prompt override */\n systemPrompt?: string | null;\n /** Whether to include the general-purpose agent */\n generalPurposeAgent?: boolean;\n /** Custom description for the task tool */\n taskDescription?: string | null;\n}\n\n/**\n * Create subagent middleware with task tool\n */\nexport function createSubAgentMiddleware(options: SubAgentMiddlewareOptions) {\n const {\n defaultModel,\n defaultTools = [],\n defaultMiddleware = null,\n generalPurposeMiddleware = null,\n defaultInterruptOn = null,\n subagents = [],\n systemPrompt = TASK_SYSTEM_PROMPT,\n generalPurposeAgent = true,\n taskDescription = null,\n } = options;\n\n const taskTool = createTaskTool({\n defaultModel,\n defaultTools,\n defaultMiddleware,\n generalPurposeMiddleware,\n defaultInterruptOn,\n subagents,\n generalPurposeAgent,\n taskDescription,\n });\n\n return createMiddleware({\n name: \"subAgentMiddleware\",\n tools: [taskTool],\n wrapModelCall: async (request, handler) => {\n if (systemPrompt !== null) {\n return handler({\n ...request,\n systemMessage: request.systemMessage.concat(\n new SystemMessage({ content: systemPrompt }),\n ),\n });\n }\n return handler(request);\n },\n });\n}\n","import {\n createMiddleware,\n ToolMessage,\n AIMessage,\n /**\n * required for type inference\n */\n type AgentMiddleware as _AgentMiddleware,\n} from \"langchain\";\nimport { RemoveMessage, type BaseMessage } from \"@langchain/core/messages\";\nimport { REMOVE_ALL_MESSAGES } from \"@langchain/langgraph\";\n\n/**\n * Patch dangling tool calls in a messages array.\n * Returns the patched messages array and a flag indicating if patching was needed.\n *\n * @param messages - The messages array to patch\n * @returns Object with patched messages and needsPatch flag\n */\nexport function patchDanglingToolCalls(messages: BaseMessage[]): {\n patchedMessages: BaseMessage[];\n needsPatch: boolean;\n} {\n if (!messages || messages.length === 0) {\n return { patchedMessages: [], needsPatch: false };\n }\n\n const patchedMessages: BaseMessage[] = [];\n let needsPatch = false;\n\n // Iterate over the messages and add any dangling tool calls\n for (let i = 0; i < messages.length; i++) {\n const msg = messages[i];\n patchedMessages.push(msg);\n\n // Check if this is an AI message with tool calls\n if (AIMessage.isInstance(msg) && msg.tool_calls != null) {\n for (const toolCall of msg.tool_calls) {\n // Look for a corresponding ToolMessage in the messages after this one\n const correspondingToolMsg = messages\n .slice(i)\n .find(\n (m) => ToolMessage.isInstance(m) && m.tool_call_id === toolCall.id,\n );\n\n if (!correspondingToolMsg) {\n // We have a dangling tool call which needs a ToolMessage\n needsPatch = true;\n const toolMsg = `Tool call ${toolCall.name} with id ${toolCall.id} was cancelled - another message came in before it could be completed.`;\n patchedMessages.push(\n new ToolMessage({\n content: toolMsg,\n name: toolCall.name,\n tool_call_id: toolCall.id!,\n }),\n );\n }\n }\n }\n }\n\n return { patchedMessages, needsPatch };\n}\n\n/**\n * Create middleware that patches dangling tool calls in the messages history.\n *\n * When an AI message contains tool_calls but subsequent messages don't include\n * the corresponding ToolMessage responses, this middleware adds synthetic\n * ToolMessages saying the tool call was cancelled.\n *\n * This middleware patches in two places:\n * 1. `beforeAgent`: Patches state at the start of the agent loop (handles most cases)\n * 2. `wrapModelCall`: Patches the request right before model invocation (handles\n * edge cases like HITL rejection during graph resume where state updates from\n * beforeAgent may not be applied in time)\n *\n * @returns AgentMiddleware that patches dangling tool calls\n *\n * @example\n * ```typescript\n * import { createAgent } from \"langchain\";\n * import { createPatchToolCallsMiddleware } from \"./middleware/patch_tool_calls\";\n *\n * const agent = createAgent({\n * model: \"claude-sonnet-4-5-20250929\",\n * middleware: [createPatchToolCallsMiddleware()],\n * });\n * ```\n */\nexport function createPatchToolCallsMiddleware() {\n return createMiddleware({\n name: \"patchToolCallsMiddleware\",\n beforeAgent: async (state) => {\n const messages = state.messages;\n\n if (!messages || messages.length === 0) {\n return;\n }\n\n const { patchedMessages, needsPatch } = patchDanglingToolCalls(messages);\n\n /**\n * Only trigger REMOVE_ALL_MESSAGES if patching is actually needed\n */\n if (!needsPatch) {\n return;\n }\n\n // Return state update with RemoveMessage followed by patched messages\n return {\n messages: [\n new RemoveMessage({ id: REMOVE_ALL_MESSAGES }),\n ...patchedMessages,\n ],\n };\n },\n\n /**\n * Also patch in wrapModelCall as a safety net.\n * This handles edge cases where:\n * - HITL rejects a tool call during graph resume\n * - The state update from beforeAgent might not be applied in time\n * - The model would otherwise receive dangling tool_call_ids\n */\n wrapModelCall: async (request, handler) => {\n const messages = request.messages;\n\n if (!messages || messages.length === 0) {\n return handler(request);\n }\n\n const { patchedMessages, needsPatch } = patchDanglingToolCalls(messages);\n\n if (!needsPatch) {\n return handler(request);\n }\n\n // Pass patched messages to the model\n return handler({\n ...request,\n messages: patchedMessages,\n });\n },\n });\n}\n","/**\n * Shared state values for use in StateSchema definitions.\n *\n * This module provides pre-configured ReducedValue instances that can be\n * reused across different state schemas, similar to LangGraph's messagesValue.\n */\n\nimport { z } from \"zod\";\nimport { ReducedValue } from \"@langchain/langgraph\";\nimport { FileDataSchema, fileDataReducer } from \"./middleware/fs.js\";\n\n/**\n * Shared ReducedValue for file data state management.\n *\n * This provides a reusable pattern for managing file state with automatic\n * merging of concurrent updates from parallel subagents. Files can be updated\n * or deleted (using null values) and the reducer handles the merge logic.\n *\n * Similar to LangGraph's messagesValue, this encapsulates the common pattern\n * of managing files in agent state so you don't have to manually configure\n * the ReducedValue each time.\n *\n * @example\n * ```typescript\n * import { filesValue } from \"@anthropic/deepagents\";\n * import { StateSchema } from \"@langchain/langgraph\";\n *\n * const MyStateSchema = new StateSchema({\n * files: filesValue,\n * // ... other state fields\n * });\n * ```\n */\nexport const filesValue = new ReducedValue(\n z.record(z.string(), FileDataSchema).default(() => ({})),\n {\n inputSchema: z.record(z.string(), FileDataSchema.nullable()).optional(),\n reducer: fileDataReducer,\n },\n);\n","/**\n * Middleware for loading agent memory/context from AGENTS.md files.\n *\n * This module implements support for the AGENTS.md specification (https://agents.md/),\n * loading memory/context from configurable sources and injecting into the system prompt.\n *\n * ## Overview\n *\n * AGENTS.md files provide project-specific context and instructions to help AI agents\n * work effectively. Unlike skills (which are on-demand workflows), memory is always\n * loaded and provides persistent context.\n *\n * ## Usage\n *\n * ```typescript\n * import { createMemoryMiddleware } from \"@anthropic/deepagents\";\n * import { FilesystemBackend } from \"@anthropic/deepagents\";\n *\n * // Security: FilesystemBackend allows reading/writing from the entire filesystem.\n * // Either ensure the agent is running within a sandbox OR add human-in-the-loop (HIL)\n * // approval to file operations.\n * const backend = new FilesystemBackend({ rootDir: \"/\" });\n *\n * const middleware = createMemoryMiddleware({\n * backend,\n * sources: [\n * \"~/.deepagents/AGENTS.md\",\n * \"./.deepagents/AGENTS.md\",\n * ],\n * });\n *\n * const agent = createDeepAgent({ middleware: [middleware] });\n * ```\n *\n * ## Memory Sources\n *\n * Sources are simply paths to AGENTS.md files that are loaded in order and combined.\n * Multiple sources are concatenated in order, with all content included.\n * Later sources appear after earlier ones in the combined prompt.\n *\n * ## File Format\n *\n * AGENTS.md files are standard Markdown with no required structure.\n * Common sections include:\n * - Project overview\n * - Build/test commands\n * - Code style guidelines\n * - Architecture notes\n */\n\nimport { z } from \"zod\";\nimport {\n createMiddleware,\n SystemMessage,\n /**\n * required for type inference\n */\n type AgentMiddleware as _AgentMiddleware,\n} from \"langchain\";\n\nimport type { BackendProtocol, BackendFactory } from \"../backends/protocol.js\";\nimport type { StateBackend } from \"../backends/state.js\";\nimport type { BaseStore } from \"@langchain/langgraph-checkpoint\";\nimport { filesValue } from \"../values.js\";\nimport { StateSchema } from \"@langchain/langgraph\";\n\n/**\n * Options for the memory middleware.\n */\nexport interface MemoryMiddlewareOptions {\n /**\n * Backend instance or factory function for file operations.\n * Use a factory for StateBackend since it requires runtime state.\n */\n backend:\n | BackendProtocol\n | BackendFactory\n | ((config: { state: unknown; store?: BaseStore }) => StateBackend);\n\n /**\n * List of memory file paths to load (e.g., [\"~/.deepagents/AGENTS.md\", \"./.deepagents/AGENTS.md\"]).\n * Display names are automatically derived from the paths.\n * Sources are loaded in order.\n */\n sources: string[];\n}\n\n/**\n * State schema for memory middleware.\n */\nconst MemoryStateSchema = new StateSchema({\n /**\n * Dict mapping source paths to their loaded content.\n * Marked as private so it's not included in the final agent state.\n */\n memoryContents: z.record(z.string(), z.string()).optional(),\n files: filesValue,\n});\n\n/**\n * Default system prompt template for memory.\n * Ported from Python's comprehensive memory guidelines.\n */\nconst MEMORY_SYSTEM_PROMPT = `<agent_memory>\n{memory_contents}\n</agent_memory>\n\n<memory_guidelines>\n The above <agent_memory> was loaded in from files in your filesystem. As you learn from your interactions with the user, you can save new knowledge by calling the \\`edit_file\\` tool.\n\n **Learning from feedback:**\n - One of your MAIN PRIORITIES is to learn from your interactions with the user. These learnings can be implicit or explicit. This means that in the future, you will remember this important information.\n - When you need to remember something, updating memory must be your FIRST, IMMEDIATE action - before responding to the user, before calling other tools, before doing anything else. Just update memory immediately.\n - When user says something is better/worse, capture WHY and encode it as a pattern.\n - Each correction is a chance to improve permanently - don't just fix the immediate issue, update your instructions.\n - A great opportunity to update your memories is when the user interrupts a tool call and provides feedback. You should update your memories immediately before revising the tool call.\n - Look for the underlying principle behind corrections, not just the specific mistake.\n - The user might not explicitly ask you to remember something, but if they provide information that is useful for future use, you should update your memories immediately.\n\n **Asking for information:**\n - If you lack context to perform an action (e.g. send a Slack DM, requires a user ID/email) you should explicitly ask the user for this information.\n - It is preferred for you to ask for information, don't assume anything that you do not know!\n - When the user provides information that is useful for future use, you should update your memories immediately.\n\n **When to update memories:**\n - When the user explicitly asks you to remember something (e.g., \"remember my email\", \"save this preference\")\n - When the user describes your role or how you should behave (e.g., \"you are a web researcher\", \"always do X\")\n - When the user gives feedback on your work - capture what was wrong and how to improve\n - When the user provides information required for tool use (e.g., slack channel ID, email addresses)\n - When the user provides context useful for future tasks, such as how to use tools, or which actions to take in a particular situation\n - When you discover new patterns or preferences (coding styles, conventions, workflows)\n\n **When to NOT update memories:**\n - When the information is temporary or transient (e.g., \"I'm running late\", \"I'm on my phone right now\")\n - When the information is a one-time task request (e.g., \"Find me a recipe\", \"What's 25 * 4?\")\n - When the information is a simple question that doesn't reveal lasting preferences (e.g., \"What day is it?\", \"Can you explain X?\")\n - When the information is an acknowledgment or small talk (e.g., \"Sounds good!\", \"Hello\", \"Thanks for that\")\n - When the information is stale or irrelevant in future conversations\n - Never store API keys, access tokens, passwords, or any other credentials in any file, memory, or system prompt.\n - If the user asks where to put API keys or provides an API key, do NOT echo or save it.\n\n **Examples:**\n Example 1 (remembering user information):\n User: Can you connect to my google account?\n Agent: Sure, I'll connect to your google account, what's your google account email?\n User: john@example.com\n Agent: Let me save this to my memory.\n Tool Call: edit_file(...) -> remembers that the user's google account email is john@example.com\n\n Example 2 (remembering implicit user preferences):\n User: Can you write me an example for creating a deep agent in LangChain?\n Agent: Sure, I'll write you an example for creating a deep agent in LangChain <example code in Python>\n User: Can you do this in JavaScript\n Agent: Let me save this to my memory.\n Tool Call: edit_file(...) -> remembers that the user prefers to get LangChain code examples in JavaScript\n Agent: Sure, here is the JavaScript example<example code in JavaScript>\n\n Example 3 (do not remember transient information):\n User: I'm going to play basketball tonight so I will be offline for a few hours.\n Agent: Okay I'll add a block to your calendar.\n Tool Call: create_calendar_event(...) -> just calls a tool, does not commit anything to memory, as it is transient information\n</memory_guidelines>`;\n\n/**\n * Format loaded memory contents for injection into prompt.\n * Pairs memory locations with their contents for clarity.\n */\nfunction formatMemoryContents(\n contents: Record<string, string>,\n sources: string[],\n): string {\n if (Object.keys(contents).length === 0) {\n return \"(No memory loaded)\";\n }\n\n const sections: string[] = [];\n for (const path of sources) {\n if (contents[path]) {\n sections.push(`${path}\\n${contents[path]}`);\n }\n }\n\n if (sections.length === 0) {\n return \"(No memory loaded)\";\n }\n\n return sections.join(\"\\n\\n\");\n}\n\n/**\n * Load memory content from a backend path.\n *\n * @param backend - Backend to load from.\n * @param path - Path to the AGENTS.md file.\n * @returns File content if found, null otherwise.\n */\nasync function loadMemoryFromBackend(\n backend: BackendProtocol,\n path: string,\n): Promise<string | null> {\n // Use downloadFiles if available, otherwise fall back to read\n if (!backend.downloadFiles) {\n const content = await backend.read(path);\n if (content.startsWith(\"Error:\")) {\n return null;\n }\n return content;\n }\n\n const results = await backend.downloadFiles([path]);\n\n // Should get exactly one response for one path\n if (results.length !== 1) {\n throw new Error(\n `Expected 1 response for path ${path}, got ${results.length}`,\n );\n }\n const response = results[0];\n\n if (response.error != null) {\n // For now, memory files are treated as optional. file_not_found is expected\n // and we skip silently to allow graceful degradation.\n if (response.error === \"file_not_found\") {\n return null;\n }\n // Other errors should be raised\n throw new Error(`Failed to download ${path}: ${response.error}`);\n }\n\n if (response.content != null) {\n // Content is a Uint8Array, decode to string\n return new TextDecoder().decode(response.content);\n }\n\n return null;\n}\n\n/**\n * Create middleware for loading agent memory from AGENTS.md files.\n *\n * Loads memory content from configured sources and injects into the system prompt.\n * Supports multiple sources that are combined together.\n *\n * @param options - Configuration options\n * @returns AgentMiddleware for memory loading and injection\n *\n * @example\n * ```typescript\n * const middleware = createMemoryMiddleware({\n * backend: new FilesystemBackend({ rootDir: \"/\" }),\n * sources: [\n * \"~/.deepagents/AGENTS.md\",\n * \"./.deepagents/AGENTS.md\",\n * ],\n * });\n * ```\n */\nexport function createMemoryMiddleware(options: MemoryMiddlewareOptions) {\n const { backend, sources } = options;\n\n /**\n * Resolve backend from instance or factory.\n */\n function getBackend(state: unknown): BackendProtocol {\n if (typeof backend === \"function\") {\n // It's a factory - call it with state\n return backend({ state }) as BackendProtocol;\n }\n return backend;\n }\n\n return createMiddleware({\n name: \"MemoryMiddleware\",\n stateSchema: MemoryStateSchema,\n\n async beforeAgent(state) {\n // Skip if already loaded\n if (\"memoryContents\" in state && state.memoryContents != null) {\n return undefined;\n }\n\n const resolvedBackend = getBackend(state);\n const contents: Record<string, string> = {};\n\n for (const path of sources) {\n try {\n const content = await loadMemoryFromBackend(resolvedBackend, path);\n if (content) {\n contents[path] = content;\n }\n } catch (error) {\n // Log but continue - memory is optional\n // eslint-disable-next-line no-console\n console.debug(`Failed to load memory from ${path}:`, error);\n }\n }\n\n return { memoryContents: contents };\n },\n\n wrapModelCall(request, handler) {\n // Get memory contents from state\n const memoryContents: Record<string, string> =\n request.state?.memoryContents || {};\n\n // Format memory section\n const formattedContents = formatMemoryContents(memoryContents, sources);\n\n const memorySection = MEMORY_SYSTEM_PROMPT.replace(\n \"{memory_contents}\",\n formattedContents,\n );\n\n // Concat memory section to system prompt\n const memoryMessage = new SystemMessage(memorySection);\n const newSystemMessage = memoryMessage.concat(request.systemMessage);\n\n return handler({\n ...request,\n systemMessage: newSystemMessage,\n });\n },\n });\n}\n","/* eslint-disable no-console */\n/**\n * Backend-agnostic skills middleware for loading agent skills from any backend.\n *\n * This middleware implements Anthropic's agent skills pattern with progressive disclosure,\n * loading skills from backend storage via configurable sources.\n *\n * ## Architecture\n *\n * Skills are loaded from one or more **sources** - paths in a backend where skills are\n * organized. Sources are loaded in order, with later sources overriding earlier ones\n * when skills have the same name (last one wins). This enables layering: base -> user\n * -> project -> team skills.\n *\n * The middleware uses backend APIs exclusively (no direct filesystem access), making it\n * portable across different storage backends (filesystem, state, remote storage, etc.).\n *\n * ## Usage\n *\n * ```typescript\n * import { createSkillsMiddleware, FilesystemBackend } from \"@anthropic/deepagents\";\n *\n * const middleware = createSkillsMiddleware({\n * backend: new FilesystemBackend({ rootDir: \"/\" }),\n * sources: [\n * \"/skills/user/\",\n * \"/skills/project/\",\n * ],\n * });\n *\n * const agent = createDeepAgent({ middleware: [middleware] });\n * ```\n *\n * Or use the `skills` parameter on createDeepAgent:\n *\n * ```typescript\n * const agent = createDeepAgent({\n * skills: [\"/skills/user/\", \"/skills/project/\"],\n * });\n * ```\n */\n\nimport { z } from \"zod\";\nimport yaml from \"yaml\";\nimport {\n createMiddleware,\n /**\n * required for type inference\n */\n type AgentMiddleware as _AgentMiddleware,\n} from \"langchain\";\nimport { StateSchema, ReducedValue } from \"@langchain/langgraph\";\n\nimport type { BackendProtocol, BackendFactory } from \"../backends/protocol.js\";\nimport type { StateBackend } from \"../backends/state.js\";\nimport type { BaseStore } from \"@langchain/langgraph-checkpoint\";\nimport { filesValue } from \"../values.js\";\n\n// Security: Maximum size for SKILL.md files to prevent DoS attacks (10MB)\nexport const MAX_SKILL_FILE_SIZE = 10 * 1024 * 1024;\n\n// Agent Skills specification constraints (https://agentskills.io/specification)\nexport const MAX_SKILL_NAME_LENGTH = 64;\nexport const MAX_SKILL_DESCRIPTION_LENGTH = 1024;\nexport const MAX_SKILL_COMPATIBILITY_LENGTH = 500;\n\n/**\n * Metadata for a skill per Agent Skills specification.\n */\nexport interface SkillMetadata {\n /**\n * Skill identifier.\n *\n * Constraints per Agent Skills specification:\n *\n * - 1-64 characters\n * - Unicode lowercase alphanumeric and hyphens only (`a-z` and `-`).\n * - Must not start or end with `-`\n * - Must not contain consecutive `--`\n * - Must match the parent directory name containing the `SKILL.md` file\n */\n name: string;\n\n /**\n * What the skill does.\n *\n * Constraints per Agent Skills specification:\n *\n * - 1-1024 characters\n * - Should describe both what the skill does and when to use it\n * - Should include specific keywords that help agents identify relevant tasks\n */\n description: string;\n\n /** Path to the SKILL.md file in the backend */\n path: string;\n\n /** License name or reference to bundled license file. */\n license?: string | null;\n\n /**\n * Environment requirements.\n *\n * Constraints per Agent Skills specification:\n *\n * - 1-500 characters if provided\n * - Should only be included if there are specific compatibility requirements\n * - Can indicate intended product, required packages, etc.\n */\n compatibility?: string | null;\n\n /**\n * Arbitrary key-value mapping for additional metadata.\n *\n * Clients can use this to store additional properties not defined by the spec.\n *\n * It is recommended to keep key names unique to avoid conflicts.\n */\n metadata?: Record<string, string>;\n\n /**\n * Tool names the skill recommends using.\n *\n * Warning: this is experimental.\n *\n * Constraints per Agent Skills specification:\n *\n * - Space-delimited list of tool names\n */\n allowedTools?: string[];\n}\n\n/**\n * Options for the skills middleware.\n */\nexport interface SkillsMiddlewareOptions {\n /**\n * Backend instance or factory function for file operations.\n * Use a factory for StateBackend since it requires runtime state.\n */\n backend:\n | BackendProtocol\n | BackendFactory\n | ((config: { state: unknown; store?: BaseStore }) => StateBackend);\n\n /**\n * List of skill source paths to load (e.g., [\"/skills/user/\", \"/skills/project/\"]).\n * Paths must use POSIX conventions (forward slashes).\n * Later sources override earlier ones for skills with the same name (last one wins).\n */\n sources: string[];\n}\n\n/**\n * Zod schema for a single skill metadata entry.\n */\nexport const SkillMetadataEntrySchema = z.object({\n name: z.string(),\n description: z.string(),\n path: z.string(),\n license: z.string().nullable().optional(),\n compatibility: z.string().nullable().optional(),\n metadata: z.record(z.string(), z.string()).optional(),\n allowedTools: z.array(z.string()).optional(),\n});\n\n/**\n * Type for a single skill metadata entry.\n */\nexport type SkillMetadataEntry = z.infer<typeof SkillMetadataEntrySchema>;\n\n/**\n * Reducer for skillsMetadata that merges arrays from parallel subagents.\n * Skills are deduplicated by name, with later values overriding earlier ones.\n *\n * @param current - The current skillsMetadata array (from state)\n * @param update - The new skillsMetadata array (from a subagent update)\n * @returns Merged array with duplicates resolved by name (later values win)\n */\nexport function skillsMetadataReducer(\n current: SkillMetadataEntry[] | undefined,\n update: SkillMetadataEntry[] | undefined,\n): SkillMetadataEntry[] {\n // If no update, return current (or empty array)\n if (!update || update.length === 0) {\n return current || [];\n }\n // If no current, return update\n if (!current || current.length === 0) {\n return update;\n }\n // Merge by skill name (later values override earlier ones)\n const merged = new Map<string, SkillMetadataEntry>();\n for (const skill of current) {\n merged.set(skill.name, skill);\n }\n for (const skill of update) {\n merged.set(skill.name, skill);\n }\n return Array.from(merged.values());\n}\n\n/**\n * State schema for skills middleware.\n * Uses ReducedValue for skillsMetadata to allow concurrent updates from parallel subagents.\n */\nconst SkillsStateSchema = new StateSchema({\n skillsMetadata: new ReducedValue(\n z.array(SkillMetadataEntrySchema).default(() => []),\n {\n inputSchema: z.array(SkillMetadataEntrySchema).optional(),\n reducer: skillsMetadataReducer,\n },\n ),\n files: filesValue,\n});\n\n/**\n * Skills System Documentation prompt template.\n */\nconst SKILLS_SYSTEM_PROMPT = `\n## Skills System\n\nYou have access to a skills library that provides specialized capabilities and domain knowledge.\n\n{skills_locations}\n\n**Available Skills:**\n\n{skills_list}\n\n**How to Use Skills (Progressive Disclosure):**\n\nSkills follow a **progressive disclosure** pattern - you know they exist (name + description above), but you only read the full instructions when needed:\n\n1. **Recognize when a skill applies**: Check if the user's task matches any skill's description\n2. **Read the skill's full instructions**: The skill list above shows the exact path to use with read_file\n3. **Follow the skill's instructions**: SKILL.md contains step-by-step workflows, best practices, and examples\n4. **Access supporting files**: Skills may include Python scripts, configs, or reference docs - use absolute paths\n\n**When to Use Skills:**\n- When the user's request matches a skill's domain (e.g., \"research X\" → web-research skill)\n- When you need specialized knowledge or structured workflows\n- When a skill provides proven patterns for complex tasks\n\n**Skills are Self-Documenting:**\n- Each SKILL.md tells you exactly what the skill does and how to use it\n- The skill list above shows the full path for each skill's SKILL.md file\n\n**Executing Skill Scripts:**\nSkills may contain Python scripts or other executable files. Always use absolute paths from the skill list.\n\n**Example Workflow:**\n\nUser: \"Can you research the latest developments in quantum computing?\"\n\n1. Check available skills above → See \"web-research\" skill with its full path\n2. Read the skill using the path shown in the list\n3. Follow the skill's research workflow (search → organize → synthesize)\n4. Use any helper scripts with absolute paths\n\nRemember: Skills are tools to make you more capable and consistent. When in doubt, check if a skill exists for the task!\n`;\n\n/**\n * Validate skill name per Agent Skills specification.\n *\n * Constraints per Agent Skills specification:\n *\n * - 1-64 characters\n * - Unicode lowercase alphanumeric and hyphens only (`a-z` and `-`).\n * - Must not start or end with `-`\n * - Must not contain consecutive `--`\n * - Must match the parent directory name containing the `SKILL.md` file\n *\n * Unicode lowercase alphanumeric means any lowercase or decimal digit, which\n * covers accented Latin characters (e.g., `'café'`, `'über-tool'`) and other\n * scripts.\n *\n * @param name - The skill name from YAML frontmatter\n * @param directoryName - The parent directory name\n * @returns `{ valid, error }` tuple. Error is empty string if valid.\n */\nexport function validateSkillName(\n name: string,\n directoryName: string,\n): { valid: boolean; error: string } {\n if (!name) {\n return { valid: false, error: \"name is required\" };\n }\n if (name.length > MAX_SKILL_NAME_LENGTH) {\n return { valid: false, error: \"name exceeds 64 characters\" };\n }\n if (name.startsWith(\"-\") || name.endsWith(\"-\") || name.includes(\"--\")) {\n return {\n valid: false,\n error: \"name must be lowercase alphanumeric with single hyphens only\",\n };\n }\n for (const c of name) {\n if (c === \"-\") continue;\n if (/\\p{Ll}/u.test(c) || /\\p{Nd}/u.test(c)) continue;\n return {\n valid: false,\n error: \"name must be lowercase alphanumeric with single hyphens only\",\n };\n }\n if (name !== directoryName) {\n return {\n valid: false,\n error: `name '${name}' must match directory name '${directoryName}'`,\n };\n }\n return { valid: true, error: \"\" };\n}\n\n/**\n * Validate and normalize the metadata field from YAML frontmatter.\n *\n * YAML parsing can return any type for the `metadata` key. This ensures the\n * value in {@link SkillMetadata} is always a `Record<string, string>` by\n * coercing via `String()` and rejecting non-object inputs.\n *\n * @param raw - Raw value from `frontmatterData.metadata`.\n * @param skillPath - Path to the `SKILL.md` file (for warning messages).\n * @returns A validated `Record<string, string>`.\n */\nexport function validateMetadata(\n raw: unknown,\n skillPath: string,\n): Record<string, string> {\n if (typeof raw !== \"object\" || raw === null || Array.isArray(raw)) {\n if (raw) {\n console.warn(\n `Ignoring non-object metadata in ${skillPath} (got ${typeof raw})`,\n );\n }\n return {};\n }\n const result: Record<string, string> = {};\n for (const [k, v] of Object.entries(raw)) {\n result[String(k)] = String(v);\n }\n return result;\n}\n\n/**\n * Build a parenthetical annotation string from optional skill fields.\n *\n * Combines license and compatibility into a comma-separated string for\n * display in the system prompt skill listing.\n *\n * @param skill - Skill metadata to extract annotations from.\n * @returns Annotation string like `'License: MIT, Compatibility: Python 3.10+'`,\n * or empty string if neither field is set.\n */\nexport function formatSkillAnnotations(skill: SkillMetadata): string {\n const parts: string[] = [];\n if (skill.license) {\n parts.push(`License: ${skill.license}`);\n }\n if (skill.compatibility) {\n parts.push(`Compatibility: ${skill.compatibility}`);\n }\n return parts.join(\", \");\n}\n\n/**\n * Parse YAML frontmatter from `SKILL.md` content.\n *\n * Extracts metadata per Agent Skills specification from YAML frontmatter\n * delimited by `---` markers at the start of the content.\n *\n * @param content - Content of the `SKILL.md` file\n * @param skillPath - Path to the `SKILL.md` file (for error messages and metadata)\n * @param directoryName - Name of the parent directory containing the skill\n * @returns `SkillMetadata` if parsing succeeds, `null` if parsing fails or\n * validation errors occur\n */\nexport function parseSkillMetadataFromContent(\n content: string,\n skillPath: string,\n directoryName: string,\n): SkillMetadata | null {\n if (content.length > MAX_SKILL_FILE_SIZE) {\n console.warn(\n `Skipping ${skillPath}: content too large (${content.length} bytes)`,\n );\n return null;\n }\n\n // Match YAML frontmatter between --- delimiters\n const frontmatterPattern = /^---\\s*\\n([\\s\\S]*?)\\n---\\s*\\n/;\n const match = content.match(frontmatterPattern);\n\n if (!match) {\n console.warn(`Skipping ${skillPath}: no valid YAML frontmatter found`);\n return null;\n }\n\n const frontmatterStr = match[1];\n\n // Parse YAML\n let frontmatterData: Record<string, unknown>;\n try {\n frontmatterData = yaml.parse(frontmatterStr);\n } catch (e) {\n console.warn(`Invalid YAML in ${skillPath}:`, e);\n return null;\n }\n\n if (!frontmatterData || typeof frontmatterData !== \"object\") {\n console.warn(`Skipping ${skillPath}: frontmatter is not a mapping`);\n return null;\n }\n\n // Validate required fields - coerce and strip whitespace\n const name = String(frontmatterData.name ?? \"\").trim();\n const description = String(frontmatterData.description ?? \"\").trim();\n\n if (!name || !description) {\n console.warn(\n `Skipping ${skillPath}: missing required 'name' or 'description'`,\n );\n return null;\n }\n\n // Validate name format per spec (warn but continue for backwards compatibility)\n const validation = validateSkillName(name, directoryName);\n if (!validation.valid) {\n console.warn(\n `Skill '${name}' in ${skillPath} does not follow Agent Skills specification: ${validation.error}. Consider renaming for spec compliance.`,\n );\n }\n\n // Validate description length per spec (max 1024 chars)\n let descriptionStr = description;\n if (descriptionStr.length > MAX_SKILL_DESCRIPTION_LENGTH) {\n console.warn(\n `Description exceeds ${MAX_SKILL_DESCRIPTION_LENGTH} characters in ${skillPath}, truncating`,\n );\n descriptionStr = descriptionStr.slice(0, MAX_SKILL_DESCRIPTION_LENGTH);\n }\n\n // Parse allowed-tools: support both YAML list and space-delimited string\n const rawTools = frontmatterData[\"allowed-tools\"];\n let allowedTools: string[];\n if (rawTools) {\n if (Array.isArray(rawTools)) {\n allowedTools = rawTools.map((t) => String(t).trim()).filter(Boolean);\n } else {\n // Split on whitespace (handles multiple consecutive spaces)\n allowedTools = String(rawTools).split(/\\s+/).filter(Boolean);\n }\n } else {\n allowedTools = [];\n }\n\n // Validate and truncate compatibility length\n let compatibilityStr =\n String(frontmatterData.compatibility ?? \"\").trim() || null;\n if (\n compatibilityStr &&\n compatibilityStr.length > MAX_SKILL_COMPATIBILITY_LENGTH\n ) {\n console.warn(\n `Compatibility exceeds ${MAX_SKILL_COMPATIBILITY_LENGTH} characters in ${skillPath}, truncating`,\n );\n compatibilityStr = compatibilityStr.slice(\n 0,\n MAX_SKILL_COMPATIBILITY_LENGTH,\n );\n }\n\n return {\n name,\n description: descriptionStr,\n path: skillPath,\n metadata: validateMetadata(frontmatterData.metadata ?? {}, skillPath),\n license: String(frontmatterData.license ?? \"\").trim() || null,\n compatibility: compatibilityStr,\n allowedTools,\n };\n}\n\n/**\n * List all skills from a backend source.\n */\nasync function listSkillsFromBackend(\n backend: BackendProtocol,\n sourcePath: string,\n): Promise<SkillMetadata[]> {\n const skills: SkillMetadata[] = [];\n\n // Detect path separator (Windows uses \\, Unix uses /)\n const pathSep = sourcePath.includes(\"\\\\\") ? \"\\\\\" : \"/\";\n\n // Normalize path to ensure it ends with the appropriate separator\n const normalizedPath =\n sourcePath.endsWith(\"/\") || sourcePath.endsWith(\"\\\\\")\n ? sourcePath\n : `${sourcePath}${pathSep}`;\n\n // List directories in the source path using lsInfo\n let fileInfos: { path: string; is_dir?: boolean }[];\n try {\n fileInfos = await backend.lsInfo(normalizedPath);\n } catch {\n // Source path doesn't exist or can't be listed\n return [];\n }\n\n // Convert FileInfo[] to entries format\n // Handle both forward slashes (Unix) and backslashes (Windows) in paths\n const entries = fileInfos.map((info) => ({\n name:\n info.path\n .replace(/[/\\\\]$/, \"\") // Remove trailing slash or backslash\n .split(/[/\\\\]/) // Split on either separator\n .pop() || \"\",\n type: (info.is_dir ? \"directory\" : \"file\") as \"file\" | \"directory\",\n }));\n\n // Look for subdirectories containing SKILL.md\n for (const entry of entries) {\n if (entry.type !== \"directory\") {\n continue;\n }\n\n const skillMdPath = `${normalizedPath}${entry.name}${pathSep}SKILL.md`;\n\n // Try to download the SKILL.md file\n let content: string;\n if (backend.downloadFiles) {\n const results = await backend.downloadFiles([skillMdPath]);\n if (results.length !== 1) {\n continue;\n }\n\n const response = results[0];\n if (response.error != null || response.content == null) {\n continue;\n }\n\n // Decode content\n content = new TextDecoder().decode(response.content);\n } else {\n // Fall back to read if downloadFiles is not available\n const readResult = await backend.read(skillMdPath);\n if (readResult.startsWith(\"Error:\")) {\n continue;\n }\n content = readResult;\n }\n const metadata = parseSkillMetadataFromContent(\n content,\n skillMdPath,\n entry.name,\n );\n\n if (metadata) {\n skills.push(metadata);\n }\n }\n\n return skills;\n}\n\n/**\n * Format skills locations for display in system prompt.\n * Shows priority indicator for the last source (highest priority).\n */\nfunction formatSkillsLocations(sources: string[]): string {\n if (sources.length === 0) {\n return \"**Skills Sources:** None configured\";\n }\n\n const lines: string[] = [];\n for (let i = 0; i < sources.length; i++) {\n const sourcePath = sources[i];\n // Extract a friendly name from the path (last non-empty component)\n // Handle both Unix (/) and Windows (\\) path separators\n const name =\n sourcePath\n .replace(/[/\\\\]$/, \"\")\n .split(/[/\\\\]/)\n .filter(Boolean)\n .pop()\n ?.replace(/^./, (c) => c.toUpperCase()) || \"Skills\";\n const suffix = i === sources.length - 1 ? \" (higher priority)\" : \"\";\n lines.push(`**${name} Skills**: \\`${sourcePath}\\`${suffix}`);\n }\n return lines.join(\"\\n\");\n}\n\n/**\n * Format skills metadata for display in system prompt.\n * Shows allowed tools for each skill if specified.\n */\nexport function formatSkillsList(\n skills: SkillMetadata[],\n sources: string[],\n): string {\n if (skills.length === 0) {\n const paths = sources.map((s) => `\\`${s}\\``).join(\" or \");\n return `(No skills available yet. You can create skills in ${paths})`;\n }\n\n const lines: string[] = [];\n for (const skill of skills) {\n const annotations = formatSkillAnnotations(skill);\n let descLine = `- **${skill.name}**: ${skill.description}`;\n if (annotations) {\n descLine += ` (${annotations})`;\n }\n lines.push(descLine);\n if (skill.allowedTools && skill.allowedTools.length > 0) {\n lines.push(` → Allowed tools: ${skill.allowedTools.join(\", \")}`);\n }\n lines.push(` → Read \\`${skill.path}\\` for full instructions`);\n }\n\n return lines.join(\"\\n\");\n}\n\n/**\n * Create backend-agnostic middleware for loading and exposing agent skills.\n *\n * This middleware loads skills from configurable backend sources and injects\n * skill metadata into the system prompt. It implements the progressive disclosure\n * pattern: skill names and descriptions are shown in the prompt, but the agent\n * reads full SKILL.md content only when needed.\n *\n * @param options - Configuration options\n * @returns AgentMiddleware for skills loading and injection\n *\n * @example\n * ```typescript\n * const middleware = createSkillsMiddleware({\n * backend: new FilesystemBackend({ rootDir: \"/\" }),\n * sources: [\"/skills/user/\", \"/skills/project/\"],\n * });\n * ```\n */\nexport function createSkillsMiddleware(options: SkillsMiddlewareOptions) {\n const { backend, sources } = options;\n\n // Closure variable to store loaded skills - wrapModelCall can access this\n // directly since beforeAgent state updates aren't immediately available\n let loadedSkills: SkillMetadata[] = [];\n\n /**\n * Resolve backend from instance or factory.\n */\n function getBackend(state: unknown): BackendProtocol {\n if (typeof backend === \"function\") {\n return backend({ state }) as BackendProtocol;\n }\n return backend;\n }\n\n return createMiddleware({\n name: \"SkillsMiddleware\",\n stateSchema: SkillsStateSchema,\n\n async beforeAgent(state) {\n // Skip if already loaded (check both closure and state)\n if (loadedSkills.length > 0) {\n return undefined;\n }\n // Check if skills were restored from checkpoint (non-empty array in state)\n if (\n \"skillsMetadata\" in state &&\n Array.isArray(state.skillsMetadata) &&\n state.skillsMetadata.length > 0\n ) {\n // Restore from state (e.g., after checkpoint restore)\n loadedSkills = state.skillsMetadata as SkillMetadata[];\n return undefined;\n }\n\n const resolvedBackend = getBackend(state);\n const allSkills: Map<string, SkillMetadata> = new Map();\n\n // Load skills from each source in order (later sources override earlier)\n for (const sourcePath of sources) {\n try {\n const skills = await listSkillsFromBackend(\n resolvedBackend,\n sourcePath,\n );\n for (const skill of skills) {\n allSkills.set(skill.name, skill);\n }\n } catch (error) {\n // Log but continue - individual source failures shouldn't break everything\n console.debug(\n `[BackendSkillsMiddleware] Failed to load skills from ${sourcePath}:`,\n error,\n );\n }\n }\n\n // Store in closure for immediate access by wrapModelCall\n loadedSkills = Array.from(allSkills.values());\n\n return { skillsMetadata: loadedSkills };\n },\n\n wrapModelCall(request, handler) {\n // Use closure variable which is populated by beforeAgent\n // Fall back to state for checkpoint restore scenarios\n const skillsMetadata: SkillMetadata[] =\n loadedSkills.length > 0\n ? loadedSkills\n : (request.state?.skillsMetadata as SkillMetadata[]) || [];\n\n // Format skills section\n const skillsLocations = formatSkillsLocations(sources);\n const skillsList = formatSkillsList(skillsMetadata, sources);\n\n const skillsSection = SKILLS_SYSTEM_PROMPT.replace(\n \"{skills_locations}\",\n skillsLocations,\n ).replace(\"{skills_list}\", skillsList);\n\n // Combine with existing system message\n const newSystemMessage = request.systemMessage.concat(skillsSection);\n\n return handler({ ...request, systemMessage: newSystemMessage });\n },\n });\n}\n","/**\n * Utility functions for middleware.\n *\n * This module provides shared helpers used across middleware implementations.\n */\n\nimport { SystemMessage } from \"@langchain/core/messages\";\n\n/**\n * Append text to a system message.\n *\n * Creates a new SystemMessage with the text appended to the existing content.\n * If the original message has content, the new text is separated by two newlines.\n *\n * @param systemMessage - Existing system message or null/undefined.\n * @param text - Text to add to the system message.\n * @returns New SystemMessage with the text appended.\n *\n * @example\n * ```typescript\n * const original = new SystemMessage({ content: \"You are a helpful assistant.\" });\n * const updated = appendToSystemMessage(original, \"Always be concise.\");\n * // Result: SystemMessage with content \"You are a helpful assistant.\\n\\nAlways be concise.\"\n * ```\n */\nexport function appendToSystemMessage(\n systemMessage: SystemMessage | null | undefined,\n text: string,\n): SystemMessage {\n if (!systemMessage) {\n return new SystemMessage({ content: text });\n }\n\n // Handle both string and array content formats\n const existingContent = systemMessage.content;\n\n if (typeof existingContent === \"string\") {\n const newContent = existingContent ? `${existingContent}\\n\\n${text}` : text;\n return new SystemMessage({ content: newContent });\n }\n\n // For array content (content blocks), append as a new text block\n if (Array.isArray(existingContent)) {\n const newContent = [...existingContent];\n const textToAdd = newContent.length > 0 ? `\\n\\n${text}` : text;\n newContent.push({ type: \"text\", text: textToAdd });\n return new SystemMessage({ content: newContent });\n }\n\n // Fallback for unknown content type\n return new SystemMessage({ content: text });\n}\n\n/**\n * Prepend text to a system message.\n *\n * Creates a new SystemMessage with the text prepended to the existing content.\n * If the original message has content, the new text is separated by two newlines.\n *\n * @param systemMessage - Existing system message or null/undefined.\n * @param text - Text to prepend to the system message.\n * @returns New SystemMessage with the text prepended.\n *\n * @example\n * ```typescript\n * const original = new SystemMessage({ content: \"Always be concise.\" });\n * const updated = prependToSystemMessage(original, \"You are a helpful assistant.\");\n * // Result: SystemMessage with content \"You are a helpful assistant.\\n\\nAlways be concise.\"\n * ```\n */\nexport function prependToSystemMessage(\n systemMessage: SystemMessage | null | undefined,\n text: string,\n): SystemMessage {\n if (!systemMessage) {\n return new SystemMessage({ content: text });\n }\n\n // Handle both string and array content formats\n const existingContent = systemMessage.content;\n\n if (typeof existingContent === \"string\") {\n const newContent = existingContent ? `${text}\\n\\n${existingContent}` : text;\n return new SystemMessage({ content: newContent });\n }\n\n // For array content (content blocks), prepend as a new text block\n if (Array.isArray(existingContent)) {\n const textToAdd = existingContent.length > 0 ? `${text}\\n\\n` : text;\n const newContent = [{ type: \"text\", text: textToAdd }, ...existingContent];\n return new SystemMessage({ content: newContent });\n }\n\n // Fallback for unknown content type\n return new SystemMessage({ content: text });\n}\n","/**\n * Summarization middleware with backend support for conversation history offloading.\n *\n * This module extends the base LangChain summarization middleware with additional\n * backend-based features for persisting conversation history before summarization.\n *\n * ## Usage\n *\n * ```typescript\n * import { createSummarizationMiddleware } from \"@anthropic/deepagents\";\n * import { FilesystemBackend } from \"@anthropic/deepagents\";\n *\n * const backend = new FilesystemBackend({ rootDir: \"/data\" });\n *\n * const middleware = createSummarizationMiddleware({\n * model: \"gpt-4o-mini\",\n * backend,\n * trigger: { type: \"fraction\", value: 0.85 },\n * keep: { type: \"fraction\", value: 0.10 },\n * });\n *\n * const agent = createDeepAgent({ middleware: [middleware] });\n * ```\n *\n * ## Storage\n *\n * Offloaded messages are stored as markdown at `/conversation_history/{thread_id}.md`.\n *\n * Each summarization event appends a new section to this file, creating a running log\n * of all evicted messages.\n *\n * ## Relationship to LangChain Summarization Middleware\n *\n * The base `summarizationMiddleware` from `langchain` provides core summarization\n * functionality. This middleware adds:\n * - Backend-based conversation history offloading\n * - Tool argument truncation for old messages\n *\n * For simple use cases without backend offloading, use `summarizationMiddleware`\n * from `langchain` directly.\n */\n\nimport { z } from \"zod\";\nimport { v4 as uuidv4 } from \"uuid\";\nimport {\n createMiddleware,\n countTokensApproximately,\n HumanMessage,\n AIMessage,\n ToolMessage,\n SystemMessage,\n BaseMessage,\n type AgentMiddleware as _AgentMiddleware,\n} from \"langchain\";\nimport { getBufferString } from \"@langchain/core/messages\";\nimport type { BaseChatModel } from \"@langchain/core/language_models/chat_models\";\nimport type { BaseLanguageModel } from \"@langchain/core/language_models/base\";\nimport type { ClientTool, ServerTool } from \"@langchain/core/tools\";\nimport { ContextOverflowError } from \"@langchain/core/errors\";\nimport { initChatModel } from \"langchain/chat_models/universal\";\nimport { Command } from \"@langchain/langgraph\";\n\nimport type { BackendProtocol, BackendFactory } from \"../backends/protocol.js\";\nimport type { StateBackend } from \"../backends/state.js\";\nimport type { BaseStore } from \"@langchain/langgraph-checkpoint\";\n\n// Re-export the base summarization middleware from langchain for users who don't need backend offloading\nexport { summarizationMiddleware } from \"langchain\";\n\n/**\n * Context size specification for summarization triggers and retention policies.\n */\nexport interface ContextSize {\n /** Type of context measurement */\n type: \"messages\" | \"tokens\" | \"fraction\";\n /** Threshold value */\n value: number;\n}\n\n/**\n * Settings for truncating large tool arguments in old messages.\n */\nexport interface TruncateArgsSettings {\n /**\n * Threshold to trigger argument truncation.\n * If not provided, truncation is disabled.\n */\n trigger?: ContextSize;\n\n /**\n * Context retention policy for message truncation.\n * Defaults to keeping last 20 messages.\n */\n keep?: ContextSize;\n\n /**\n * Maximum character length for tool arguments before truncation.\n * Defaults to 2000.\n */\n maxLength?: number;\n\n /**\n * Text to replace truncated arguments with.\n * Defaults to \"...(argument truncated)\".\n */\n truncationText?: string;\n}\n\n/**\n * Options for the summarization middleware.\n */\nexport interface SummarizationMiddlewareOptions {\n /**\n * The language model to use for generating summaries.\n * Can be a model string (e.g., \"gpt-4o-mini\") or a language model instance.\n */\n model: string | BaseChatModel | BaseLanguageModel;\n\n /**\n * Backend instance or factory for persisting conversation history.\n */\n backend:\n | BackendProtocol\n | BackendFactory\n | ((config: { state: unknown; store?: BaseStore }) => StateBackend);\n\n /**\n * Threshold(s) that trigger summarization.\n * Can be a single ContextSize or an array for multiple triggers.\n */\n trigger?: ContextSize | ContextSize[];\n\n /**\n * Context retention policy after summarization.\n * Defaults to keeping last 20 messages.\n */\n keep?: ContextSize;\n\n /**\n * Prompt template for generating summaries.\n */\n summaryPrompt?: string;\n\n /**\n * Max tokens to include when generating summary.\n * Defaults to 4000.\n */\n trimTokensToSummarize?: number;\n\n /**\n * Path prefix for storing conversation history.\n * Defaults to \"/conversation_history\".\n */\n historyPathPrefix?: string;\n\n /**\n * Settings for truncating large tool arguments in old messages.\n * If not provided, argument truncation is disabled.\n */\n truncateArgsSettings?: TruncateArgsSettings;\n}\n\n// Default values\nconst DEFAULT_MESSAGES_TO_KEEP = 20;\nconst DEFAULT_TRIM_TOKEN_LIMIT = 4000;\n\n// Fallback defaults when model has no profile (matches Python's fallback)\nconst FALLBACK_TRIGGER: ContextSize = { type: \"tokens\", value: 170_000 };\nconst FALLBACK_KEEP: ContextSize = { type: \"messages\", value: 6 };\nconst FALLBACK_TRUNCATE_ARGS: TruncateArgsSettings = {\n trigger: { type: \"messages\", value: 20 },\n keep: { type: \"messages\", value: 20 },\n};\n\n// Profile-based defaults (when model has max_input_tokens in profile)\nconst PROFILE_TRIGGER: ContextSize = { type: \"fraction\", value: 0.85 };\nconst PROFILE_KEEP: ContextSize = { type: \"fraction\", value: 0.1 };\nconst PROFILE_TRUNCATE_ARGS: TruncateArgsSettings = {\n trigger: { type: \"fraction\", value: 0.85 },\n keep: { type: \"fraction\", value: 0.1 },\n};\n\n/**\n * Compute summarization defaults based on model profile.\n * Mirrors Python's `_compute_summarization_defaults`.\n *\n * If the model has a profile with `maxInputTokens`, uses fraction-based\n * settings. Otherwise, uses fixed token/message counts.\n *\n * @param resolvedModel - The resolved chat model instance.\n */\nexport function computeSummarizationDefaults(resolvedModel: BaseChatModel): {\n trigger: ContextSize;\n keep: ContextSize;\n truncateArgsSettings: TruncateArgsSettings;\n} {\n const hasProfile =\n resolvedModel.profile &&\n typeof resolvedModel.profile === \"object\" &&\n \"maxInputTokens\" in resolvedModel.profile &&\n typeof resolvedModel.profile.maxInputTokens === \"number\";\n\n if (hasProfile) {\n return {\n trigger: PROFILE_TRIGGER,\n keep: PROFILE_KEEP,\n truncateArgsSettings: PROFILE_TRUNCATE_ARGS,\n };\n }\n\n return {\n trigger: FALLBACK_TRIGGER,\n keep: FALLBACK_KEEP,\n truncateArgsSettings: FALLBACK_TRUNCATE_ARGS,\n };\n}\nconst DEFAULT_SUMMARY_PROMPT = `You are a conversation summarizer. Your task is to create a concise summary of the conversation that captures:\n1. The main topics discussed\n2. Key decisions or conclusions reached\n3. Any important context that would be needed for continuing the conversation\n\nKeep the summary focused and informative. Do not include unnecessary details.\n\nConversation to summarize:\n{conversation}\n\nSummary:`;\n\n/**\n * Zod schema for a summarization event that tracks what was summarized and\n * where the cutoff is.\n *\n * Instead of rewriting LangGraph state with `RemoveMessage(REMOVE_ALL_MESSAGES)`,\n * the middleware stores this event and uses it to reconstruct the effective message\n * list on subsequent calls.\n */\nconst SummarizationEventSchema = z.object({\n /**\n * The index in the state messages list where summarization occurred.\n * Messages before this index have been summarized. */\n cutoffIndex: z.number(),\n /** The HumanMessage containing the summary. */\n summaryMessage: z.instanceof(HumanMessage),\n /** Path where the conversation history was offloaded, or null if offload failed. */\n filePath: z.string().nullable(),\n});\n\n/**\n * Represents a summarization event that tracks what was summarized and where the cutoff is.\n */\nexport type SummarizationEvent = z.infer<typeof SummarizationEventSchema>;\n\n/**\n * State schema for summarization middleware.\n */\nconst SummarizationStateSchema = z.object({\n /** Session ID for history file naming */\n _summarizationSessionId: z.string().optional(),\n /** Most recent summarization event (private state, not visible to agent) */\n _summarizationEvent: SummarizationEventSchema.optional(),\n});\n\n/**\n * Check if a message is a previous summarization message.\n * Summary messages are HumanMessage objects with lc_source='summarization' in additional_kwargs.\n */\nfunction isSummaryMessage(msg: BaseMessage): boolean {\n if (!HumanMessage.isInstance(msg)) {\n return false;\n }\n return msg.additional_kwargs?.lc_source === \"summarization\";\n}\n\n/**\n * Create summarization middleware with backend support for conversation history offloading.\n *\n * This middleware:\n * 1. Monitors conversation length against configured thresholds\n * 2. When triggered, offloads old messages to backend storage\n * 3. Generates a summary of offloaded messages\n * 4. Replaces old messages with the summary, preserving recent context\n *\n * @param options - Configuration options\n * @returns AgentMiddleware for summarization and history offloading\n */\nexport function createSummarizationMiddleware(\n options: SummarizationMiddlewareOptions,\n) {\n const {\n model,\n backend,\n summaryPrompt = DEFAULT_SUMMARY_PROMPT,\n trimTokensToSummarize = DEFAULT_TRIM_TOKEN_LIMIT,\n historyPathPrefix = \"/conversation_history\",\n } = options;\n\n // Mutable config that may be lazily computed from model profile.\n // When trigger/keep/truncateArgsSettings are not provided, they will be\n // computed from the model profile on first wrapModelCall, matching\n // Python's `_compute_summarization_defaults` behavior.\n let trigger = options.trigger;\n let keep: ContextSize = options.keep ?? {\n type: \"messages\",\n value: DEFAULT_MESSAGES_TO_KEEP,\n };\n let truncateArgsSettings = options.truncateArgsSettings;\n let defaultsComputed = trigger != null;\n\n // Parse truncate settings (will be re-parsed after defaults are computed)\n let truncateTrigger = truncateArgsSettings?.trigger;\n let truncateKeep: ContextSize = truncateArgsSettings?.keep ?? {\n type: \"messages\" as const,\n value: 20,\n };\n let maxArgLength = truncateArgsSettings?.maxLength ?? 2000;\n let truncationText =\n truncateArgsSettings?.truncationText ?? \"...(argument truncated)\";\n\n /**\n * Lazily compute defaults from model profile when trigger was not provided.\n * Called once when the model is first resolved.\n */\n function applyModelDefaults(resolvedModel: BaseChatModel): void {\n if (defaultsComputed) {\n return;\n }\n defaultsComputed = true;\n\n const defaults = computeSummarizationDefaults(resolvedModel);\n\n trigger = defaults.trigger;\n keep = options.keep ?? defaults.keep;\n\n if (!options.truncateArgsSettings) {\n truncateArgsSettings = defaults.truncateArgsSettings;\n truncateTrigger = defaults.truncateArgsSettings.trigger;\n truncateKeep = defaults.truncateArgsSettings.keep ?? {\n type: \"messages\" as const,\n value: 20,\n };\n maxArgLength = defaults.truncateArgsSettings.maxLength ?? 2000;\n truncationText =\n defaults.truncateArgsSettings.truncationText ??\n \"...(argument truncated)\";\n }\n }\n\n // Session ID for this middleware instance (fallback if no thread_id)\n let sessionId: string | null = null;\n\n // Calibration multiplier for token estimation. countTokensApproximately\n // can significantly undercount (e.g. it ignores tool_use content blocks,\n // JSON structural overhead). After a ContextOverflowError we learn the\n // gap between estimated and actual tokens and adjust future comparisons\n // so proactive summarization fires before the hard limit is hit.\n let tokenEstimationMultiplier = 1.0;\n\n /**\n * Resolve backend from instance or factory.\n */\n function getBackend(state: unknown): BackendProtocol {\n if (typeof backend === \"function\") {\n return backend({ state }) as BackendProtocol;\n }\n return backend;\n }\n\n /**\n * Get or create session ID for history file naming.\n */\n function getSessionId(state: Record<string, unknown>): string {\n if (state._summarizationSessionId) {\n return state._summarizationSessionId as string;\n }\n if (!sessionId) {\n sessionId = `session_${uuidv4().substring(0, 8)}`;\n }\n return sessionId;\n }\n\n /**\n * Get the history file path.\n */\n function getHistoryPath(state: Record<string, unknown>): string {\n const id = getSessionId(state);\n return `${historyPathPrefix}/${id}.md`;\n }\n\n /**\n * Cached resolved model to avoid repeated initChatModel calls\n */\n let cachedModel: BaseChatModel | undefined = undefined;\n\n /**\n * Resolve the chat model.\n * Uses initChatModel to support any model provider from a string name.\n * The resolved model is cached for subsequent calls.\n */\n async function getChatModel(): Promise<BaseChatModel> {\n if (cachedModel) {\n return cachedModel;\n }\n\n if (typeof model === \"string\") {\n cachedModel = await initChatModel(model);\n } else {\n cachedModel = model as BaseChatModel;\n }\n return cachedModel;\n }\n\n /**\n * Get the max input tokens from the model's profile.\n * Similar to Python's _get_profile_limits.\n *\n * When the profile is unavailable, returns undefined. In that case the\n * middleware uses fixed token/message-count fallback defaults for\n * trigger/keep, and relies on the ContextOverflowError catch as a\n * safety net if the prompt still exceeds the model's actual limit.\n */\n function getMaxInputTokens(resolvedModel: BaseChatModel): number | undefined {\n const profile = resolvedModel.profile;\n if (\n profile &&\n typeof profile === \"object\" &&\n \"maxInputTokens\" in profile &&\n typeof profile.maxInputTokens === \"number\"\n ) {\n return profile.maxInputTokens;\n }\n return undefined;\n }\n\n /**\n * Check if summarization should be triggered.\n */\n function shouldSummarize(\n messages: BaseMessage[],\n totalTokens: number,\n maxInputTokens?: number,\n ): boolean {\n if (!trigger) {\n return false;\n }\n\n const adjustedTokens = totalTokens * tokenEstimationMultiplier;\n const triggers = Array.isArray(trigger) ? trigger : [trigger];\n\n for (const t of triggers) {\n if (t.type === \"messages\" && messages.length >= t.value) {\n return true;\n }\n if (t.type === \"tokens\" && adjustedTokens >= t.value) {\n return true;\n }\n if (t.type === \"fraction\" && maxInputTokens) {\n const threshold = Math.floor(maxInputTokens * t.value);\n if (adjustedTokens >= threshold) {\n return true;\n }\n }\n }\n\n return false;\n }\n\n /**\n * Find a safe cutoff point that doesn't split AI/Tool message pairs.\n *\n * If the message at `cutoffIndex` is a ToolMessage, this adjusts the boundary\n * so that related AI and Tool messages stay together. Two strategies are used:\n *\n * 1. **Move backward** to include the AIMessage that produced the tool calls,\n * keeping the pair in the preserved set. Preferred when it doesn't move\n * the cutoff too far back.\n *\n * 2. **Advance forward** past all consecutive ToolMessages, putting the entire\n * pair into the summarized set. Used when moving backward would preserve\n * too many messages (e.g., a single AIMessage made 20+ tool calls).\n */\n function findSafeCutoffPoint(\n messages: BaseMessage[],\n cutoffIndex: number,\n ): number {\n if (\n cutoffIndex >= messages.length ||\n !ToolMessage.isInstance(messages[cutoffIndex])\n ) {\n return cutoffIndex;\n }\n\n // Advance past all consecutive ToolMessages at the cutoff point\n let forwardIdx = cutoffIndex;\n while (\n forwardIdx < messages.length &&\n ToolMessage.isInstance(messages[forwardIdx])\n ) {\n forwardIdx++;\n }\n\n // Collect tool_call_ids from the ToolMessages at the cutoff boundary\n const toolCallIds = new Set<string>();\n for (let i = cutoffIndex; i < forwardIdx; i++) {\n const toolMsg = messages[i] as InstanceType<typeof ToolMessage>;\n if (toolMsg.tool_call_id) {\n toolCallIds.add(toolMsg.tool_call_id);\n }\n }\n\n // Search backward for AIMessage with matching tool_calls\n let backwardIdx: number | null = null;\n for (let i = cutoffIndex - 1; i >= 0; i--) {\n const msg = messages[i];\n if (AIMessage.isInstance(msg) && msg.tool_calls) {\n const aiToolCallIds = new Set(\n msg.tool_calls\n .map((tc) => tc.id)\n .filter((id): id is string => id != null),\n );\n for (const id of toolCallIds) {\n if (aiToolCallIds.has(id)) {\n backwardIdx = i;\n break;\n }\n }\n if (backwardIdx !== null) break;\n }\n }\n\n if (backwardIdx === null) {\n // No matching AIMessage found - advance forward past ToolMessages\n return forwardIdx;\n }\n\n // Choose strategy: prefer backward (preserves more context) unless it\n // would move the cutoff back by more than half the original position,\n // which indicates a single AIMessage with many tool calls that would\n // defeat the purpose of summarization.\n const backwardDistance = cutoffIndex - backwardIdx;\n if (backwardDistance > cutoffIndex / 2 && cutoffIndex > 2) {\n return forwardIdx;\n }\n\n return backwardIdx;\n }\n\n /**\n * Determine cutoff index for messages to summarize.\n * Messages at index < cutoff will be summarized.\n * Messages at index >= cutoff will be preserved.\n *\n * Uses findSafeCutoffPoint to ensure tool call/result pairs stay together.\n */\n function determineCutoffIndex(\n messages: BaseMessage[],\n maxInputTokens?: number,\n ): number {\n let rawCutoff: number;\n\n if (keep.type === \"messages\") {\n if (messages.length <= keep.value) {\n return 0;\n }\n rawCutoff = messages.length - keep.value;\n } else if (keep.type === \"tokens\" || keep.type === \"fraction\") {\n const targetTokenCount =\n keep.type === \"fraction\" && maxInputTokens\n ? Math.floor(maxInputTokens * keep.value)\n : keep.value;\n\n let tokensKept = 0;\n rawCutoff = 0;\n for (let i = messages.length - 1; i >= 0; i--) {\n const msgTokens = countTokensApproximately([messages[i]]);\n if (tokensKept + msgTokens > targetTokenCount) {\n rawCutoff = i + 1;\n break;\n }\n tokensKept += msgTokens;\n }\n } else {\n return 0;\n }\n\n return findSafeCutoffPoint(messages, rawCutoff);\n }\n\n /**\n * Check if argument truncation should be triggered.\n */\n function shouldTruncateArgs(\n messages: BaseMessage[],\n totalTokens: number,\n maxInputTokens?: number,\n ): boolean {\n if (!truncateTrigger) {\n return false;\n }\n\n const adjustedTokens = totalTokens * tokenEstimationMultiplier;\n if (truncateTrigger.type === \"messages\") {\n return messages.length >= truncateTrigger.value;\n }\n if (truncateTrigger.type === \"tokens\") {\n return adjustedTokens >= truncateTrigger.value;\n }\n if (truncateTrigger.type === \"fraction\" && maxInputTokens) {\n const threshold = Math.floor(maxInputTokens * truncateTrigger.value);\n return adjustedTokens >= threshold;\n }\n\n return false;\n }\n\n /**\n * Determine cutoff index for argument truncation.\n * Uses findSafeCutoffPoint to ensure tool call/result pairs stay together.\n */\n function determineTruncateCutoffIndex(\n messages: BaseMessage[],\n maxInputTokens?: number,\n ): number {\n let rawCutoff: number;\n\n if (truncateKeep.type === \"messages\") {\n if (messages.length <= truncateKeep.value) {\n return messages.length;\n }\n rawCutoff = messages.length - truncateKeep.value;\n } else if (\n truncateKeep.type === \"tokens\" ||\n truncateKeep.type === \"fraction\"\n ) {\n const targetTokenCount =\n truncateKeep.type === \"fraction\" && maxInputTokens\n ? Math.floor(maxInputTokens * truncateKeep.value)\n : truncateKeep.value;\n\n let tokensKept = 0;\n rawCutoff = 0;\n for (let i = messages.length - 1; i >= 0; i--) {\n const msgTokens = countTokensApproximately([messages[i]]);\n if (tokensKept + msgTokens > targetTokenCount) {\n rawCutoff = i + 1;\n break;\n }\n tokensKept += msgTokens;\n }\n } else {\n return messages.length;\n }\n\n return findSafeCutoffPoint(messages, rawCutoff);\n }\n\n /**\n * Count tokens including system message and tools, matching Python's approach.\n * This gives a more accurate picture of what actually gets sent to the model.\n */\n function countTotalTokens(\n messages: BaseMessage[],\n systemMessage?: SystemMessage | unknown,\n tools?: (ServerTool | ClientTool)[] | unknown[],\n ): number {\n const countedMessages: BaseMessage[] =\n systemMessage && SystemMessage.isInstance(systemMessage)\n ? [systemMessage as SystemMessage, ...messages]\n : [...messages];\n\n const toolsArray =\n tools && Array.isArray(tools) && tools.length > 0\n ? (tools as Array<Record<string, unknown>>)\n : null;\n\n return countTokensApproximately(countedMessages, toolsArray);\n }\n\n /**\n * Truncate ToolMessage content so that the total payload fits within the\n * model's context window. Each ToolMessage gets an equal share of the\n * remaining token budget after accounting for non-tool messages, system\n * message, and tool schemas.\n *\n * This is critical for conversations where a single AIMessage triggers\n * many tool calls whose results collectively exceed the context window.\n * Without this, findSafeCutoffPoint cannot split the AI/Tool group and\n * summarization would discard everything, causing the model to re-call\n * the same tools in an infinite loop.\n */\n function compactToolResults(\n messages: BaseMessage[],\n maxInputTokens: number,\n systemMessage?: SystemMessage | unknown,\n tools?: (ServerTool | ClientTool)[] | unknown[],\n ): { messages: BaseMessage[]; modified: boolean } {\n const toolMessageIndices: number[] = [];\n for (let i = 0; i < messages.length; i++) {\n if (ToolMessage.isInstance(messages[i])) {\n toolMessageIndices.push(i);\n }\n }\n if (toolMessageIndices.length === 0) {\n return { messages, modified: false };\n }\n\n const nonToolMessages = messages.filter((m) => !ToolMessage.isInstance(m));\n const overheadTokens = countTotalTokens(\n nonToolMessages,\n systemMessage,\n tools,\n );\n\n // Target: fit within maxInputTokens / multiplier, leaving 30% headroom\n const adjustedMax = maxInputTokens / tokenEstimationMultiplier;\n const budgetForTools = Math.max(adjustedMax * 0.7 - overheadTokens, 1000);\n const perToolBudgetTokens = Math.floor(\n budgetForTools / toolMessageIndices.length,\n );\n const perToolBudgetChars = perToolBudgetTokens * 4;\n\n let modified = false;\n const result = [...messages];\n\n for (const idx of toolMessageIndices) {\n const msg = messages[idx] as InstanceType<typeof ToolMessage>;\n const content =\n typeof msg.content === \"string\"\n ? msg.content\n : JSON.stringify(msg.content);\n\n if (content.length > perToolBudgetChars) {\n result[idx] = new ToolMessage({\n content:\n content.substring(0, perToolBudgetChars) +\n \"\\n...(result truncated)\",\n tool_call_id: msg.tool_call_id,\n name: msg.name,\n });\n modified = true;\n }\n }\n\n return { messages: result, modified };\n }\n\n /**\n * Truncate large tool arguments in old messages.\n */\n function truncateArgs(\n messages: BaseMessage[],\n maxInputTokens?: number,\n systemMessage?: SystemMessage | unknown,\n tools?: (ServerTool | ClientTool)[] | unknown[],\n ): { messages: BaseMessage[]; modified: boolean } {\n const totalTokens = countTotalTokens(messages, systemMessage, tools);\n if (!shouldTruncateArgs(messages, totalTokens, maxInputTokens)) {\n return { messages, modified: false };\n }\n\n const cutoffIndex = determineTruncateCutoffIndex(messages, maxInputTokens);\n if (cutoffIndex >= messages.length) {\n return { messages, modified: false };\n }\n\n const truncatedMessages: BaseMessage[] = [];\n let modified = false;\n\n for (let i = 0; i < messages.length; i++) {\n const msg = messages[i];\n\n if (i < cutoffIndex && AIMessage.isInstance(msg) && msg.tool_calls) {\n const truncatedToolCalls = msg.tool_calls.map((toolCall) => {\n const args = toolCall.args || {};\n const truncatedArgs: Record<string, unknown> = {};\n let toolModified = false;\n\n for (const [key, value] of Object.entries(args)) {\n if (\n typeof value === \"string\" &&\n value.length > maxArgLength &&\n (toolCall.name === \"write_file\" || toolCall.name === \"edit_file\")\n ) {\n truncatedArgs[key] = value.substring(0, 20) + truncationText;\n toolModified = true;\n } else {\n truncatedArgs[key] = value;\n }\n }\n\n if (toolModified) {\n modified = true;\n return { ...toolCall, args: truncatedArgs };\n }\n return toolCall;\n });\n\n if (modified) {\n const truncatedMsg = new AIMessage({\n content: msg.content,\n tool_calls: truncatedToolCalls,\n additional_kwargs: msg.additional_kwargs,\n });\n truncatedMessages.push(truncatedMsg);\n } else {\n truncatedMessages.push(msg);\n }\n } else {\n truncatedMessages.push(msg);\n }\n }\n\n return { messages: truncatedMessages, modified };\n }\n\n /**\n * Filter out previous summary messages.\n */\n function filterSummaryMessages(messages: BaseMessage[]): BaseMessage[] {\n return messages.filter((msg) => !isSummaryMessage(msg));\n }\n\n /**\n * Offload messages to backend.\n */\n async function offloadToBackend(\n resolvedBackend: BackendProtocol,\n messages: BaseMessage[],\n state: Record<string, unknown>,\n ): Promise<string | null> {\n const path = getHistoryPath(state);\n const filteredMessages = filterSummaryMessages(messages);\n\n const timestamp = new Date().toISOString();\n const newSection = `## Summarized at ${timestamp}\\n\\n${getBufferString(filteredMessages)}\\n\\n`;\n\n // Read existing content\n let existingContent = \"\";\n try {\n if (resolvedBackend.downloadFiles) {\n const responses = await resolvedBackend.downloadFiles([path]);\n if (\n responses.length > 0 &&\n responses[0].content &&\n !responses[0].error\n ) {\n existingContent = new TextDecoder().decode(responses[0].content);\n }\n }\n } catch {\n // File doesn't exist yet, that's fine\n }\n\n const combinedContent = existingContent + newSection;\n\n try {\n let result;\n if (existingContent) {\n result = await resolvedBackend.edit(\n path,\n existingContent,\n combinedContent,\n );\n } else {\n result = await resolvedBackend.write(path, combinedContent);\n }\n\n if (result.error) {\n // eslint-disable-next-line no-console\n console.warn(\n `Failed to offload conversation history to ${path}: ${result.error}`,\n );\n return null;\n }\n\n return path;\n } catch (e) {\n // eslint-disable-next-line no-console\n console.warn(`Exception offloading conversation history to ${path}:`, e);\n return null;\n }\n }\n\n /**\n * Create summary of messages.\n */\n async function createSummary(\n messages: BaseMessage[],\n chatModel: BaseChatModel,\n ): Promise<string> {\n // Trim messages if too long\n let messagesToSummarize = messages;\n const tokens = countTokensApproximately(messages);\n if (tokens > trimTokensToSummarize) {\n // Keep only recent messages that fit\n let kept = 0;\n const trimmedMessages: BaseMessage[] = [];\n for (let i = messages.length - 1; i >= 0; i--) {\n const msgTokens = countTokensApproximately([messages[i]]);\n if (kept + msgTokens > trimTokensToSummarize) {\n break;\n }\n trimmedMessages.unshift(messages[i]);\n kept += msgTokens;\n }\n messagesToSummarize = trimmedMessages;\n }\n\n const conversation = getBufferString(messagesToSummarize);\n const prompt = summaryPrompt.replace(\"{conversation}\", conversation);\n\n const response = await chatModel.invoke([\n new HumanMessage({ content: prompt }),\n ]);\n\n return typeof response.content === \"string\"\n ? response.content\n : JSON.stringify(response.content);\n }\n\n /**\n * Build the summary message with file path reference.\n */\n function buildSummaryMessage(\n summary: string,\n filePath: string | null,\n ): HumanMessage {\n let content: string;\n if (filePath) {\n content = `You are in the middle of a conversation that has been summarized.\n\nThe full conversation history has been saved to ${filePath} should you need to refer back to it for details.\n\nA condensed summary follows:\n\n<summary>\n${summary}\n</summary>`;\n } else {\n content = `Here is a summary of the conversation to date:\\n\\n${summary}`;\n }\n\n return new HumanMessage({\n content,\n additional_kwargs: { lc_source: \"summarization\" },\n });\n }\n\n /**\n * Reconstruct the effective message list based on any previous summarization event.\n *\n * After summarization, instead of using all messages from state, we use the summary\n * message plus messages after the cutoff index. This avoids full state rewrites.\n */\n function getEffectiveMessages(\n messages: BaseMessage[],\n state: Record<string, unknown>,\n ): BaseMessage[] {\n const event = state._summarizationEvent as SummarizationEvent | undefined;\n\n // If no summarization event, return all messages as-is\n if (!event) {\n return messages;\n }\n\n // Build effective messages: summary message, then messages from cutoff onward\n const result: BaseMessage[] = [event.summaryMessage];\n result.push(...messages.slice(event.cutoffIndex));\n\n return result;\n }\n\n /**\n * Summarize a set of messages using the given model and build the\n * summary message + backend offload. Returns the summary message,\n * the file path, and the state cutoff index.\n */\n async function summarizeMessages(\n messagesToSummarize: BaseMessage[],\n resolvedModel: BaseChatModel,\n state: Record<string, unknown>,\n previousCutoffIndex: number | undefined,\n cutoffIndex: number,\n ): Promise<{\n summaryMessage: HumanMessage;\n filePath: string | null;\n stateCutoffIndex: number;\n }> {\n const resolvedBackend = getBackend(state);\n const filePath = await offloadToBackend(\n resolvedBackend,\n messagesToSummarize,\n state,\n );\n\n if (filePath === null) {\n // eslint-disable-next-line no-console\n console.warn(\n `[SummarizationMiddleware] Backend offload failed during summarization. Proceeding with summary generation.`,\n );\n }\n\n const summary = await createSummary(messagesToSummarize, resolvedModel);\n const summaryMessage = buildSummaryMessage(summary, filePath);\n\n const stateCutoffIndex =\n previousCutoffIndex != null\n ? previousCutoffIndex + cutoffIndex - 1\n : cutoffIndex;\n\n return { summaryMessage, filePath, stateCutoffIndex };\n }\n\n /**\n * Check if an error (possibly wrapped in MiddlewareError layers) is a\n * ContextOverflowError by walking the `cause` chain.\n */\n function isContextOverflow(err: unknown): boolean {\n let cause: unknown = err;\n while (cause != null) {\n if (ContextOverflowError.isInstance(cause)) {\n return true;\n }\n cause =\n typeof cause === \"object\" && cause !== null && \"cause\" in cause\n ? (cause as { cause?: unknown }).cause\n : undefined;\n }\n return false;\n }\n\n async function performSummarization(\n request: {\n messages: BaseMessage[];\n state: Record<string, unknown>;\n systemMessage?: SystemMessage | unknown;\n tools?: (ServerTool | ClientTool)[] | unknown[];\n [key: string]: unknown;\n },\n handler: (req: any) => any,\n truncatedMessages: BaseMessage[],\n resolvedModel: BaseChatModel,\n maxInputTokens: number | undefined,\n ): Promise<any> {\n const cutoffIndex = determineCutoffIndex(truncatedMessages, maxInputTokens);\n if (cutoffIndex <= 0) {\n return handler({ ...request, messages: truncatedMessages });\n }\n\n const messagesToSummarize = truncatedMessages.slice(0, cutoffIndex);\n const preservedMessages = truncatedMessages.slice(cutoffIndex);\n\n // When ALL messages would be summarized (preserving 0), the model loses\n // all tool call context and re-invokes the same tools, creating an\n // infinite loop. Instead, try truncating ToolMessage content so the\n // entire AI/Tool group fits in context without summarization.\n if (preservedMessages.length === 0 && maxInputTokens) {\n const compact = compactToolResults(\n truncatedMessages,\n maxInputTokens,\n request.systemMessage,\n request.tools,\n );\n\n if (compact.modified) {\n try {\n return await handler({\n ...request,\n messages: compact.messages,\n });\n } catch (err: unknown) {\n if (!isContextOverflow(err)) {\n throw err;\n }\n }\n }\n }\n\n const previousEvent = request.state._summarizationEvent;\n const previousCutoffIndex =\n previousEvent != null\n ? (previousEvent as SummarizationEvent).cutoffIndex\n : undefined;\n\n const { summaryMessage, filePath, stateCutoffIndex } =\n await summarizeMessages(\n messagesToSummarize,\n resolvedModel,\n request.state,\n previousCutoffIndex,\n cutoffIndex,\n );\n\n let modifiedMessages = [summaryMessage, ...preservedMessages];\n const modifiedTokens = countTotalTokens(\n modifiedMessages,\n request.systemMessage,\n request.tools,\n );\n\n let finalStateCutoffIndex = stateCutoffIndex;\n let finalSummaryMessage = summaryMessage;\n let finalFilePath = filePath;\n\n try {\n await handler({ ...request, messages: modifiedMessages });\n } catch (err: unknown) {\n if (!isContextOverflow(err)) {\n throw err;\n }\n\n if (maxInputTokens && modifiedTokens > 0) {\n const observedRatio = maxInputTokens / modifiedTokens;\n if (observedRatio > tokenEstimationMultiplier) {\n tokenEstimationMultiplier = observedRatio * 1.1;\n }\n }\n\n const allMessages = [...messagesToSummarize, ...preservedMessages];\n const reSumResult = await summarizeMessages(\n allMessages,\n resolvedModel,\n request.state,\n previousCutoffIndex,\n truncatedMessages.length,\n );\n\n finalSummaryMessage = reSumResult.summaryMessage;\n finalFilePath = reSumResult.filePath;\n finalStateCutoffIndex = reSumResult.stateCutoffIndex;\n\n modifiedMessages = [reSumResult.summaryMessage];\n\n await handler({ ...request, messages: modifiedMessages });\n }\n\n return new Command({\n update: {\n _summarizationEvent: {\n cutoffIndex: finalStateCutoffIndex,\n summaryMessage: finalSummaryMessage,\n filePath: finalFilePath,\n } satisfies SummarizationEvent,\n _summarizationSessionId: getSessionId(request.state),\n },\n });\n }\n\n return createMiddleware({\n name: \"SummarizationMiddleware\",\n stateSchema: SummarizationStateSchema,\n\n async wrapModelCall(request, handler) {\n // Get effective messages based on previous summarization events\n const effectiveMessages = getEffectiveMessages(\n request.messages ?? [],\n request.state,\n );\n\n if (effectiveMessages.length === 0) {\n return handler(request);\n }\n\n /**\n * Resolve the chat model and get max input tokens from its profile.\n */\n const resolvedModel = await getChatModel();\n const maxInputTokens = getMaxInputTokens(resolvedModel);\n applyModelDefaults(resolvedModel);\n\n /**\n * Step 1: Truncate args if configured\n */\n const { messages: truncatedMessages } = truncateArgs(\n effectiveMessages,\n maxInputTokens,\n request.systemMessage,\n request.tools,\n );\n\n /**\n * Step 2: Check if summarization should happen.\n * Count tokens including system message and tools to match what's\n * actually sent to the model (matching Python implementation).\n */\n const totalTokens = countTotalTokens(\n truncatedMessages,\n request.systemMessage,\n request.tools,\n );\n\n const shouldDoSummarization = shouldSummarize(\n truncatedMessages,\n totalTokens,\n maxInputTokens,\n );\n\n /**\n * If no summarization needed, try passing through.\n * If the handler throws a ContextOverflowError, fall back to\n * emergency summarization (matching Python's behavior).\n */\n if (!shouldDoSummarization) {\n try {\n return await handler({\n ...request,\n messages: truncatedMessages,\n });\n } catch (err: unknown) {\n if (!isContextOverflow(err)) {\n throw err;\n }\n\n if (maxInputTokens && totalTokens > 0) {\n const observedRatio = maxInputTokens / totalTokens;\n if (observedRatio > tokenEstimationMultiplier) {\n tokenEstimationMultiplier = observedRatio * 1.1;\n }\n }\n // Fall through to summarization below\n }\n }\n\n /**\n * Step 3: Perform summarization\n */\n return performSummarization(\n request as any,\n handler,\n truncatedMessages,\n resolvedModel,\n maxInputTokens,\n );\n },\n });\n}\n","/**\n * StoreBackend: Adapter for LangGraph's BaseStore (persistent, cross-thread).\n */\n\nimport type { Item } from \"@langchain/langgraph\";\nimport type {\n BackendProtocol,\n EditResult,\n FileData,\n FileDownloadResponse,\n FileInfo,\n FileUploadResponse,\n GrepMatch,\n StateAndStore,\n WriteResult,\n} from \"./protocol.js\";\nimport {\n createFileData,\n fileDataToString,\n formatReadResponse,\n globSearchFiles,\n grepMatchesFromFiles,\n performStringReplacement,\n updateFileData,\n} from \"./utils.js\";\n\n/**\n * Backend that stores files in LangGraph's BaseStore (persistent).\n *\n * Uses LangGraph's Store for persistent, cross-conversation storage.\n * Files are organized via namespaces and persist across all threads.\n *\n * The namespace can include an optional assistant_id for multi-agent isolation.\n */\nexport class StoreBackend implements BackendProtocol {\n private stateAndStore: StateAndStore;\n\n constructor(stateAndStore: StateAndStore) {\n this.stateAndStore = stateAndStore;\n }\n\n /**\n * Get the store instance.\n *\n * @returns BaseStore instance\n * @throws Error if no store is available\n */\n private getStore() {\n const store = this.stateAndStore.store;\n if (!store) {\n throw new Error(\"Store is required but not available in StateAndStore\");\n }\n return store;\n }\n\n /**\n * Get the namespace for store operations.\n *\n * If an assistant_id is available in stateAndStore, return\n * [assistant_id, \"filesystem\"] to provide per-assistant isolation.\n * Otherwise return [\"filesystem\"].\n */\n protected getNamespace(): string[] {\n const namespace = \"filesystem\";\n const assistantId = this.stateAndStore.assistantId;\n\n if (assistantId) {\n return [assistantId, namespace];\n }\n\n return [namespace];\n }\n\n /**\n * Convert a store Item to FileData format.\n *\n * @param storeItem - The store Item containing file data\n * @returns FileData object\n * @throws Error if required fields are missing or have incorrect types\n */\n private convertStoreItemToFileData(storeItem: Item): FileData {\n const value = storeItem.value as any;\n\n if (\n !value.content ||\n !Array.isArray(value.content) ||\n typeof value.created_at !== \"string\" ||\n typeof value.modified_at !== \"string\"\n ) {\n throw new Error(\n `Store item does not contain valid FileData fields. Got keys: ${Object.keys(value).join(\", \")}`,\n );\n }\n\n return {\n content: value.content,\n created_at: value.created_at,\n modified_at: value.modified_at,\n };\n }\n\n /**\n * Convert FileData to a value suitable for store.put().\n *\n * @param fileData - The FileData to convert\n * @returns Object with content, created_at, and modified_at fields\n */\n private convertFileDataToStoreValue(fileData: FileData): Record<string, any> {\n return {\n content: fileData.content,\n created_at: fileData.created_at,\n modified_at: fileData.modified_at,\n };\n }\n\n /**\n * Search store with automatic pagination to retrieve all results.\n *\n * @param store - The store to search\n * @param namespace - Hierarchical path prefix to search within\n * @param options - Optional query, filter, and page_size\n * @returns List of all items matching the search criteria\n */\n private async searchStorePaginated(\n store: any,\n namespace: string[],\n options: {\n query?: string;\n filter?: Record<string, any>;\n pageSize?: number;\n } = {},\n ): Promise<Item[]> {\n const { query, filter, pageSize = 100 } = options;\n const allItems: Item[] = [];\n let offset = 0;\n\n while (true) {\n const pageItems = await store.search(namespace, {\n query,\n filter,\n limit: pageSize,\n offset,\n });\n\n if (!pageItems || pageItems.length === 0) {\n break;\n }\n\n allItems.push(...pageItems);\n\n if (pageItems.length < pageSize) {\n break;\n }\n\n offset += pageSize;\n }\n\n return allItems;\n }\n\n /**\n * List files and directories in the specified directory (non-recursive).\n *\n * @param path - Absolute path to directory\n * @returns List of FileInfo objects for files and directories directly in the directory.\n * Directories have a trailing / in their path and is_dir=true.\n */\n async lsInfo(path: string): Promise<FileInfo[]> {\n const store = this.getStore();\n const namespace = this.getNamespace();\n\n // Retrieve all items and filter by path prefix locally to avoid\n // coupling to store-specific filter semantics\n const items = await this.searchStorePaginated(store, namespace);\n const infos: FileInfo[] = [];\n const subdirs = new Set<string>();\n\n // Normalize path to have trailing slash for proper prefix matching\n const normalizedPath = path.endsWith(\"/\") ? path : path + \"/\";\n\n for (const item of items) {\n const itemKey = String(item.key);\n\n // Check if file is in the specified directory or a subdirectory\n if (!itemKey.startsWith(normalizedPath)) {\n continue;\n }\n\n // Get the relative path after the directory\n const relative = itemKey.substring(normalizedPath.length);\n\n // If relative path contains '/', it's in a subdirectory\n if (relative.includes(\"/\")) {\n // Extract the immediate subdirectory name\n const subdirName = relative.split(\"/\")[0];\n subdirs.add(normalizedPath + subdirName + \"/\");\n continue;\n }\n\n // This is a file directly in the current directory\n try {\n const fd = this.convertStoreItemToFileData(item);\n const size = fd.content.join(\"\\n\").length;\n infos.push({\n path: itemKey,\n is_dir: false,\n size: size,\n modified_at: fd.modified_at,\n });\n } catch {\n // Skip invalid items\n continue;\n }\n }\n\n // Add directories to the results\n for (const subdir of Array.from(subdirs).sort()) {\n infos.push({\n path: subdir,\n is_dir: true,\n size: 0,\n modified_at: \"\",\n });\n }\n\n infos.sort((a, b) => a.path.localeCompare(b.path));\n return infos;\n }\n\n /**\n * Read file content with line numbers.\n *\n * @param filePath - Absolute file path\n * @param offset - Line offset to start reading from (0-indexed)\n * @param limit - Maximum number of lines to read\n * @returns Formatted file content with line numbers, or error message\n */\n async read(\n filePath: string,\n offset: number = 0,\n limit: number = 500,\n ): Promise<string> {\n try {\n const fileData = await this.readRaw(filePath);\n return formatReadResponse(fileData, offset, limit);\n } catch (e: any) {\n return `Error: ${e.message}`;\n }\n }\n\n /**\n * Read file content as raw FileData.\n *\n * @param filePath - Absolute file path\n * @returns Raw file content as FileData\n */\n async readRaw(filePath: string): Promise<FileData> {\n const store = this.getStore();\n const namespace = this.getNamespace();\n const item = await store.get(namespace, filePath);\n\n if (!item) throw new Error(`File '${filePath}' not found`);\n return this.convertStoreItemToFileData(item);\n }\n\n /**\n * Create a new file with content.\n * Returns WriteResult. External storage sets filesUpdate=null.\n */\n async write(filePath: string, content: string): Promise<WriteResult> {\n const store = this.getStore();\n const namespace = this.getNamespace();\n\n // Check if file exists\n const existing = await store.get(namespace, filePath);\n if (existing) {\n return {\n error: `Cannot write to ${filePath} because it already exists. Read and then make an edit, or write to a new path.`,\n };\n }\n\n // Create new file\n const fileData = createFileData(content);\n const storeValue = this.convertFileDataToStoreValue(fileData);\n await store.put(namespace, filePath, storeValue);\n return { path: filePath, filesUpdate: null };\n }\n\n /**\n * Edit a file by replacing string occurrences.\n * Returns EditResult. External storage sets filesUpdate=null.\n */\n async edit(\n filePath: string,\n oldString: string,\n newString: string,\n replaceAll: boolean = false,\n ): Promise<EditResult> {\n const store = this.getStore();\n const namespace = this.getNamespace();\n\n // Get existing file\n const item = await store.get(namespace, filePath);\n if (!item) {\n return { error: `Error: File '${filePath}' not found` };\n }\n\n try {\n const fileData = this.convertStoreItemToFileData(item);\n const content = fileDataToString(fileData);\n const result = performStringReplacement(\n content,\n oldString,\n newString,\n replaceAll,\n );\n\n if (typeof result === \"string\") {\n return { error: result };\n }\n\n const [newContent, occurrences] = result;\n const newFileData = updateFileData(fileData, newContent);\n\n // Update file in store\n const storeValue = this.convertFileDataToStoreValue(newFileData);\n await store.put(namespace, filePath, storeValue);\n return { path: filePath, filesUpdate: null, occurrences: occurrences };\n } catch (e: any) {\n return { error: `Error: ${e.message}` };\n }\n }\n\n /**\n * Structured search results or error string for invalid input.\n */\n async grepRaw(\n pattern: string,\n path: string = \"/\",\n glob: string | null = null,\n ): Promise<GrepMatch[] | string> {\n const store = this.getStore();\n const namespace = this.getNamespace();\n const items = await this.searchStorePaginated(store, namespace);\n\n const files: Record<string, FileData> = {};\n for (const item of items) {\n try {\n files[item.key] = this.convertStoreItemToFileData(item);\n } catch {\n // Skip invalid items\n continue;\n }\n }\n\n return grepMatchesFromFiles(files, pattern, path, glob);\n }\n\n /**\n * Structured glob matching returning FileInfo objects.\n */\n async globInfo(pattern: string, path: string = \"/\"): Promise<FileInfo[]> {\n const store = this.getStore();\n const namespace = this.getNamespace();\n const items = await this.searchStorePaginated(store, namespace);\n\n const files: Record<string, FileData> = {};\n for (const item of items) {\n try {\n files[item.key] = this.convertStoreItemToFileData(item);\n } catch {\n // Skip invalid items\n continue;\n }\n }\n\n const result = globSearchFiles(files, pattern, path);\n if (result === \"No files found\") {\n return [];\n }\n\n const paths = result.split(\"\\n\");\n const infos: FileInfo[] = [];\n for (const p of paths) {\n const fd = files[p];\n const size = fd ? fd.content.join(\"\\n\").length : 0;\n infos.push({\n path: p,\n is_dir: false,\n size: size,\n modified_at: fd?.modified_at || \"\",\n });\n }\n return infos;\n }\n\n /**\n * Upload multiple files.\n *\n * @param files - List of [path, content] tuples to upload\n * @returns List of FileUploadResponse objects, one per input file\n */\n async uploadFiles(\n files: Array<[string, Uint8Array]>,\n ): Promise<FileUploadResponse[]> {\n const store = this.getStore();\n const namespace = this.getNamespace();\n const responses: FileUploadResponse[] = [];\n\n for (const [path, content] of files) {\n try {\n const contentStr = new TextDecoder().decode(content);\n const fileData = createFileData(contentStr);\n const storeValue = this.convertFileDataToStoreValue(fileData);\n await store.put(namespace, path, storeValue);\n responses.push({ path, error: null });\n } catch {\n responses.push({ path, error: \"invalid_path\" });\n }\n }\n\n return responses;\n }\n\n /**\n * Download multiple files.\n *\n * @param paths - List of file paths to download\n * @returns List of FileDownloadResponse objects, one per input path\n */\n async downloadFiles(paths: string[]): Promise<FileDownloadResponse[]> {\n const store = this.getStore();\n const namespace = this.getNamespace();\n const responses: FileDownloadResponse[] = [];\n\n for (const path of paths) {\n try {\n const item = await store.get(namespace, path);\n if (!item) {\n responses.push({ path, content: null, error: \"file_not_found\" });\n continue;\n }\n\n const fileData = this.convertStoreItemToFileData(item);\n const contentStr = fileDataToString(fileData);\n const content = new TextEncoder().encode(contentStr);\n responses.push({ path, content, error: null });\n } catch {\n responses.push({ path, content: null, error: \"file_not_found\" });\n }\n }\n\n return responses;\n }\n}\n","/**\n * FilesystemBackend: Read and write files directly from the filesystem.\n *\n * Security and search upgrades:\n * - Secure path resolution with root containment when in virtual_mode (sandboxed to cwd)\n * - Prevent symlink-following on file I/O using O_NOFOLLOW when available\n * - Ripgrep-powered grep with literal (fixed-string) search, plus substring fallback\n * and optional glob include filtering, while preserving virtual path behavior\n */\n\nimport fs from \"node:fs/promises\";\nimport fsSync from \"node:fs\";\nimport path from \"node:path\";\nimport { spawn } from \"node:child_process\";\n\nimport fg from \"fast-glob\";\nimport micromatch from \"micromatch\";\nimport type {\n BackendProtocol,\n EditResult,\n FileData,\n FileDownloadResponse,\n FileInfo,\n FileUploadResponse,\n GrepMatch,\n WriteResult,\n} from \"./protocol.js\";\nimport {\n checkEmptyContent,\n formatContentWithLineNumbers,\n performStringReplacement,\n} from \"./utils.js\";\n\nconst SUPPORTS_NOFOLLOW = fsSync.constants.O_NOFOLLOW !== undefined;\n\n/**\n * Backend that reads and writes files directly from the filesystem.\n *\n * Files are accessed using their actual filesystem paths. Relative paths are\n * resolved relative to the current working directory. Content is read/written\n * as plain text, and metadata (timestamps) are derived from filesystem stats.\n */\nexport class FilesystemBackend implements BackendProtocol {\n protected cwd: string;\n protected virtualMode: boolean;\n private maxFileSizeBytes: number;\n\n constructor(\n options: {\n rootDir?: string;\n virtualMode?: boolean;\n maxFileSizeMb?: number;\n } = {},\n ) {\n const { rootDir, virtualMode = false, maxFileSizeMb = 10 } = options;\n this.cwd = rootDir ? path.resolve(rootDir) : process.cwd();\n this.virtualMode = virtualMode;\n this.maxFileSizeBytes = maxFileSizeMb * 1024 * 1024;\n }\n\n /**\n * Resolve a file path with security checks.\n *\n * When virtualMode=true, treat incoming paths as virtual absolute paths under\n * this.cwd, disallow traversal (.., ~) and ensure resolved path stays within root.\n * When virtualMode=false, preserve legacy behavior: absolute paths are allowed\n * as-is; relative paths resolve under cwd.\n *\n * @param key - File path (absolute, relative, or virtual when virtualMode=true)\n * @returns Resolved absolute path string\n * @throws Error if path traversal detected or path outside root\n */\n private resolvePath(key: string): string {\n if (this.virtualMode) {\n const vpath = key.startsWith(\"/\") ? key : \"/\" + key;\n if (vpath.includes(\"..\") || vpath.startsWith(\"~\")) {\n throw new Error(\"Path traversal not allowed\");\n }\n const full = path.resolve(this.cwd, vpath.substring(1));\n const relative = path.relative(this.cwd, full);\n if (relative.startsWith(\"..\") || path.isAbsolute(relative)) {\n throw new Error(`Path: ${full} outside root directory: ${this.cwd}`);\n }\n return full;\n }\n\n if (path.isAbsolute(key)) {\n return key;\n }\n return path.resolve(this.cwd, key);\n }\n\n /**\n * List files and directories in the specified directory (non-recursive).\n *\n * @param dirPath - Absolute directory path to list files from\n * @returns List of FileInfo objects for files and directories directly in the directory.\n * Directories have a trailing / in their path and is_dir=true.\n */\n async lsInfo(dirPath: string): Promise<FileInfo[]> {\n try {\n const resolvedPath = this.resolvePath(dirPath);\n const stat = await fs.stat(resolvedPath);\n\n if (!stat.isDirectory()) {\n return [];\n }\n\n const entries = await fs.readdir(resolvedPath, { withFileTypes: true });\n const results: FileInfo[] = [];\n\n const cwdStr = this.cwd.endsWith(path.sep)\n ? this.cwd\n : this.cwd + path.sep;\n\n for (const entry of entries) {\n const fullPath = path.join(resolvedPath, entry.name);\n\n try {\n const entryStat = await fs.stat(fullPath);\n const isFile = entryStat.isFile();\n const isDir = entryStat.isDirectory();\n\n if (!this.virtualMode) {\n // Non-virtual mode: use absolute paths\n if (isFile) {\n results.push({\n path: fullPath,\n is_dir: false,\n size: entryStat.size,\n modified_at: entryStat.mtime.toISOString(),\n });\n } else if (isDir) {\n results.push({\n path: fullPath + path.sep,\n is_dir: true,\n size: 0,\n modified_at: entryStat.mtime.toISOString(),\n });\n }\n } else {\n let relativePath: string;\n if (fullPath.startsWith(cwdStr)) {\n relativePath = fullPath.substring(cwdStr.length);\n } else if (fullPath.startsWith(this.cwd)) {\n relativePath = fullPath\n .substring(this.cwd.length)\n .replace(/^[/\\\\]/, \"\");\n } else {\n relativePath = fullPath;\n }\n\n relativePath = relativePath.split(path.sep).join(\"/\");\n const virtPath = \"/\" + relativePath;\n\n if (isFile) {\n results.push({\n path: virtPath,\n is_dir: false,\n size: entryStat.size,\n modified_at: entryStat.mtime.toISOString(),\n });\n } else if (isDir) {\n results.push({\n path: virtPath + \"/\",\n is_dir: true,\n size: 0,\n modified_at: entryStat.mtime.toISOString(),\n });\n }\n }\n } catch {\n // Skip entries we can't stat\n continue;\n }\n }\n\n results.sort((a, b) => a.path.localeCompare(b.path));\n return results;\n } catch {\n return [];\n }\n }\n\n /**\n * Read file content with line numbers.\n *\n * @param filePath - Absolute or relative file path\n * @param offset - Line offset to start reading from (0-indexed)\n * @param limit - Maximum number of lines to read\n * @returns Formatted file content with line numbers, or error message\n */\n async read(\n filePath: string,\n offset: number = 0,\n limit: number = 500,\n ): Promise<string> {\n try {\n const resolvedPath = this.resolvePath(filePath);\n\n let content: string;\n\n if (SUPPORTS_NOFOLLOW) {\n const stat = await fs.stat(resolvedPath);\n if (!stat.isFile()) {\n return `Error: File '${filePath}' not found`;\n }\n const fd = await fs.open(\n resolvedPath,\n fsSync.constants.O_RDONLY | fsSync.constants.O_NOFOLLOW,\n );\n try {\n content = await fd.readFile({ encoding: \"utf-8\" });\n } finally {\n await fd.close();\n }\n } else {\n const stat = await fs.lstat(resolvedPath);\n if (stat.isSymbolicLink()) {\n return `Error: Symlinks are not allowed: ${filePath}`;\n }\n if (!stat.isFile()) {\n return `Error: File '${filePath}' not found`;\n }\n content = await fs.readFile(resolvedPath, \"utf-8\");\n }\n\n const emptyMsg = checkEmptyContent(content);\n if (emptyMsg) {\n return emptyMsg;\n }\n\n const lines = content.split(\"\\n\");\n const startIdx = offset;\n const endIdx = Math.min(startIdx + limit, lines.length);\n\n if (startIdx >= lines.length) {\n return `Error: Line offset ${offset} exceeds file length (${lines.length} lines)`;\n }\n\n const selectedLines = lines.slice(startIdx, endIdx);\n return formatContentWithLineNumbers(selectedLines, startIdx + 1);\n } catch (e: any) {\n return `Error reading file '${filePath}': ${e.message}`;\n }\n }\n\n /**\n * Read file content as raw FileData.\n *\n * @param filePath - Absolute file path\n * @returns Raw file content as FileData\n */\n async readRaw(filePath: string): Promise<FileData> {\n const resolvedPath = this.resolvePath(filePath);\n\n let content: string;\n let stat: fsSync.Stats;\n\n if (SUPPORTS_NOFOLLOW) {\n stat = await fs.stat(resolvedPath);\n if (!stat.isFile()) throw new Error(`File '${filePath}' not found`);\n const fd = await fs.open(\n resolvedPath,\n fsSync.constants.O_RDONLY | fsSync.constants.O_NOFOLLOW,\n );\n try {\n content = await fd.readFile({ encoding: \"utf-8\" });\n } finally {\n await fd.close();\n }\n } else {\n stat = await fs.lstat(resolvedPath);\n if (stat.isSymbolicLink()) {\n throw new Error(`Symlinks are not allowed: ${filePath}`);\n }\n if (!stat.isFile()) throw new Error(`File '${filePath}' not found`);\n content = await fs.readFile(resolvedPath, \"utf-8\");\n }\n\n return {\n content: content.split(\"\\n\"),\n created_at: stat.ctime.toISOString(),\n modified_at: stat.mtime.toISOString(),\n };\n }\n\n /**\n * Create a new file with content.\n * Returns WriteResult. External storage sets filesUpdate=null.\n */\n async write(filePath: string, content: string): Promise<WriteResult> {\n try {\n const resolvedPath = this.resolvePath(filePath);\n\n try {\n const stat = await fs.lstat(resolvedPath);\n if (stat.isSymbolicLink()) {\n return {\n error: `Cannot write to ${filePath} because it is a symlink. Symlinks are not allowed.`,\n };\n }\n return {\n error: `Cannot write to ${filePath} because it already exists. Read and then make an edit, or write to a new path.`,\n };\n } catch {\n // File doesn't exist, good to proceed\n }\n\n await fs.mkdir(path.dirname(resolvedPath), { recursive: true });\n\n if (SUPPORTS_NOFOLLOW) {\n const flags =\n fsSync.constants.O_WRONLY |\n fsSync.constants.O_CREAT |\n fsSync.constants.O_TRUNC |\n fsSync.constants.O_NOFOLLOW;\n\n const fd = await fs.open(resolvedPath, flags, 0o644);\n try {\n await fd.writeFile(content, \"utf-8\");\n } finally {\n await fd.close();\n }\n } else {\n await fs.writeFile(resolvedPath, content, \"utf-8\");\n }\n\n return { path: filePath, filesUpdate: null };\n } catch (e: any) {\n return { error: `Error writing file '${filePath}': ${e.message}` };\n }\n }\n\n /**\n * Edit a file by replacing string occurrences.\n * Returns EditResult. External storage sets filesUpdate=null.\n */\n async edit(\n filePath: string,\n oldString: string,\n newString: string,\n replaceAll: boolean = false,\n ): Promise<EditResult> {\n try {\n const resolvedPath = this.resolvePath(filePath);\n\n let content: string;\n\n if (SUPPORTS_NOFOLLOW) {\n const stat = await fs.stat(resolvedPath);\n if (!stat.isFile()) {\n return { error: `Error: File '${filePath}' not found` };\n }\n\n const fd = await fs.open(\n resolvedPath,\n fsSync.constants.O_RDONLY | fsSync.constants.O_NOFOLLOW,\n );\n try {\n content = await fd.readFile({ encoding: \"utf-8\" });\n } finally {\n await fd.close();\n }\n } else {\n const stat = await fs.lstat(resolvedPath);\n if (stat.isSymbolicLink()) {\n return { error: `Error: Symlinks are not allowed: ${filePath}` };\n }\n if (!stat.isFile()) {\n return { error: `Error: File '${filePath}' not found` };\n }\n content = await fs.readFile(resolvedPath, \"utf-8\");\n }\n\n const result = performStringReplacement(\n content,\n oldString,\n newString,\n replaceAll,\n );\n\n if (typeof result === \"string\") {\n return { error: result };\n }\n\n const [newContent, occurrences] = result;\n\n // Write securely\n if (SUPPORTS_NOFOLLOW) {\n const flags =\n fsSync.constants.O_WRONLY |\n fsSync.constants.O_TRUNC |\n fsSync.constants.O_NOFOLLOW;\n\n const fd = await fs.open(resolvedPath, flags);\n try {\n await fd.writeFile(newContent, \"utf-8\");\n } finally {\n await fd.close();\n }\n } else {\n await fs.writeFile(resolvedPath, newContent, \"utf-8\");\n }\n\n return { path: filePath, filesUpdate: null, occurrences: occurrences };\n } catch (e: any) {\n return { error: `Error editing file '${filePath}': ${e.message}` };\n }\n }\n\n /**\n * Search for a literal text pattern in files.\n *\n * Uses ripgrep if available, falling back to substring search.\n *\n * @param pattern - Literal string to search for (NOT regex).\n * @param dirPath - Directory or file path to search in. Defaults to current directory.\n * @param glob - Optional glob pattern to filter which files to search.\n * @returns List of GrepMatch dicts containing path, line number, and matched text.\n */\n async grepRaw(\n pattern: string,\n dirPath: string = \"/\",\n glob: string | null = null,\n ): Promise<GrepMatch[] | string> {\n // Resolve base path\n let baseFull: string;\n try {\n baseFull = this.resolvePath(dirPath || \".\");\n } catch {\n return [];\n }\n\n try {\n await fs.stat(baseFull);\n } catch {\n return [];\n }\n\n // Try ripgrep first (with -F flag for literal search), fallback to substring search\n let results = await this.ripgrepSearch(pattern, baseFull, glob);\n if (results === null) {\n results = await this.literalSearch(pattern, baseFull, glob);\n }\n\n const matches: GrepMatch[] = [];\n for (const [fpath, items] of Object.entries(results)) {\n for (const [lineNum, lineText] of items) {\n matches.push({ path: fpath, line: lineNum, text: lineText });\n }\n }\n return matches;\n }\n\n /**\n * Search using ripgrep with fixed-string (literal) mode.\n *\n * @param pattern - Literal string to search for (unescaped).\n * @param baseFull - Resolved base path to search in.\n * @param includeGlob - Optional glob pattern to filter files.\n * @returns Dict mapping file paths to list of (line_number, line_text) tuples.\n * Returns null if ripgrep is unavailable or times out.\n */\n private async ripgrepSearch(\n pattern: string,\n baseFull: string,\n includeGlob: string | null,\n ): Promise<Record<string, Array<[number, string]>> | null> {\n return new Promise((resolve) => {\n // -F enables fixed-string (literal) mode\n const args = [\"--json\", \"-F\"];\n if (includeGlob) {\n args.push(\"--glob\", includeGlob);\n }\n args.push(\"--\", pattern, baseFull);\n\n const proc = spawn(\"rg\", args, { timeout: 30000 });\n const results: Record<string, Array<[number, string]>> = {};\n let output = \"\";\n\n proc.stdout.on(\"data\", (data) => {\n output += data.toString();\n });\n\n proc.on(\"close\", (code) => {\n if (code !== 0 && code !== 1) {\n // Error (code 1 means no matches, which is ok)\n resolve(null);\n return;\n }\n\n for (const line of output.split(\"\\n\")) {\n if (!line.trim()) continue;\n try {\n const data = JSON.parse(line);\n if (data.type !== \"match\") continue;\n\n const pdata = data.data || {};\n const ftext = pdata.path?.text;\n if (!ftext) continue;\n\n let virtPath: string;\n if (this.virtualMode) {\n try {\n const resolved = path.resolve(ftext);\n const relative = path.relative(this.cwd, resolved);\n if (relative.startsWith(\"..\")) continue;\n const normalizedRelative = relative.split(path.sep).join(\"/\");\n virtPath = \"/\" + normalizedRelative;\n } catch {\n continue;\n }\n } else {\n virtPath = ftext;\n }\n\n const ln = pdata.line_number;\n const lt = pdata.lines?.text?.replace(/\\n$/, \"\") || \"\";\n if (ln === undefined) continue;\n\n if (!results[virtPath]) {\n results[virtPath] = [];\n }\n results[virtPath].push([ln, lt]);\n } catch {\n // Skip invalid JSON\n continue;\n }\n }\n\n resolve(results);\n });\n\n proc.on(\"error\", () => {\n resolve(null);\n });\n });\n }\n\n /**\n * Fallback search using literal substring matching when ripgrep is unavailable.\n *\n * Recursively searches files, respecting maxFileSizeBytes limit.\n *\n * @param pattern - Literal string to search for.\n * @param baseFull - Resolved base path to search in.\n * @param includeGlob - Optional glob pattern to filter files by name.\n * @returns Dict mapping file paths to list of (line_number, line_text) tuples.\n */\n private async literalSearch(\n pattern: string,\n baseFull: string,\n includeGlob: string | null,\n ): Promise<Record<string, Array<[number, string]>>> {\n const results: Record<string, Array<[number, string]>> = {};\n const stat = await fs.stat(baseFull);\n const root = stat.isDirectory() ? baseFull : path.dirname(baseFull);\n\n // Use fast-glob to recursively find all files\n const files = await fg(\"**/*\", {\n cwd: root,\n absolute: true,\n onlyFiles: true,\n dot: true,\n });\n\n for (const fp of files) {\n try {\n // Filter by glob if provided\n if (\n includeGlob &&\n !micromatch.isMatch(path.basename(fp), includeGlob)\n ) {\n continue;\n }\n\n // Check file size\n const stat = await fs.stat(fp);\n if (stat.size > this.maxFileSizeBytes) {\n continue;\n }\n\n // Read and search using literal substring matching\n const content = await fs.readFile(fp, \"utf-8\");\n const lines = content.split(\"\\n\");\n\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i];\n // Simple substring search for literal matching\n if (line.includes(pattern)) {\n let virtPath: string;\n if (this.virtualMode) {\n try {\n const relative = path.relative(this.cwd, fp);\n if (relative.startsWith(\"..\")) continue;\n const normalizedRelative = relative.split(path.sep).join(\"/\");\n virtPath = \"/\" + normalizedRelative;\n } catch {\n continue;\n }\n } else {\n virtPath = fp;\n }\n\n if (!results[virtPath]) {\n results[virtPath] = [];\n }\n results[virtPath].push([i + 1, line]);\n }\n }\n } catch {\n // Skip files we can't read\n continue;\n }\n }\n\n return results;\n }\n\n /**\n * Structured glob matching returning FileInfo objects.\n */\n async globInfo(\n pattern: string,\n searchPath: string = \"/\",\n ): Promise<FileInfo[]> {\n if (pattern.startsWith(\"/\")) {\n pattern = pattern.substring(1);\n }\n\n const resolvedSearchPath =\n searchPath === \"/\" ? this.cwd : this.resolvePath(searchPath);\n\n try {\n const stat = await fs.stat(resolvedSearchPath);\n if (!stat.isDirectory()) {\n return [];\n }\n } catch {\n return [];\n }\n\n const results: FileInfo[] = [];\n\n try {\n // Use fast-glob for pattern matching\n const matches = await fg(pattern, {\n cwd: resolvedSearchPath,\n absolute: true,\n onlyFiles: true,\n dot: true,\n });\n\n for (const matchedPath of matches) {\n try {\n const stat = await fs.stat(matchedPath);\n if (!stat.isFile()) continue;\n\n // Normalize fast-glob paths to platform separators\n // fast-glob returns forward slashes on all platforms, but we need\n // platform-native separators for path comparisons on Windows\n const normalizedPath = matchedPath.split(\"/\").join(path.sep);\n\n if (!this.virtualMode) {\n results.push({\n path: normalizedPath,\n is_dir: false,\n size: stat.size,\n modified_at: stat.mtime.toISOString(),\n });\n } else {\n const cwdStr = this.cwd.endsWith(path.sep)\n ? this.cwd\n : this.cwd + path.sep;\n let relativePath: string;\n\n if (normalizedPath.startsWith(cwdStr)) {\n relativePath = normalizedPath.substring(cwdStr.length);\n } else if (normalizedPath.startsWith(this.cwd)) {\n relativePath = normalizedPath\n .substring(this.cwd.length)\n .replace(/^[/\\\\]/, \"\");\n } else {\n relativePath = normalizedPath;\n }\n\n relativePath = relativePath.split(path.sep).join(\"/\");\n const virt = \"/\" + relativePath;\n results.push({\n path: virt,\n is_dir: false,\n size: stat.size,\n modified_at: stat.mtime.toISOString(),\n });\n }\n } catch {\n // Skip files we can't stat\n continue;\n }\n }\n } catch {\n // Ignore glob errors\n }\n\n results.sort((a, b) => a.path.localeCompare(b.path));\n return results;\n }\n\n /**\n * Upload multiple files to the filesystem.\n *\n * @param files - List of [path, content] tuples to upload\n * @returns List of FileUploadResponse objects, one per input file\n */\n async uploadFiles(\n files: Array<[string, Uint8Array]>,\n ): Promise<FileUploadResponse[]> {\n const responses: FileUploadResponse[] = [];\n\n for (const [filePath, content] of files) {\n try {\n const resolvedPath = this.resolvePath(filePath);\n\n // Ensure parent directory exists\n await fs.mkdir(path.dirname(resolvedPath), { recursive: true });\n\n // Write file\n await fs.writeFile(resolvedPath, content);\n responses.push({ path: filePath, error: null });\n } catch (e: any) {\n if (e.code === \"ENOENT\") {\n responses.push({ path: filePath, error: \"file_not_found\" });\n } else if (e.code === \"EACCES\") {\n responses.push({ path: filePath, error: \"permission_denied\" });\n } else if (e.code === \"EISDIR\") {\n responses.push({ path: filePath, error: \"is_directory\" });\n } else {\n responses.push({ path: filePath, error: \"invalid_path\" });\n }\n }\n }\n\n return responses;\n }\n\n /**\n * Download multiple files from the filesystem.\n *\n * @param paths - List of file paths to download\n * @returns List of FileDownloadResponse objects, one per input path\n */\n async downloadFiles(paths: string[]): Promise<FileDownloadResponse[]> {\n const responses: FileDownloadResponse[] = [];\n\n for (const filePath of paths) {\n try {\n const resolvedPath = this.resolvePath(filePath);\n const content = await fs.readFile(resolvedPath);\n responses.push({ path: filePath, content, error: null });\n } catch (e: any) {\n if (e.code === \"ENOENT\") {\n responses.push({\n path: filePath,\n content: null,\n error: \"file_not_found\",\n });\n } else if (e.code === \"EACCES\") {\n responses.push({\n path: filePath,\n content: null,\n error: \"permission_denied\",\n });\n } else if (e.code === \"EISDIR\") {\n responses.push({\n path: filePath,\n content: null,\n error: \"is_directory\",\n });\n } else {\n responses.push({\n path: filePath,\n content: null,\n error: \"invalid_path\",\n });\n }\n }\n }\n\n return responses;\n }\n}\n","/**\n * CompositeBackend: Route operations to different backends based on path prefix.\n */\n\nimport type {\n BackendProtocol,\n EditResult,\n ExecuteResponse,\n FileData,\n FileDownloadResponse,\n FileInfo,\n FileUploadResponse,\n GrepMatch,\n WriteResult,\n} from \"./protocol.js\";\nimport { isSandboxBackend } from \"./protocol.js\";\n\n/**\n * Backend that routes file operations to different backends based on path prefix.\n *\n * This enables hybrid storage strategies like:\n * - `/memories/` → StoreBackend (persistent, cross-thread)\n * - Everything else → StateBackend (ephemeral, per-thread)\n *\n * The CompositeBackend handles path prefix stripping/re-adding transparently.\n */\nexport class CompositeBackend implements BackendProtocol {\n private default: BackendProtocol;\n private routes: Record<string, BackendProtocol>;\n private sortedRoutes: Array<[string, BackendProtocol]>;\n\n constructor(\n defaultBackend: BackendProtocol,\n routes: Record<string, BackendProtocol>,\n ) {\n this.default = defaultBackend;\n this.routes = routes;\n\n // Sort routes by length (longest first) for correct prefix matching\n this.sortedRoutes = Object.entries(routes).sort(\n (a, b) => b[0].length - a[0].length,\n );\n }\n\n /**\n * Determine which backend handles this key and strip prefix.\n *\n * @param key - Original file path\n * @returns Tuple of [backend, stripped_key] where stripped_key has the route\n * prefix removed (but keeps leading slash).\n */\n private getBackendAndKey(key: string): [BackendProtocol, string] {\n // Check routes in order of length (longest first)\n for (const [prefix, backend] of this.sortedRoutes) {\n if (key.startsWith(prefix)) {\n // Strip full prefix and ensure a leading slash remains\n // e.g., \"/memories/notes.txt\" → \"/notes.txt\"; \"/memories/\" → \"/\"\n const suffix = key.substring(prefix.length);\n const strippedKey = suffix ? \"/\" + suffix : \"/\";\n return [backend, strippedKey];\n }\n }\n\n return [this.default, key];\n }\n\n /**\n * List files and directories in the specified directory (non-recursive).\n *\n * @param path - Absolute path to directory\n * @returns List of FileInfo objects with route prefixes added, for files and directories\n * directly in the directory. Directories have a trailing / in their path and is_dir=true.\n */\n async lsInfo(path: string): Promise<FileInfo[]> {\n // Check if path matches a specific route\n for (const [routePrefix, backend] of this.sortedRoutes) {\n if (path.startsWith(routePrefix.replace(/\\/$/, \"\"))) {\n // Query only the matching routed backend\n const suffix = path.substring(routePrefix.length);\n const searchPath = suffix ? \"/\" + suffix : \"/\";\n const infos = await backend.lsInfo(searchPath);\n\n // Add route prefix back to paths\n const prefixed: FileInfo[] = [];\n for (const fi of infos) {\n prefixed.push({\n ...fi,\n path: routePrefix.slice(0, -1) + fi.path,\n });\n }\n return prefixed;\n }\n }\n\n // At root, aggregate default and all routed backends\n if (path === \"/\") {\n const results: FileInfo[] = [];\n const defaultInfos = await this.default.lsInfo(path);\n results.push(...defaultInfos);\n\n // Add the route itself as a directory (e.g., /memories/)\n for (const [routePrefix] of this.sortedRoutes) {\n results.push({\n path: routePrefix,\n is_dir: true,\n size: 0,\n modified_at: \"\",\n });\n }\n\n results.sort((a, b) => a.path.localeCompare(b.path));\n return results;\n }\n\n // Path doesn't match a route: query only default backend\n return await this.default.lsInfo(path);\n }\n\n /**\n * Read file content, routing to appropriate backend.\n *\n * @param filePath - Absolute file path\n * @param offset - Line offset to start reading from (0-indexed)\n * @param limit - Maximum number of lines to read\n * @returns Formatted file content with line numbers, or error message\n */\n async read(\n filePath: string,\n offset: number = 0,\n limit: number = 500,\n ): Promise<string> {\n const [backend, strippedKey] = this.getBackendAndKey(filePath);\n return await backend.read(strippedKey, offset, limit);\n }\n\n /**\n * Read file content as raw FileData.\n *\n * @param filePath - Absolute file path\n * @returns Raw file content as FileData\n */\n async readRaw(filePath: string): Promise<FileData> {\n const [backend, strippedKey] = this.getBackendAndKey(filePath);\n return await backend.readRaw(strippedKey);\n }\n\n /**\n * Structured search results or error string for invalid input.\n */\n async grepRaw(\n pattern: string,\n path: string = \"/\",\n glob: string | null = null,\n ): Promise<GrepMatch[] | string> {\n // If path targets a specific route, search only that backend\n for (const [routePrefix, backend] of this.sortedRoutes) {\n if (path.startsWith(routePrefix.replace(/\\/$/, \"\"))) {\n const searchPath = path.substring(routePrefix.length - 1);\n const raw = await backend.grepRaw(pattern, searchPath || \"/\", glob);\n\n if (typeof raw === \"string\") {\n return raw;\n }\n\n // Add route prefix back\n return raw.map((m) => ({\n ...m,\n path: routePrefix.slice(0, -1) + m.path,\n }));\n }\n }\n\n // Otherwise, search default and all routed backends and merge\n const allMatches: GrepMatch[] = [];\n const rawDefault = await this.default.grepRaw(pattern, path, glob);\n\n if (typeof rawDefault === \"string\") {\n return rawDefault;\n }\n\n allMatches.push(...rawDefault);\n\n // Search all routes\n for (const [routePrefix, backend] of Object.entries(this.routes)) {\n const raw = await backend.grepRaw(pattern, \"/\", glob);\n\n if (typeof raw === \"string\") {\n return raw;\n }\n\n // Add route prefix back\n allMatches.push(\n ...raw.map((m) => ({\n ...m,\n path: routePrefix.slice(0, -1) + m.path,\n })),\n );\n }\n\n return allMatches;\n }\n\n /**\n * Structured glob matching returning FileInfo objects.\n */\n async globInfo(pattern: string, path: string = \"/\"): Promise<FileInfo[]> {\n const results: FileInfo[] = [];\n\n // Route based on path, not pattern\n for (const [routePrefix, backend] of this.sortedRoutes) {\n if (path.startsWith(routePrefix.replace(/\\/$/, \"\"))) {\n const searchPath = path.substring(routePrefix.length - 1);\n const infos = await backend.globInfo(pattern, searchPath || \"/\");\n\n // Add route prefix back\n return infos.map((fi) => ({\n ...fi,\n path: routePrefix.slice(0, -1) + fi.path,\n }));\n }\n }\n\n // Path doesn't match any specific route - search default backend AND all routed backends\n const defaultInfos = await this.default.globInfo(pattern, path);\n results.push(...defaultInfos);\n\n for (const [routePrefix, backend] of Object.entries(this.routes)) {\n const infos = await backend.globInfo(pattern, \"/\");\n results.push(\n ...infos.map((fi) => ({\n ...fi,\n path: routePrefix.slice(0, -1) + fi.path,\n })),\n );\n }\n\n // Deterministic ordering\n results.sort((a, b) => a.path.localeCompare(b.path));\n return results;\n }\n\n /**\n * Create a new file, routing to appropriate backend.\n *\n * @param filePath - Absolute file path\n * @param content - File content as string\n * @returns WriteResult with path or error\n */\n async write(filePath: string, content: string): Promise<WriteResult> {\n const [backend, strippedKey] = this.getBackendAndKey(filePath);\n return await backend.write(strippedKey, content);\n }\n\n /**\n * Edit a file, routing to appropriate backend.\n *\n * @param filePath - Absolute file path\n * @param oldString - String to find and replace\n * @param newString - Replacement string\n * @param replaceAll - If true, replace all occurrences\n * @returns EditResult with path, occurrences, or error\n */\n async edit(\n filePath: string,\n oldString: string,\n newString: string,\n replaceAll: boolean = false,\n ): Promise<EditResult> {\n const [backend, strippedKey] = this.getBackendAndKey(filePath);\n return await backend.edit(strippedKey, oldString, newString, replaceAll);\n }\n\n /**\n * Execute a command via the default backend.\n * Execution is not path-specific, so it always delegates to the default backend.\n *\n * @param command - Full shell command string to execute\n * @returns ExecuteResponse with combined output, exit code, and truncation flag\n * @throws Error if the default backend doesn't support command execution\n */\n execute(command: string): Promise<ExecuteResponse> {\n if (!isSandboxBackend(this.default)) {\n throw new Error(\n \"Default backend doesn't support command execution (SandboxBackendProtocol). \" +\n \"To enable execution, provide a default backend that implements SandboxBackendProtocol.\",\n );\n }\n return Promise.resolve(this.default.execute(command));\n }\n\n /**\n * Upload multiple files, batching by backend for efficiency.\n *\n * @param files - List of [path, content] tuples to upload\n * @returns List of FileUploadResponse objects, one per input file\n */\n async uploadFiles(\n files: Array<[string, Uint8Array]>,\n ): Promise<FileUploadResponse[]> {\n const results: Array<FileUploadResponse | null> = Array.from(\n { length: files.length },\n () => null,\n );\n const batchesByBackend = new Map<\n BackendProtocol,\n Array<{ idx: number; path: string; content: Uint8Array }>\n >();\n\n for (let idx = 0; idx < files.length; idx++) {\n const [path, content] = files[idx];\n const [backend, strippedPath] = this.getBackendAndKey(path);\n\n if (!batchesByBackend.has(backend)) {\n batchesByBackend.set(backend, []);\n }\n batchesByBackend.get(backend)!.push({ idx, path: strippedPath, content });\n }\n\n for (const [backend, batch] of batchesByBackend) {\n if (!backend.uploadFiles) {\n throw new Error(\"Backend does not support uploadFiles\");\n }\n\n const batchFiles = batch.map(\n (b) => [b.path, b.content] as [string, Uint8Array],\n );\n const batchResponses = await backend.uploadFiles(batchFiles);\n\n for (let i = 0; i < batch.length; i++) {\n const originalIdx = batch[i].idx;\n results[originalIdx] = {\n path: files[originalIdx][0], // Original path\n error: batchResponses[i]?.error ?? null,\n };\n }\n }\n\n return results as FileUploadResponse[];\n }\n\n /**\n * Download multiple files, batching by backend for efficiency.\n *\n * @param paths - List of file paths to download\n * @returns List of FileDownloadResponse objects, one per input path\n */\n async downloadFiles(paths: string[]): Promise<FileDownloadResponse[]> {\n const results: Array<FileDownloadResponse | null> = Array.from(\n { length: paths.length },\n () => null,\n );\n const batchesByBackend = new Map<\n BackendProtocol,\n Array<{ idx: number; path: string }>\n >();\n\n for (let idx = 0; idx < paths.length; idx++) {\n const path = paths[idx];\n const [backend, strippedPath] = this.getBackendAndKey(path);\n\n if (!batchesByBackend.has(backend)) {\n batchesByBackend.set(backend, []);\n }\n batchesByBackend.get(backend)!.push({ idx, path: strippedPath });\n }\n\n for (const [backend, batch] of batchesByBackend) {\n if (!backend.downloadFiles) {\n throw new Error(\"Backend does not support downloadFiles\");\n }\n\n const batchPaths = batch.map((b) => b.path);\n const batchResponses = await backend.downloadFiles(batchPaths);\n\n for (let i = 0; i < batch.length; i++) {\n const originalIdx = batch[i].idx;\n results[originalIdx] = {\n path: paths[originalIdx], // Original path\n content: batchResponses[i]?.content ?? null,\n error: batchResponses[i]?.error ?? null,\n };\n }\n }\n\n return results as FileDownloadResponse[];\n }\n}\n","/**\n * LocalShellBackend: Node.js implementation of the filesystem backend with unrestricted local shell execution.\n *\n * This backend extends FilesystemBackend to add shell command execution on the local\n * host system. It provides NO sandboxing or isolation - all operations run directly\n * on the host machine with full system access.\n *\n * @module\n */\n\nimport cp from \"node:child_process\";\nimport fs from \"node:fs/promises\";\nimport path from \"node:path\";\n\nimport fg from \"fast-glob\";\n\nimport { FilesystemBackend } from \"./filesystem.js\";\nimport type {\n EditResult,\n ExecuteResponse,\n FileInfo,\n SandboxBackendProtocol,\n} from \"./protocol.js\";\nimport { SandboxError } from \"./protocol.js\";\n\n/**\n * Options for creating a LocalShellBackend instance.\n */\nexport interface LocalShellBackendOptions {\n /**\n * Working directory for both filesystem operations and shell commands.\n * @defaultValue `process.cwd()`\n */\n rootDir?: string;\n\n /**\n * Enable virtual path mode for filesystem operations.\n * When true, treats rootDir as a virtual root filesystem.\n * Does NOT restrict shell commands.\n * @defaultValue `false`\n */\n virtualMode?: boolean;\n\n /**\n * Maximum time in seconds to wait for shell command execution.\n * Commands exceeding this timeout will be terminated.\n * @defaultValue `120`\n */\n timeout?: number;\n\n /**\n * Maximum number of bytes to capture from command output.\n * Output exceeding this limit will be truncated.\n * @defaultValue `100_000`\n */\n maxOutputBytes?: number;\n\n /**\n * Environment variables for shell commands. If undefined, starts with an empty\n * environment (unless inheritEnv is true).\n * @defaultValue `undefined`\n */\n env?: Record<string, string>;\n\n /**\n * Whether to inherit the parent process's environment variables.\n * When false, only variables in env dict are available.\n * When true, inherits all process.env variables and applies env overrides.\n * @defaultValue `false`\n */\n inheritEnv?: boolean;\n\n /**\n * Files to create on disk during `create()`.\n * Keys are file paths (resolved via the backend's path handling),\n * values are string content.\n * @defaultValue `undefined`\n */\n initialFiles?: Record<string, string>;\n}\n\n/**\n * Filesystem backend with unrestricted local shell command execution.\n *\n * This backend extends FilesystemBackend to add shell command execution\n * capabilities. Commands are executed directly on the host system without any\n * sandboxing, process isolation, or security restrictions.\n *\n * **Security Warning:**\n * This backend grants agents BOTH direct filesystem access AND unrestricted\n * shell execution on your local machine. Use with extreme caution and only in\n * appropriate environments.\n *\n * **Appropriate use cases:**\n * - Local development CLIs (coding assistants, development tools)\n * - Personal development environments where you trust the agent's code\n * - CI/CD pipelines with proper secret management\n *\n * **Inappropriate use cases:**\n * - Production environments (e.g., web servers, APIs, multi-tenant systems)\n * - Processing untrusted user input or executing untrusted code\n *\n * Use StateBackend, StoreBackend, or extend BaseSandbox for production.\n *\n * @example\n * ```typescript\n * import { LocalShellBackend } from \"@langchain/deepagents\";\n *\n * // Create backend with explicit environment\n * const backend = new LocalShellBackend({\n * rootDir: \"/home/user/project\",\n * env: { PATH: \"/usr/bin:/bin\" },\n * });\n *\n * // Execute shell commands (runs directly on host)\n * const result = await backend.execute(\"ls -la\");\n * console.log(result.output);\n * console.log(result.exitCode);\n *\n * // Use filesystem operations (inherited from FilesystemBackend)\n * const content = await backend.read(\"/README.md\");\n * await backend.write(\"/output.txt\", \"Hello world\");\n *\n * // Inherit all environment variables\n * const backend2 = new LocalShellBackend({\n * rootDir: \"/home/user/project\",\n * inheritEnv: true,\n * });\n * ```\n */\nexport class LocalShellBackend\n extends FilesystemBackend\n implements SandboxBackendProtocol\n{\n #timeout: number;\n #maxOutputBytes: number;\n #env: Record<string, string>;\n #sandboxId: string;\n #initialized = false;\n\n constructor(options: LocalShellBackendOptions = {}) {\n const {\n rootDir,\n virtualMode = false,\n timeout = 120,\n maxOutputBytes = 100_000,\n env,\n inheritEnv = false,\n } = options;\n\n super({ rootDir, virtualMode, maxFileSizeMb: 10 });\n\n this.#timeout = timeout;\n this.#maxOutputBytes = maxOutputBytes;\n const bytes = new Uint8Array(4);\n crypto.getRandomValues(bytes);\n this.#sandboxId = `local-${[...bytes].map((b) => b.toString(16).padStart(2, \"0\")).join(\"\")}`;\n\n if (inheritEnv) {\n this.#env = { ...process.env } as Record<string, string>;\n if (env) {\n Object.assign(this.#env, env);\n }\n } else {\n this.#env = env ?? {};\n }\n }\n\n /** Unique identifier for this backend instance (format: \"local-{random_hex}\"). */\n get id(): string {\n return this.#sandboxId;\n }\n\n /** Whether the backend has been initialized and is ready to use. */\n get isInitialized(): boolean {\n return this.#initialized;\n }\n\n /** Alias for `isInitialized`, matching the standard sandbox interface. */\n get isRunning(): boolean {\n return this.#initialized;\n }\n\n /**\n * Initialize the backend by ensuring the rootDir exists.\n *\n * Creates the rootDir (and any parent directories) if it does not already\n * exist. Safe to call on an existing directory. Must be called before\n * `execute()`, or use the static `LocalShellBackend.create()` factory.\n *\n * @throws {SandboxError} If already initialized (`ALREADY_INITIALIZED`)\n */\n async initialize(): Promise<void> {\n if (this.#initialized) {\n throw new SandboxError(\n \"Backend is already initialized. Each LocalShellBackend instance can only be initialized once.\",\n \"ALREADY_INITIALIZED\",\n );\n }\n await fs.mkdir(this.cwd, { recursive: true });\n this.#initialized = true;\n }\n\n /**\n * Mark the backend as no longer running.\n *\n * For local shell backends there is no remote resource to tear down,\n * so this simply flips the `isRunning` / `isInitialized` flag.\n */\n async close(): Promise<void> {\n this.#initialized = false;\n }\n\n /**\n * Read a file, adapting error messages to the standard sandbox format.\n */\n override async read(\n filePath: string,\n offset: number = 0,\n limit: number = 500,\n ): Promise<string> {\n const result = await super.read(filePath, offset, limit);\n if (\n typeof result === \"string\" &&\n result.startsWith(\"Error reading file\") &&\n result.includes(\"ENOENT\")\n ) {\n return `Error: File '${filePath}' not found`;\n }\n return result;\n }\n\n /**\n * Edit a file, adapting error messages to the standard sandbox format.\n */\n override async edit(\n filePath: string,\n oldString: string,\n newString: string,\n replaceAll: boolean = false,\n ): Promise<EditResult> {\n const result = await super.edit(filePath, oldString, newString, replaceAll);\n if (result.error?.includes(\"ENOENT\")) {\n return { ...result, error: `Error: File '${filePath}' not found` };\n }\n return result;\n }\n\n /**\n * List directory contents, returning paths relative to rootDir.\n */\n override async lsInfo(dirPath: string): Promise<FileInfo[]> {\n const results = await super.lsInfo(dirPath);\n if (this.virtualMode) {\n return results;\n }\n const cwdPrefix = this.cwd.endsWith(path.sep)\n ? this.cwd\n : this.cwd + path.sep;\n return results.map((info) => ({\n ...info,\n path: info.path.startsWith(cwdPrefix)\n ? info.path.slice(cwdPrefix.length)\n : info.path,\n }));\n }\n\n /**\n * Glob matching that returns relative paths and includes directories.\n */\n override async globInfo(\n pattern: string,\n searchPath: string = \"/\",\n ): Promise<FileInfo[]> {\n if (pattern.startsWith(\"/\")) {\n pattern = pattern.substring(1);\n }\n\n const resolvedSearchPath =\n searchPath === \"/\" || searchPath === \"\"\n ? this.cwd\n : this.virtualMode\n ? path.resolve(this.cwd, searchPath.replace(/^\\//, \"\"))\n : path.resolve(this.cwd, searchPath);\n\n try {\n const stat = await fs.stat(resolvedSearchPath);\n if (!stat.isDirectory()) return [];\n } catch {\n return [];\n }\n\n const formatPath = (rel: string) => (this.virtualMode ? `/${rel}` : rel);\n\n const globOpts = { cwd: resolvedSearchPath, absolute: false, dot: true };\n const [fileMatches, dirMatches] = await Promise.all([\n fg(pattern, { ...globOpts, onlyFiles: true }),\n fg(pattern, { ...globOpts, onlyDirectories: true }),\n ]);\n\n const statFile = async (match: string): Promise<FileInfo | null> => {\n try {\n const entryStat = await fs.stat(path.join(resolvedSearchPath, match));\n if (entryStat.isFile()) {\n return {\n path: formatPath(match),\n is_dir: false,\n size: entryStat.size,\n modified_at: entryStat.mtime.toISOString(),\n };\n }\n } catch {\n /* skip unstatable entries */\n }\n return null;\n };\n\n const statDir = async (match: string): Promise<FileInfo | null> => {\n try {\n const entryStat = await fs.stat(path.join(resolvedSearchPath, match));\n if (entryStat.isDirectory()) {\n return {\n path: formatPath(match),\n is_dir: true,\n size: 0,\n modified_at: entryStat.mtime.toISOString(),\n };\n }\n } catch {\n /* skip unstatable entries */\n }\n return null;\n };\n\n const [fileInfos, dirInfos] = await Promise.all([\n Promise.all(fileMatches.map(statFile)),\n Promise.all(dirMatches.map(statDir)),\n ]);\n\n const results = [...fileInfos, ...dirInfos].filter(\n (info): info is FileInfo => info !== null,\n );\n results.sort((a, b) => a.path.localeCompare(b.path));\n return results;\n }\n\n /**\n * Execute a shell command directly on the host system.\n *\n * Commands are executed directly on your host system using `spawn()`\n * with `shell: true`. There is NO sandboxing, isolation, or security\n * restrictions. The command runs with your user's full permissions.\n *\n * The command is executed using the system shell with the working directory\n * set to the backend's rootDir. Stdout and stderr are combined into a single\n * output stream, with stderr lines prefixed with `[stderr]`.\n *\n * @param command - Shell command string to execute\n * @returns ExecuteResponse containing output, exit code, and truncation flag\n */\n async execute(command: string): Promise<ExecuteResponse> {\n if (!command || typeof command !== \"string\") {\n return {\n output: \"Error: Command must be a non-empty string.\",\n exitCode: 1,\n truncated: false,\n };\n }\n\n return new Promise<ExecuteResponse>((resolve) => {\n let stdout = \"\";\n let stderr = \"\";\n let timedOut = false;\n\n const child = cp.spawn(command, {\n shell: true,\n env: this.#env,\n cwd: this.cwd,\n });\n\n const timer = setTimeout(() => {\n timedOut = true;\n child.kill(\"SIGTERM\");\n }, this.#timeout * 1000);\n\n child.stdout.on(\"data\", (data: Buffer) => {\n stdout += data.toString();\n });\n\n child.stderr.on(\"data\", (data: Buffer) => {\n stderr += data.toString();\n });\n\n child.on(\"error\", (err) => {\n clearTimeout(timer);\n resolve({\n output: `Error executing command: ${err.message}`,\n exitCode: 1,\n truncated: false,\n });\n });\n\n child.on(\"close\", (code, signal) => {\n clearTimeout(timer);\n\n if (timedOut || signal === \"SIGTERM\") {\n resolve({\n output: `Error: Command timed out after ${this.#timeout.toFixed(1)} seconds.`,\n exitCode: 124,\n truncated: false,\n });\n return;\n }\n\n const outputParts: string[] = [];\n if (stdout) {\n outputParts.push(stdout);\n }\n if (stderr) {\n const stderrLines = stderr.trim().split(\"\\n\");\n outputParts.push(\n ...stderrLines.map((line: string) => `[stderr] ${line}`),\n );\n }\n\n let output =\n outputParts.length > 0 ? outputParts.join(\"\\n\") : \"<no output>\";\n\n let truncated = false;\n if (output.length > this.#maxOutputBytes) {\n output = output.slice(0, this.#maxOutputBytes);\n output += `\\n\\n... Output truncated at ${this.#maxOutputBytes} bytes.`;\n truncated = true;\n }\n\n const exitCode = code ?? 1;\n\n if (exitCode !== 0) {\n output = `${output.trimEnd()}\\n\\nExit code: ${exitCode}`;\n }\n\n resolve({\n output,\n exitCode,\n truncated,\n });\n });\n });\n }\n\n /**\n * Create and initialize a new LocalShellBackend in one step.\n *\n * This is the recommended way to create a backend when the rootDir may\n * not exist yet. It combines construction and initialization (ensuring\n * rootDir exists) into a single async operation.\n *\n * @param options - Configuration options for the backend\n * @returns An initialized and ready-to-use backend\n */\n static async create(\n options: LocalShellBackendOptions = {},\n ): Promise<LocalShellBackend> {\n const { initialFiles, ...backendOptions } = options;\n const backend = new LocalShellBackend(backendOptions);\n await backend.initialize();\n\n if (initialFiles) {\n const encoder = new TextEncoder();\n const files: Array<[string, Uint8Array]> = Object.entries(\n initialFiles,\n ).map(([filePath, content]) => [filePath, encoder.encode(content)]);\n await backend.uploadFiles(files);\n }\n\n return backend;\n }\n}\n","/**\n * BaseSandbox: Abstract base class for sandbox backends with command execution.\n *\n * This class provides default implementations for all SandboxBackendProtocol\n * methods. Concrete implementations only need to implement execute(),\n * uploadFiles(), and downloadFiles().\n *\n * Runtime requirements on the sandbox host:\n * - read, grep: Pure POSIX shell (awk, grep) — works on any Linux including Alpine\n * - write, edit, readRaw: No runtime needed — uses uploadFiles/downloadFiles directly\n * - ls, glob: Pure POSIX shell (find, stat) — works on any Linux including Alpine\n *\n * No Python, Node.js, or other runtime required.\n */\n\nimport type {\n EditResult,\n ExecuteResponse,\n FileData,\n FileDownloadResponse,\n FileInfo,\n FileUploadResponse,\n GrepMatch,\n MaybePromise,\n SandboxBackendProtocol,\n WriteResult,\n} from \"./protocol.js\";\n\n/**\n * Shell-quote a string using single quotes (POSIX).\n * Escapes embedded single quotes with the '\\'' technique.\n */\nfunction shellQuote(s: string): string {\n return \"'\" + s.replace(/'/g, \"'\\\\''\") + \"'\";\n}\n\n/**\n * Convert a glob pattern to a path-aware RegExp.\n *\n * Inspired by the just-bash project's glob utilities:\n * - `*` matches any characters except `/`\n * - `**` matches any characters including `/` (recursive)\n * - `?` matches a single character except `/`\n * - `[...]` character classes\n */\nfunction globToPathRegex(pattern: string): RegExp {\n let regex = \"^\";\n let i = 0;\n\n while (i < pattern.length) {\n const c = pattern[i];\n\n if (c === \"*\") {\n if (i + 1 < pattern.length && pattern[i + 1] === \"*\") {\n // ** (globstar) matches everything including /\n i += 2;\n if (i < pattern.length && pattern[i] === \"/\") {\n // **/ matches zero or more directory segments\n regex += \"(.*/)?\";\n i++;\n } else {\n // ** at end matches anything\n regex += \".*\";\n }\n } else {\n // * matches anything except /\n regex += \"[^/]*\";\n i++;\n }\n } else if (c === \"?\") {\n regex += \"[^/]\";\n i++;\n } else if (c === \"[\") {\n // Character class — find closing bracket\n let j = i + 1;\n while (j < pattern.length && pattern[j] !== \"]\") j++;\n regex += pattern.slice(i, j + 1);\n i = j + 1;\n } else if (\n c === \".\" ||\n c === \"+\" ||\n c === \"^\" ||\n c === \"$\" ||\n c === \"{\" ||\n c === \"}\" ||\n c === \"(\" ||\n c === \")\" ||\n c === \"|\" ||\n c === \"\\\\\"\n ) {\n regex += `\\\\${c}`;\n i++;\n } else {\n regex += c;\n i++;\n }\n }\n\n regex += \"$\";\n return new RegExp(regex);\n}\n\n/**\n * Parse a single line of stat/find output in the format: size\\tmtime\\ttype\\tpath\n *\n * The first three tab-delimited fields are always fixed (number, number, string),\n * so we safely take everything after the third tab as the file path — even if the\n * path itself contains tabs.\n *\n * The type field varies by platform / tool:\n * - GNU find -printf %y: single letter \"d\", \"f\", \"l\"\n * - BSD stat -f %Sp: permission strings like \"drwxr-xr-x\", \"-rw-r--r--\"\n *\n * The mtime field may be a float (GNU find %T@ → \"1234567890.0000000000\")\n * or an integer (BSD stat %m → \"1234567890\"); parseInt handles both.\n */\nfunction parseStatLine(\n line: string,\n): { size: number; mtime: number; isDir: boolean; fullPath: string } | null {\n const firstTab = line.indexOf(\"\\t\");\n if (firstTab === -1) return null;\n\n const secondTab = line.indexOf(\"\\t\", firstTab + 1);\n if (secondTab === -1) return null;\n\n const thirdTab = line.indexOf(\"\\t\", secondTab + 1);\n if (thirdTab === -1) return null;\n\n const size = parseInt(line.slice(0, firstTab), 10);\n const mtime = parseInt(line.slice(firstTab + 1, secondTab), 10);\n const fileType = line.slice(secondTab + 1, thirdTab);\n const fullPath = line.slice(thirdTab + 1);\n\n if (isNaN(size) || isNaN(mtime)) return null;\n\n return {\n size,\n mtime,\n // GNU find %y outputs \"d\"; BSD stat %Sp outputs \"drwxr-xr-x\"\n isDir:\n fileType === \"d\" || fileType === \"directory\" || fileType.startsWith(\"d\"),\n fullPath,\n };\n}\n\n/**\n * BusyBox/Alpine fallback script for stat -c.\n *\n * Determines file type with POSIX test builtins, then uses stat -c\n * (supported by both GNU coreutils and BusyBox) for size and mtime.\n * printf handles tab-delimited output formatting.\n */\nconst STAT_C_SCRIPT =\n \"for f; do \" +\n 'if [ -d \"$f\" ]; then t=d; elif [ -L \"$f\" ]; then t=l; else t=f; fi; ' +\n 'sz=$(stat -c %s \"$f\" 2>/dev/null) || continue; ' +\n 'mt=$(stat -c %Y \"$f\" 2>/dev/null) || continue; ' +\n 'printf \"%s\\\\t%s\\\\t%s\\\\t%s\\\\n\" \"$sz\" \"$mt\" \"$t\" \"$f\"; ' +\n \"done\";\n\n/**\n * Shell command for listing directory contents with metadata.\n *\n * Detects the environment at runtime with three-way probing:\n * 1. GNU find (full Linux): uses built-in `-printf` (most efficient)\n * 2. BusyBox / Alpine: uses `find -exec sh -c` with `stat -c` fallback\n * 3. BSD / macOS: uses `find -exec stat -f`\n *\n * Output format per line: size\\tmtime\\ttype\\tpath\n */\nfunction buildLsCommand(dirPath: string): string {\n const quotedPath = shellQuote(dirPath);\n const findBase = `find ${quotedPath} -maxdepth 1 -not -path ${quotedPath}`;\n return (\n `if find /dev/null -maxdepth 0 -printf '' 2>/dev/null; then ` +\n `${findBase} -printf '%s\\\\t%T@\\\\t%y\\\\t%p\\\\n' 2>/dev/null; ` +\n `elif stat -c %s /dev/null >/dev/null 2>&1; then ` +\n `${findBase} -exec sh -c '${STAT_C_SCRIPT}' _ {} +; ` +\n `else ` +\n `${findBase} -exec stat -f '%z\\t%m\\t%Sp\\t%N' {} + 2>/dev/null; ` +\n `fi || true`\n );\n}\n\n/**\n * Shell command for listing files recursively with metadata.\n * Same three-way detection as buildLsCommand (GNU -printf / stat -c / BSD stat -f).\n *\n * Output format per line: size\\tmtime\\ttype\\tpath\n */\nfunction buildFindCommand(searchPath: string): string {\n const quotedPath = shellQuote(searchPath);\n const findBase = `find ${quotedPath} -not -path ${quotedPath}`;\n return (\n `if find /dev/null -maxdepth 0 -printf '' 2>/dev/null; then ` +\n `${findBase} -printf '%s\\\\t%T@\\\\t%y\\\\t%p\\\\n' 2>/dev/null; ` +\n `elif stat -c %s /dev/null >/dev/null 2>&1; then ` +\n `${findBase} -exec sh -c '${STAT_C_SCRIPT}' _ {} +; ` +\n `else ` +\n `${findBase} -exec stat -f '%z\\t%m\\t%Sp\\t%N' {} + 2>/dev/null; ` +\n `fi || true`\n );\n}\n\n/**\n * Pure POSIX shell command for reading files with line numbers.\n * Uses awk for line numbering with offset/limit — works on any Linux including Alpine.\n */\nfunction buildReadCommand(\n filePath: string,\n offset: number,\n limit: number,\n): string {\n const quotedPath = shellQuote(filePath);\n // Coerce offset and limit to safe non-negative integers.\n const safeOffset =\n Number.isFinite(offset) && offset > 0 ? Math.floor(offset) : 0;\n const safeLimit =\n Number.isFinite(limit) && limit > 0\n ? Math.min(Math.floor(limit), 999_999_999)\n : 999_999_999;\n // awk NR is 1-based, our offset is 0-based\n const start = safeOffset + 1;\n const end = safeOffset + safeLimit;\n\n return [\n `if [ ! -f ${quotedPath} ]; then echo \"Error: File not found\"; exit 1; fi`,\n `if [ ! -s ${quotedPath} ]; then echo \"System reminder: File exists but has empty contents\"; exit 0; fi`,\n `awk 'NR >= ${start} && NR <= ${end} { printf \"%6d\\\\t%s\\\\n\", NR, $0 }' ${quotedPath}`,\n ].join(\"; \");\n}\n\n/**\n * Build a grep command for literal (fixed-string) search.\n * Uses grep -rHnF for recursive, with-filename, with-line-number, fixed-string search.\n *\n * When a glob pattern is provided, uses `find -name GLOB -exec grep` instead of\n * `grep --include=GLOB` for universal compatibility (BusyBox grep lacks --include).\n *\n * @param pattern - Literal string to search for (NOT regex).\n * @param searchPath - Base path to search in.\n * @param globPattern - Optional glob pattern to filter files.\n */\nfunction buildGrepCommand(\n pattern: string,\n searchPath: string,\n globPattern: string | null,\n): string {\n const patternEscaped = shellQuote(pattern);\n const searchPathQuoted = shellQuote(searchPath);\n\n if (globPattern) {\n // Use find + grep for BusyBox compatibility (BusyBox grep lacks --include)\n const globEscaped = shellQuote(globPattern);\n return `find ${searchPathQuoted} -type f -name ${globEscaped} -exec grep -HnF -e ${patternEscaped} {} + 2>/dev/null || true`;\n }\n\n return `grep -rHnF -e ${patternEscaped} ${searchPathQuoted} 2>/dev/null || true`;\n}\n\n/**\n * Base sandbox implementation with execute() as the only abstract method.\n *\n * This class provides default implementations for all SandboxBackendProtocol\n * methods using shell commands executed via execute(). Concrete implementations\n * only need to implement execute(), uploadFiles(), and downloadFiles().\n *\n * All shell commands use pure POSIX utilities (awk, grep, find, stat) that are\n * available on any Linux including Alpine/busybox. No Python, Node.js, or\n * other runtime is required on the sandbox host.\n */\nexport abstract class BaseSandbox implements SandboxBackendProtocol {\n /** Unique identifier for the sandbox backend */\n abstract readonly id: string;\n\n /**\n * Execute a command in the sandbox.\n * This is the only method concrete implementations must provide.\n */\n abstract execute(command: string): MaybePromise<ExecuteResponse>;\n\n /**\n * Upload multiple files to the sandbox.\n * Implementations must support partial success.\n */\n abstract uploadFiles(\n files: Array<[string, Uint8Array]>,\n ): MaybePromise<FileUploadResponse[]>;\n\n /**\n * Download multiple files from the sandbox.\n * Implementations must support partial success.\n */\n abstract downloadFiles(paths: string[]): MaybePromise<FileDownloadResponse[]>;\n\n /**\n * List files and directories in the specified directory (non-recursive).\n *\n * Uses pure POSIX shell (find + stat) via execute() — works on any Linux\n * including Alpine. No Python or Node.js needed.\n *\n * @param path - Absolute path to directory\n * @returns List of FileInfo objects for files and directories directly in the directory.\n */\n async lsInfo(path: string): Promise<FileInfo[]> {\n const command = buildLsCommand(path);\n const result = await this.execute(command);\n\n const infos: FileInfo[] = [];\n const lines = result.output.trim().split(\"\\n\").filter(Boolean);\n\n for (const line of lines) {\n const parsed = parseStatLine(line);\n if (!parsed) continue;\n\n infos.push({\n path: parsed.isDir ? parsed.fullPath + \"/\" : parsed.fullPath,\n is_dir: parsed.isDir,\n size: parsed.size,\n modified_at: new Date(parsed.mtime * 1000).toISOString(),\n });\n }\n\n return infos;\n }\n\n /**\n * Read file content with line numbers.\n *\n * Uses pure POSIX shell (awk) via execute() — only the requested slice\n * is returned over the wire, making this efficient for large files.\n * Works on any Linux including Alpine (no Python or Node.js needed).\n *\n * @param filePath - Absolute file path\n * @param offset - Line offset to start reading from (0-indexed)\n * @param limit - Maximum number of lines to read\n * @returns Formatted file content with line numbers, or error message\n */\n async read(\n filePath: string,\n offset: number = 0,\n limit: number = 500,\n ): Promise<string> {\n // limit=0 means return nothing\n if (limit === 0) return \"\";\n\n const command = buildReadCommand(filePath, offset, limit);\n const result = await this.execute(command);\n\n if (result.exitCode !== 0) {\n return `Error: File '${filePath}' not found`;\n }\n\n return result.output;\n }\n\n /**\n * Read file content as raw FileData.\n *\n * Uses downloadFiles() directly — no runtime needed on the sandbox host.\n *\n * @param filePath - Absolute file path\n * @returns Raw file content as FileData\n */\n async readRaw(filePath: string): Promise<FileData> {\n const results = await this.downloadFiles([filePath]);\n if (results[0].error || !results[0].content) {\n throw new Error(`File '${filePath}' not found`);\n }\n\n const content = new TextDecoder().decode(results[0].content);\n const lines = content.split(\"\\n\");\n\n const now = new Date().toISOString();\n return {\n content: lines,\n created_at: now,\n modified_at: now,\n };\n }\n\n /**\n * Search for a literal text pattern in files using grep.\n *\n * @param pattern - Literal string to search for (NOT regex).\n * @param path - Directory or file path to search in.\n * @param glob - Optional glob pattern to filter which files to search.\n * @returns List of GrepMatch dicts containing path, line number, and matched text.\n */\n async grepRaw(\n pattern: string,\n path: string = \"/\",\n glob: string | null = null,\n ): Promise<GrepMatch[] | string> {\n const command = buildGrepCommand(pattern, path, glob);\n const result = await this.execute(command);\n\n const output = result.output.trim();\n if (!output) {\n return [];\n }\n\n // Parse grep output format: path:line_number:text\n const matches: GrepMatch[] = [];\n for (const line of output.split(\"\\n\")) {\n const parts = line.split(\":\");\n if (parts.length >= 3) {\n const lineNum = parseInt(parts[1], 10);\n if (!isNaN(lineNum)) {\n matches.push({\n path: parts[0],\n line: lineNum,\n text: parts.slice(2).join(\":\"),\n });\n }\n }\n }\n\n return matches;\n }\n\n /**\n * Structured glob matching returning FileInfo objects.\n *\n * Uses pure POSIX shell (find + stat) via execute() to list all files,\n * then applies glob-to-regex matching in TypeScript. No Python or Node.js\n * needed on the sandbox host.\n *\n * Glob patterns are matched against paths relative to the search base:\n * - `*` matches any characters except `/`\n * - `**` matches any characters including `/` (recursive)\n * - `?` matches a single character except `/`\n * - `[...]` character classes\n */\n async globInfo(pattern: string, path: string = \"/\"): Promise<FileInfo[]> {\n const command = buildFindCommand(path);\n const result = await this.execute(command);\n\n const regex = globToPathRegex(pattern);\n const infos: FileInfo[] = [];\n const lines = result.output.trim().split(\"\\n\").filter(Boolean);\n\n // Normalise base path (strip trailing /)\n const basePath = path.endsWith(\"/\") ? path.slice(0, -1) : path;\n\n for (const line of lines) {\n const parsed = parseStatLine(line);\n if (!parsed) continue;\n\n // Compute path relative to the search base\n const relPath = parsed.fullPath.startsWith(basePath + \"/\")\n ? parsed.fullPath.slice(basePath.length + 1)\n : parsed.fullPath;\n\n if (regex.test(relPath)) {\n infos.push({\n path: relPath,\n is_dir: parsed.isDir,\n size: parsed.size,\n modified_at: new Date(parsed.mtime * 1000).toISOString(),\n });\n }\n }\n\n return infos;\n }\n\n /**\n * Create a new file with content.\n *\n * Uses downloadFiles() to check existence and uploadFiles() to write.\n * No runtime needed on the sandbox host.\n */\n async write(filePath: string, content: string): Promise<WriteResult> {\n // Check if file already exists\n try {\n const existCheck = await this.downloadFiles([filePath]);\n if (existCheck[0].content !== null && existCheck[0].error === null) {\n return {\n error: `Cannot write to ${filePath} because it already exists. Read and then make an edit, or write to a new path.`,\n };\n }\n } catch {\n // File doesn't exist, which is what we want for write\n }\n\n const encoder = new TextEncoder();\n const results = await this.uploadFiles([\n [filePath, encoder.encode(content)],\n ]);\n\n if (results[0].error) {\n return {\n error: `Failed to write to ${filePath}: ${results[0].error}`,\n };\n }\n\n return { path: filePath, filesUpdate: null };\n }\n\n /**\n * Edit a file by replacing string occurrences.\n *\n * Uses downloadFiles() to read, performs string replacement in TypeScript,\n * then uploadFiles() to write back. No runtime needed on the sandbox host.\n */\n async edit(\n filePath: string,\n oldString: string,\n newString: string,\n replaceAll: boolean = false,\n ): Promise<EditResult> {\n // Read the file\n const results = await this.downloadFiles([filePath]);\n if (results[0].error || !results[0].content) {\n return { error: `Error: File '${filePath}' not found` };\n }\n\n const text = new TextDecoder().decode(results[0].content);\n const count = text.split(oldString).length - 1;\n\n if (count === 0) {\n return { error: `String not found in file '${filePath}'` };\n }\n if (count > 1 && !replaceAll) {\n return {\n error: `Multiple occurrences found in '${filePath}'. Use replaceAll=true to replace all.`,\n };\n }\n\n // Perform replacement\n const newText = replaceAll\n ? text.split(oldString).join(newString)\n : text.replace(oldString, newString);\n\n // Write back\n const encoder = new TextEncoder();\n const uploadResults = await this.uploadFiles([\n [filePath, encoder.encode(newText)],\n ]);\n\n if (uploadResults[0].error) {\n return {\n error: `Failed to write edited file '${filePath}': ${uploadResults[0].error}`,\n };\n }\n\n return { path: filePath, filesUpdate: null, occurrences: count };\n }\n}\n","import {\n createAgent,\n humanInTheLoopMiddleware,\n anthropicPromptCachingMiddleware,\n todoListMiddleware,\n SystemMessage,\n type AgentMiddleware,\n} from \"langchain\";\nimport type {\n ClientTool,\n ServerTool,\n StructuredTool,\n} from \"@langchain/core/tools\";\nimport { Runnable } from \"@langchain/core/runnables\";\nimport type { BaseStore } from \"@langchain/langgraph-checkpoint\";\n\nimport {\n createFilesystemMiddleware,\n createSubAgentMiddleware,\n createPatchToolCallsMiddleware,\n createSummarizationMiddleware,\n createMemoryMiddleware,\n createSkillsMiddleware,\n type SubAgent,\n} from \"./middleware/index.js\";\nimport { StateBackend } from \"./backends/index.js\";\nimport { InteropZodObject } from \"@langchain/core/utils/types\";\nimport { CompiledSubAgent } from \"./middleware/subagents.js\";\nimport type {\n CreateDeepAgentParams,\n DeepAgent,\n DeepAgentTypeConfig,\n FlattenSubAgentMiddleware,\n InferStructuredResponse,\n SupportedResponseFormat,\n} from \"./types.js\";\n\n/**\n * required for type inference\n */\nimport type * as _messages from \"@langchain/core/messages\";\nimport type * as _Command from \"@langchain/langgraph\";\n\nconst BASE_PROMPT = `In order to complete the objective that the user asks of you, you have access to a number of standard tools.`;\n\n/**\n * Create a Deep Agent with middleware-based architecture.\n *\n * Matches Python's create_deep_agent function, using middleware for all features:\n * - Todo management (todoListMiddleware)\n * - Filesystem tools (createFilesystemMiddleware)\n * - Subagent delegation (createSubAgentMiddleware)\n * - Conversation summarization (createSummarizationMiddleware) with backend offloading\n * - Prompt caching (anthropicPromptCachingMiddleware)\n * - Tool call patching (createPatchToolCallsMiddleware)\n * - Human-in-the-loop (humanInTheLoopMiddleware) - optional\n *\n * @param params Configuration parameters for the agent\n * @returns ReactAgent instance ready for invocation with properly inferred state types\n *\n * @example\n * ```typescript\n * // Middleware with custom state\n * const ResearchMiddleware = createMiddleware({\n * name: \"ResearchMiddleware\",\n * stateSchema: z.object({ research: z.string().default(\"\") }),\n * });\n *\n * const agent = createDeepAgent({\n * middleware: [ResearchMiddleware],\n * });\n *\n * const result = await agent.invoke({ messages: [...] });\n * // result.research is properly typed as string\n * ```\n */\nexport function createDeepAgent<\n TResponse extends SupportedResponseFormat = SupportedResponseFormat,\n ContextSchema extends InteropZodObject = InteropZodObject,\n const TMiddleware extends readonly AgentMiddleware[] = readonly [],\n const TSubagents extends readonly (SubAgent | CompiledSubAgent)[] =\n readonly [],\n const TTools extends readonly (ClientTool | ServerTool)[] = readonly [],\n>(\n params: CreateDeepAgentParams<\n TResponse,\n ContextSchema,\n TMiddleware,\n TSubagents,\n TTools\n > = {} as CreateDeepAgentParams<\n TResponse,\n ContextSchema,\n TMiddleware,\n TSubagents,\n TTools\n >,\n) {\n const {\n model = \"claude-sonnet-4-5-20250929\",\n tools = [],\n systemPrompt,\n middleware: customMiddleware = [],\n subagents = [],\n responseFormat,\n contextSchema,\n checkpointer,\n store,\n backend,\n interruptOn,\n name,\n memory,\n skills,\n } = params;\n\n /**\n * Combine system prompt with base prompt like Python implementation\n */\n const finalSystemPrompt = systemPrompt\n ? typeof systemPrompt === \"string\"\n ? `${systemPrompt}\\n\\n${BASE_PROMPT}`\n : new SystemMessage({\n content: [\n {\n type: \"text\",\n text: BASE_PROMPT,\n },\n ...(typeof systemPrompt.content === \"string\"\n ? [{ type: \"text\", text: systemPrompt.content }]\n : systemPrompt.content),\n ],\n })\n : BASE_PROMPT;\n\n /**\n * Create backend configuration for filesystem middleware\n * If no backend is provided, use a factory that creates a StateBackend\n */\n const filesystemBackend = backend\n ? backend\n : (config: { state: unknown; store?: BaseStore }) =>\n new StateBackend(config);\n\n /**\n * Skills middleware (created conditionally for runtime use)\n */\n const skillsMiddlewareArray =\n skills != null && skills.length > 0\n ? [\n createSkillsMiddleware({\n backend: filesystemBackend,\n sources: skills,\n }),\n ]\n : [];\n\n /**\n * Memory middleware (created conditionally for runtime use)\n */\n const memoryMiddlewareArray =\n memory != null && memory.length > 0\n ? [\n createMemoryMiddleware({\n backend: filesystemBackend,\n sources: memory,\n }),\n ]\n : [];\n\n /**\n * Process subagents to add SkillsMiddleware for those with their own skills.\n *\n * Custom subagents do NOT inherit skills from the main agent by default.\n * Only the general-purpose subagent inherits the main agent's skills (via defaultMiddleware).\n * If a custom subagent needs skills, it must specify its own `skills` array.\n */\n const processedSubagents = subagents.map((subagent) => {\n /**\n * CompiledSubAgent - use as-is (already has its own middleware baked in)\n */\n if (Runnable.isRunnable(subagent)) {\n return subagent;\n }\n\n /**\n * SubAgent without skills - use as-is\n */\n if (!(\"skills\" in subagent) || subagent.skills?.length === 0) {\n return subagent;\n }\n\n /**\n * SubAgent with skills - add SkillsMiddleware BEFORE user's middleware\n * Order: base middleware (via defaultMiddleware) → skills → user's middleware\n * This matches Python's ordering in create_deep_agent\n */\n const subagentSkillsMiddleware = createSkillsMiddleware({\n backend: filesystemBackend,\n sources: subagent.skills ?? [],\n });\n\n return {\n ...subagent,\n middleware: [\n subagentSkillsMiddleware,\n ...(subagent.middleware || []),\n ] as readonly AgentMiddleware[],\n };\n });\n\n /**\n * Middleware for custom subagents (does NOT include skills from main agent).\n * Custom subagents must define their own `skills` property to get skills.\n *\n * Uses createSummarizationMiddleware (deepagents version) with backend support\n * and auto-computed defaults from model profile, matching Python's create_deep_agent.\n * When trigger is not provided, defaults are lazily computed:\n * - With model profile: fraction-based (trigger=0.85, keep=0.10)\n * - Without profile: fixed (trigger=170k tokens, keep=6 messages)\n */\n const subagentMiddleware = [\n todoListMiddleware(),\n createFilesystemMiddleware({\n backend: filesystemBackend,\n }),\n createSummarizationMiddleware({\n model,\n backend: filesystemBackend,\n }),\n anthropicPromptCachingMiddleware({\n unsupportedModelBehavior: \"ignore\",\n }),\n createPatchToolCallsMiddleware(),\n ];\n\n /**\n * Built-in middleware array - core middleware with known types\n * This tuple is typed without conditional spreads to preserve TypeScript's tuple inference.\n * Optional middleware (skills, memory, HITL) are handled at runtime but typed explicitly.\n */\n const builtInMiddleware = [\n /**\n * Provides todo list management capabilities for tracking tasks\n */\n todoListMiddleware(),\n /**\n * Enables filesystem operations and optional long-term memory storage\n */\n createFilesystemMiddleware({ backend: filesystemBackend }),\n /**\n * Enables delegation to specialized subagents for complex tasks\n */\n createSubAgentMiddleware({\n defaultModel: model,\n defaultTools: tools as StructuredTool[],\n /**\n * Custom subagents must define their own `skills` property to get skills.\n */\n defaultMiddleware: subagentMiddleware,\n /**\n * Middleware for the general-purpose subagent (inherits skills from main agent).\n */\n generalPurposeMiddleware: [\n ...subagentMiddleware,\n ...skillsMiddlewareArray,\n ],\n defaultInterruptOn: interruptOn,\n subagents: processedSubagents,\n generalPurposeAgent: true,\n }),\n /**\n * Automatically summarizes conversation history when token limits are approached.\n * Uses createSummarizationMiddleware (deepagents version) with backend support\n * for conversation history offloading and auto-computed defaults from model profile.\n */\n createSummarizationMiddleware({\n model,\n backend: filesystemBackend,\n }),\n /**\n * Enables Anthropic prompt caching for improved performance and reduced costs\n */\n anthropicPromptCachingMiddleware({\n unsupportedModelBehavior: \"ignore\",\n }),\n /**\n * Patches tool calls to ensure compatibility across different model providers\n */\n createPatchToolCallsMiddleware(),\n ] as const;\n\n /**\n * Runtime middleware array: combine built-in + optional middleware\n * Note: The type is handled separately via AllMiddleware type alias\n */\n const runtimeMiddleware: AgentMiddleware[] = [\n ...builtInMiddleware,\n ...skillsMiddlewareArray,\n ...memoryMiddlewareArray,\n ...(interruptOn ? [humanInTheLoopMiddleware({ interruptOn })] : []),\n ...(customMiddleware as unknown as AgentMiddleware[]),\n ];\n\n const agent = createAgent({\n model,\n systemPrompt: finalSystemPrompt,\n tools: tools as StructuredTool[],\n middleware: runtimeMiddleware,\n ...(responseFormat != null && { responseFormat }),\n contextSchema,\n checkpointer,\n store,\n name,\n }).withConfig({ recursionLimit: 10_000 });\n\n /**\n * Combine custom middleware with flattened subagent middleware for complete type inference\n * This ensures InferMiddlewareStates captures state from both sources\n */\n type AllMiddleware = readonly [\n ...typeof builtInMiddleware,\n ...TMiddleware,\n ...FlattenSubAgentMiddleware<TSubagents>,\n ];\n\n /**\n * Return as DeepAgent with proper DeepAgentTypeConfig\n * - Response: InferStructuredResponse<TResponse> (unwraps ToolStrategy<T>/ProviderStrategy<T> → T)\n * - State: undefined (state comes from middleware)\n * - Context: ContextSchema\n * - Middleware: AllMiddleware (built-in + custom + subagent middleware for state inference)\n * - Tools: TTools\n * - Subagents: TSubagents (for type-safe streaming)\n */\n return agent as unknown as DeepAgent<\n DeepAgentTypeConfig<\n InferStructuredResponse<TResponse>,\n undefined,\n ContextSchema,\n AllMiddleware,\n TTools,\n TSubagents\n >\n >;\n}\n","/**\n * Configuration and settings for deepagents.\n *\n * Provides project detection, path management, and environment configuration\n * for skills and agent memory middleware.\n */\n\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport os from \"node:os\";\n\n/**\n * Options for creating a Settings instance.\n */\nexport interface SettingsOptions {\n /** Starting directory for project detection (defaults to cwd) */\n startPath?: string;\n}\n\n/**\n * Settings interface for project detection and path management.\n *\n * Provides access to:\n * - Project root detection (via .git directory)\n * - User-level deepagents directory (~/.deepagents)\n * - Agent-specific directories and files\n * - Skills directories (user and project level)\n */\nexport interface Settings {\n /** Detected project root directory, or null if not in a git project */\n readonly projectRoot: string | null;\n\n /** Base user-level .deepagents directory (~/.deepagents) */\n readonly userDeepagentsDir: string;\n\n /** Check if currently in a git project */\n readonly hasProject: boolean;\n\n /**\n * Get the agent directory path.\n * @param agentName - Name of the agent\n * @returns Path to ~/.deepagents/{agentName}\n * @throws Error if agent name is invalid\n */\n getAgentDir(agentName: string): string;\n\n /**\n * Ensure agent directory exists and return path.\n * @param agentName - Name of the agent\n * @returns Path to ~/.deepagents/{agentName}\n * @throws Error if agent name is invalid\n */\n ensureAgentDir(agentName: string): string;\n\n /**\n * Get user-level agent.md path for a specific agent.\n * @param agentName - Name of the agent\n * @returns Path to ~/.deepagents/{agentName}/agent.md\n */\n getUserAgentMdPath(agentName: string): string;\n\n /**\n * Get project-level agent.md path.\n * @returns Path to {projectRoot}/.deepagents/agent.md, or null if not in a project\n */\n getProjectAgentMdPath(): string | null;\n\n /**\n * Get user-level skills directory path for a specific agent.\n * @param agentName - Name of the agent\n * @returns Path to ~/.deepagents/{agentName}/skills/\n */\n getUserSkillsDir(agentName: string): string;\n\n /**\n * Ensure user-level skills directory exists and return path.\n * @param agentName - Name of the agent\n * @returns Path to ~/.deepagents/{agentName}/skills/\n */\n ensureUserSkillsDir(agentName: string): string;\n\n /**\n * Get project-level skills directory path.\n * @returns Path to {projectRoot}/.deepagents/skills/, or null if not in a project\n */\n getProjectSkillsDir(): string | null;\n\n /**\n * Ensure project-level skills directory exists and return path.\n * @returns Path to {projectRoot}/.deepagents/skills/, or null if not in a project\n */\n ensureProjectSkillsDir(): string | null;\n\n /**\n * Ensure project .deepagents directory exists.\n * @returns Path to {projectRoot}/.deepagents/, or null if not in a project\n */\n ensureProjectDeepagentsDir(): string | null;\n}\n\n/**\n * Find the project root by looking for .git directory.\n *\n * Walks up the directory tree from startPath (or cwd) looking for a .git\n * directory, which indicates the project root.\n *\n * @param startPath - Directory to start searching from. Defaults to current working directory.\n * @returns Path to the project root if found, null otherwise.\n */\nexport function findProjectRoot(startPath?: string): string | null {\n let current = path.resolve(startPath || process.cwd());\n\n // Walk up the directory tree\n while (current !== path.dirname(current)) {\n const gitDir = path.join(current, \".git\");\n if (fs.existsSync(gitDir)) {\n return current;\n }\n current = path.dirname(current);\n }\n\n // Check root directory as well\n const rootGitDir = path.join(current, \".git\");\n if (fs.existsSync(rootGitDir)) {\n return current;\n }\n\n return null;\n}\n\n/**\n * Validate agent name to prevent invalid filesystem paths and security issues.\n *\n * @param agentName - The agent name to validate\n * @returns True if valid, false otherwise\n */\nfunction isValidAgentName(agentName: string): boolean {\n if (!agentName || !agentName.trim()) {\n return false;\n }\n // Allow only alphanumeric, hyphens, underscores, and whitespace\n return /^[a-zA-Z0-9_\\-\\s]+$/.test(agentName);\n}\n\n/**\n * Create a Settings instance with detected environment.\n *\n * @param options - Configuration options\n * @returns Settings instance with project detection and path management\n */\nexport function createSettings(options: SettingsOptions = {}): Settings {\n const projectRoot = findProjectRoot(options.startPath);\n const userDeepagentsDir = path.join(os.homedir(), \".deepagents\");\n\n return {\n projectRoot,\n userDeepagentsDir,\n hasProject: projectRoot !== null,\n\n getAgentDir(agentName: string): string {\n if (!isValidAgentName(agentName)) {\n throw new Error(\n `Invalid agent name: ${JSON.stringify(agentName)}. ` +\n \"Agent names can only contain letters, numbers, hyphens, underscores, and spaces.\",\n );\n }\n return path.join(userDeepagentsDir, agentName);\n },\n\n ensureAgentDir(agentName: string): string {\n const agentDir = this.getAgentDir(agentName);\n fs.mkdirSync(agentDir, { recursive: true });\n return agentDir;\n },\n\n getUserAgentMdPath(agentName: string): string {\n return path.join(this.getAgentDir(agentName), \"agent.md\");\n },\n\n getProjectAgentMdPath(): string | null {\n if (!projectRoot) {\n return null;\n }\n return path.join(projectRoot, \".deepagents\", \"agent.md\");\n },\n\n getUserSkillsDir(agentName: string): string {\n return path.join(this.getAgentDir(agentName), \"skills\");\n },\n\n ensureUserSkillsDir(agentName: string): string {\n const skillsDir = this.getUserSkillsDir(agentName);\n fs.mkdirSync(skillsDir, { recursive: true });\n return skillsDir;\n },\n\n getProjectSkillsDir(): string | null {\n if (!projectRoot) {\n return null;\n }\n return path.join(projectRoot, \".deepagents\", \"skills\");\n },\n\n ensureProjectSkillsDir(): string | null {\n const skillsDir = this.getProjectSkillsDir();\n if (!skillsDir) {\n return null;\n }\n fs.mkdirSync(skillsDir, { recursive: true });\n return skillsDir;\n },\n\n ensureProjectDeepagentsDir(): string | null {\n if (!projectRoot) {\n return null;\n }\n const deepagentsDir = path.join(projectRoot, \".deepagents\");\n fs.mkdirSync(deepagentsDir, { recursive: true });\n return deepagentsDir;\n },\n };\n}\n","/**\n * Middleware for loading agent-specific long-term memory into the system prompt.\n *\n * This middleware loads the agent's long-term memory from agent.md files\n * and injects it into the system prompt. Memory is loaded from:\n * - User memory: ~/.deepagents/{agent_name}/agent.md\n * - Project memory: {project_root}/.deepagents/agent.md\n *\n * @deprecated Use `createMemoryMiddleware` from `./memory.js` instead.\n * This middleware uses direct filesystem access (Node.js fs module) which is not\n * portable across backends. The `createMemoryMiddleware` function uses the\n * `BackendProtocol` abstraction and follows the AGENTS.md specification.\n *\n * Migration example:\n * ```typescript\n * // Before (deprecated):\n * import { createAgentMemoryMiddleware } from \"./agent-memory.js\";\n * const middleware = createAgentMemoryMiddleware({ settings, assistantId });\n *\n * // After (recommended):\n * import { createMemoryMiddleware } from \"./memory.js\";\n * import { FilesystemBackend } from \"../backends/filesystem.js\";\n *\n * const middleware = createMemoryMiddleware({\n * backend: new FilesystemBackend({ rootDir: \"/\" }),\n * sources: [\n * `~/.deepagents/${assistantId}/AGENTS.md`,\n * `${projectRoot}/.deepagents/AGENTS.md`,\n * ],\n * });\n * ```\n */\n\nimport fs from \"node:fs\";\nimport { z } from \"zod\";\nimport {\n createMiddleware,\n /**\n * required for type inference\n */\n type AgentMiddleware as _AgentMiddleware,\n} from \"langchain\";\n\nimport type { Settings } from \"../config.js\";\n\n/**\n * Options for the agent memory middleware.\n */\nexport interface AgentMemoryMiddlewareOptions {\n /** Settings instance with project detection and paths */\n settings: Settings;\n\n /** The agent identifier */\n assistantId: string;\n\n /** Optional custom template for injecting agent memory into system prompt */\n systemPromptTemplate?: string;\n}\n\n/**\n * State schema for agent memory middleware.\n */\nconst AgentMemoryStateSchema = z.object({\n /** Personal preferences from ~/.deepagents/{agent}/ (applies everywhere) */\n userMemory: z.string().optional(),\n\n /** Project-specific context (loaded from project root) */\n projectMemory: z.string().optional(),\n});\n\n/**\n * Default template for memory injection.\n */\nconst DEFAULT_MEMORY_TEMPLATE = `<user_memory>\n{user_memory}\n</user_memory>\n\n<project_memory>\n{project_memory}\n</project_memory>`;\n\n/**\n * Long-term Memory Documentation system prompt.\n */\nconst LONGTERM_MEMORY_SYSTEM_PROMPT = `\n\n## Long-term Memory\n\nYour long-term memory is stored in files on the filesystem and persists across sessions.\n\n**User Memory Location**: \\`{agent_dir_absolute}\\` (displays as \\`{agent_dir_display}\\`)\n**Project Memory Location**: {project_memory_info}\n\nYour system prompt is loaded from TWO sources at startup:\n1. **User agent.md**: \\`{agent_dir_absolute}/agent.md\\` - Your personal preferences across all projects\n2. **Project agent.md**: Loaded from project root if available - Project-specific instructions\n\nProject-specific agent.md is loaded from these locations (both combined if both exist):\n- \\`[project-root]/.deepagents/agent.md\\` (preferred)\n- \\`[project-root]/agent.md\\` (fallback, but also included if both exist)\n\n**When to CHECK/READ memories (CRITICAL - do this FIRST):**\n- **At the start of ANY new session**: Check both user and project memories\n - User: \\`ls {agent_dir_absolute}\\`\n - Project: \\`ls {project_deepagents_dir}\\` (if in a project)\n- **BEFORE answering questions**: If asked \"what do you know about X?\" or \"how do I do Y?\", check project memories FIRST, then user\n- **When user asks you to do something**: Check if you have project-specific guides or examples\n- **When user references past work**: Search project memory files for related context\n\n**Memory-first response pattern:**\n1. User asks a question → Check project directory first: \\`ls {project_deepagents_dir}\\`\n2. If relevant files exist → Read them with \\`read_file '{project_deepagents_dir}/[filename]'\\`\n3. Check user memory if needed → \\`ls {agent_dir_absolute}\\`\n4. Base your answer on saved knowledge supplemented by general knowledge\n\n**When to update memories:**\n- **IMMEDIATELY when the user describes your role or how you should behave**\n- **IMMEDIATELY when the user gives feedback on your work** - Update memories to capture what was wrong and how to do it better\n- When the user explicitly asks you to remember something\n- When patterns or preferences emerge (coding styles, conventions, workflows)\n- After significant work where context would help in future sessions\n\n**Learning from feedback:**\n- When user says something is better/worse, capture WHY and encode it as a pattern\n- Each correction is a chance to improve permanently - don't just fix the immediate issue, update your instructions\n- When user says \"you should remember X\" or \"be careful about Y\", treat this as HIGH PRIORITY - update memories IMMEDIATELY\n- Look for the underlying principle behind corrections, not just the specific mistake\n\n## Deciding Where to Store Memory\n\nWhen writing or updating agent memory, decide whether each fact, configuration, or behavior belongs in:\n\n### User Agent File: \\`{agent_dir_absolute}/agent.md\\`\n→ Describes the agent's **personality, style, and universal behavior** across all projects.\n\n**Store here:**\n- Your general tone and communication style\n- Universal coding preferences (formatting, comment style, etc.)\n- General workflows and methodologies you follow\n- Tool usage patterns that apply everywhere\n- Personal preferences that don't change per-project\n\n**Examples:**\n- \"Be concise and direct in responses\"\n- \"Always use type hints in Python\"\n- \"Prefer functional programming patterns\"\n\n### Project Agent File: \\`{project_deepagents_dir}/agent.md\\`\n→ Describes **how this specific project works** and **how the agent should behave here only.**\n\n**Store here:**\n- Project-specific architecture and design patterns\n- Coding conventions specific to this codebase\n- Project structure and organization\n- Testing strategies for this project\n- Deployment processes and workflows\n- Team conventions and guidelines\n\n**Examples:**\n- \"This project uses FastAPI with SQLAlchemy\"\n- \"Tests go in tests/ directory mirroring src/ structure\"\n- \"All API changes require updating OpenAPI spec\"\n\n### Project Memory Files: \\`{project_deepagents_dir}/*.md\\`\n→ Use for **project-specific reference information** and structured notes.\n\n**Store here:**\n- API design documentation\n- Architecture decisions and rationale\n- Deployment procedures\n- Common debugging patterns\n- Onboarding information\n\n**Examples:**\n- \\`{project_deepagents_dir}/api-design.md\\` - REST API patterns used\n- \\`{project_deepagents_dir}/architecture.md\\` - System architecture overview\n- \\`{project_deepagents_dir}/deployment.md\\` - How to deploy this project\n\n### File Operations:\n\n**User memory:**\n\\`\\`\\`\nls {agent_dir_absolute} # List user memory files\nread_file '{agent_dir_absolute}/agent.md' # Read user preferences\nedit_file '{agent_dir_absolute}/agent.md' ... # Update user preferences\n\\`\\`\\`\n\n**Project memory (preferred for project-specific information):**\n\\`\\`\\`\nls {project_deepagents_dir} # List project memory files\nread_file '{project_deepagents_dir}/agent.md' # Read project instructions\nedit_file '{project_deepagents_dir}/agent.md' ... # Update project instructions\nwrite_file '{project_deepagents_dir}/agent.md' ... # Create project memory file\n\\`\\`\\`\n\n**Important**:\n- Project memory files are stored in \\`.deepagents/\\` inside the project root\n- Always use absolute paths for file operations\n- Check project memories BEFORE user when answering project-specific questions`;\n\n/**\n * Create middleware for loading agent-specific long-term memory.\n *\n * This middleware loads the agent's long-term memory from a file (agent.md)\n * and injects it into the system prompt. The memory is loaded once at the\n * start of the conversation and stored in state.\n *\n * @param options - Configuration options\n * @returns AgentMiddleware for memory loading and injection\n *\n * @deprecated Use `createMemoryMiddleware` from `./memory.js` instead.\n * This function uses direct filesystem access which limits portability.\n */\nexport function createAgentMemoryMiddleware(\n options: AgentMemoryMiddlewareOptions,\n) {\n const { settings, assistantId, systemPromptTemplate } = options;\n\n // Compute paths\n const agentDir = settings.getAgentDir(assistantId);\n const agentDirDisplay = `~/.deepagents/${assistantId}`;\n const agentDirAbsolute = agentDir;\n const projectRoot = settings.projectRoot;\n\n // Build project memory info for documentation\n const projectMemoryInfo = projectRoot\n ? `\\`${projectRoot}\\` (detected)`\n : \"None (not in a git project)\";\n\n // Build project deepagents directory path\n const projectDeepagentsDir = projectRoot\n ? `${projectRoot}/.deepagents`\n : \"[project-root]/.deepagents (not in a project)\";\n\n const template = systemPromptTemplate || DEFAULT_MEMORY_TEMPLATE;\n\n return createMiddleware({\n name: \"AgentMemoryMiddleware\",\n stateSchema: AgentMemoryStateSchema as any,\n\n beforeAgent(state: any) {\n const result: Record<string, string> = {};\n\n // Load user memory if not already in state\n if (!(\"userMemory\" in state)) {\n const userPath = settings.getUserAgentMdPath(assistantId);\n if (fs.existsSync(userPath)) {\n try {\n result.userMemory = fs.readFileSync(userPath, \"utf-8\");\n } catch {\n // Ignore read errors\n }\n }\n }\n\n // Load project memory if not already in state\n if (!(\"projectMemory\" in state)) {\n const projectPath = settings.getProjectAgentMdPath();\n if (projectPath && fs.existsSync(projectPath)) {\n try {\n result.projectMemory = fs.readFileSync(projectPath, \"utf-8\");\n } catch {\n // Ignore read errors\n }\n }\n }\n\n return Object.keys(result).length > 0 ? result : undefined;\n },\n\n wrapModelCall(request: any, handler: any) {\n // Extract memory from state\n const userMemory = request.state?.userMemory;\n const projectMemory = request.state?.projectMemory;\n const baseSystemPrompt = request.systemPrompt || \"\";\n\n // Format memory section with both memories\n const memorySection = template\n .replace(\"{user_memory}\", userMemory || \"(No user agent.md)\")\n .replace(\"{project_memory}\", projectMemory || \"(No project agent.md)\");\n\n // Format long-term memory documentation\n const memoryDocs = LONGTERM_MEMORY_SYSTEM_PROMPT.replaceAll(\n \"{agent_dir_absolute}\",\n agentDirAbsolute,\n )\n .replaceAll(\"{agent_dir_display}\", agentDirDisplay)\n .replaceAll(\"{project_memory_info}\", projectMemoryInfo)\n .replaceAll(\"{project_deepagents_dir}\", projectDeepagentsDir);\n\n // Memory content at start, base prompt in middle, documentation at end\n let systemPrompt = memorySection;\n if (baseSystemPrompt) {\n systemPrompt += \"\\n\\n\" + baseSystemPrompt;\n }\n systemPrompt += \"\\n\\n\" + memoryDocs;\n\n return handler({ ...request, systemPrompt });\n },\n });\n}\n","/**\n * Skill loader for parsing and loading agent skills from SKILL.md files.\n *\n * This module implements Anthropic's agent skills pattern with YAML frontmatter parsing.\n * Each skill is a directory containing a SKILL.md file with:\n * - YAML frontmatter (name, description required)\n * - Markdown instructions for the agent\n * - Optional supporting files (scripts, configs, etc.)\n *\n * @example\n * ```markdown\n * ---\n * name: web-research\n * description: Structured approach to conducting thorough web research\n * ---\n *\n * # Web Research Skill\n *\n * ## When to Use\n * - User asks you to research a topic\n * ...\n * ```\n *\n * @see https://agentskills.io/specification\n */\n\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport yaml from \"yaml\";\n\n/** Maximum size for SKILL.md files (10MB) */\nexport const MAX_SKILL_FILE_SIZE = 10 * 1024 * 1024;\n\n/** Agent Skills spec constraints */\nexport const MAX_SKILL_NAME_LENGTH = 64;\nexport const MAX_SKILL_DESCRIPTION_LENGTH = 1024;\n\n/** Pattern for validating skill names per Agent Skills spec */\nconst SKILL_NAME_PATTERN = /^[a-z0-9]+(-[a-z0-9]+)*$/;\n\n/** Pattern for extracting YAML frontmatter */\nconst FRONTMATTER_PATTERN = /^---\\s*\\n([\\s\\S]*?)\\n---\\s*\\n/;\n\n/**\n * Metadata for a skill per Agent Skills spec.\n * @see https://agentskills.io/specification\n */\nexport interface SkillMetadata {\n /** Name of the skill (max 64 chars, lowercase alphanumeric and hyphens) */\n name: string;\n\n /** Description of what the skill does (max 1024 chars) */\n description: string;\n\n /** Absolute path to the SKILL.md file */\n path: string;\n\n /** Source of the skill ('user' or 'project') */\n source: \"user\" | \"project\";\n\n /** Optional: License name or reference to bundled license file */\n license?: string;\n\n /** Optional: Environment requirements (max 500 chars) */\n compatibility?: string;\n\n /** Optional: Arbitrary key-value mapping for additional metadata */\n metadata?: Record<string, string>;\n\n /** Optional: Space-delimited list of pre-approved tools */\n allowedTools?: string;\n}\n\n/**\n * Options for listing skills.\n */\nexport interface ListSkillsOptions {\n /** Path to user-level skills directory */\n userSkillsDir?: string | null;\n\n /** Path to project-level skills directory */\n projectSkillsDir?: string | null;\n}\n\n/**\n * Result of skill name validation.\n */\ninterface ValidationResult {\n valid: boolean;\n error?: string;\n}\n\n/**\n * Check if a path is safely contained within base_dir.\n *\n * This prevents directory traversal attacks via symlinks or path manipulation.\n * The function resolves both paths to their canonical form (following symlinks)\n * and verifies that the target path is within the base directory.\n *\n * @param targetPath - The path to validate\n * @param baseDir - The base directory that should contain the path\n * @returns True if the path is safely within baseDir, false otherwise\n */\nfunction isSafePath(targetPath: string, baseDir: string): boolean {\n try {\n // Resolve both paths to their canonical form (follows symlinks)\n const resolvedPath = fs.realpathSync(targetPath);\n const resolvedBase = fs.realpathSync(baseDir);\n\n // Check if the resolved path is within the base directory\n return (\n resolvedPath.startsWith(resolvedBase + path.sep) ||\n resolvedPath === resolvedBase\n );\n } catch {\n // Error resolving paths (e.g., circular symlinks, too many levels)\n return false;\n }\n}\n\n/**\n * Validate skill name per Agent Skills spec.\n *\n * Requirements:\n * - Max 64 characters\n * - Lowercase alphanumeric and hyphens only (a-z, 0-9, -)\n * - Cannot start or end with hyphen\n * - No consecutive hyphens\n * - Must match parent directory name\n *\n * @param name - The skill name from YAML frontmatter\n * @param directoryName - The parent directory name\n * @returns Validation result with error message if invalid\n */\nfunction validateSkillName(\n name: string,\n directoryName: string,\n): ValidationResult {\n if (!name) {\n return { valid: false, error: \"name is required\" };\n }\n if (name.length > MAX_SKILL_NAME_LENGTH) {\n return { valid: false, error: \"name exceeds 64 characters\" };\n }\n // Pattern: lowercase alphanumeric, single hyphens between segments, no start/end hyphen\n if (!SKILL_NAME_PATTERN.test(name)) {\n return {\n valid: false,\n error: \"name must be lowercase alphanumeric with single hyphens only\",\n };\n }\n if (name !== directoryName) {\n return {\n valid: false,\n error: `name '${name}' must match directory name '${directoryName}'`,\n };\n }\n return { valid: true };\n}\n\n/**\n * Parse YAML frontmatter from content.\n *\n * @param content - The file content\n * @returns Parsed frontmatter object, or null if parsing fails\n */\nfunction parseFrontmatter(content: string): Record<string, unknown> | null {\n const match = content.match(FRONTMATTER_PATTERN);\n if (!match) {\n return null;\n }\n\n try {\n const parsed = yaml.parse(match[1]);\n return typeof parsed === \"object\" && parsed !== null ? parsed : null;\n } catch {\n return null;\n }\n}\n\n/**\n * Parse YAML frontmatter from a SKILL.md file per Agent Skills spec.\n *\n * @param skillMdPath - Path to the SKILL.md file\n * @param source - Source of the skill ('user' or 'project')\n * @returns SkillMetadata with all fields, or null if parsing fails\n */\nexport function parseSkillMetadata(\n skillMdPath: string,\n source: \"user\" | \"project\",\n): SkillMetadata | null {\n try {\n // Security: Check file size to prevent DoS attacks\n const stats = fs.statSync(skillMdPath);\n if (stats.size > MAX_SKILL_FILE_SIZE) {\n // eslint-disable-next-line no-console\n console.warn(\n `Skipping ${skillMdPath}: file too large (${stats.size} bytes)`,\n );\n return null;\n }\n\n const content = fs.readFileSync(skillMdPath, \"utf-8\");\n const frontmatter = parseFrontmatter(content);\n\n if (!frontmatter) {\n // eslint-disable-next-line no-console\n console.warn(`Skipping ${skillMdPath}: no valid YAML frontmatter found`);\n return null;\n }\n\n // Validate required fields\n const name = frontmatter.name;\n const description = frontmatter.description;\n\n if (!name || !description) {\n // eslint-disable-next-line no-console\n console.warn(\n `Skipping ${skillMdPath}: missing required 'name' or 'description'`,\n );\n return null;\n }\n\n // Validate name format per spec (warn but still load for backwards compatibility)\n const directoryName = path.basename(path.dirname(skillMdPath));\n const validation = validateSkillName(String(name), directoryName);\n if (!validation.valid) {\n // eslint-disable-next-line no-console\n console.warn(\n `Skill '${name}' in ${skillMdPath} does not follow Agent Skills spec: ${validation.error}. ` +\n \"Consider renaming to be spec-compliant.\",\n );\n }\n\n // Truncate description if too long (spec: max 1024 chars)\n let descriptionStr = String(description);\n if (descriptionStr.length > MAX_SKILL_DESCRIPTION_LENGTH) {\n // eslint-disable-next-line no-console\n console.warn(\n `Description exceeds ${MAX_SKILL_DESCRIPTION_LENGTH} chars in ${skillMdPath}, truncating`,\n );\n descriptionStr = descriptionStr.slice(0, MAX_SKILL_DESCRIPTION_LENGTH);\n }\n\n return {\n name: String(name),\n description: descriptionStr,\n path: skillMdPath,\n source,\n license: frontmatter.license ? String(frontmatter.license) : undefined,\n compatibility: frontmatter.compatibility\n ? String(frontmatter.compatibility)\n : undefined,\n metadata:\n frontmatter.metadata && typeof frontmatter.metadata === \"object\"\n ? (frontmatter.metadata as Record<string, string>)\n : undefined,\n allowedTools: frontmatter[\"allowed-tools\"]\n ? String(frontmatter[\"allowed-tools\"])\n : undefined,\n };\n } catch (error) {\n // eslint-disable-next-line no-console\n console.warn(`Error reading ${skillMdPath}: ${error}`);\n return null;\n }\n}\n\n/**\n * List all skills from a single skills directory (internal helper).\n *\n * Scans the skills directory for subdirectories containing SKILL.md files,\n * parses YAML frontmatter, and returns skill metadata.\n *\n * Skills are organized as:\n * ```\n * skills/\n * ├── skill-name/\n * │ ├── SKILL.md # Required: instructions with YAML frontmatter\n * │ ├── script.py # Optional: supporting files\n * │ └── config.json # Optional: supporting files\n * ```\n *\n * @param skillsDir - Path to the skills directory\n * @param source - Source of the skills ('user' or 'project')\n * @returns List of skill metadata\n */\nfunction listSkillsFromDir(\n skillsDir: string,\n source: \"user\" | \"project\",\n): SkillMetadata[] {\n // Check if skills directory exists\n const expandedDir = skillsDir.startsWith(\"~\")\n ? path.join(\n process.env.HOME || process.env.USERPROFILE || \"\",\n skillsDir.slice(1),\n )\n : skillsDir;\n\n if (!fs.existsSync(expandedDir)) {\n return [];\n }\n\n // Resolve base directory to canonical path for security checks\n let resolvedBase: string;\n try {\n resolvedBase = fs.realpathSync(expandedDir);\n } catch {\n // Can't resolve base directory, fail safe\n return [];\n }\n\n const skills: SkillMetadata[] = [];\n\n // Iterate through subdirectories\n let entries: fs.Dirent[];\n try {\n entries = fs.readdirSync(resolvedBase, { withFileTypes: true });\n } catch {\n return [];\n }\n\n for (const entry of entries) {\n const skillDir = path.join(resolvedBase, entry.name);\n\n // Security: Catch symlinks pointing outside the skills directory\n if (!isSafePath(skillDir, resolvedBase)) {\n continue;\n }\n\n if (!entry.isDirectory()) {\n continue;\n }\n\n // Look for SKILL.md file\n const skillMdPath = path.join(skillDir, \"SKILL.md\");\n if (!fs.existsSync(skillMdPath)) {\n continue;\n }\n\n // Security: Validate SKILL.md path is safe before reading\n if (!isSafePath(skillMdPath, resolvedBase)) {\n continue;\n }\n\n // Parse metadata\n const metadata = parseSkillMetadata(skillMdPath, source);\n if (metadata) {\n skills.push(metadata);\n }\n }\n\n return skills;\n}\n\n/**\n * List skills from user and/or project directories.\n *\n * When both directories are provided, project skills with the same name as\n * user skills will override them.\n *\n * @param options - Options specifying which directories to search\n * @returns Merged list of skill metadata from both sources, with project skills\n * taking precedence over user skills when names conflict\n */\nexport function listSkills(options: ListSkillsOptions): SkillMetadata[] {\n const allSkills: Map<string, SkillMetadata> = new Map();\n\n // Load user skills first (foundation)\n if (options.userSkillsDir) {\n const userSkills = listSkillsFromDir(options.userSkillsDir, \"user\");\n for (const skill of userSkills) {\n allSkills.set(skill.name, skill);\n }\n }\n\n // Load project skills second (override/augment)\n if (options.projectSkillsDir) {\n const projectSkills = listSkillsFromDir(\n options.projectSkillsDir,\n \"project\",\n );\n for (const skill of projectSkills) {\n // Project skills override user skills with the same name\n allSkills.set(skill.name, skill);\n }\n }\n\n return Array.from(allSkills.values());\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["path","z","StateSchema","ReducedValue","ToolMessage","Command","Command","ToolMessage","HumanMessage","z","SystemMessage","AIMessage","ToolMessage","RemoveMessage","REMOVE_ALL_MESSAGES","ReducedValue","z","StateSchema","z","SystemMessage","z","StateSchema","ReducedValue","validateSkillName","z","HumanMessage","ToolMessage","AIMessage","SystemMessage","ContextOverflowError","Command","fsSync","path","fs","#timeout","#maxOutputBytes","#sandboxId","#env","#initialized","fs","path","cp","SystemMessage","SystemMessage","Runnable","path","fs","os","z","fs","MAX_SKILL_FILE_SIZE","MAX_SKILL_NAME_LENGTH","MAX_SKILL_DESCRIPTION_LENGTH","fs","path"],"sources":["../src/backends/protocol.ts","../src/backends/utils.ts","../src/backends/state.ts","../src/middleware/fs.ts","../src/middleware/subagents.ts","../src/middleware/patch_tool_calls.ts","../src/values.ts","../src/middleware/memory.ts","../src/middleware/skills.ts","../src/middleware/utils.ts","../src/middleware/summarization.ts","../src/backends/store.ts","../src/backends/filesystem.ts","../src/backends/composite.ts","../src/backends/local-shell.ts","../src/backends/sandbox.ts","../src/middleware/cache.ts","../src/agent.ts","../src/config.ts","../src/middleware/agent-memory.ts","../src/skills/loader.ts"],"sourcesContent":["/**\n * Protocol definition for pluggable memory backends.\n *\n * This module defines the BackendProtocol that all backend implementations\n * must follow. Backends can store files in different locations (state, filesystem,\n * database, etc.) and provide a uniform interface for file operations.\n */\n\nimport type { BaseStore } from \"@langchain/langgraph-checkpoint\";\n\nexport type MaybePromise<T> = T | Promise<T>;\n\n/**\n * Structured file listing info.\n *\n * Minimal contract used across backends. Only \"path\" is required.\n * Other fields are best-effort and may be absent depending on backend.\n */\nexport interface FileInfo {\n /** File path */\n path: string;\n /** Whether this is a directory */\n is_dir?: boolean;\n /** File size in bytes (approximate) */\n size?: number;\n /** ISO 8601 timestamp of last modification */\n modified_at?: string;\n}\n\n/**\n * Structured grep match entry.\n */\nexport interface GrepMatch {\n /** File path where match was found */\n path: string;\n /** Line number (1-indexed) */\n line: number;\n /** The matching line text */\n text: string;\n}\n\n/**\n * File data structure used by backends.\n *\n * All file data is represented as objects with this structure:\n */\nexport interface FileData {\n /** Lines of text content */\n content: string[];\n /** ISO format timestamp of creation */\n created_at: string;\n /** ISO format timestamp of last modification */\n modified_at: string;\n}\n\n/**\n * Result from backend write operations.\n *\n * Checkpoint backends populate filesUpdate with {file_path: file_data} for LangGraph state.\n * External backends set filesUpdate to null (already persisted to disk/S3/database/etc).\n */\nexport interface WriteResult {\n /** Error message on failure, undefined on success */\n error?: string;\n /** File path of written file, undefined on failure */\n path?: string;\n /**\n * State update dict for checkpoint backends, null for external storage.\n * Checkpoint backends populate this with {file_path: file_data} for LangGraph state.\n * External backends set null (already persisted to disk/S3/database/etc).\n */\n filesUpdate?: Record<string, FileData> | null;\n /** Metadata for the write operation, attached to the ToolMessage */\n metadata?: Record<string, unknown>;\n}\n\n/**\n * Result from backend edit operations.\n *\n * Checkpoint backends populate filesUpdate with {file_path: file_data} for LangGraph state.\n * External backends set filesUpdate to null (already persisted to disk/S3/database/etc).\n */\nexport interface EditResult {\n /** Error message on failure, undefined on success */\n error?: string;\n /** File path of edited file, undefined on failure */\n path?: string;\n /**\n * State update dict for checkpoint backends, null for external storage.\n * Checkpoint backends populate this with {file_path: file_data} for LangGraph state.\n * External backends set null (already persisted to disk/S3/database/etc).\n */\n filesUpdate?: Record<string, FileData> | null;\n /** Number of replacements made, undefined on failure */\n occurrences?: number;\n /** Metadata for the edit operation, attached to the ToolMessage */\n metadata?: Record<string, unknown>;\n}\n\n/**\n * Result of code execution.\n * Simplified schema optimized for LLM consumption.\n */\nexport interface ExecuteResponse {\n /** Combined stdout and stderr output of the executed command */\n output: string;\n /** The process exit code. 0 indicates success, non-zero indicates failure */\n exitCode: number | null;\n /** Whether the output was truncated due to backend limitations */\n truncated: boolean;\n}\n\n/**\n * Standardized error codes for file upload/download operations.\n */\nexport type FileOperationError =\n | \"file_not_found\"\n | \"permission_denied\"\n | \"is_directory\"\n | \"invalid_path\";\n\n/**\n * Result of a single file download operation.\n */\nexport interface FileDownloadResponse {\n /** The file path that was requested */\n path: string;\n /** File contents as Uint8Array on success, null on failure */\n content: Uint8Array | null;\n /** Standardized error code on failure, null on success */\n error: FileOperationError | null;\n}\n\n/**\n * Result of a single file upload operation.\n */\nexport interface FileUploadResponse {\n /** The file path that was requested */\n path: string;\n /** Standardized error code on failure, null on success */\n error: FileOperationError | null;\n}\n\n/**\n * Protocol for pluggable memory backends (single, unified).\n *\n * Backends can store files in different locations (state, filesystem, database, etc.)\n * and provide a uniform interface for file operations.\n *\n * All file data is represented as objects with the FileData structure.\n *\n * Methods can return either direct values or Promises, allowing both\n * synchronous and asynchronous implementations.\n */\nexport interface BackendProtocol {\n /**\n * Structured listing with file metadata.\n *\n * Lists files and directories in the specified directory (non-recursive).\n * Directories have a trailing / in their path and is_dir=true.\n *\n * @param path - Absolute path to directory\n * @returns List of FileInfo objects for files and directories directly in the directory\n */\n lsInfo(path: string): MaybePromise<FileInfo[]>;\n\n /**\n * Read file content with line numbers or an error string.\n *\n * @param filePath - Absolute file path\n * @param offset - Line offset to start reading from (0-indexed), default 0\n * @param limit - Maximum number of lines to read, default 500\n * @returns Formatted file content with line numbers, or error message\n */\n read(filePath: string, offset?: number, limit?: number): MaybePromise<string>;\n\n /**\n * Read file content as raw FileData.\n *\n * @param filePath - Absolute file path\n * @returns Raw file content as FileData\n */\n readRaw(filePath: string): MaybePromise<FileData>;\n\n /**\n * Structured search results or error string for invalid input.\n *\n * Searches file contents for a regex pattern.\n *\n * @param pattern - Regex pattern to search for\n * @param path - Base path to search from (default: null)\n * @param glob - Optional glob pattern to filter files (e.g., \"*.py\")\n * @returns List of GrepMatch objects or error string for invalid regex\n */\n grepRaw(\n pattern: string,\n path?: string | null,\n glob?: string | null,\n ): MaybePromise<GrepMatch[] | string>;\n\n /**\n * Structured glob matching returning FileInfo objects.\n *\n * @param pattern - Glob pattern (e.g., `*.py`, `**\\/*.ts`)\n * @param path - Base path to search from (default: \"/\")\n * @returns List of FileInfo objects matching the pattern\n */\n globInfo(pattern: string, path?: string): MaybePromise<FileInfo[]>;\n\n /**\n * Create a new file.\n *\n * @param filePath - Absolute file path\n * @param content - File content as string\n * @returns WriteResult with error populated on failure\n */\n write(filePath: string, content: string): MaybePromise<WriteResult>;\n\n /**\n * Edit a file by replacing string occurrences.\n *\n * @param filePath - Absolute file path\n * @param oldString - String to find and replace\n * @param newString - Replacement string\n * @param replaceAll - If true, replace all occurrences (default: false)\n * @returns EditResult with error, path, filesUpdate, and occurrences\n */\n edit(\n filePath: string,\n oldString: string,\n newString: string,\n replaceAll?: boolean,\n ): MaybePromise<EditResult>;\n\n /**\n * Upload multiple files.\n * Optional - backends that don't support file upload can omit this.\n *\n * @param files - List of [path, content] tuples to upload\n * @returns List of FileUploadResponse objects, one per input file\n */\n uploadFiles?(\n files: Array<[string, Uint8Array]>,\n ): MaybePromise<FileUploadResponse[]>;\n\n /**\n * Download multiple files.\n * Optional - backends that don't support file download can omit this.\n *\n * @param paths - List of file paths to download\n * @returns List of FileDownloadResponse objects, one per input path\n */\n downloadFiles?(paths: string[]): MaybePromise<FileDownloadResponse[]>;\n}\n\n/**\n * Protocol for sandboxed backends with isolated runtime.\n * Sandboxed backends run in isolated environments (e.g., containers)\n * and communicate via defined interfaces.\n */\nexport interface SandboxBackendProtocol extends BackendProtocol {\n /**\n * Execute a command in the sandbox.\n *\n * @param command - Full shell command string to execute\n * @returns ExecuteResponse with combined output, exit code, and truncation flag\n */\n execute(command: string): MaybePromise<ExecuteResponse>;\n\n /** Unique identifier for the sandbox backend instance */\n readonly id: string;\n}\n\n/**\n * Type guard to check if a backend supports execution.\n *\n * @param backend - Backend instance to check\n * @returns True if the backend implements SandboxBackendProtocol\n */\nexport function isSandboxBackend(\n backend: BackendProtocol,\n): backend is SandboxBackendProtocol {\n return (\n typeof (backend as SandboxBackendProtocol).execute === \"function\" &&\n typeof (backend as SandboxBackendProtocol).id === \"string\"\n );\n}\n\n/**\n * Metadata for a single sandbox instance.\n *\n * This lightweight structure is returned from list operations and provides\n * basic information about a sandbox without requiring a full connection.\n *\n * @typeParam MetadataT - Type of the metadata field. Providers can define\n * their own interface for type-safe metadata access.\n *\n * @example\n * ```typescript\n * // Using default metadata type\n * const info: SandboxInfo = {\n * sandboxId: \"sb_abc123\",\n * metadata: { status: \"running\", createdAt: \"2024-01-15T10:30:00Z\" },\n * };\n *\n * // Using typed metadata\n * interface MyMetadata {\n * status: \"running\" | \"stopped\";\n * createdAt: string;\n * }\n * const typedInfo: SandboxInfo<MyMetadata> = {\n * sandboxId: \"sb_abc123\",\n * metadata: { status: \"running\", createdAt: \"2024-01-15T10:30:00Z\" },\n * };\n * ```\n */\nexport interface SandboxInfo<MetadataT = Record<string, unknown>> {\n /** Unique identifier for the sandbox instance */\n sandboxId: string;\n /** Optional provider-specific metadata (e.g., creation time, status, template) */\n metadata?: MetadataT;\n}\n\n/**\n * Paginated response from a sandbox list operation.\n *\n * This structure supports cursor-based pagination for efficiently browsing\n * large collections of sandboxes.\n *\n * @typeParam MetadataT - Type of the metadata field in SandboxInfo items.\n *\n * @example\n * ```typescript\n * const response: SandboxListResponse = {\n * items: [\n * { sandboxId: \"sb_001\", metadata: { status: \"running\" } },\n * { sandboxId: \"sb_002\", metadata: { status: \"stopped\" } },\n * ],\n * cursor: \"eyJvZmZzZXQiOjEwMH0=\",\n * };\n *\n * // Fetch next page\n * const nextResponse = await provider.list({ cursor: response.cursor });\n * ```\n */\nexport interface SandboxListResponse<MetadataT = Record<string, unknown>> {\n /** List of sandbox metadata objects for the current page */\n items: SandboxInfo<MetadataT>[];\n /**\n * Opaque continuation token for retrieving the next page.\n * null indicates no more pages available.\n */\n cursor: string | null;\n}\n\n/**\n * Options for listing sandboxes.\n */\nexport interface SandboxListOptions {\n /**\n * Continuation token from a previous list() call.\n * Pass undefined to start from the beginning.\n */\n cursor?: string;\n}\n\n/**\n * Options for getting or creating a sandbox.\n */\nexport interface SandboxGetOrCreateOptions {\n /**\n * Unique identifier of an existing sandbox to retrieve.\n * If undefined, creates a new sandbox instance.\n * If provided but the sandbox doesn't exist, an error will be thrown.\n */\n sandboxId?: string;\n}\n\n/**\n * Options for deleting a sandbox.\n */\nexport interface SandboxDeleteOptions {\n /** Unique identifier of the sandbox to delete */\n sandboxId: string;\n}\n\n/**\n * Common error codes shared across all sandbox provider implementations.\n *\n * These represent the core error conditions that any sandbox provider may encounter.\n * Provider-specific error codes should extend this type with additional codes.\n *\n * @example\n * ```typescript\n * // Provider-specific error code type extending the common codes:\n * type MySandboxErrorCode = SandboxErrorCode | \"CUSTOM_ERROR\";\n * ```\n */\nexport type SandboxErrorCode =\n /** Sandbox has not been initialized - call initialize() first */\n | \"NOT_INITIALIZED\"\n /** Sandbox is already initialized - cannot initialize twice */\n | \"ALREADY_INITIALIZED\"\n /** Command execution timed out */\n | \"COMMAND_TIMEOUT\"\n /** Command execution failed */\n | \"COMMAND_FAILED\"\n /** File operation (read/write) failed */\n | \"FILE_OPERATION_FAILED\";\n\nconst SANDBOX_ERROR_SYMBOL = Symbol.for(\"sandbox.error\");\n\n/**\n * Custom error class for sandbox operations.\n *\n * @param message - Human-readable error description\n * @param code - Structured error code for programmatic handling\n * @returns SandboxError with message and code\n *\n * @example\n * ```typescript\n * try {\n * await sandbox.execute(\"some command\");\n * } catch (error) {\n * if (error instanceof SandboxError) {\n * switch (error.code) {\n * case \"NOT_INITIALIZED\":\n * await sandbox.initialize();\n * break;\n * case \"COMMAND_TIMEOUT\":\n * console.error(\"Command took too long\");\n * break;\n * default:\n * throw error;\n * }\n * }\n * }\n * ```\n */\nexport class SandboxError extends Error {\n /** Symbol for identifying sandbox error instances */\n [SANDBOX_ERROR_SYMBOL] = true as const;\n\n /** Error name for instanceof checks and logging */\n override readonly name: string = \"SandboxError\";\n\n /**\n * Creates a new SandboxError.\n *\n * @param message - Human-readable error description\n * @param code - Structured error code for programmatic handling\n */\n constructor(\n message: string,\n public readonly code: string,\n public readonly cause?: Error,\n ) {\n super(message);\n Object.setPrototypeOf(this, SandboxError.prototype);\n }\n\n static isInstance(error: unknown): error is SandboxError {\n return (\n typeof error === \"object\" &&\n error !== null &&\n (error as Record<symbol, unknown>)[SANDBOX_ERROR_SYMBOL] === true\n );\n }\n}\n\n/**\n * State and store container for backend initialization.\n *\n * This provides a clean interface for what backends need to access:\n * - state: Current agent state (with files, messages, etc.)\n * - store: Optional persistent store for cross-conversation data\n *\n * Different contexts build this differently:\n * - Tools: Extract state via getCurrentTaskInput(config)\n * - Middleware: Use request.state directly\n */\nexport interface StateAndStore {\n /** Current agent state with files, messages, etc. */\n state: unknown;\n /** Optional BaseStore for persistent cross-conversation storage */\n store?: BaseStore;\n /** Optional assistant ID for per-assistant isolation in store */\n assistantId?: string;\n}\n\n/**\n * Factory function type for creating backend instances.\n *\n * Backends receive StateAndStore which contains the current state\n * and optional store, extracted from the execution context.\n *\n * @example\n * ```typescript\n * // Using in middleware\n * const middleware = createFilesystemMiddleware({\n * backend: (stateAndStore) => new StateBackend(stateAndStore)\n * });\n * ```\n */\nexport type BackendFactory = (stateAndStore: StateAndStore) => BackendProtocol;\n","/**\n * Shared utility functions for memory backend implementations.\n *\n * This module contains both user-facing string formatters and structured\n * helpers used by backends and the composite router. Structured helpers\n * enable composition without fragile string parsing.\n */\n\nimport micromatch from \"micromatch\";\nimport { basename } from \"path\";\nimport type { FileData, GrepMatch } from \"./protocol.js\";\n\n// Constants\nexport const EMPTY_CONTENT_WARNING =\n \"System reminder: File exists but has empty contents\";\nexport const MAX_LINE_LENGTH = 10000;\nexport const LINE_NUMBER_WIDTH = 6;\nexport const TOOL_RESULT_TOKEN_LIMIT = 20000; // Same threshold as eviction\nexport const TRUNCATION_GUIDANCE =\n \"... [results truncated, try being more specific with your parameters]\";\n\n/**\n * Sanitize tool_call_id to prevent path traversal and separator issues.\n *\n * Replaces dangerous characters (., /, \\) with underscores.\n */\nexport function sanitizeToolCallId(toolCallId: string): string {\n return toolCallId.replace(/\\./g, \"_\").replace(/\\//g, \"_\").replace(/\\\\/g, \"_\");\n}\n\n/**\n * Format file content with line numbers (cat -n style).\n *\n * Chunks lines longer than MAX_LINE_LENGTH with continuation markers (e.g., 5.1, 5.2).\n *\n * @param content - File content as string or list of lines\n * @param startLine - Starting line number (default: 1)\n * @returns Formatted content with line numbers and continuation markers\n */\nexport function formatContentWithLineNumbers(\n content: string | string[],\n startLine: number = 1,\n): string {\n let lines: string[];\n if (typeof content === \"string\") {\n lines = content.split(\"\\n\");\n if (lines.length > 0 && lines[lines.length - 1] === \"\") {\n lines = lines.slice(0, -1);\n }\n } else {\n lines = content;\n }\n\n const resultLines: string[] = [];\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i];\n const lineNum = i + startLine;\n\n if (line.length <= MAX_LINE_LENGTH) {\n resultLines.push(\n `${lineNum.toString().padStart(LINE_NUMBER_WIDTH)}\\t${line}`,\n );\n } else {\n // Split long line into chunks with continuation markers\n const numChunks = Math.ceil(line.length / MAX_LINE_LENGTH);\n for (let chunkIdx = 0; chunkIdx < numChunks; chunkIdx++) {\n const start = chunkIdx * MAX_LINE_LENGTH;\n const end = Math.min(start + MAX_LINE_LENGTH, line.length);\n const chunk = line.substring(start, end);\n if (chunkIdx === 0) {\n // First chunk: use normal line number\n resultLines.push(\n `${lineNum.toString().padStart(LINE_NUMBER_WIDTH)}\\t${chunk}`,\n );\n } else {\n // Continuation chunks: use decimal notation (e.g., 5.1, 5.2)\n const continuationMarker = `${lineNum}.${chunkIdx}`;\n resultLines.push(\n `${continuationMarker.padStart(LINE_NUMBER_WIDTH)}\\t${chunk}`,\n );\n }\n }\n }\n }\n\n return resultLines.join(\"\\n\");\n}\n\n/**\n * Check if content is empty and return warning message.\n *\n * @param content - Content to check\n * @returns Warning message if empty, null otherwise\n */\nexport function checkEmptyContent(content: string): string | null {\n if (!content || content.trim() === \"\") {\n return EMPTY_CONTENT_WARNING;\n }\n return null;\n}\n\n/**\n * Convert FileData to plain string content.\n *\n * @param fileData - FileData object with 'content' key\n * @returns Content as string with lines joined by newlines\n */\nexport function fileDataToString(fileData: FileData): string {\n return fileData.content.join(\"\\n\");\n}\n\n/**\n * Create a FileData object with timestamps.\n *\n * @param content - File content as string\n * @param createdAt - Optional creation timestamp (ISO format)\n * @returns FileData object with content and timestamps\n */\nexport function createFileData(content: string, createdAt?: string): FileData {\n const lines = typeof content === \"string\" ? content.split(\"\\n\") : content;\n const now = new Date().toISOString();\n\n return {\n content: lines,\n created_at: createdAt || now,\n modified_at: now,\n };\n}\n\n/**\n * Update FileData with new content, preserving creation timestamp.\n *\n * @param fileData - Existing FileData object\n * @param content - New content as string\n * @returns Updated FileData object\n */\nexport function updateFileData(fileData: FileData, content: string): FileData {\n const lines = typeof content === \"string\" ? content.split(\"\\n\") : content;\n const now = new Date().toISOString();\n\n return {\n content: lines,\n created_at: fileData.created_at,\n modified_at: now,\n };\n}\n\n/**\n * Format file data for read response with line numbers.\n *\n * @param fileData - FileData object\n * @param offset - Line offset (0-indexed)\n * @param limit - Maximum number of lines\n * @returns Formatted content or error message\n */\nexport function formatReadResponse(\n fileData: FileData,\n offset: number,\n limit: number,\n): string {\n const content = fileDataToString(fileData);\n const emptyMsg = checkEmptyContent(content);\n if (emptyMsg) {\n return emptyMsg;\n }\n\n const lines = content.split(\"\\n\");\n const startIdx = offset;\n const endIdx = Math.min(startIdx + limit, lines.length);\n\n if (startIdx >= lines.length) {\n return `Error: Line offset ${offset} exceeds file length (${lines.length} lines)`;\n }\n\n const selectedLines = lines.slice(startIdx, endIdx);\n return formatContentWithLineNumbers(selectedLines, startIdx + 1);\n}\n\n/**\n * Perform string replacement with occurrence validation.\n *\n * @param content - Original content\n * @param oldString - String to replace\n * @param newString - Replacement string\n * @param replaceAll - Whether to replace all occurrences\n * @returns Tuple of [new_content, occurrences] on success, or error message string\n *\n * Special case: When both content and oldString are empty, this sets the initial\n * content to newString. This allows editing empty files by treating empty oldString\n * as \"set initial content\" rather than \"replace nothing\".\n */\nexport function performStringReplacement(\n content: string,\n oldString: string,\n newString: string,\n replaceAll: boolean,\n): [string, number] | string {\n // Special case: empty file with empty oldString sets initial content\n if (content === \"\" && oldString === \"\") {\n return [newString, 0];\n }\n\n // Validate that oldString is not empty (for non-empty files)\n if (oldString === \"\") {\n return \"Error: oldString cannot be empty when file has content\";\n }\n\n // Use split to count occurrences (simpler than regex)\n const occurrences = content.split(oldString).length - 1;\n\n if (occurrences === 0) {\n return `Error: String not found in file: '${oldString}'`;\n }\n\n if (occurrences > 1 && !replaceAll) {\n return `Error: String '${oldString}' has multiple occurrences (appears ${occurrences} times) in file. Use replace_all=True to replace all instances, or provide a more specific string with surrounding context.`;\n }\n\n // Python's str.replace() replaces ALL occurrences\n // Use split/join for consistent behavior\n const newContent = content.split(oldString).join(newString);\n\n return [newContent, occurrences];\n}\n\n/**\n * Truncate list or string result if it exceeds token limit (rough estimate: 4 chars/token).\n */\nexport function truncateIfTooLong(\n result: string[] | string,\n): string[] | string {\n if (Array.isArray(result)) {\n const totalChars = result.reduce((sum, item) => sum + item.length, 0);\n if (totalChars > TOOL_RESULT_TOKEN_LIMIT * 4) {\n const truncateAt = Math.floor(\n (result.length * TOOL_RESULT_TOKEN_LIMIT * 4) / totalChars,\n );\n return [...result.slice(0, truncateAt), TRUNCATION_GUIDANCE];\n }\n return result;\n }\n // string\n if (result.length > TOOL_RESULT_TOKEN_LIMIT * 4) {\n return (\n result.substring(0, TOOL_RESULT_TOKEN_LIMIT * 4) +\n \"\\n\" +\n TRUNCATION_GUIDANCE\n );\n }\n return result;\n}\n\n/**\n * Validate and normalize a directory path.\n *\n * Ensures paths are safe to use by preventing directory traversal attacks\n * and enforcing consistent formatting. All paths are normalized to use\n * forward slashes and start with a leading slash.\n *\n * This function is designed for virtual filesystem paths and rejects\n * Windows absolute paths (e.g., C:/..., F:/...) to maintain consistency\n * and prevent path format ambiguity.\n *\n * @param path - Path to validate\n * @returns Normalized path starting with / and ending with /\n * @throws Error if path is invalid\n *\n * @example\n * ```typescript\n * validatePath(\"foo/bar\") // Returns: \"/foo/bar/\"\n * validatePath(\"/./foo//bar\") // Returns: \"/foo/bar/\"\n * validatePath(\"../etc/passwd\") // Throws: Path traversal not allowed\n * validatePath(\"C:\\\\Users\\\\file\") // Throws: Windows absolute paths not supported\n * ```\n */\nexport function validatePath(path: string | null | undefined): string {\n const pathStr = path || \"/\";\n if (!pathStr || pathStr.trim() === \"\") {\n throw new Error(\"Path cannot be empty\");\n }\n\n let normalized = pathStr.startsWith(\"/\") ? pathStr : \"/\" + pathStr;\n\n if (!normalized.endsWith(\"/\")) {\n normalized += \"/\";\n }\n\n return normalized;\n}\n\n/**\n * Validate and normalize a file path for security.\n *\n * Ensures paths are safe to use by preventing directory traversal attacks\n * and enforcing consistent formatting. All paths are normalized to use\n * forward slashes and start with a leading slash.\n *\n * This function is designed for virtual filesystem paths and rejects\n * Windows absolute paths (e.g., C:/..., F:/...) to maintain consistency\n * and prevent path format ambiguity.\n *\n * @param path - The path to validate and normalize.\n * @param allowedPrefixes - Optional list of allowed path prefixes. If provided,\n * the normalized path must start with one of these prefixes.\n * @returns Normalized canonical path starting with `/` and using forward slashes.\n * @throws Error if path contains traversal sequences (`..` or `~`), is a Windows\n * absolute path (e.g., C:/...), or does not start with an allowed prefix\n * when `allowedPrefixes` is specified.\n *\n * @example\n * ```typescript\n * validateFilePath(\"foo/bar\") // Returns: \"/foo/bar\"\n * validateFilePath(\"/./foo//bar\") // Returns: \"/foo/bar\"\n * validateFilePath(\"../etc/passwd\") // Throws: Path traversal not allowed\n * validateFilePath(\"C:\\\\Users\\\\file.txt\") // Throws: Windows absolute paths not supported\n * validateFilePath(\"/data/file.txt\", [\"/data/\"]) // Returns: \"/data/file.txt\"\n * validateFilePath(\"/etc/file.txt\", [\"/data/\"]) // Throws: Path must start with...\n * ```\n */\nexport function validateFilePath(\n path: string,\n allowedPrefixes?: string[],\n): string {\n // Check for path traversal\n if (path.includes(\"..\") || path.startsWith(\"~\")) {\n throw new Error(`Path traversal not allowed: ${path}`);\n }\n\n // Reject Windows absolute paths (e.g., C:\\..., D:/...)\n // This maintains consistency in virtual filesystem paths\n if (/^[a-zA-Z]:/.test(path)) {\n throw new Error(\n `Windows absolute paths are not supported: ${path}. Please use virtual paths starting with / (e.g., /workspace/file.txt)`,\n );\n }\n\n // Normalize path separators and remove redundant slashes\n let normalized = path.replace(/\\\\/g, \"/\");\n\n // Remove redundant path components (./foo becomes foo, foo//bar becomes foo/bar)\n const parts: string[] = [];\n for (const part of normalized.split(\"/\")) {\n if (part === \".\" || part === \"\") {\n continue;\n }\n parts.push(part);\n }\n normalized = \"/\" + parts.join(\"/\");\n\n // Check allowed prefixes if provided\n if (\n allowedPrefixes &&\n !allowedPrefixes.some((prefix) => normalized.startsWith(prefix))\n ) {\n throw new Error(\n `Path must start with one of ${JSON.stringify(allowedPrefixes)}: ${path}`,\n );\n }\n\n return normalized;\n}\n\n/**\n * Search files dict for paths matching glob pattern.\n *\n * @param files - Dictionary of file paths to FileData\n * @param pattern - Glob pattern (e.g., `*.py`, `**\\/*.ts`)\n * @param path - Base path to search from\n * @returns Newline-separated file paths, sorted by modification time (most recent first).\n * Returns \"No files found\" if no matches.\n *\n * @example\n * ```typescript\n * const files = {\"/src/main.py\": FileData(...), \"/test.py\": FileData(...)};\n * globSearchFiles(files, \"*.py\", \"/\");\n * // Returns: \"/test.py\\n/src/main.py\" (sorted by modified_at)\n * ```\n */\nexport function globSearchFiles(\n files: Record<string, FileData>,\n pattern: string,\n path: string = \"/\",\n): string {\n let normalizedPath: string;\n try {\n normalizedPath = validatePath(path);\n } catch {\n return \"No files found\";\n }\n\n const filtered = Object.fromEntries(\n Object.entries(files).filter(([fp]) => fp.startsWith(normalizedPath)),\n );\n\n // Respect standard glob semantics:\n // - Patterns without path separators (e.g., \"*.py\") match only in the current\n // directory (non-recursive) relative to `path`.\n // - Use \"**\" explicitly for recursive matching.\n const effectivePattern = pattern;\n\n const matches: Array<[string, string]> = [];\n for (const [filePath, fileData] of Object.entries(filtered)) {\n let relative = filePath.substring(normalizedPath.length);\n if (relative.startsWith(\"/\")) {\n relative = relative.substring(1);\n }\n if (!relative) {\n const parts = filePath.split(\"/\");\n relative = parts[parts.length - 1] || \"\";\n }\n\n if (\n micromatch.isMatch(relative, effectivePattern, {\n dot: true,\n nobrace: false,\n })\n ) {\n matches.push([filePath, fileData.modified_at]);\n }\n }\n\n matches.sort((a, b) => b[1].localeCompare(a[1])); // Sort by modified_at descending\n\n if (matches.length === 0) {\n return \"No files found\";\n }\n\n return matches.map(([fp]) => fp).join(\"\\n\");\n}\n\n/**\n * Format grep search results based on output mode.\n *\n * @param results - Dictionary mapping file paths to list of [line_num, line_content] tuples\n * @param outputMode - Output format - \"files_with_matches\", \"content\", or \"count\"\n * @returns Formatted string output\n */\nexport function formatGrepResults(\n results: Record<string, Array<[number, string]>>,\n outputMode: \"files_with_matches\" | \"content\" | \"count\",\n): string {\n if (outputMode === \"files_with_matches\") {\n return Object.keys(results).sort().join(\"\\n\");\n }\n if (outputMode === \"count\") {\n const lines: string[] = [];\n for (const filePath of Object.keys(results).sort()) {\n const count = results[filePath].length;\n lines.push(`${filePath}: ${count}`);\n }\n return lines.join(\"\\n\");\n }\n // content mode\n const lines: string[] = [];\n for (const filePath of Object.keys(results).sort()) {\n lines.push(`${filePath}:`);\n for (const [lineNum, line] of results[filePath]) {\n lines.push(` ${lineNum}: ${line}`);\n }\n }\n return lines.join(\"\\n\");\n}\n\n/**\n * Search file contents for literal text pattern.\n *\n * Performs literal text search.\n *\n * @param files - Dictionary of file paths to FileData\n * @param pattern - Literal text to search for\n * @param path - Base path to search from\n * @param glob - Optional glob pattern to filter files (e.g., \"*.py\")\n * @param outputMode - Output format - \"files_with_matches\", \"content\", or \"count\"\n * @returns Formatted search results. Returns \"No matches found\" if no results.\n *\n * @example\n * ```typescript\n * const files = {\"/file.py\": FileData({content: [\"import os\", \"print('hi')\"], ...})};\n * grepSearchFiles(files, \"import\", \"/\");\n * // Returns: \"/file.py\" (with output_mode=\"files_with_matches\")\n * ```\n */\nexport function grepSearchFiles(\n files: Record<string, FileData>,\n pattern: string,\n path: string | null = null,\n glob: string | null = null,\n outputMode: \"files_with_matches\" | \"content\" | \"count\" = \"files_with_matches\",\n): string {\n let normalizedPath: string;\n try {\n normalizedPath = validatePath(path);\n } catch {\n return \"No matches found\";\n }\n\n let filtered = Object.fromEntries(\n Object.entries(files).filter(([fp]) => fp.startsWith(normalizedPath)),\n );\n\n if (glob) {\n filtered = Object.fromEntries(\n Object.entries(filtered).filter(([fp]) =>\n micromatch.isMatch(basename(fp), glob, { dot: true, nobrace: false }),\n ),\n );\n }\n\n const results: Record<string, Array<[number, string]>> = {};\n for (const [filePath, fileData] of Object.entries(filtered)) {\n for (let i = 0; i < fileData.content.length; i++) {\n const line = fileData.content[i];\n const lineNum = i + 1;\n // Simple substring search for literal matching\n if (line.includes(pattern)) {\n if (!results[filePath]) {\n results[filePath] = [];\n }\n results[filePath].push([lineNum, line]);\n }\n }\n }\n\n if (Object.keys(results).length === 0) {\n return \"No matches found\";\n }\n return formatGrepResults(results, outputMode);\n}\n\n/**\n * Return structured grep matches from an in-memory files mapping.\n *\n * Performs literal text search (not regex).\n *\n * Returns a list of GrepMatch on success, or a string for invalid inputs.\n * We deliberately do not raise here to keep backends non-throwing in tool\n * contexts and preserve user-facing error messages.\n */\nexport function grepMatchesFromFiles(\n files: Record<string, FileData>,\n pattern: string,\n path: string | null = null,\n glob: string | null = null,\n): GrepMatch[] | string {\n let normalizedPath: string;\n try {\n normalizedPath = validatePath(path);\n } catch {\n return [];\n }\n\n let filtered = Object.fromEntries(\n Object.entries(files).filter(([fp]) => fp.startsWith(normalizedPath)),\n );\n\n if (glob) {\n filtered = Object.fromEntries(\n Object.entries(filtered).filter(([fp]) =>\n micromatch.isMatch(basename(fp), glob, { dot: true, nobrace: false }),\n ),\n );\n }\n\n const matches: GrepMatch[] = [];\n for (const [filePath, fileData] of Object.entries(filtered)) {\n for (let i = 0; i < fileData.content.length; i++) {\n const line = fileData.content[i];\n const lineNum = i + 1;\n // Simple substring search for literal matching\n if (line.includes(pattern)) {\n matches.push({ path: filePath, line: lineNum, text: line });\n }\n }\n }\n\n return matches;\n}\n\n/**\n * Group structured matches into the legacy dict form used by formatters.\n */\nexport function buildGrepResultsDict(\n matches: GrepMatch[],\n): Record<string, Array<[number, string]>> {\n const grouped: Record<string, Array<[number, string]>> = {};\n for (const m of matches) {\n if (!grouped[m.path]) {\n grouped[m.path] = [];\n }\n grouped[m.path].push([m.line, m.text]);\n }\n return grouped;\n}\n\n/**\n * Format structured grep matches using existing formatting logic.\n */\nexport function formatGrepMatches(\n matches: GrepMatch[],\n outputMode: \"files_with_matches\" | \"content\" | \"count\",\n): string {\n if (matches.length === 0) {\n return \"No matches found\";\n }\n return formatGrepResults(buildGrepResultsDict(matches), outputMode);\n}\n","/**\n * StateBackend: Store files in LangGraph agent state (ephemeral).\n */\n\nimport type {\n BackendProtocol,\n EditResult,\n FileData,\n FileDownloadResponse,\n FileInfo,\n FileUploadResponse,\n GrepMatch,\n StateAndStore,\n WriteResult,\n} from \"./protocol.js\";\nimport {\n createFileData,\n fileDataToString,\n formatReadResponse,\n globSearchFiles,\n grepMatchesFromFiles,\n performStringReplacement,\n updateFileData,\n} from \"./utils.js\";\n\n/**\n * Backend that stores files in agent state (ephemeral).\n *\n * Uses LangGraph's state management and checkpointing. Files persist within\n * a conversation thread but not across threads. State is automatically\n * checkpointed after each agent step.\n *\n * Special handling: Since LangGraph state must be updated via Command objects\n * (not direct mutation), operations return filesUpdate in WriteResult/EditResult\n * for the middleware to apply via Command.\n */\nexport class StateBackend implements BackendProtocol {\n private stateAndStore: StateAndStore;\n\n constructor(stateAndStore: StateAndStore) {\n this.stateAndStore = stateAndStore;\n }\n\n /**\n * Get files from current state.\n */\n private getFiles(): Record<string, FileData> {\n return (\n ((this.stateAndStore.state as any).files as Record<string, FileData>) ||\n {}\n );\n }\n\n /**\n * List files and directories in the specified directory (non-recursive).\n *\n * @param path - Absolute path to directory\n * @returns List of FileInfo objects for files and directories directly in the directory.\n * Directories have a trailing / in their path and is_dir=true.\n */\n lsInfo(path: string): FileInfo[] {\n const files = this.getFiles();\n const infos: FileInfo[] = [];\n const subdirs = new Set<string>();\n\n // Normalize path to have trailing slash for proper prefix matching\n const normalizedPath = path.endsWith(\"/\") ? path : path + \"/\";\n\n for (const [k, fd] of Object.entries(files)) {\n // Check if file is in the specified directory or a subdirectory\n if (!k.startsWith(normalizedPath)) {\n continue;\n }\n\n // Get the relative path after the directory\n const relative = k.substring(normalizedPath.length);\n\n // If relative path contains '/', it's in a subdirectory\n if (relative.includes(\"/\")) {\n // Extract the immediate subdirectory name\n const subdirName = relative.split(\"/\")[0];\n subdirs.add(normalizedPath + subdirName + \"/\");\n continue;\n }\n\n // This is a file directly in the current directory\n const size = fd.content.join(\"\\n\").length;\n infos.push({\n path: k,\n is_dir: false,\n size: size,\n modified_at: fd.modified_at,\n });\n }\n\n // Add directories to the results\n for (const subdir of Array.from(subdirs).sort()) {\n infos.push({\n path: subdir,\n is_dir: true,\n size: 0,\n modified_at: \"\",\n });\n }\n\n infos.sort((a, b) => a.path.localeCompare(b.path));\n return infos;\n }\n\n /**\n * Read file content with line numbers.\n *\n * @param filePath - Absolute file path\n * @param offset - Line offset to start reading from (0-indexed)\n * @param limit - Maximum number of lines to read\n * @returns Formatted file content with line numbers, or error message\n */\n read(filePath: string, offset: number = 0, limit: number = 500): string {\n const files = this.getFiles();\n const fileData = files[filePath];\n\n if (!fileData) {\n return `Error: File '${filePath}' not found`;\n }\n\n return formatReadResponse(fileData, offset, limit);\n }\n\n /**\n * Read file content as raw FileData.\n *\n * @param filePath - Absolute file path\n * @returns Raw file content as FileData\n */\n readRaw(filePath: string): FileData {\n const files = this.getFiles();\n const fileData = files[filePath];\n\n if (!fileData) throw new Error(`File '${filePath}' not found`);\n return fileData;\n }\n\n /**\n * Create a new file with content.\n * Returns WriteResult with filesUpdate to update LangGraph state.\n */\n write(filePath: string, content: string): WriteResult {\n const files = this.getFiles();\n\n if (filePath in files) {\n return {\n error: `Cannot write to ${filePath} because it already exists. Read and then make an edit, or write to a new path.`,\n };\n }\n\n const newFileData = createFileData(content);\n return {\n path: filePath,\n filesUpdate: { [filePath]: newFileData },\n };\n }\n\n /**\n * Edit a file by replacing string occurrences.\n * Returns EditResult with filesUpdate and occurrences.\n */\n edit(\n filePath: string,\n oldString: string,\n newString: string,\n replaceAll: boolean = false,\n ): EditResult {\n const files = this.getFiles();\n const fileData = files[filePath];\n\n if (!fileData) {\n return { error: `Error: File '${filePath}' not found` };\n }\n\n const content = fileDataToString(fileData);\n const result = performStringReplacement(\n content,\n oldString,\n newString,\n replaceAll,\n );\n\n if (typeof result === \"string\") {\n return { error: result };\n }\n\n const [newContent, occurrences] = result;\n const newFileData = updateFileData(fileData, newContent);\n return {\n path: filePath,\n filesUpdate: { [filePath]: newFileData },\n occurrences: occurrences,\n };\n }\n\n /**\n * Structured search results or error string for invalid input.\n */\n grepRaw(\n pattern: string,\n path: string = \"/\",\n glob: string | null = null,\n ): GrepMatch[] | string {\n const files = this.getFiles();\n return grepMatchesFromFiles(files, pattern, path, glob);\n }\n\n /**\n * Structured glob matching returning FileInfo objects.\n */\n globInfo(pattern: string, path: string = \"/\"): FileInfo[] {\n const files = this.getFiles();\n const result = globSearchFiles(files, pattern, path);\n\n if (result === \"No files found\") {\n return [];\n }\n\n const paths = result.split(\"\\n\");\n const infos: FileInfo[] = [];\n for (const p of paths) {\n const fd = files[p];\n const size = fd ? fd.content.join(\"\\n\").length : 0;\n infos.push({\n path: p,\n is_dir: false,\n size: size,\n modified_at: fd?.modified_at || \"\",\n });\n }\n return infos;\n }\n\n /**\n * Upload multiple files.\n *\n * Note: Since LangGraph state must be updated via Command objects,\n * the caller must apply filesUpdate via Command after calling this method.\n *\n * @param files - List of [path, content] tuples to upload\n * @returns List of FileUploadResponse objects, one per input file\n */\n uploadFiles(\n files: Array<[string, Uint8Array]>,\n ): FileUploadResponse[] & { filesUpdate?: Record<string, FileData> } {\n const responses: FileUploadResponse[] = [];\n const updates: Record<string, FileData> = {};\n\n for (const [path, content] of files) {\n try {\n const contentStr = new TextDecoder().decode(content);\n const fileData = createFileData(contentStr);\n updates[path] = fileData;\n responses.push({ path, error: null });\n } catch {\n responses.push({ path, error: \"invalid_path\" });\n }\n }\n\n // Attach filesUpdate for the caller to apply via Command\n const result = responses as FileUploadResponse[] & {\n filesUpdate?: Record<string, FileData>;\n };\n result.filesUpdate = updates;\n return result;\n }\n\n /**\n * Download multiple files.\n *\n * @param paths - List of file paths to download\n * @returns List of FileDownloadResponse objects, one per input path\n */\n downloadFiles(paths: string[]): FileDownloadResponse[] {\n const files = this.getFiles();\n const responses: FileDownloadResponse[] = [];\n\n for (const path of paths) {\n const fileData = files[path];\n if (!fileData) {\n responses.push({ path, content: null, error: \"file_not_found\" });\n continue;\n }\n\n const contentStr = fileDataToString(fileData);\n const content = new TextEncoder().encode(contentStr);\n responses.push({ path, content, error: null });\n }\n\n return responses;\n }\n}\n","/**\n * Middleware for providing filesystem tools to an agent.\n *\n * Provides ls, read_file, write_file, edit_file, glob, and grep tools with support for:\n * - Pluggable backends (StateBackend, StoreBackend, FilesystemBackend, CompositeBackend)\n * - Tool result eviction for large outputs\n */\n\nimport {\n createMiddleware,\n tool,\n ToolMessage,\n type AgentMiddleware as _AgentMiddleware,\n} from \"langchain\";\nimport {\n Command,\n isCommand,\n getCurrentTaskInput,\n StateSchema,\n ReducedValue,\n} from \"@langchain/langgraph\";\nimport { z } from \"zod/v4\";\nimport type {\n BackendProtocol,\n BackendFactory,\n FileData,\n StateAndStore,\n} from \"../backends/protocol.js\";\nimport { isSandboxBackend } from \"../backends/protocol.js\";\nimport { StateBackend } from \"../backends/state.js\";\nimport {\n sanitizeToolCallId,\n formatContentWithLineNumbers,\n truncateIfTooLong,\n} from \"../backends/utils.js\";\n\n/**\n * Tools that should be excluded from the large result eviction logic.\n *\n * This array contains tools that should NOT have their results evicted to the filesystem\n * when they exceed token limits. Tools are excluded for different reasons:\n *\n * 1. Tools with built-in truncation (ls, glob, grep):\n * These tools truncate their own output when it becomes too large. When these tools\n * produce truncated output due to many matches, it typically indicates the query\n * needs refinement rather than full result preservation. In such cases, the truncated\n * matches are potentially more like noise and the LLM should be prompted to narrow\n * its search criteria instead.\n *\n * 2. Tools with problematic truncation behavior (read_file):\n * read_file is tricky to handle as the failure mode here is single long lines\n * (e.g., imagine a jsonl file with very long payloads on each line). If we try to\n * truncate the result of read_file, the agent may then attempt to re-read the\n * truncated file using read_file again, which won't help.\n *\n * 3. Tools that never exceed limits (edit_file, write_file):\n * These tools return minimal confirmation messages and are never expected to produce\n * output large enough to exceed token limits, so checking them would be unnecessary.\n */\nexport const TOOLS_EXCLUDED_FROM_EVICTION = [\n \"ls\",\n \"glob\",\n \"grep\",\n \"read_file\",\n \"edit_file\",\n \"write_file\",\n] as const;\n\n/**\n * Approximate number of characters per token for truncation calculations.\n * Using 4 chars per token as a conservative approximation (actual ratio varies by content)\n * This errs on the high side to avoid premature eviction of content that might fit.\n */\nexport const NUM_CHARS_PER_TOKEN = 4;\n\n/**\n * Default values for read_file tool pagination (in lines).\n */\nexport const DEFAULT_READ_LINE_OFFSET = 0;\nexport const DEFAULT_READ_LINE_LIMIT = 100;\n\n/**\n * Template for truncation message in read_file.\n * {file_path} will be filled in at runtime.\n */\nconst READ_FILE_TRUNCATION_MSG = `\n\n[Output was truncated due to size limits. The file content is very large. Consider reformatting the file to make it easier to navigate. For example, if this is JSON, use execute(command='jq . {file_path}') to pretty-print it with line breaks. For other formats, you can use appropriate formatting tools to split long lines.]`;\n\n/**\n * Message template for evicted tool results.\n */\nconst TOO_LARGE_TOOL_MSG = `Tool result too large, the result of this tool call {tool_call_id} was saved in the filesystem at this path: {file_path}\nYou can read the result from the filesystem by using the read_file tool, but make sure to only read part of the result at a time.\nYou can do this by specifying an offset and limit in the read_file tool call.\nFor example, to read the first 100 lines, you can use the read_file tool with offset=0 and limit=100.\n\nHere is a preview showing the head and tail of the result (lines of the form\n... [N lines truncated] ...\nindicate omitted lines in the middle of the content):\n\n{content_sample}`;\n\n/**\n * Create a preview of content showing head and tail with truncation marker.\n *\n * @param contentStr - The full content string to preview.\n * @param headLines - Number of lines to show from the start (default: 5).\n * @param tailLines - Number of lines to show from the end (default: 5).\n * @returns Formatted preview string with line numbers.\n */\nexport function createContentPreview(\n contentStr: string,\n headLines: number = 5,\n tailLines: number = 5,\n): string {\n const lines = contentStr.split(\"\\n\");\n\n if (lines.length <= headLines + tailLines) {\n // If file is small enough, show all lines\n const previewLines = lines.map((line) => line.substring(0, 1000));\n return formatContentWithLineNumbers(previewLines, 1);\n }\n\n // Show head and tail with truncation marker\n const head = lines.slice(0, headLines).map((line) => line.substring(0, 1000));\n const tail = lines.slice(-tailLines).map((line) => line.substring(0, 1000));\n\n const headSample = formatContentWithLineNumbers(head, 1);\n const truncationNotice = `\\n... [${lines.length - headLines - tailLines} lines truncated] ...\\n`;\n const tailSample = formatContentWithLineNumbers(\n tail,\n lines.length - tailLines + 1,\n );\n\n return headSample + truncationNotice + tailSample;\n}\n\n/**\n * required for type inference\n */\nimport type * as _zodTypes from \"@langchain/core/utils/types\";\nimport type * as _zodMeta from \"@langchain/langgraph/zod\";\nimport type * as _messages from \"@langchain/core/messages\";\n\n/**\n * Zod v3 schema for FileData (re-export from backends)\n */\nexport const FileDataSchema = z.object({\n content: z.array(z.string()),\n created_at: z.string(),\n modified_at: z.string(),\n});\n\n/**\n * Type for the files state record.\n */\nexport type FilesRecord = Record<string, FileData>;\n\n/**\n * Type for file updates, where null indicates deletion.\n */\nexport type FilesRecordUpdate = Record<string, FileData | null>;\n\n/**\n * Reducer for files state that merges file updates with support for deletions.\n * When a file value is null, the file is deleted from state.\n * When a file value is non-null, it is added or updated in state.\n *\n * This reducer enables concurrent updates from parallel subagents by properly\n * merging their file changes instead of requiring LastValue semantics.\n *\n * @param current - The current files record (from state)\n * @param update - The new files record (from a subagent update), with null values for deletions\n * @returns Merged files record with deletions applied\n */\nexport function fileDataReducer(\n current: FilesRecord | undefined,\n update: FilesRecordUpdate | undefined,\n): FilesRecord {\n // If no update, return current (or empty object)\n if (update === undefined) {\n return current || {};\n }\n\n // If no current, filter out null values from update\n if (current === undefined) {\n const result: FilesRecord = {};\n for (const [key, value] of Object.entries(update)) {\n if (value !== null) {\n result[key] = value;\n }\n }\n return result;\n }\n\n // Merge: apply updates and deletions\n const result = { ...current };\n for (const [key, value] of Object.entries(update)) {\n if (value === null) {\n delete result[key];\n } else {\n result[key] = value;\n }\n }\n return result;\n}\n\n/**\n * Shared filesystem state schema.\n * Defined at module level to ensure the same object identity is used across all agents,\n * preventing \"Channel already exists with different type\" errors when multiple agents\n * use createFilesystemMiddleware.\n *\n * Uses ReducedValue for files to allow concurrent updates from parallel subagents.\n */\nconst FilesystemStateSchema = new StateSchema({\n files: new ReducedValue(\n z.record(z.string(), FileDataSchema).default(() => ({})),\n {\n inputSchema: z.record(z.string(), FileDataSchema.nullable()).optional(),\n reducer: fileDataReducer,\n },\n ),\n});\n\n/**\n * Resolve backend from factory or instance.\n *\n * @param backend - Backend instance or factory function\n * @param stateAndStore - State and store container for backend initialization\n */\nfunction getBackend(\n backend: BackendProtocol | BackendFactory,\n stateAndStore: StateAndStore,\n): BackendProtocol {\n if (typeof backend === \"function\") {\n return backend(stateAndStore);\n }\n return backend;\n}\n\n// System prompts\nconst FILESYSTEM_SYSTEM_PROMPT = `## Filesystem Tools \\`ls\\`, \\`read_file\\`, \\`write_file\\`, \\`edit_file\\`, \\`glob\\`, \\`grep\\`\n\nYou have access to a filesystem which you can interact with using these tools.\nAll file paths must start with a /.\n\n- ls: list files in a directory (requires absolute path)\n- read_file: read a file from the filesystem\n- write_file: write to a file in the filesystem\n- edit_file: edit a file in the filesystem\n- glob: find files matching a pattern (e.g., \"**/*.py\")\n- grep: search for text within files`;\n\n// Tool descriptions - ported from Python for comprehensive LLM guidance\nexport const LS_TOOL_DESCRIPTION = `Lists all files in a directory.\n\nThis is useful for exploring the filesystem and finding the right file to read or edit.\nYou should almost ALWAYS use this tool before using the read_file or edit_file tools.`;\n\nexport const READ_FILE_TOOL_DESCRIPTION = `Reads a file from the filesystem.\n\nAssume this tool is able to read all files. If the User provides a path to a file assume that path is valid. It is okay to read a file that does not exist; an error will be returned.\n\nUsage:\n- By default, it reads up to 100 lines starting from the beginning of the file\n- **IMPORTANT for large files and codebase exploration**: Use pagination with offset and limit parameters to avoid context overflow\n - First scan: read_file(path, limit=100) to see file structure\n - Read more sections: read_file(path, offset=100, limit=200) for next 200 lines\n - Only omit limit (read full file) when necessary for editing\n- Specify offset and limit: read_file(path, offset=0, limit=100) reads first 100 lines\n- Results are returned using cat -n format, with line numbers starting at 1\n- Lines longer than 10,000 characters will be split into multiple lines with continuation markers (e.g., 5.1, 5.2, etc.). When you specify a limit, these continuation lines count towards the limit.\n- You have the capability to call multiple tools in a single response. It is always better to speculatively read multiple files as a batch that are potentially useful.\n- If you read a file that exists but has empty contents you will receive a system reminder warning in place of file contents.\n- You should ALWAYS make sure a file has been read before editing it.`;\n\nexport const WRITE_FILE_TOOL_DESCRIPTION = `Writes to a new file in the filesystem.\n\nUsage:\n- The write_file tool will create a new file.\n- Prefer to edit existing files (with the edit_file tool) over creating new ones when possible.`;\n\nexport const EDIT_FILE_TOOL_DESCRIPTION = `Performs exact string replacements in files.\n\nUsage:\n- You must read the file before editing. This tool will error if you attempt an edit without reading the file first.\n- When editing, preserve the exact indentation (tabs/spaces) from the read output. Never include line number prefixes in old_string or new_string.\n- ALWAYS prefer editing existing files over creating new ones.\n- Only use emojis if the user explicitly requests it.`;\n\nexport const GLOB_TOOL_DESCRIPTION = `Find files matching a glob pattern.\n\nSupports standard glob patterns: \\`*\\` (any characters), \\`**\\` (any directories), \\`?\\` (single character).\nReturns a list of absolute file paths that match the pattern.\n\nExamples:\n- \\`**/*.py\\` - Find all Python files\n- \\`*.txt\\` - Find all text files in root\n- \\`/subdir/**/*.md\\` - Find all markdown files under /subdir`;\n\nexport const GREP_TOOL_DESCRIPTION = `Search for a text pattern across files.\n\nSearches for literal text (not regex) and returns matching files or content based on output_mode.\nSpecial characters like parentheses, brackets, pipes, etc. are treated as literal characters, not regex operators.\n\nExamples:\n- Search all files: \\`grep(pattern=\"TODO\")\\`\n- Search Python files only: \\`grep(pattern=\"import\", glob=\"*.py\")\\`\n- Show matching lines: \\`grep(pattern=\"error\", output_mode=\"content\")\\`\n- Search for code with special chars: \\`grep(pattern=\"def __init__(self):\")\\``;\nexport const EXECUTE_TOOL_DESCRIPTION = `Executes a shell command in an isolated sandbox environment.\n\nUsage:\nExecutes a given command in the sandbox environment with proper handling and security measures.\nBefore executing the command, please follow these steps:\n\n1. Directory Verification:\n - If the command will create new directories or files, first use the ls tool to verify the parent directory exists and is the correct location\n - For example, before running \"mkdir foo/bar\", first use ls to check that \"foo\" exists and is the intended parent directory\n\n2. Command Execution:\n - Always quote file paths that contain spaces with double quotes (e.g., cd \"path with spaces/file.txt\")\n - Examples of proper quoting:\n - cd \"/Users/name/My Documents\" (correct)\n - cd /Users/name/My Documents (incorrect - will fail)\n - python \"/path/with spaces/script.py\" (correct)\n - python /path/with spaces/script.py (incorrect - will fail)\n - After ensuring proper quoting, execute the command\n - Capture the output of the command\n\nUsage notes:\n - Commands run in an isolated sandbox environment\n - Returns combined stdout/stderr output with exit code\n - If the output is very large, it may be truncated\n - VERY IMPORTANT: You MUST avoid using search commands like find and grep. Instead use the grep, glob tools to search. You MUST avoid read tools like cat, head, tail, and use read_file to read files.\n - When issuing multiple commands, use the ';' or '&&' operator to separate them. DO NOT use newlines (newlines are ok in quoted strings)\n - Use '&&' when commands depend on each other (e.g., \"mkdir dir && cd dir\")\n - Use ';' only when you need to run commands sequentially but don't care if earlier commands fail\n - Try to maintain your current working directory throughout the session by using absolute paths and avoiding usage of cd\n\nExamples:\n Good examples:\n - execute(command=\"pytest /foo/bar/tests\")\n - execute(command=\"python /path/to/script.py\")\n - execute(command=\"npm install && npm test\")\n\n Bad examples (avoid these):\n - execute(command=\"cd /foo/bar && pytest tests\") # Use absolute path instead\n - execute(command=\"cat file.txt\") # Use read_file tool instead\n - execute(command=\"find . -name '*.py'\") # Use glob tool instead\n - execute(command=\"grep -r 'pattern' .\") # Use grep tool instead\n\nNote: This tool is only available if the backend supports execution (SandboxBackendProtocol).\nIf execution is not supported, the tool will return an error message.`;\n\n// System prompt for execution capability\nexport const EXECUTION_SYSTEM_PROMPT = `## Execute Tool \\`execute\\`\n\nYou have access to an \\`execute\\` tool for running shell commands in a sandboxed environment.\nUse this tool to run commands, scripts, tests, builds, and other shell operations.\n\n- execute: run a shell command in the sandbox (returns output and exit code)`;\n\n/**\n * Create ls tool using backend.\n */\nfunction createLsTool(\n backend: BackendProtocol | BackendFactory,\n options: { customDescription: string | undefined },\n) {\n const { customDescription } = options;\n return tool(\n async (input, config) => {\n const stateAndStore: StateAndStore = {\n state: getCurrentTaskInput(config),\n store: (config as any).store,\n };\n const resolvedBackend = getBackend(backend, stateAndStore);\n const path = input.path || \"/\";\n const infos = await resolvedBackend.lsInfo(path);\n\n if (infos.length === 0) {\n return `No files found in ${path}`;\n }\n\n // Format output\n const lines: string[] = [];\n for (const info of infos) {\n if (info.is_dir) {\n lines.push(`${info.path} (directory)`);\n } else {\n const size = info.size ? ` (${info.size} bytes)` : \"\";\n lines.push(`${info.path}${size}`);\n }\n }\n\n const result = truncateIfTooLong(lines);\n\n if (Array.isArray(result)) {\n return result.join(\"\\n\");\n }\n return result;\n },\n {\n name: \"ls\",\n description: customDescription || LS_TOOL_DESCRIPTION,\n schema: z.object({\n path: z\n .string()\n .optional()\n .default(\"/\")\n .describe(\"Directory path to list (default: /)\"),\n }),\n },\n );\n}\n\n/**\n * Create read_file tool using backend.\n */\nfunction createReadFileTool(\n backend: BackendProtocol | BackendFactory,\n options: {\n customDescription: string | undefined;\n toolTokenLimitBeforeEvict: number | null;\n },\n) {\n const { customDescription, toolTokenLimitBeforeEvict } = options;\n return tool(\n async (input, config) => {\n const stateAndStore: StateAndStore = {\n state: getCurrentTaskInput(config),\n store: (config as any).store,\n };\n const resolvedBackend = getBackend(backend, stateAndStore);\n const {\n file_path,\n offset = DEFAULT_READ_LINE_OFFSET,\n limit = DEFAULT_READ_LINE_LIMIT,\n } = input;\n let result = await resolvedBackend.read(file_path, offset, limit);\n\n // Enforce line limit on result (in case backend returns more)\n const lines = result.split(\"\\n\");\n if (lines.length > limit) {\n result = lines.slice(0, limit).join(\"\\n\");\n }\n\n // Check if result exceeds token threshold and truncate if necessary\n if (\n toolTokenLimitBeforeEvict &&\n result.length >= NUM_CHARS_PER_TOKEN * toolTokenLimitBeforeEvict\n ) {\n // Calculate truncation message length to ensure final result stays under threshold\n const truncationMsg = READ_FILE_TRUNCATION_MSG.replace(\n \"{file_path}\",\n file_path,\n );\n const maxContentLength =\n NUM_CHARS_PER_TOKEN * toolTokenLimitBeforeEvict -\n truncationMsg.length;\n result = result.substring(0, maxContentLength) + truncationMsg;\n }\n\n return result;\n },\n {\n name: \"read_file\",\n description: customDescription || READ_FILE_TOOL_DESCRIPTION,\n schema: z.object({\n file_path: z.string().describe(\"Absolute path to the file to read\"),\n offset: z.coerce\n .number()\n .optional()\n .default(DEFAULT_READ_LINE_OFFSET)\n .describe(\"Line offset to start reading from (0-indexed)\"),\n limit: z.coerce\n .number()\n .optional()\n .default(DEFAULT_READ_LINE_LIMIT)\n .describe(\"Maximum number of lines to read\"),\n }),\n },\n );\n}\n\n/**\n * Create write_file tool using backend.\n */\nfunction createWriteFileTool(\n backend: BackendProtocol | BackendFactory,\n options: { customDescription: string | undefined },\n) {\n const { customDescription } = options;\n return tool(\n async (input, config) => {\n const stateAndStore: StateAndStore = {\n state: getCurrentTaskInput(config),\n store: (config as any).store,\n };\n const resolvedBackend = getBackend(backend, stateAndStore);\n const { file_path, content } = input;\n const result = await resolvedBackend.write(file_path, content);\n\n if (result.error) {\n return result.error;\n }\n\n // If filesUpdate is present, return Command to update state\n const message = new ToolMessage({\n content: `Successfully wrote to '${file_path}'`,\n tool_call_id: config.toolCall?.id as string,\n name: \"write_file\",\n metadata: result.metadata,\n });\n\n if (result.filesUpdate) {\n return new Command({\n update: { files: result.filesUpdate, messages: [message] },\n });\n }\n\n return message;\n },\n {\n name: \"write_file\",\n description: customDescription || WRITE_FILE_TOOL_DESCRIPTION,\n schema: z.object({\n file_path: z.string().describe(\"Absolute path to the file to write\"),\n content: z\n .string()\n .default(\"\")\n .describe(\"Content to write to the file\"),\n }),\n },\n );\n}\n\n/**\n * Create edit_file tool using backend.\n */\nfunction createEditFileTool(\n backend: BackendProtocol | BackendFactory,\n options: { customDescription: string | undefined },\n) {\n const { customDescription } = options;\n return tool(\n async (input, config) => {\n const stateAndStore: StateAndStore = {\n state: getCurrentTaskInput(config),\n store: (config as any).store,\n };\n const resolvedBackend = getBackend(backend, stateAndStore);\n const { file_path, old_string, new_string, replace_all = false } = input;\n const result = await resolvedBackend.edit(\n file_path,\n old_string,\n new_string,\n replace_all,\n );\n\n if (result.error) {\n return result.error;\n }\n\n const message = new ToolMessage({\n content: `Successfully replaced ${result.occurrences} occurrence(s) in '${file_path}'`,\n tool_call_id: config.toolCall?.id as string,\n name: \"edit_file\",\n metadata: result.metadata,\n });\n\n // If filesUpdate is present, return Command to update state\n if (result.filesUpdate) {\n return new Command({\n update: { files: result.filesUpdate, messages: [message] },\n });\n }\n\n // External storage (filesUpdate is null)\n return message;\n },\n {\n name: \"edit_file\",\n description: customDescription || EDIT_FILE_TOOL_DESCRIPTION,\n schema: z.object({\n file_path: z.string().describe(\"Absolute path to the file to edit\"),\n old_string: z\n .string()\n .describe(\"String to be replaced (must match exactly)\"),\n new_string: z.string().describe(\"String to replace with\"),\n replace_all: z\n .boolean()\n .optional()\n .default(false)\n .describe(\"Whether to replace all occurrences\"),\n }),\n },\n );\n}\n\n/**\n * Create glob tool using backend.\n */\nfunction createGlobTool(\n backend: BackendProtocol | BackendFactory,\n options: { customDescription: string | undefined },\n) {\n const { customDescription } = options;\n return tool(\n async (input, config) => {\n const stateAndStore: StateAndStore = {\n state: getCurrentTaskInput(config),\n store: (config as any).store,\n };\n const resolvedBackend = getBackend(backend, stateAndStore);\n const { pattern, path = \"/\" } = input;\n const infos = await resolvedBackend.globInfo(pattern, path);\n\n if (infos.length === 0) {\n return `No files found matching pattern '${pattern}'`;\n }\n\n const paths = infos.map((info) => info.path);\n const result = truncateIfTooLong(paths);\n\n if (Array.isArray(result)) {\n return result.join(\"\\n\");\n }\n return result;\n },\n {\n name: \"glob\",\n description: customDescription || GLOB_TOOL_DESCRIPTION,\n schema: z.object({\n pattern: z.string().describe(\"Glob pattern (e.g., '*.py', '**/*.ts')\"),\n path: z\n .string()\n .optional()\n .default(\"/\")\n .describe(\"Base path to search from (default: /)\"),\n }),\n },\n );\n}\n\n/**\n * Create grep tool using backend.\n */\nfunction createGrepTool(\n backend: BackendProtocol | BackendFactory,\n options: { customDescription: string | undefined },\n) {\n const { customDescription } = options;\n return tool(\n async (input, config) => {\n const stateAndStore: StateAndStore = {\n state: getCurrentTaskInput(config),\n store: (config as any).store,\n };\n const resolvedBackend = getBackend(backend, stateAndStore);\n const { pattern, path = \"/\", glob = null } = input;\n const result = await resolvedBackend.grepRaw(pattern, path, glob);\n\n // If string, it's an error\n if (typeof result === \"string\") {\n return result;\n }\n\n if (result.length === 0) {\n return `No matches found for pattern '${pattern}'`;\n }\n\n // Format output: group by file\n const lines: string[] = [];\n let currentFile: string | null = null;\n for (const match of result) {\n if (match.path !== currentFile) {\n currentFile = match.path;\n lines.push(`\\n${currentFile}:`);\n }\n lines.push(` ${match.line}: ${match.text}`);\n }\n\n const truncated = truncateIfTooLong(lines);\n\n if (Array.isArray(truncated)) {\n return truncated.join(\"\\n\");\n }\n return truncated;\n },\n {\n name: \"grep\",\n description: customDescription || GREP_TOOL_DESCRIPTION,\n schema: z.object({\n pattern: z.string().describe(\"Regex pattern to search for\"),\n path: z\n .string()\n .optional()\n .default(\"/\")\n .describe(\"Base path to search from (default: /)\"),\n glob: z\n .string()\n .optional()\n .nullable()\n .describe(\"Optional glob pattern to filter files (e.g., '*.py')\"),\n }),\n },\n );\n}\n\n/**\n * Create execute tool using backend.\n */\nfunction createExecuteTool(\n backend: BackendProtocol | BackendFactory,\n options: { customDescription: string | undefined },\n) {\n const { customDescription } = options;\n return tool(\n async (input, config) => {\n const stateAndStore: StateAndStore = {\n state: getCurrentTaskInput(config),\n store: (config as any).store,\n };\n const resolvedBackend = getBackend(backend, stateAndStore);\n\n // Runtime check - fail gracefully if not supported\n if (!isSandboxBackend(resolvedBackend)) {\n return (\n \"Error: Execution not available. This agent's backend \" +\n \"does not support command execution (SandboxBackendProtocol). \" +\n \"To use the execute tool, provide a backend that implements SandboxBackendProtocol.\"\n );\n }\n\n const result = await resolvedBackend.execute(input.command);\n\n // Format output for LLM consumption\n const parts = [result.output];\n\n if (result.exitCode !== null) {\n const status = result.exitCode === 0 ? \"succeeded\" : \"failed\";\n parts.push(`\\n[Command ${status} with exit code ${result.exitCode}]`);\n }\n\n if (result.truncated) {\n parts.push(\"\\n[Output was truncated due to size limits]\");\n }\n\n return parts.join(\"\");\n },\n {\n name: \"execute\",\n description: customDescription || EXECUTE_TOOL_DESCRIPTION,\n schema: z.object({\n command: z.string().describe(\"The shell command to execute\"),\n }),\n },\n );\n}\n\n/**\n * Options for creating filesystem middleware.\n */\nexport interface FilesystemMiddlewareOptions {\n /** Backend instance or factory (default: StateBackend) */\n backend?: BackendProtocol | BackendFactory;\n /** Optional custom system prompt override */\n systemPrompt?: string | null;\n /** Optional custom tool descriptions override */\n customToolDescriptions?: Record<string, string> | null;\n /** Optional token limit before evicting a tool result to the filesystem (default: 20000 tokens, ~80KB) */\n toolTokenLimitBeforeEvict?: number | null;\n}\n\n/**\n * Create filesystem middleware with all tools and features.\n */\nexport function createFilesystemMiddleware(\n options: FilesystemMiddlewareOptions = {},\n) {\n const {\n backend = (stateAndStore: StateAndStore) => new StateBackend(stateAndStore),\n systemPrompt: customSystemPrompt = null,\n customToolDescriptions = null,\n toolTokenLimitBeforeEvict = 20000,\n } = options;\n\n const baseSystemPrompt = customSystemPrompt || FILESYSTEM_SYSTEM_PROMPT;\n\n // All tools including execute (execute will be filtered at runtime if backend doesn't support it)\n const allTools = [\n createLsTool(backend, {\n customDescription: customToolDescriptions?.ls,\n }),\n createReadFileTool(backend, {\n customDescription: customToolDescriptions?.read_file,\n toolTokenLimitBeforeEvict,\n }),\n createWriteFileTool(backend, {\n customDescription: customToolDescriptions?.write_file,\n }),\n createEditFileTool(backend, {\n customDescription: customToolDescriptions?.edit_file,\n }),\n createGlobTool(backend, {\n customDescription: customToolDescriptions?.glob,\n }),\n createGrepTool(backend, {\n customDescription: customToolDescriptions?.grep,\n }),\n createExecuteTool(backend, {\n customDescription: customToolDescriptions?.execute,\n }),\n ];\n\n return createMiddleware({\n name: \"FilesystemMiddleware\",\n stateSchema: FilesystemStateSchema,\n tools: allTools,\n wrapModelCall: async (request, handler) => {\n // Check if backend supports execution\n const stateAndStore: StateAndStore = {\n state: request.state || {},\n // @ts-expect-error - request.config is incorrect typed\n store: request.config?.store,\n };\n const resolvedBackend = getBackend(backend, stateAndStore);\n const supportsExecution = isSandboxBackend(resolvedBackend);\n\n // Filter tools based on backend capabilities\n let tools = request.tools;\n if (!supportsExecution) {\n tools = tools.filter((t: { name: string }) => t.name !== \"execute\");\n }\n\n // Build system prompt - add execution instructions if available\n let filesystemPrompt = baseSystemPrompt;\n if (supportsExecution) {\n filesystemPrompt = `${filesystemPrompt}\\n\\n${EXECUTION_SYSTEM_PROMPT}`;\n }\n\n // Combine with existing system message\n const newSystemMessage = request.systemMessage.concat(filesystemPrompt);\n\n return handler({ ...request, tools, systemMessage: newSystemMessage });\n },\n wrapToolCall: async (request, handler) => {\n // Return early if eviction is disabled\n if (!toolTokenLimitBeforeEvict) {\n return handler(request);\n }\n\n // Check if this tool is excluded from eviction\n const toolName = request.toolCall?.name;\n if (\n toolName &&\n TOOLS_EXCLUDED_FROM_EVICTION.includes(\n toolName as (typeof TOOLS_EXCLUDED_FROM_EVICTION)[number],\n )\n ) {\n return handler(request);\n }\n\n const result = await handler(request);\n\n async function processToolMessage(\n msg: ToolMessage,\n toolTokenLimitBeforeEvict: number,\n ) {\n if (\n typeof msg.content === \"string\" &&\n msg.content.length > toolTokenLimitBeforeEvict * NUM_CHARS_PER_TOKEN\n ) {\n // Build StateAndStore from request\n const stateAndStore: StateAndStore = {\n state: request.state || {},\n // @ts-expect-error - request.config is incorrect typed\n store: request.config?.store,\n };\n const resolvedBackend = getBackend(backend, stateAndStore);\n const sanitizedId = sanitizeToolCallId(\n request.toolCall?.id || msg.tool_call_id,\n );\n const evictPath = `/large_tool_results/${sanitizedId}`;\n\n const writeResult = await resolvedBackend.write(\n evictPath,\n msg.content,\n );\n\n if (writeResult.error) {\n return { message: msg, filesUpdate: null };\n }\n\n // Create preview showing head and tail of the result\n const contentSample = createContentPreview(msg.content);\n const replacementText = TOO_LARGE_TOOL_MSG.replace(\n \"{tool_call_id}\",\n msg.tool_call_id,\n )\n .replace(\"{file_path}\", evictPath)\n .replace(\"{content_sample}\", contentSample);\n\n const truncatedMessage = new ToolMessage({\n content: replacementText,\n tool_call_id: msg.tool_call_id,\n name: msg.name,\n id: msg.id,\n artifact: msg.artifact,\n status: msg.status,\n metadata: msg.metadata,\n additional_kwargs: msg.additional_kwargs,\n response_metadata: msg.response_metadata,\n });\n\n return {\n message: truncatedMessage,\n filesUpdate: writeResult.filesUpdate,\n };\n }\n return { message: msg, filesUpdate: null };\n }\n\n if (ToolMessage.isInstance(result)) {\n const processed = await processToolMessage(\n result,\n toolTokenLimitBeforeEvict,\n );\n\n if (processed.filesUpdate) {\n return new Command({\n update: {\n files: processed.filesUpdate,\n messages: [processed.message],\n },\n });\n }\n\n return processed.message;\n }\n\n if (isCommand(result)) {\n const update = result.update as any;\n if (!update?.messages) {\n return result;\n }\n\n let hasLargeResults = false;\n const accumulatedFiles: Record<string, FileData> = update.files\n ? { ...update.files }\n : {};\n const processedMessages: ToolMessage[] = [];\n\n for (const msg of update.messages) {\n if (ToolMessage.isInstance(msg)) {\n const processed = await processToolMessage(\n msg,\n toolTokenLimitBeforeEvict,\n );\n processedMessages.push(processed.message);\n\n if (processed.filesUpdate) {\n hasLargeResults = true;\n Object.assign(accumulatedFiles, processed.filesUpdate);\n }\n } else {\n processedMessages.push(msg);\n }\n }\n\n if (hasLargeResults) {\n return new Command({\n update: {\n ...update,\n messages: processedMessages,\n files: accumulatedFiles,\n },\n });\n }\n }\n\n return result;\n },\n });\n}\n","import { z } from \"zod/v4\";\nimport {\n createMiddleware,\n createAgent,\n AgentMiddleware,\n tool,\n ToolMessage,\n humanInTheLoopMiddleware,\n SystemMessage,\n type ContentBlock,\n type BaseMessage,\n type InterruptOnConfig,\n type ReactAgent,\n StructuredTool,\n} from \"langchain\";\nimport { Command, getCurrentTaskInput } from \"@langchain/langgraph\";\nimport type { LanguageModelLike } from \"@langchain/core/language_models/base\";\nimport type { Runnable } from \"@langchain/core/runnables\";\nimport { HumanMessage } from \"@langchain/core/messages\";\n\nexport type { AgentMiddleware };\n\n/**\n * Default system prompt for subagents.\n * Provides a minimal base prompt that can be extended by specific subagent configurations.\n */\nexport const DEFAULT_SUBAGENT_PROMPT =\n \"In order to complete the objective that the user asks of you, you have access to a number of standard tools.\";\n\n/**\n * State keys that are excluded when passing state to subagents and when returning\n * updates from subagents.\n *\n * When returning updates:\n * 1. The messages key is handled explicitly to ensure only the final message is included\n * 2. The todos and structuredResponse keys are excluded as they do not have a defined reducer\n * and no clear meaning for returning them from a subagent to the main agent.\n * 3. The skillsMetadata and memoryContents keys are automatically excluded from subagent output\n * to prevent parent state from leaking to child agents. Each agent loads its own skills/memory\n * independently based on its middleware configuration.\n */\nconst EXCLUDED_STATE_KEYS = [\n \"messages\",\n \"todos\",\n \"structuredResponse\",\n \"skillsMetadata\",\n \"memoryContents\",\n] as const;\n\n/**\n * Default description for the general-purpose subagent.\n * This description is shown to the model when selecting which subagent to use.\n */\nexport const DEFAULT_GENERAL_PURPOSE_DESCRIPTION =\n \"General-purpose agent for researching complex questions, searching for files and content, and executing multi-step tasks. When you are searching for a keyword or file and are not confident that you will find the right match in the first few tries use this agent to perform the search for you. This agent has access to all tools as the main agent.\";\n\n// Comprehensive task tool description from Python\nfunction getTaskToolDescription(subagentDescriptions: string[]): string {\n return `\nLaunch an ephemeral subagent to handle complex, multi-step independent tasks with isolated context windows.\n\nAvailable agent types and the tools they have access to:\n${subagentDescriptions.join(\"\\n\")}\n\nWhen using the Task tool, you must specify a subagent_type parameter to select which agent type to use.\n\n## Usage notes:\n1. Launch multiple agents concurrently whenever possible, to maximize performance; to do that, use a single message with multiple tool uses\n2. When the agent is done, it will return a single message back to you. The result returned by the agent is not visible to the user. To show the user the result, you should send a text message back to the user with a concise summary of the result.\n3. Each agent invocation is stateless. You will not be able to send additional messages to the agent, nor will the agent be able to communicate with you outside of its final report. Therefore, your prompt should contain a highly detailed task description for the agent to perform autonomously and you should specify exactly what information the agent should return back to you in its final and only message to you.\n4. The agent's outputs should generally be trusted\n5. Clearly tell the agent whether you expect it to create content, perform analysis, or just do research (search, file reads, web fetches, etc.), since it is not aware of the user's intent\n6. If the agent description mentions that it should be used proactively, then you should try your best to use it without the user having to ask for it first. Use your judgement.\n7. When only the general-purpose agent is provided, you should use it for all tasks. It is great for isolating context and token usage, and completing specific, complex tasks, as it has all the same capabilities as the main agent.\n\n### Example usage of the general-purpose agent:\n\n<example_agent_descriptions>\n\"general-purpose\": use this agent for general purpose tasks, it has access to all tools as the main agent.\n</example_agent_descriptions>\n\n<example>\nUser: \"I want to conduct research on the accomplishments of Lebron James, Michael Jordan, and Kobe Bryant, and then compare them.\"\nAssistant: *Uses the task tool in parallel to conduct isolated research on each of the three players*\nAssistant: *Synthesizes the results of the three isolated research tasks and responds to the User*\n<commentary>\nResearch is a complex, multi-step task in it of itself.\nThe research of each individual player is not dependent on the research of the other players.\nThe assistant uses the task tool to break down the complex objective into three isolated tasks.\nEach research task only needs to worry about context and tokens about one player, then returns synthesized information about each player as the Tool Result.\nThis means each research task can dive deep and spend tokens and context deeply researching each player, but the final result is synthesized information, and saves us tokens in the long run when comparing the players to each other.\n</commentary>\n</example>\n\n<example>\nUser: \"Analyze a single large code repository for security vulnerabilities and generate a report.\"\nAssistant: *Launches a single \\`task\\` subagent for the repository analysis*\nAssistant: *Receives report and integrates results into final summary*\n<commentary>\nSubagent is used to isolate a large, context-heavy task, even though there is only one. This prevents the main thread from being overloaded with details.\nIf the user then asks followup questions, we have a concise report to reference instead of the entire history of analysis and tool calls, which is good and saves us time and money.\n</commentary>\n</example>\n\n<example>\nUser: \"Schedule two meetings for me and prepare agendas for each.\"\nAssistant: *Calls the task tool in parallel to launch two \\`task\\` subagents (one per meeting) to prepare agendas*\nAssistant: *Returns final schedules and agendas*\n<commentary>\nTasks are simple individually, but subagents help silo agenda preparation.\nEach subagent only needs to worry about the agenda for one meeting.\n</commentary>\n</example>\n\n<example>\nUser: \"I want to order a pizza from Dominos, order a burger from McDonald's, and order a salad from Subway.\"\nAssistant: *Calls tools directly in parallel to order a pizza from Dominos, a burger from McDonald's, and a salad from Subway*\n<commentary>\nThe assistant did not use the task tool because the objective is super simple and clear and only requires a few trivial tool calls.\nIt is better to just complete the task directly and NOT use the \\`task\\`tool.\n</commentary>\n</example>\n\n### Example usage with custom agents:\n\n<example_agent_descriptions>\n\"content-reviewer\": use this agent after you are done creating significant content or documents\n\"greeting-responder\": use this agent when to respond to user greetings with a friendly joke\n\"research-analyst\": use this agent to conduct thorough research on complex topics\n</example_agent_description>\n\n<example>\nuser: \"Please write a function that checks if a number is prime\"\nassistant: Sure let me write a function that checks if a number is prime\nassistant: First let me use the Write tool to write a function that checks if a number is prime\nassistant: I'm going to use the Write tool to write the following code:\n<code>\nfunction isPrime(n) {\n if (n <= 1) return false\n for (let i = 2; i * i <= n; i++) {\n if (n % i === 0) return false\n }\n return true\n}\n</code>\n<commentary>\nSince significant content was created and the task was completed, now use the content-reviewer agent to review the work\n</commentary>\nassistant: Now let me use the content-reviewer agent to review the code\nassistant: Uses the Task tool to launch with the content-reviewer agent\n</example>\n\n<example>\nuser: \"Can you help me research the environmental impact of different renewable energy sources and create a comprehensive report?\"\n<commentary>\nThis is a complex research task that would benefit from using the research-analyst agent to conduct thorough analysis\n</commentary>\nassistant: I'll help you research the environmental impact of renewable energy sources. Let me use the research-analyst agent to conduct comprehensive research on this topic.\nassistant: Uses the Task tool to launch with the research-analyst agent, providing detailed instructions about what research to conduct and what format the report should take\n</example>\n\n<example>\nuser: \"Hello\"\n<commentary>\nSince the user is greeting, use the greeting-responder agent to respond with a friendly joke\n</commentary>\nassistant: \"I'm going to use the Task tool to launch with the greeting-responder agent\"\n</example>\n `.trim();\n}\n\n/**\n * System prompt section that explains how to use the task tool for spawning subagents.\n *\n * This prompt is automatically appended to the main agent's system prompt when\n * using `createSubAgentMiddleware`. It provides guidance on:\n * - When to use the task tool\n * - Subagent lifecycle (spawn → run → return → reconcile)\n * - When NOT to use the task tool\n * - Best practices for parallel task execution\n *\n * You can provide a custom `systemPrompt` to `createSubAgentMiddleware` to override\n * or extend this default.\n */\nexport const TASK_SYSTEM_PROMPT = `## \\`task\\` (subagent spawner)\n\nYou have access to a \\`task\\` tool to launch short-lived subagents that handle isolated tasks. These agents are ephemeral — they live only for the duration of the task and return a single result.\n\nWhen to use the task tool:\n- When a task is complex and multi-step, and can be fully delegated in isolation\n- When a task is independent of other tasks and can run in parallel\n- When a task requires focused reasoning or heavy token/context usage that would bloat the orchestrator thread\n- When sandboxing improves reliability (e.g. code execution, structured searches, data formatting)\n- When you only care about the output of the subagent, and not the intermediate steps (ex. performing a lot of research and then returned a synthesized report, performing a series of computations or lookups to achieve a concise, relevant answer.)\n\nSubagent lifecycle:\n1. **Spawn** → Provide clear role, instructions, and expected output\n2. **Run** → The subagent completes the task autonomously\n3. **Return** → The subagent provides a single structured result\n4. **Reconcile** → Incorporate or synthesize the result into the main thread\n\nWhen NOT to use the task tool:\n- If you need to see the intermediate reasoning or steps after the subagent has completed (the task tool hides them)\n- If the task is trivial (a few tool calls or simple lookup)\n- If delegating does not reduce token usage, complexity, or context switching\n- If splitting would add latency without benefit\n\n## Important Task Tool Usage Notes to Remember\n- Whenever possible, parallelize the work that you do. This is true for both tool_calls, and for tasks. Whenever you have independent steps to complete - make tool_calls, or kick off tasks (subagents) in parallel to accomplish them faster. This saves time for the user, which is incredibly important.\n- Remember to use the \\`task\\` tool to silo independent tasks within a multi-part objective.\n- You should use the \\`task\\` tool whenever you have a complex task that will take multiple steps, and is independent from other tasks that the agent needs to complete. These agents are highly competent and efficient.`;\n\n/**\n * Type definitions for pre-compiled agents.\n *\n * @typeParam TRunnable - The type of the runnable (ReactAgent or Runnable).\n * When using `createAgent` or `createDeepAgent`, this preserves the middleware\n * types for type inference. Uses `ReactAgent<any>` to accept agents with any\n * type configuration (including DeepAgent instances).\n */\nexport interface CompiledSubAgent<\n TRunnable extends ReactAgent<any> | Runnable = ReactAgent<any> | Runnable,\n> {\n /** The name of the agent */\n name: string;\n /** The description of the agent */\n description: string;\n /** The agent instance */\n runnable: TRunnable;\n}\n\n/**\n * Specification for a subagent that can be dynamically created.\n *\n * When using `createDeepAgent`, subagents automatically receive a default middleware\n * stack (todoListMiddleware, filesystemMiddleware, summarizationMiddleware, etc.) before\n * any custom `middleware` specified in this spec.\n *\n * Required fields:\n * - `name`: Identifier used to select this subagent in the task tool\n * - `description`: Shown to the model for subagent selection\n * - `systemPrompt`: The system prompt for the subagent\n *\n * Optional fields:\n * - `model`: Override the default model for this subagent\n * - `tools`: Override the default tools for this subagent\n * - `middleware`: Additional middleware appended after defaults\n * - `interruptOn`: Human-in-the-loop configuration for specific tools\n * - `skills`: Skill source paths for SkillsMiddleware (e.g., `[\"/skills/user/\", \"/skills/project/\"]`)\n *\n * @example\n * ```typescript\n * const researcher: SubAgent = {\n * name: \"researcher\",\n * description: \"Research assistant for complex topics\",\n * systemPrompt: \"You are a research assistant.\",\n * tools: [webSearchTool],\n * skills: [\"/skills/research/\"],\n * };\n * ```\n */\nexport interface SubAgent {\n /** Identifier used to select this subagent in the task tool */\n name: string;\n\n /** Description shown to the model for subagent selection */\n description: string;\n\n /** The system prompt to use for the agent */\n systemPrompt: string;\n\n /** The tools to use for the agent (tool instances, not names). Defaults to defaultTools */\n tools?: StructuredTool[];\n\n /** The model for the agent. Defaults to defaultModel */\n model?: LanguageModelLike | string;\n\n /** Additional middleware to append after default_middleware */\n middleware?: readonly AgentMiddleware[];\n\n /** Human-in-the-loop configuration for specific tools. Requires a checkpointer. */\n interruptOn?: Record<string, boolean | InterruptOnConfig>;\n\n /**\n * Skill source paths for SkillsMiddleware.\n *\n * List of paths to skill directories (e.g., `[\"/skills/user/\", \"/skills/project/\"]`).\n * When specified, the subagent will have its own SkillsMiddleware that loads skills\n * from these paths. This allows subagents to have different skill sets than the main agent.\n *\n * Note: Custom subagents do NOT inherit skills from the main agent by default.\n * Only the general-purpose subagent inherits the main agent's skills.\n *\n * @example\n * ```typescript\n * const researcher: SubAgent = {\n * name: \"researcher\",\n * description: \"Research assistant\",\n * systemPrompt: \"You are a researcher.\",\n * skills: [\"/skills/research/\", \"/skills/web-search/\"],\n * };\n * ```\n */\n skills?: string[];\n}\n\n/**\n * Base specification for the general-purpose subagent.\n *\n * This constant provides the default configuration for the general-purpose subagent\n * that is automatically included when `generalPurposeAgent: true` (the default).\n *\n * The general-purpose subagent:\n * - Has access to all tools from the main agent\n * - Inherits skills from the main agent (when skills are configured)\n * - Uses the same model as the main agent (by default)\n * - Is ideal for delegating complex, multi-step tasks\n *\n * You can spread this constant and override specific properties when creating\n * custom subagents that should behave similarly to the general-purpose agent:\n *\n * @example\n * ```typescript\n * import { GENERAL_PURPOSE_SUBAGENT, createDeepAgent } from \"@anthropic/deepagents\";\n *\n * // Use as-is (automatically included with generalPurposeAgent: true)\n * const agent = createDeepAgent({ model: \"claude-sonnet-4-5-20250929\" });\n *\n * // Or create a custom variant with different tools\n * const customGP: SubAgent = {\n * ...GENERAL_PURPOSE_SUBAGENT,\n * name: \"research-gp\",\n * tools: [webSearchTool, readFileTool],\n * };\n *\n * const agent = createDeepAgent({\n * model: \"claude-sonnet-4-5-20250929\",\n * subagents: [customGP],\n * // Disable the default general-purpose agent since we're providing our own\n * // (handled automatically when using createSubAgentMiddleware directly)\n * });\n * ```\n */\nexport const GENERAL_PURPOSE_SUBAGENT: Pick<\n SubAgent,\n \"name\" | \"description\" | \"systemPrompt\"\n> = {\n name: \"general-purpose\",\n description: DEFAULT_GENERAL_PURPOSE_DESCRIPTION,\n systemPrompt: DEFAULT_SUBAGENT_PROMPT,\n} as const;\n\n/**\n * Filter state to exclude certain keys when passing to subagents\n */\nfunction filterStateForSubagent(\n state: Record<string, unknown>,\n): Record<string, unknown> {\n const filtered: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(state)) {\n if (!EXCLUDED_STATE_KEYS.includes(key as never)) {\n filtered[key] = value;\n }\n }\n return filtered;\n}\n\n/**\n * Invalid tool message block types\n */\nconst INVALID_TOOL_MESSAGE_BLOCK_TYPES = [\n \"tool_use\",\n \"thinking\",\n \"redacted_thinking\",\n];\n\n/**\n * Create Command with filtered state update from subagent result\n */\nfunction returnCommandWithStateUpdate(\n result: Record<string, unknown>,\n toolCallId: string,\n): Command {\n const stateUpdate = filterStateForSubagent(result);\n const messages = result.messages as BaseMessage[];\n const lastMessage = messages?.[messages.length - 1];\n\n let content: string | ContentBlock[] =\n lastMessage?.content || \"Task completed\";\n if (Array.isArray(content)) {\n content = content.filter(\n (block) => !INVALID_TOOL_MESSAGE_BLOCK_TYPES.includes(block.type),\n );\n if (content.length === 0) {\n content = \"Task completed\";\n }\n }\n\n return new Command({\n update: {\n ...stateUpdate,\n messages: [\n new ToolMessage({\n content,\n tool_call_id: toolCallId,\n name: \"task\",\n }),\n ],\n },\n });\n}\n\n/**\n * Create subagent instances from specifications\n */\nfunction getSubagents(options: {\n defaultModel: LanguageModelLike | string;\n defaultTools: StructuredTool[];\n defaultMiddleware: AgentMiddleware[] | null;\n /** Middleware specifically for the general-purpose subagent (includes skills from main agent) */\n generalPurposeMiddleware: AgentMiddleware[] | null;\n defaultInterruptOn: Record<string, boolean | InterruptOnConfig> | null;\n subagents: (SubAgent | CompiledSubAgent)[];\n generalPurposeAgent: boolean;\n}): {\n agents: Record<string, ReactAgent | Runnable>;\n descriptions: string[];\n} {\n const {\n defaultModel,\n defaultTools,\n defaultMiddleware,\n generalPurposeMiddleware: gpMiddleware,\n defaultInterruptOn,\n subagents,\n generalPurposeAgent,\n } = options;\n\n const defaultSubagentMiddleware = defaultMiddleware || [];\n // General-purpose middleware includes skills from main agent, falls back to default\n const generalPurposeMiddlewareBase =\n gpMiddleware || defaultSubagentMiddleware;\n const agents: Record<string, ReactAgent | Runnable> = {};\n const subagentDescriptions: string[] = [];\n\n // Create general-purpose agent if enabled\n if (generalPurposeAgent) {\n const generalPurposeMiddleware = [...generalPurposeMiddlewareBase];\n if (defaultInterruptOn) {\n generalPurposeMiddleware.push(\n humanInTheLoopMiddleware({ interruptOn: defaultInterruptOn }),\n );\n }\n\n const generalPurposeSubagent = createAgent({\n model: defaultModel,\n systemPrompt: DEFAULT_SUBAGENT_PROMPT,\n tools: defaultTools as any,\n middleware: generalPurposeMiddleware,\n name: \"general-purpose\",\n });\n\n agents[\"general-purpose\"] = generalPurposeSubagent;\n subagentDescriptions.push(\n `- general-purpose: ${DEFAULT_GENERAL_PURPOSE_DESCRIPTION}`,\n );\n }\n\n // Process custom subagents (use defaultMiddleware WITHOUT skills)\n for (const agentParams of subagents) {\n subagentDescriptions.push(\n `- ${agentParams.name}: ${agentParams.description}`,\n );\n\n if (\"runnable\" in agentParams) {\n agents[agentParams.name] = agentParams.runnable;\n } else {\n const middleware = agentParams.middleware\n ? [...defaultSubagentMiddleware, ...agentParams.middleware]\n : [...defaultSubagentMiddleware];\n\n const interruptOn = agentParams.interruptOn || defaultInterruptOn;\n if (interruptOn)\n middleware.push(humanInTheLoopMiddleware({ interruptOn }));\n\n agents[agentParams.name] = createAgent({\n model: agentParams.model ?? defaultModel,\n systemPrompt: agentParams.systemPrompt,\n tools: agentParams.tools ?? defaultTools,\n middleware,\n name: agentParams.name,\n });\n }\n }\n\n return { agents, descriptions: subagentDescriptions };\n}\n\n/**\n * Create the task tool for invoking subagents\n */\nfunction createTaskTool(options: {\n defaultModel: LanguageModelLike | string;\n defaultTools: StructuredTool[];\n defaultMiddleware: AgentMiddleware[] | null;\n /** Middleware specifically for the general-purpose subagent (includes skills from main agent) */\n generalPurposeMiddleware: AgentMiddleware[] | null;\n defaultInterruptOn: Record<string, boolean | InterruptOnConfig> | null;\n subagents: (SubAgent | CompiledSubAgent)[];\n generalPurposeAgent: boolean;\n taskDescription: string | null;\n}) {\n const {\n defaultModel,\n defaultTools,\n defaultMiddleware,\n generalPurposeMiddleware,\n defaultInterruptOn,\n subagents,\n generalPurposeAgent,\n taskDescription,\n } = options;\n\n const { agents: subagentGraphs, descriptions: subagentDescriptions } =\n getSubagents({\n defaultModel,\n defaultTools,\n defaultMiddleware,\n generalPurposeMiddleware,\n defaultInterruptOn,\n subagents,\n generalPurposeAgent,\n });\n\n const finalTaskDescription = taskDescription\n ? taskDescription\n : getTaskToolDescription(subagentDescriptions);\n\n return tool(\n async (\n input: { description: string; subagent_type: string },\n config,\n ): Promise<Command | string> => {\n const { description, subagent_type } = input;\n\n // Validate subagent type\n if (!(subagent_type in subagentGraphs)) {\n const allowedTypes = Object.keys(subagentGraphs)\n .map((k) => `\\`${k}\\``)\n .join(\", \");\n throw new Error(\n `Error: invoked agent of type ${subagent_type}, the only allowed types are ${allowedTypes}`,\n );\n }\n\n const subagent = subagentGraphs[subagent_type];\n\n // Get current state and filter it for subagent\n const currentState = getCurrentTaskInput<Record<string, unknown>>();\n const subagentState = filterStateForSubagent(currentState);\n subagentState.messages = [new HumanMessage({ content: description })];\n\n // Invoke the subagent\n const result = (await subagent.invoke(subagentState, config)) as Record<\n string,\n unknown\n >;\n\n if (!config.toolCall?.id) {\n const messages = result.messages as BaseMessage[];\n const lastMessage = messages?.[messages.length - 1];\n let content: string | ContentBlock[] =\n lastMessage?.content || \"Task completed\";\n if (Array.isArray(content)) {\n content = content.filter(\n (block) => !INVALID_TOOL_MESSAGE_BLOCK_TYPES.includes(block.type),\n );\n if (content.length === 0) {\n return \"Task completed\";\n }\n return content\n .map((block) =>\n \"text\" in block ? block.text : JSON.stringify(block),\n )\n .join(\"\\n\");\n }\n return content;\n }\n\n return returnCommandWithStateUpdate(result, config.toolCall.id);\n },\n {\n name: \"task\",\n description: finalTaskDescription,\n schema: z.object({\n description: z\n .string()\n .describe(\"The task to execute with the selected agent\"),\n subagent_type: z\n .string()\n .describe(\n `Name of the agent to use. Available: ${Object.keys(subagentGraphs).join(\", \")}`,\n ),\n }),\n },\n );\n}\n\n/**\n * Options for creating subagent middleware\n */\nexport interface SubAgentMiddlewareOptions {\n /** The model to use for subagents */\n defaultModel: LanguageModelLike | string;\n /** The tools to use for the default general-purpose subagent */\n defaultTools?: StructuredTool[];\n /** Default middleware to apply to custom subagents (WITHOUT skills from main agent) */\n defaultMiddleware?: AgentMiddleware[] | null;\n /**\n * Middleware specifically for the general-purpose subagent (includes skills from main agent).\n * If not provided, falls back to defaultMiddleware.\n */\n generalPurposeMiddleware?: AgentMiddleware[] | null;\n /** The tool configs for the default general-purpose subagent */\n defaultInterruptOn?: Record<string, boolean | InterruptOnConfig> | null;\n /** A list of additional subagents to provide to the agent */\n subagents?: (SubAgent | CompiledSubAgent)[];\n /** Full system prompt override */\n systemPrompt?: string | null;\n /** Whether to include the general-purpose agent */\n generalPurposeAgent?: boolean;\n /** Custom description for the task tool */\n taskDescription?: string | null;\n}\n\n/**\n * Create subagent middleware with task tool\n */\nexport function createSubAgentMiddleware(options: SubAgentMiddlewareOptions) {\n const {\n defaultModel,\n defaultTools = [],\n defaultMiddleware = null,\n generalPurposeMiddleware = null,\n defaultInterruptOn = null,\n subagents = [],\n systemPrompt = TASK_SYSTEM_PROMPT,\n generalPurposeAgent = true,\n taskDescription = null,\n } = options;\n\n const taskTool = createTaskTool({\n defaultModel,\n defaultTools,\n defaultMiddleware,\n generalPurposeMiddleware,\n defaultInterruptOn,\n subagents,\n generalPurposeAgent,\n taskDescription,\n });\n\n return createMiddleware({\n name: \"subAgentMiddleware\",\n tools: [taskTool],\n wrapModelCall: async (request, handler) => {\n if (systemPrompt !== null) {\n return handler({\n ...request,\n systemMessage: request.systemMessage.concat(\n new SystemMessage({ content: systemPrompt }),\n ),\n });\n }\n return handler(request);\n },\n });\n}\n","import {\n createMiddleware,\n ToolMessage,\n AIMessage,\n /**\n * required for type inference\n */\n type AgentMiddleware as _AgentMiddleware,\n} from \"langchain\";\nimport { RemoveMessage, type BaseMessage } from \"@langchain/core/messages\";\nimport { REMOVE_ALL_MESSAGES } from \"@langchain/langgraph\";\n\n/**\n * Patch dangling tool calls in a messages array.\n * Returns the patched messages array and a flag indicating if patching was needed.\n *\n * @param messages - The messages array to patch\n * @returns Object with patched messages and needsPatch flag\n */\nexport function patchDanglingToolCalls(messages: BaseMessage[]): {\n patchedMessages: BaseMessage[];\n needsPatch: boolean;\n} {\n if (!messages || messages.length === 0) {\n return { patchedMessages: [], needsPatch: false };\n }\n\n const patchedMessages: BaseMessage[] = [];\n let needsPatch = false;\n\n // Iterate over the messages and add any dangling tool calls\n for (let i = 0; i < messages.length; i++) {\n const msg = messages[i];\n patchedMessages.push(msg);\n\n // Check if this is an AI message with tool calls\n if (AIMessage.isInstance(msg) && msg.tool_calls != null) {\n for (const toolCall of msg.tool_calls) {\n // Look for a corresponding ToolMessage in the messages after this one\n const correspondingToolMsg = messages\n .slice(i)\n .find(\n (m) => ToolMessage.isInstance(m) && m.tool_call_id === toolCall.id,\n );\n\n if (!correspondingToolMsg) {\n // We have a dangling tool call which needs a ToolMessage\n needsPatch = true;\n const toolMsg = `Tool call ${toolCall.name} with id ${toolCall.id} was cancelled - another message came in before it could be completed.`;\n patchedMessages.push(\n new ToolMessage({\n content: toolMsg,\n name: toolCall.name,\n tool_call_id: toolCall.id!,\n }),\n );\n }\n }\n }\n }\n\n return { patchedMessages, needsPatch };\n}\n\n/**\n * Create middleware that patches dangling tool calls in the messages history.\n *\n * When an AI message contains tool_calls but subsequent messages don't include\n * the corresponding ToolMessage responses, this middleware adds synthetic\n * ToolMessages saying the tool call was cancelled.\n *\n * This middleware patches in two places:\n * 1. `beforeAgent`: Patches state at the start of the agent loop (handles most cases)\n * 2. `wrapModelCall`: Patches the request right before model invocation (handles\n * edge cases like HITL rejection during graph resume where state updates from\n * beforeAgent may not be applied in time)\n *\n * @returns AgentMiddleware that patches dangling tool calls\n *\n * @example\n * ```typescript\n * import { createAgent } from \"langchain\";\n * import { createPatchToolCallsMiddleware } from \"./middleware/patch_tool_calls\";\n *\n * const agent = createAgent({\n * model: \"claude-sonnet-4-5-20250929\",\n * middleware: [createPatchToolCallsMiddleware()],\n * });\n * ```\n */\nexport function createPatchToolCallsMiddleware() {\n return createMiddleware({\n name: \"patchToolCallsMiddleware\",\n beforeAgent: async (state) => {\n const messages = state.messages;\n\n if (!messages || messages.length === 0) {\n return;\n }\n\n const { patchedMessages, needsPatch } = patchDanglingToolCalls(messages);\n\n /**\n * Only trigger REMOVE_ALL_MESSAGES if patching is actually needed\n */\n if (!needsPatch) {\n return;\n }\n\n // Return state update with RemoveMessage followed by patched messages\n return {\n messages: [\n new RemoveMessage({ id: REMOVE_ALL_MESSAGES }),\n ...patchedMessages,\n ],\n };\n },\n\n /**\n * Also patch in wrapModelCall as a safety net.\n * This handles edge cases where:\n * - HITL rejects a tool call during graph resume\n * - The state update from beforeAgent might not be applied in time\n * - The model would otherwise receive dangling tool_call_ids\n */\n wrapModelCall: async (request, handler) => {\n const messages = request.messages;\n\n if (!messages || messages.length === 0) {\n return handler(request);\n }\n\n const { patchedMessages, needsPatch } = patchDanglingToolCalls(messages);\n\n if (!needsPatch) {\n return handler(request);\n }\n\n // Pass patched messages to the model\n return handler({\n ...request,\n messages: patchedMessages,\n });\n },\n });\n}\n","/**\n * Shared state values for use in StateSchema definitions.\n *\n * This module provides pre-configured ReducedValue instances that can be\n * reused across different state schemas, similar to LangGraph's messagesValue.\n */\n\nimport { z } from \"zod\";\nimport { ReducedValue } from \"@langchain/langgraph\";\nimport { FileDataSchema, fileDataReducer } from \"./middleware/fs.js\";\n\n/**\n * Shared ReducedValue for file data state management.\n *\n * This provides a reusable pattern for managing file state with automatic\n * merging of concurrent updates from parallel subagents. Files can be updated\n * or deleted (using null values) and the reducer handles the merge logic.\n *\n * Similar to LangGraph's messagesValue, this encapsulates the common pattern\n * of managing files in agent state so you don't have to manually configure\n * the ReducedValue each time.\n *\n * @example\n * ```typescript\n * import { filesValue } from \"@anthropic/deepagents\";\n * import { StateSchema } from \"@langchain/langgraph\";\n *\n * const MyStateSchema = new StateSchema({\n * files: filesValue,\n * // ... other state fields\n * });\n * ```\n */\nexport const filesValue = new ReducedValue(\n z.record(z.string(), FileDataSchema).default(() => ({})),\n {\n inputSchema: z.record(z.string(), FileDataSchema.nullable()).optional(),\n reducer: fileDataReducer,\n },\n);\n","/**\n * Middleware for loading agent memory/context from AGENTS.md files.\n *\n * This module implements support for the AGENTS.md specification (https://agents.md/),\n * loading memory/context from configurable sources and injecting into the system prompt.\n *\n * ## Overview\n *\n * AGENTS.md files provide project-specific context and instructions to help AI agents\n * work effectively. Unlike skills (which are on-demand workflows), memory is always\n * loaded and provides persistent context.\n *\n * ## Usage\n *\n * ```typescript\n * import { createMemoryMiddleware } from \"@anthropic/deepagents\";\n * import { FilesystemBackend } from \"@anthropic/deepagents\";\n *\n * // Security: FilesystemBackend allows reading/writing from the entire filesystem.\n * // Either ensure the agent is running within a sandbox OR add human-in-the-loop (HIL)\n * // approval to file operations.\n * const backend = new FilesystemBackend({ rootDir: \"/\" });\n *\n * const middleware = createMemoryMiddleware({\n * backend,\n * sources: [\n * \"~/.deepagents/AGENTS.md\",\n * \"./.deepagents/AGENTS.md\",\n * ],\n * });\n *\n * const agent = createDeepAgent({ middleware: [middleware] });\n * ```\n *\n * ## Memory Sources\n *\n * Sources are simply paths to AGENTS.md files that are loaded in order and combined.\n * Multiple sources are concatenated in order, with all content included.\n * Later sources appear after earlier ones in the combined prompt.\n *\n * ## File Format\n *\n * AGENTS.md files are standard Markdown with no required structure.\n * Common sections include:\n * - Project overview\n * - Build/test commands\n * - Code style guidelines\n * - Architecture notes\n */\n\nimport { z } from \"zod\";\nimport {\n createMiddleware,\n SystemMessage,\n /**\n * required for type inference\n */\n type AgentMiddleware as _AgentMiddleware,\n} from \"langchain\";\n\nimport type { BackendProtocol, BackendFactory } from \"../backends/protocol.js\";\nimport type { StateBackend } from \"../backends/state.js\";\nimport type { BaseStore } from \"@langchain/langgraph-checkpoint\";\nimport { filesValue } from \"../values.js\";\nimport { StateSchema } from \"@langchain/langgraph\";\n\n/**\n * Options for the memory middleware.\n */\nexport interface MemoryMiddlewareOptions {\n /**\n * Backend instance or factory function for file operations.\n * Use a factory for StateBackend since it requires runtime state.\n */\n backend:\n | BackendProtocol\n | BackendFactory\n | ((config: { state: unknown; store?: BaseStore }) => StateBackend);\n\n /**\n * List of memory file paths to load (e.g., [\"~/.deepagents/AGENTS.md\", \"./.deepagents/AGENTS.md\"]).\n * Display names are automatically derived from the paths.\n * Sources are loaded in order.\n */\n sources: string[];\n\n /**\n * Whether to add cache_control breakpoints to the memory content block.\n * When true, the memory block is tagged with `cache_control: { type: \"ephemeral\" }`\n * to enable prompt caching for providers that support it (e.g., Anthropic).\n * @default false\n */\n addCacheControl?: boolean;\n}\n\n/**\n * State schema for memory middleware.\n */\nconst MemoryStateSchema = new StateSchema({\n /**\n * Dict mapping source paths to their loaded content.\n * Marked as private so it's not included in the final agent state.\n */\n memoryContents: z.record(z.string(), z.string()).optional(),\n files: filesValue,\n});\n\n/**\n * Default system prompt template for memory.\n * Ported from Python's comprehensive memory guidelines.\n */\nconst MEMORY_SYSTEM_PROMPT = `<agent_memory>\n{memory_contents}\n</agent_memory>\n\n<memory_guidelines>\n The above <agent_memory> was loaded in from files in your filesystem. As you learn from your interactions with the user, you can save new knowledge by calling the \\`edit_file\\` tool.\n\n **Learning from feedback:**\n - One of your MAIN PRIORITIES is to learn from your interactions with the user. These learnings can be implicit or explicit. This means that in the future, you will remember this important information.\n - When you need to remember something, updating memory must be your FIRST, IMMEDIATE action - before responding to the user, before calling other tools, before doing anything else. Just update memory immediately.\n - When user says something is better/worse, capture WHY and encode it as a pattern.\n - Each correction is a chance to improve permanently - don't just fix the immediate issue, update your instructions.\n - A great opportunity to update your memories is when the user interrupts a tool call and provides feedback. You should update your memories immediately before revising the tool call.\n - Look for the underlying principle behind corrections, not just the specific mistake.\n - The user might not explicitly ask you to remember something, but if they provide information that is useful for future use, you should update your memories immediately.\n\n **Asking for information:**\n - If you lack context to perform an action (e.g. send a Slack DM, requires a user ID/email) you should explicitly ask the user for this information.\n - It is preferred for you to ask for information, don't assume anything that you do not know!\n - When the user provides information that is useful for future use, you should update your memories immediately.\n\n **When to update memories:**\n - When the user explicitly asks you to remember something (e.g., \"remember my email\", \"save this preference\")\n - When the user describes your role or how you should behave (e.g., \"you are a web researcher\", \"always do X\")\n - When the user gives feedback on your work - capture what was wrong and how to improve\n - When the user provides information required for tool use (e.g., slack channel ID, email addresses)\n - When the user provides context useful for future tasks, such as how to use tools, or which actions to take in a particular situation\n - When you discover new patterns or preferences (coding styles, conventions, workflows)\n\n **When to NOT update memories:**\n - When the information is temporary or transient (e.g., \"I'm running late\", \"I'm on my phone right now\")\n - When the information is a one-time task request (e.g., \"Find me a recipe\", \"What's 25 * 4?\")\n - When the information is a simple question that doesn't reveal lasting preferences (e.g., \"What day is it?\", \"Can you explain X?\")\n - When the information is an acknowledgment or small talk (e.g., \"Sounds good!\", \"Hello\", \"Thanks for that\")\n - When the information is stale or irrelevant in future conversations\n - Never store API keys, access tokens, passwords, or any other credentials in any file, memory, or system prompt.\n - If the user asks where to put API keys or provides an API key, do NOT echo or save it.\n\n **Examples:**\n Example 1 (remembering user information):\n User: Can you connect to my google account?\n Agent: Sure, I'll connect to your google account, what's your google account email?\n User: john@example.com\n Agent: Let me save this to my memory.\n Tool Call: edit_file(...) -> remembers that the user's google account email is john@example.com\n\n Example 2 (remembering implicit user preferences):\n User: Can you write me an example for creating a deep agent in LangChain?\n Agent: Sure, I'll write you an example for creating a deep agent in LangChain <example code in Python>\n User: Can you do this in JavaScript\n Agent: Let me save this to my memory.\n Tool Call: edit_file(...) -> remembers that the user prefers to get LangChain code examples in JavaScript\n Agent: Sure, here is the JavaScript example<example code in JavaScript>\n\n Example 3 (do not remember transient information):\n User: I'm going to play basketball tonight so I will be offline for a few hours.\n Agent: Okay I'll add a block to your calendar.\n Tool Call: create_calendar_event(...) -> just calls a tool, does not commit anything to memory, as it is transient information\n</memory_guidelines>`;\n\n/**\n * Format loaded memory contents for injection into prompt.\n * Pairs memory locations with their contents for clarity.\n */\nfunction formatMemoryContents(\n contents: Record<string, string>,\n sources: string[],\n): string {\n if (Object.keys(contents).length === 0) {\n return \"(No memory loaded)\";\n }\n\n const sections: string[] = [];\n for (const path of sources) {\n if (contents[path]) {\n sections.push(`${path}\\n${contents[path]}`);\n }\n }\n\n if (sections.length === 0) {\n return \"(No memory loaded)\";\n }\n\n return sections.join(\"\\n\\n\");\n}\n\n/**\n * Load memory content from a backend path.\n *\n * @param backend - Backend to load from.\n * @param path - Path to the AGENTS.md file.\n * @returns File content if found, null otherwise.\n */\nasync function loadMemoryFromBackend(\n backend: BackendProtocol,\n path: string,\n): Promise<string | null> {\n // Use downloadFiles if available, otherwise fall back to read\n if (!backend.downloadFiles) {\n const content = await backend.read(path);\n if (content.startsWith(\"Error:\")) {\n return null;\n }\n return content;\n }\n\n const results = await backend.downloadFiles([path]);\n\n // Should get exactly one response for one path\n if (results.length !== 1) {\n throw new Error(\n `Expected 1 response for path ${path}, got ${results.length}`,\n );\n }\n const response = results[0];\n\n if (response.error != null) {\n // For now, memory files are treated as optional. file_not_found is expected\n // and we skip silently to allow graceful degradation.\n if (response.error === \"file_not_found\") {\n return null;\n }\n // Other errors should be raised\n throw new Error(`Failed to download ${path}: ${response.error}`);\n }\n\n if (response.content != null) {\n // Content is a Uint8Array, decode to string\n return new TextDecoder().decode(response.content);\n }\n\n return null;\n}\n\n/**\n * Create middleware for loading agent memory from AGENTS.md files.\n *\n * Loads memory content from configured sources and injects into the system prompt.\n * Supports multiple sources that are combined together.\n *\n * @param options - Configuration options\n * @returns AgentMiddleware for memory loading and injection\n *\n * @example\n * ```typescript\n * const middleware = createMemoryMiddleware({\n * backend: new FilesystemBackend({ rootDir: \"/\" }),\n * sources: [\n * \"~/.deepagents/AGENTS.md\",\n * \"./.deepagents/AGENTS.md\",\n * ],\n * });\n * ```\n */\nexport function createMemoryMiddleware(options: MemoryMiddlewareOptions) {\n const { backend, sources, addCacheControl = false } = options;\n\n /**\n * Resolve backend from instance or factory.\n */\n function getBackend(state: unknown): BackendProtocol {\n if (typeof backend === \"function\") {\n // It's a factory - call it with state\n return backend({ state }) as BackendProtocol;\n }\n return backend;\n }\n\n return createMiddleware({\n name: \"MemoryMiddleware\",\n stateSchema: MemoryStateSchema,\n\n async beforeAgent(state) {\n // Skip if already loaded\n if (\"memoryContents\" in state && state.memoryContents != null) {\n return undefined;\n }\n\n const resolvedBackend = getBackend(state);\n const contents: Record<string, string> = {};\n\n for (const path of sources) {\n try {\n const content = await loadMemoryFromBackend(resolvedBackend, path);\n if (content) {\n contents[path] = content;\n }\n } catch (error) {\n // Log but continue - memory is optional\n // eslint-disable-next-line no-console\n console.debug(`Failed to load memory from ${path}:`, error);\n }\n }\n\n return { memoryContents: contents };\n },\n\n wrapModelCall(request, handler) {\n // Get memory contents from state\n const memoryContents: Record<string, string> =\n request.state?.memoryContents || {};\n\n // Format memory section\n const formattedContents = formatMemoryContents(memoryContents, sources);\n const memorySection = MEMORY_SYSTEM_PROMPT.replace(\n \"{memory_contents}\",\n formattedContents,\n );\n\n const existingContent = request.systemMessage.content;\n const existingBlocks =\n typeof existingContent === \"string\"\n ? [{ type: \"text\" as const, text: existingContent }]\n : Array.isArray(existingContent)\n ? existingContent\n : [];\n\n const newSystemMessage = new SystemMessage({\n content: [\n ...existingBlocks,\n {\n type: \"text\" as const,\n text: memorySection,\n ...(addCacheControl && {\n cache_control: { type: \"ephemeral\" as const },\n }),\n },\n ],\n });\n\n return handler({\n ...request,\n systemMessage: newSystemMessage,\n });\n },\n });\n}\n","/* eslint-disable no-console */\n/**\n * Backend-agnostic skills middleware for loading agent skills from any backend.\n *\n * This middleware implements Anthropic's agent skills pattern with progressive disclosure,\n * loading skills from backend storage via configurable sources.\n *\n * ## Architecture\n *\n * Skills are loaded from one or more **sources** - paths in a backend where skills are\n * organized. Sources are loaded in order, with later sources overriding earlier ones\n * when skills have the same name (last one wins). This enables layering: base -> user\n * -> project -> team skills.\n *\n * The middleware uses backend APIs exclusively (no direct filesystem access), making it\n * portable across different storage backends (filesystem, state, remote storage, etc.).\n *\n * ## Usage\n *\n * ```typescript\n * import { createSkillsMiddleware, FilesystemBackend } from \"@anthropic/deepagents\";\n *\n * const middleware = createSkillsMiddleware({\n * backend: new FilesystemBackend({ rootDir: \"/\" }),\n * sources: [\n * \"/skills/user/\",\n * \"/skills/project/\",\n * ],\n * });\n *\n * const agent = createDeepAgent({ middleware: [middleware] });\n * ```\n *\n * Or use the `skills` parameter on createDeepAgent:\n *\n * ```typescript\n * const agent = createDeepAgent({\n * skills: [\"/skills/user/\", \"/skills/project/\"],\n * });\n * ```\n */\n\nimport { z } from \"zod\";\nimport yaml from \"yaml\";\nimport {\n createMiddleware,\n /**\n * required for type inference\n */\n type AgentMiddleware as _AgentMiddleware,\n} from \"langchain\";\nimport { StateSchema, ReducedValue } from \"@langchain/langgraph\";\n\nimport type { BackendProtocol, BackendFactory } from \"../backends/protocol.js\";\nimport type { StateBackend } from \"../backends/state.js\";\nimport type { BaseStore } from \"@langchain/langgraph-checkpoint\";\nimport { filesValue } from \"../values.js\";\n\n// Security: Maximum size for SKILL.md files to prevent DoS attacks (10MB)\nexport const MAX_SKILL_FILE_SIZE = 10 * 1024 * 1024;\n\n// Agent Skills specification constraints (https://agentskills.io/specification)\nexport const MAX_SKILL_NAME_LENGTH = 64;\nexport const MAX_SKILL_DESCRIPTION_LENGTH = 1024;\nexport const MAX_SKILL_COMPATIBILITY_LENGTH = 500;\n\n/**\n * Metadata for a skill per Agent Skills specification.\n */\nexport interface SkillMetadata {\n /**\n * Skill identifier.\n *\n * Constraints per Agent Skills specification:\n *\n * - 1-64 characters\n * - Unicode lowercase alphanumeric and hyphens only (`a-z` and `-`).\n * - Must not start or end with `-`\n * - Must not contain consecutive `--`\n * - Must match the parent directory name containing the `SKILL.md` file\n */\n name: string;\n\n /**\n * What the skill does.\n *\n * Constraints per Agent Skills specification:\n *\n * - 1-1024 characters\n * - Should describe both what the skill does and when to use it\n * - Should include specific keywords that help agents identify relevant tasks\n */\n description: string;\n\n /** Path to the SKILL.md file in the backend */\n path: string;\n\n /** License name or reference to bundled license file. */\n license?: string | null;\n\n /**\n * Environment requirements.\n *\n * Constraints per Agent Skills specification:\n *\n * - 1-500 characters if provided\n * - Should only be included if there are specific compatibility requirements\n * - Can indicate intended product, required packages, etc.\n */\n compatibility?: string | null;\n\n /**\n * Arbitrary key-value mapping for additional metadata.\n *\n * Clients can use this to store additional properties not defined by the spec.\n *\n * It is recommended to keep key names unique to avoid conflicts.\n */\n metadata?: Record<string, string>;\n\n /**\n * Tool names the skill recommends using.\n *\n * Warning: this is experimental.\n *\n * Constraints per Agent Skills specification:\n *\n * - Space-delimited list of tool names\n */\n allowedTools?: string[];\n}\n\n/**\n * Options for the skills middleware.\n */\nexport interface SkillsMiddlewareOptions {\n /**\n * Backend instance or factory function for file operations.\n * Use a factory for StateBackend since it requires runtime state.\n */\n backend:\n | BackendProtocol\n | BackendFactory\n | ((config: { state: unknown; store?: BaseStore }) => StateBackend);\n\n /**\n * List of skill source paths to load (e.g., [\"/skills/user/\", \"/skills/project/\"]).\n * Paths must use POSIX conventions (forward slashes).\n * Later sources override earlier ones for skills with the same name (last one wins).\n */\n sources: string[];\n}\n\n/**\n * Zod schema for a single skill metadata entry.\n */\nexport const SkillMetadataEntrySchema = z.object({\n name: z.string(),\n description: z.string(),\n path: z.string(),\n license: z.string().nullable().optional(),\n compatibility: z.string().nullable().optional(),\n metadata: z.record(z.string(), z.string()).optional(),\n allowedTools: z.array(z.string()).optional(),\n});\n\n/**\n * Type for a single skill metadata entry.\n */\nexport type SkillMetadataEntry = z.infer<typeof SkillMetadataEntrySchema>;\n\n/**\n * Reducer for skillsMetadata that merges arrays from parallel subagents.\n * Skills are deduplicated by name, with later values overriding earlier ones.\n *\n * @param current - The current skillsMetadata array (from state)\n * @param update - The new skillsMetadata array (from a subagent update)\n * @returns Merged array with duplicates resolved by name (later values win)\n */\nexport function skillsMetadataReducer(\n current: SkillMetadataEntry[] | undefined,\n update: SkillMetadataEntry[] | undefined,\n): SkillMetadataEntry[] {\n // If no update, return current (or empty array)\n if (!update || update.length === 0) {\n return current || [];\n }\n // If no current, return update\n if (!current || current.length === 0) {\n return update;\n }\n // Merge by skill name (later values override earlier ones)\n const merged = new Map<string, SkillMetadataEntry>();\n for (const skill of current) {\n merged.set(skill.name, skill);\n }\n for (const skill of update) {\n merged.set(skill.name, skill);\n }\n return Array.from(merged.values());\n}\n\n/**\n * State schema for skills middleware.\n * Uses ReducedValue for skillsMetadata to allow concurrent updates from parallel subagents.\n */\nconst SkillsStateSchema = new StateSchema({\n skillsMetadata: new ReducedValue(\n z.array(SkillMetadataEntrySchema).default(() => []),\n {\n inputSchema: z.array(SkillMetadataEntrySchema).optional(),\n reducer: skillsMetadataReducer,\n },\n ),\n files: filesValue,\n});\n\n/**\n * Skills System Documentation prompt template.\n */\nconst SKILLS_SYSTEM_PROMPT = `\n## Skills System\n\nYou have access to a skills library that provides specialized capabilities and domain knowledge.\n\n{skills_locations}\n\n**Available Skills:**\n\n{skills_list}\n\n**How to Use Skills (Progressive Disclosure):**\n\nSkills follow a **progressive disclosure** pattern - you know they exist (name + description above), but you only read the full instructions when needed:\n\n1. **Recognize when a skill applies**: Check if the user's task matches any skill's description\n2. **Read the skill's full instructions**: The skill list above shows the exact path to use with read_file\n3. **Follow the skill's instructions**: SKILL.md contains step-by-step workflows, best practices, and examples\n4. **Access supporting files**: Skills may include Python scripts, configs, or reference docs - use absolute paths\n\n**When to Use Skills:**\n- When the user's request matches a skill's domain (e.g., \"research X\" → web-research skill)\n- When you need specialized knowledge or structured workflows\n- When a skill provides proven patterns for complex tasks\n\n**Skills are Self-Documenting:**\n- Each SKILL.md tells you exactly what the skill does and how to use it\n- The skill list above shows the full path for each skill's SKILL.md file\n\n**Executing Skill Scripts:**\nSkills may contain Python scripts or other executable files. Always use absolute paths from the skill list.\n\n**Example Workflow:**\n\nUser: \"Can you research the latest developments in quantum computing?\"\n\n1. Check available skills above → See \"web-research\" skill with its full path\n2. Read the skill using the path shown in the list\n3. Follow the skill's research workflow (search → organize → synthesize)\n4. Use any helper scripts with absolute paths\n\nRemember: Skills are tools to make you more capable and consistent. When in doubt, check if a skill exists for the task!\n`;\n\n/**\n * Validate skill name per Agent Skills specification.\n *\n * Constraints per Agent Skills specification:\n *\n * - 1-64 characters\n * - Unicode lowercase alphanumeric and hyphens only (`a-z` and `-`).\n * - Must not start or end with `-`\n * - Must not contain consecutive `--`\n * - Must match the parent directory name containing the `SKILL.md` file\n *\n * Unicode lowercase alphanumeric means any lowercase or decimal digit, which\n * covers accented Latin characters (e.g., `'café'`, `'über-tool'`) and other\n * scripts.\n *\n * @param name - The skill name from YAML frontmatter\n * @param directoryName - The parent directory name\n * @returns `{ valid, error }` tuple. Error is empty string if valid.\n */\nexport function validateSkillName(\n name: string,\n directoryName: string,\n): { valid: boolean; error: string } {\n if (!name) {\n return { valid: false, error: \"name is required\" };\n }\n if (name.length > MAX_SKILL_NAME_LENGTH) {\n return { valid: false, error: \"name exceeds 64 characters\" };\n }\n if (name.startsWith(\"-\") || name.endsWith(\"-\") || name.includes(\"--\")) {\n return {\n valid: false,\n error: \"name must be lowercase alphanumeric with single hyphens only\",\n };\n }\n for (const c of name) {\n if (c === \"-\") continue;\n if (/\\p{Ll}/u.test(c) || /\\p{Nd}/u.test(c)) continue;\n return {\n valid: false,\n error: \"name must be lowercase alphanumeric with single hyphens only\",\n };\n }\n if (name !== directoryName) {\n return {\n valid: false,\n error: `name '${name}' must match directory name '${directoryName}'`,\n };\n }\n return { valid: true, error: \"\" };\n}\n\n/**\n * Validate and normalize the metadata field from YAML frontmatter.\n *\n * YAML parsing can return any type for the `metadata` key. This ensures the\n * value in {@link SkillMetadata} is always a `Record<string, string>` by\n * coercing via `String()` and rejecting non-object inputs.\n *\n * @param raw - Raw value from `frontmatterData.metadata`.\n * @param skillPath - Path to the `SKILL.md` file (for warning messages).\n * @returns A validated `Record<string, string>`.\n */\nexport function validateMetadata(\n raw: unknown,\n skillPath: string,\n): Record<string, string> {\n if (typeof raw !== \"object\" || raw === null || Array.isArray(raw)) {\n if (raw) {\n console.warn(\n `Ignoring non-object metadata in ${skillPath} (got ${typeof raw})`,\n );\n }\n return {};\n }\n const result: Record<string, string> = {};\n for (const [k, v] of Object.entries(raw)) {\n result[String(k)] = String(v);\n }\n return result;\n}\n\n/**\n * Build a parenthetical annotation string from optional skill fields.\n *\n * Combines license and compatibility into a comma-separated string for\n * display in the system prompt skill listing.\n *\n * @param skill - Skill metadata to extract annotations from.\n * @returns Annotation string like `'License: MIT, Compatibility: Python 3.10+'`,\n * or empty string if neither field is set.\n */\nexport function formatSkillAnnotations(skill: SkillMetadata): string {\n const parts: string[] = [];\n if (skill.license) {\n parts.push(`License: ${skill.license}`);\n }\n if (skill.compatibility) {\n parts.push(`Compatibility: ${skill.compatibility}`);\n }\n return parts.join(\", \");\n}\n\n/**\n * Parse YAML frontmatter from `SKILL.md` content.\n *\n * Extracts metadata per Agent Skills specification from YAML frontmatter\n * delimited by `---` markers at the start of the content.\n *\n * @param content - Content of the `SKILL.md` file\n * @param skillPath - Path to the `SKILL.md` file (for error messages and metadata)\n * @param directoryName - Name of the parent directory containing the skill\n * @returns `SkillMetadata` if parsing succeeds, `null` if parsing fails or\n * validation errors occur\n */\nexport function parseSkillMetadataFromContent(\n content: string,\n skillPath: string,\n directoryName: string,\n): SkillMetadata | null {\n if (content.length > MAX_SKILL_FILE_SIZE) {\n console.warn(\n `Skipping ${skillPath}: content too large (${content.length} bytes)`,\n );\n return null;\n }\n\n // Match YAML frontmatter between --- delimiters\n const frontmatterPattern = /^---\\s*\\n([\\s\\S]*?)\\n---\\s*\\n/;\n const match = content.match(frontmatterPattern);\n\n if (!match) {\n console.warn(`Skipping ${skillPath}: no valid YAML frontmatter found`);\n return null;\n }\n\n const frontmatterStr = match[1];\n\n // Parse YAML\n let frontmatterData: Record<string, unknown>;\n try {\n frontmatterData = yaml.parse(frontmatterStr);\n } catch (e) {\n console.warn(`Invalid YAML in ${skillPath}:`, e);\n return null;\n }\n\n if (!frontmatterData || typeof frontmatterData !== \"object\") {\n console.warn(`Skipping ${skillPath}: frontmatter is not a mapping`);\n return null;\n }\n\n // Validate required fields - coerce and strip whitespace\n const name = String(frontmatterData.name ?? \"\").trim();\n const description = String(frontmatterData.description ?? \"\").trim();\n\n if (!name || !description) {\n console.warn(\n `Skipping ${skillPath}: missing required 'name' or 'description'`,\n );\n return null;\n }\n\n // Validate name format per spec (warn but continue for backwards compatibility)\n const validation = validateSkillName(name, directoryName);\n if (!validation.valid) {\n console.warn(\n `Skill '${name}' in ${skillPath} does not follow Agent Skills specification: ${validation.error}. Consider renaming for spec compliance.`,\n );\n }\n\n // Validate description length per spec (max 1024 chars)\n let descriptionStr = description;\n if (descriptionStr.length > MAX_SKILL_DESCRIPTION_LENGTH) {\n console.warn(\n `Description exceeds ${MAX_SKILL_DESCRIPTION_LENGTH} characters in ${skillPath}, truncating`,\n );\n descriptionStr = descriptionStr.slice(0, MAX_SKILL_DESCRIPTION_LENGTH);\n }\n\n // Parse allowed-tools: support both YAML list and space-delimited string\n const rawTools = frontmatterData[\"allowed-tools\"];\n let allowedTools: string[];\n if (rawTools) {\n if (Array.isArray(rawTools)) {\n allowedTools = rawTools.map((t) => String(t).trim()).filter(Boolean);\n } else {\n // Split on whitespace (handles multiple consecutive spaces)\n allowedTools = String(rawTools).split(/\\s+/).filter(Boolean);\n }\n } else {\n allowedTools = [];\n }\n\n // Validate and truncate compatibility length\n let compatibilityStr =\n String(frontmatterData.compatibility ?? \"\").trim() || null;\n if (\n compatibilityStr &&\n compatibilityStr.length > MAX_SKILL_COMPATIBILITY_LENGTH\n ) {\n console.warn(\n `Compatibility exceeds ${MAX_SKILL_COMPATIBILITY_LENGTH} characters in ${skillPath}, truncating`,\n );\n compatibilityStr = compatibilityStr.slice(\n 0,\n MAX_SKILL_COMPATIBILITY_LENGTH,\n );\n }\n\n return {\n name,\n description: descriptionStr,\n path: skillPath,\n metadata: validateMetadata(frontmatterData.metadata ?? {}, skillPath),\n license: String(frontmatterData.license ?? \"\").trim() || null,\n compatibility: compatibilityStr,\n allowedTools,\n };\n}\n\n/**\n * List all skills from a backend source.\n */\nasync function listSkillsFromBackend(\n backend: BackendProtocol,\n sourcePath: string,\n): Promise<SkillMetadata[]> {\n const skills: SkillMetadata[] = [];\n\n // Detect path separator (Windows uses \\, Unix uses /)\n const pathSep = sourcePath.includes(\"\\\\\") ? \"\\\\\" : \"/\";\n\n // Normalize path to ensure it ends with the appropriate separator\n const normalizedPath =\n sourcePath.endsWith(\"/\") || sourcePath.endsWith(\"\\\\\")\n ? sourcePath\n : `${sourcePath}${pathSep}`;\n\n // List directories in the source path using lsInfo\n let fileInfos: { path: string; is_dir?: boolean }[];\n try {\n fileInfos = await backend.lsInfo(normalizedPath);\n } catch {\n // Source path doesn't exist or can't be listed\n return [];\n }\n\n // Convert FileInfo[] to entries format\n // Handle both forward slashes (Unix) and backslashes (Windows) in paths\n const entries = fileInfos.map((info) => ({\n name:\n info.path\n .replace(/[/\\\\]$/, \"\") // Remove trailing slash or backslash\n .split(/[/\\\\]/) // Split on either separator\n .pop() || \"\",\n type: (info.is_dir ? \"directory\" : \"file\") as \"file\" | \"directory\",\n }));\n\n // Look for subdirectories containing SKILL.md\n for (const entry of entries) {\n if (entry.type !== \"directory\") {\n continue;\n }\n\n const skillMdPath = `${normalizedPath}${entry.name}${pathSep}SKILL.md`;\n\n // Try to download the SKILL.md file\n let content: string;\n if (backend.downloadFiles) {\n const results = await backend.downloadFiles([skillMdPath]);\n if (results.length !== 1) {\n continue;\n }\n\n const response = results[0];\n if (response.error != null || response.content == null) {\n continue;\n }\n\n // Decode content\n content = new TextDecoder().decode(response.content);\n } else {\n // Fall back to read if downloadFiles is not available\n const readResult = await backend.read(skillMdPath);\n if (readResult.startsWith(\"Error:\")) {\n continue;\n }\n content = readResult;\n }\n const metadata = parseSkillMetadataFromContent(\n content,\n skillMdPath,\n entry.name,\n );\n\n if (metadata) {\n skills.push(metadata);\n }\n }\n\n return skills;\n}\n\n/**\n * Format skills locations for display in system prompt.\n * Shows priority indicator for the last source (highest priority).\n */\nfunction formatSkillsLocations(sources: string[]): string {\n if (sources.length === 0) {\n return \"**Skills Sources:** None configured\";\n }\n\n const lines: string[] = [];\n for (let i = 0; i < sources.length; i++) {\n const sourcePath = sources[i];\n // Extract a friendly name from the path (last non-empty component)\n // Handle both Unix (/) and Windows (\\) path separators\n const name =\n sourcePath\n .replace(/[/\\\\]$/, \"\")\n .split(/[/\\\\]/)\n .filter(Boolean)\n .pop()\n ?.replace(/^./, (c) => c.toUpperCase()) || \"Skills\";\n const suffix = i === sources.length - 1 ? \" (higher priority)\" : \"\";\n lines.push(`**${name} Skills**: \\`${sourcePath}\\`${suffix}`);\n }\n return lines.join(\"\\n\");\n}\n\n/**\n * Format skills metadata for display in system prompt.\n * Shows allowed tools for each skill if specified.\n */\nexport function formatSkillsList(\n skills: SkillMetadata[],\n sources: string[],\n): string {\n if (skills.length === 0) {\n const paths = sources.map((s) => `\\`${s}\\``).join(\" or \");\n return `(No skills available yet. You can create skills in ${paths})`;\n }\n\n const lines: string[] = [];\n for (const skill of skills) {\n const annotations = formatSkillAnnotations(skill);\n let descLine = `- **${skill.name}**: ${skill.description}`;\n if (annotations) {\n descLine += ` (${annotations})`;\n }\n lines.push(descLine);\n if (skill.allowedTools && skill.allowedTools.length > 0) {\n lines.push(` → Allowed tools: ${skill.allowedTools.join(\", \")}`);\n }\n lines.push(` → Read \\`${skill.path}\\` for full instructions`);\n }\n\n return lines.join(\"\\n\");\n}\n\n/**\n * Create backend-agnostic middleware for loading and exposing agent skills.\n *\n * This middleware loads skills from configurable backend sources and injects\n * skill metadata into the system prompt. It implements the progressive disclosure\n * pattern: skill names and descriptions are shown in the prompt, but the agent\n * reads full SKILL.md content only when needed.\n *\n * @param options - Configuration options\n * @returns AgentMiddleware for skills loading and injection\n *\n * @example\n * ```typescript\n * const middleware = createSkillsMiddleware({\n * backend: new FilesystemBackend({ rootDir: \"/\" }),\n * sources: [\"/skills/user/\", \"/skills/project/\"],\n * });\n * ```\n */\nexport function createSkillsMiddleware(options: SkillsMiddlewareOptions) {\n const { backend, sources } = options;\n\n // Closure variable to store loaded skills - wrapModelCall can access this\n // directly since beforeAgent state updates aren't immediately available\n let loadedSkills: SkillMetadata[] = [];\n\n /**\n * Resolve backend from instance or factory.\n */\n function getBackend(state: unknown): BackendProtocol {\n if (typeof backend === \"function\") {\n return backend({ state }) as BackendProtocol;\n }\n return backend;\n }\n\n return createMiddleware({\n name: \"SkillsMiddleware\",\n stateSchema: SkillsStateSchema,\n\n async beforeAgent(state) {\n // Skip if already loaded (check both closure and state)\n if (loadedSkills.length > 0) {\n return undefined;\n }\n // Check if skills were restored from checkpoint (non-empty array in state)\n if (\n \"skillsMetadata\" in state &&\n Array.isArray(state.skillsMetadata) &&\n state.skillsMetadata.length > 0\n ) {\n // Restore from state (e.g., after checkpoint restore)\n loadedSkills = state.skillsMetadata as SkillMetadata[];\n return undefined;\n }\n\n const resolvedBackend = getBackend(state);\n const allSkills: Map<string, SkillMetadata> = new Map();\n\n // Load skills from each source in order (later sources override earlier)\n for (const sourcePath of sources) {\n try {\n const skills = await listSkillsFromBackend(\n resolvedBackend,\n sourcePath,\n );\n for (const skill of skills) {\n allSkills.set(skill.name, skill);\n }\n } catch (error) {\n // Log but continue - individual source failures shouldn't break everything\n console.debug(\n `[BackendSkillsMiddleware] Failed to load skills from ${sourcePath}:`,\n error,\n );\n }\n }\n\n // Store in closure for immediate access by wrapModelCall\n loadedSkills = Array.from(allSkills.values());\n\n return { skillsMetadata: loadedSkills };\n },\n\n wrapModelCall(request, handler) {\n // Use closure variable which is populated by beforeAgent\n // Fall back to state for checkpoint restore scenarios\n const skillsMetadata: SkillMetadata[] =\n loadedSkills.length > 0\n ? loadedSkills\n : (request.state?.skillsMetadata as SkillMetadata[]) || [];\n\n // Format skills section\n const skillsLocations = formatSkillsLocations(sources);\n const skillsList = formatSkillsList(skillsMetadata, sources);\n\n const skillsSection = SKILLS_SYSTEM_PROMPT.replace(\n \"{skills_locations}\",\n skillsLocations,\n ).replace(\"{skills_list}\", skillsList);\n\n // Combine with existing system message\n const newSystemMessage = request.systemMessage.concat(skillsSection);\n\n return handler({ ...request, systemMessage: newSystemMessage });\n },\n });\n}\n","/**\n * Utility functions for middleware.\n *\n * This module provides shared helpers used across middleware implementations.\n */\n\nimport { SystemMessage } from \"@langchain/core/messages\";\n\n/**\n * Append text to a system message.\n *\n * Creates a new SystemMessage with the text appended to the existing content.\n * If the original message has content, the new text is separated by two newlines.\n *\n * @param systemMessage - Existing system message or null/undefined.\n * @param text - Text to add to the system message.\n * @returns New SystemMessage with the text appended.\n *\n * @example\n * ```typescript\n * const original = new SystemMessage({ content: \"You are a helpful assistant.\" });\n * const updated = appendToSystemMessage(original, \"Always be concise.\");\n * // Result: SystemMessage with content \"You are a helpful assistant.\\n\\nAlways be concise.\"\n * ```\n */\nexport function appendToSystemMessage(\n systemMessage: SystemMessage | null | undefined,\n text: string,\n): SystemMessage {\n if (!systemMessage) {\n return new SystemMessage({ content: text });\n }\n\n // Handle both string and array content formats\n const existingContent = systemMessage.content;\n\n if (typeof existingContent === \"string\") {\n const newContent = existingContent ? `${existingContent}\\n\\n${text}` : text;\n return new SystemMessage({ content: newContent });\n }\n\n // For array content (content blocks), append as a new text block\n if (Array.isArray(existingContent)) {\n const newContent = [...existingContent];\n const textToAdd = newContent.length > 0 ? `\\n\\n${text}` : text;\n newContent.push({ type: \"text\", text: textToAdd });\n return new SystemMessage({ content: newContent });\n }\n\n // Fallback for unknown content type\n return new SystemMessage({ content: text });\n}\n\n/**\n * Prepend text to a system message.\n *\n * Creates a new SystemMessage with the text prepended to the existing content.\n * If the original message has content, the new text is separated by two newlines.\n *\n * @param systemMessage - Existing system message or null/undefined.\n * @param text - Text to prepend to the system message.\n * @returns New SystemMessage with the text prepended.\n *\n * @example\n * ```typescript\n * const original = new SystemMessage({ content: \"Always be concise.\" });\n * const updated = prependToSystemMessage(original, \"You are a helpful assistant.\");\n * // Result: SystemMessage with content \"You are a helpful assistant.\\n\\nAlways be concise.\"\n * ```\n */\nexport function prependToSystemMessage(\n systemMessage: SystemMessage | null | undefined,\n text: string,\n): SystemMessage {\n if (!systemMessage) {\n return new SystemMessage({ content: text });\n }\n\n // Handle both string and array content formats\n const existingContent = systemMessage.content;\n\n if (typeof existingContent === \"string\") {\n const newContent = existingContent ? `${text}\\n\\n${existingContent}` : text;\n return new SystemMessage({ content: newContent });\n }\n\n // For array content (content blocks), prepend as a new text block\n if (Array.isArray(existingContent)) {\n const textToAdd = existingContent.length > 0 ? `${text}\\n\\n` : text;\n const newContent = [{ type: \"text\", text: textToAdd }, ...existingContent];\n return new SystemMessage({ content: newContent });\n }\n\n // Fallback for unknown content type\n return new SystemMessage({ content: text });\n}\n","/**\n * Summarization middleware with backend support for conversation history offloading.\n *\n * This module extends the base LangChain summarization middleware with additional\n * backend-based features for persisting conversation history before summarization.\n *\n * ## Usage\n *\n * ```typescript\n * import { createSummarizationMiddleware } from \"@anthropic/deepagents\";\n * import { FilesystemBackend } from \"@anthropic/deepagents\";\n *\n * const backend = new FilesystemBackend({ rootDir: \"/data\" });\n *\n * const middleware = createSummarizationMiddleware({\n * model: \"gpt-4o-mini\",\n * backend,\n * trigger: { type: \"fraction\", value: 0.85 },\n * keep: { type: \"fraction\", value: 0.10 },\n * });\n *\n * const agent = createDeepAgent({ middleware: [middleware] });\n * ```\n *\n * ## Storage\n *\n * Offloaded messages are stored as markdown at `/conversation_history/{thread_id}.md`.\n *\n * Each summarization event appends a new section to this file, creating a running log\n * of all evicted messages.\n *\n * ## Relationship to LangChain Summarization Middleware\n *\n * The base `summarizationMiddleware` from `langchain` provides core summarization\n * functionality. This middleware adds:\n * - Backend-based conversation history offloading\n * - Tool argument truncation for old messages\n *\n * For simple use cases without backend offloading, use `summarizationMiddleware`\n * from `langchain` directly.\n */\n\nimport { z } from \"zod\";\nimport { v4 as uuidv4 } from \"uuid\";\nimport {\n createMiddleware,\n countTokensApproximately,\n HumanMessage,\n AIMessage,\n ToolMessage,\n SystemMessage,\n BaseMessage,\n type AgentMiddleware as _AgentMiddleware,\n} from \"langchain\";\nimport { getBufferString } from \"@langchain/core/messages\";\nimport type { BaseChatModel } from \"@langchain/core/language_models/chat_models\";\nimport type { BaseLanguageModel } from \"@langchain/core/language_models/base\";\nimport type { ClientTool, ServerTool } from \"@langchain/core/tools\";\nimport { ContextOverflowError } from \"@langchain/core/errors\";\nimport { initChatModel } from \"langchain/chat_models/universal\";\nimport { Command } from \"@langchain/langgraph\";\n\nimport type { BackendProtocol, BackendFactory } from \"../backends/protocol.js\";\nimport type { StateBackend } from \"../backends/state.js\";\nimport type { BaseStore } from \"@langchain/langgraph-checkpoint\";\n\n// Re-export the base summarization middleware from langchain for users who don't need backend offloading\nexport { summarizationMiddleware } from \"langchain\";\n\n/**\n * Context size specification for summarization triggers and retention policies.\n */\nexport interface ContextSize {\n /** Type of context measurement */\n type: \"messages\" | \"tokens\" | \"fraction\";\n /** Threshold value */\n value: number;\n}\n\n/**\n * Settings for truncating large tool arguments in old messages.\n */\nexport interface TruncateArgsSettings {\n /**\n * Threshold to trigger argument truncation.\n * If not provided, truncation is disabled.\n */\n trigger?: ContextSize;\n\n /**\n * Context retention policy for message truncation.\n * Defaults to keeping last 20 messages.\n */\n keep?: ContextSize;\n\n /**\n * Maximum character length for tool arguments before truncation.\n * Defaults to 2000.\n */\n maxLength?: number;\n\n /**\n * Text to replace truncated arguments with.\n * Defaults to \"...(argument truncated)\".\n */\n truncationText?: string;\n}\n\n/**\n * Options for the summarization middleware.\n */\nexport interface SummarizationMiddlewareOptions {\n /**\n * The language model to use for generating summaries.\n * Can be a model string (e.g., \"gpt-4o-mini\") or a language model instance.\n */\n model: string | BaseChatModel | BaseLanguageModel;\n\n /**\n * Backend instance or factory for persisting conversation history.\n */\n backend:\n | BackendProtocol\n | BackendFactory\n | ((config: { state: unknown; store?: BaseStore }) => StateBackend);\n\n /**\n * Threshold(s) that trigger summarization.\n * Can be a single ContextSize or an array for multiple triggers.\n */\n trigger?: ContextSize | ContextSize[];\n\n /**\n * Context retention policy after summarization.\n * Defaults to keeping last 20 messages.\n */\n keep?: ContextSize;\n\n /**\n * Prompt template for generating summaries.\n */\n summaryPrompt?: string;\n\n /**\n * Max tokens to include when generating summary.\n * Defaults to 4000.\n */\n trimTokensToSummarize?: number;\n\n /**\n * Path prefix for storing conversation history.\n * Defaults to \"/conversation_history\".\n */\n historyPathPrefix?: string;\n\n /**\n * Settings for truncating large tool arguments in old messages.\n * If not provided, argument truncation is disabled.\n */\n truncateArgsSettings?: TruncateArgsSettings;\n}\n\n// Default values\nconst DEFAULT_MESSAGES_TO_KEEP = 20;\nconst DEFAULT_TRIM_TOKEN_LIMIT = 4000;\n\n// Fallback defaults when model has no profile (matches Python's fallback)\nconst FALLBACK_TRIGGER: ContextSize = { type: \"tokens\", value: 170_000 };\nconst FALLBACK_KEEP: ContextSize = { type: \"messages\", value: 6 };\nconst FALLBACK_TRUNCATE_ARGS: TruncateArgsSettings = {\n trigger: { type: \"messages\", value: 20 },\n keep: { type: \"messages\", value: 20 },\n};\n\n// Profile-based defaults (when model has max_input_tokens in profile)\nconst PROFILE_TRIGGER: ContextSize = { type: \"fraction\", value: 0.85 };\nconst PROFILE_KEEP: ContextSize = { type: \"fraction\", value: 0.1 };\nconst PROFILE_TRUNCATE_ARGS: TruncateArgsSettings = {\n trigger: { type: \"fraction\", value: 0.85 },\n keep: { type: \"fraction\", value: 0.1 },\n};\n\n/**\n * Compute summarization defaults based on model profile.\n * Mirrors Python's `_compute_summarization_defaults`.\n *\n * If the model has a profile with `maxInputTokens`, uses fraction-based\n * settings. Otherwise, uses fixed token/message counts.\n *\n * @param resolvedModel - The resolved chat model instance.\n */\nexport function computeSummarizationDefaults(resolvedModel: BaseChatModel): {\n trigger: ContextSize;\n keep: ContextSize;\n truncateArgsSettings: TruncateArgsSettings;\n} {\n const hasProfile =\n resolvedModel.profile &&\n typeof resolvedModel.profile === \"object\" &&\n \"maxInputTokens\" in resolvedModel.profile &&\n typeof resolvedModel.profile.maxInputTokens === \"number\";\n\n if (hasProfile) {\n return {\n trigger: PROFILE_TRIGGER,\n keep: PROFILE_KEEP,\n truncateArgsSettings: PROFILE_TRUNCATE_ARGS,\n };\n }\n\n return {\n trigger: FALLBACK_TRIGGER,\n keep: FALLBACK_KEEP,\n truncateArgsSettings: FALLBACK_TRUNCATE_ARGS,\n };\n}\nconst DEFAULT_SUMMARY_PROMPT = `You are a conversation summarizer. Your task is to create a concise summary of the conversation that captures:\n1. The main topics discussed\n2. Key decisions or conclusions reached\n3. Any important context that would be needed for continuing the conversation\n\nKeep the summary focused and informative. Do not include unnecessary details.\n\nConversation to summarize:\n{conversation}\n\nSummary:`;\n\n/**\n * Zod schema for a summarization event that tracks what was summarized and\n * where the cutoff is.\n *\n * Instead of rewriting LangGraph state with `RemoveMessage(REMOVE_ALL_MESSAGES)`,\n * the middleware stores this event and uses it to reconstruct the effective message\n * list on subsequent calls.\n */\nconst SummarizationEventSchema = z.object({\n /**\n * The index in the state messages list where summarization occurred.\n * Messages before this index have been summarized. */\n cutoffIndex: z.number(),\n /** The HumanMessage containing the summary. */\n summaryMessage: z.instanceof(HumanMessage),\n /** Path where the conversation history was offloaded, or null if offload failed. */\n filePath: z.string().nullable(),\n});\n\n/**\n * Represents a summarization event that tracks what was summarized and where the cutoff is.\n */\nexport type SummarizationEvent = z.infer<typeof SummarizationEventSchema>;\n\n/**\n * State schema for summarization middleware.\n */\nconst SummarizationStateSchema = z.object({\n /** Session ID for history file naming */\n _summarizationSessionId: z.string().optional(),\n /** Most recent summarization event (private state, not visible to agent) */\n _summarizationEvent: SummarizationEventSchema.optional(),\n});\n\n/**\n * Check if a message is a previous summarization message.\n * Summary messages are HumanMessage objects with lc_source='summarization' in additional_kwargs.\n */\nfunction isSummaryMessage(msg: BaseMessage): boolean {\n if (!HumanMessage.isInstance(msg)) {\n return false;\n }\n return msg.additional_kwargs?.lc_source === \"summarization\";\n}\n\n/**\n * Create summarization middleware with backend support for conversation history offloading.\n *\n * This middleware:\n * 1. Monitors conversation length against configured thresholds\n * 2. When triggered, offloads old messages to backend storage\n * 3. Generates a summary of offloaded messages\n * 4. Replaces old messages with the summary, preserving recent context\n *\n * @param options - Configuration options\n * @returns AgentMiddleware for summarization and history offloading\n */\nexport function createSummarizationMiddleware(\n options: SummarizationMiddlewareOptions,\n) {\n const {\n model,\n backend,\n summaryPrompt = DEFAULT_SUMMARY_PROMPT,\n trimTokensToSummarize = DEFAULT_TRIM_TOKEN_LIMIT,\n historyPathPrefix = \"/conversation_history\",\n } = options;\n\n // Mutable config that may be lazily computed from model profile.\n // When trigger/keep/truncateArgsSettings are not provided, they will be\n // computed from the model profile on first wrapModelCall, matching\n // Python's `_compute_summarization_defaults` behavior.\n let trigger = options.trigger;\n let keep: ContextSize = options.keep ?? {\n type: \"messages\",\n value: DEFAULT_MESSAGES_TO_KEEP,\n };\n let truncateArgsSettings = options.truncateArgsSettings;\n let defaultsComputed = trigger != null;\n\n // Parse truncate settings (will be re-parsed after defaults are computed)\n let truncateTrigger = truncateArgsSettings?.trigger;\n let truncateKeep: ContextSize = truncateArgsSettings?.keep ?? {\n type: \"messages\" as const,\n value: 20,\n };\n let maxArgLength = truncateArgsSettings?.maxLength ?? 2000;\n let truncationText =\n truncateArgsSettings?.truncationText ?? \"...(argument truncated)\";\n\n /**\n * Lazily compute defaults from model profile when trigger was not provided.\n * Called once when the model is first resolved.\n */\n function applyModelDefaults(resolvedModel: BaseChatModel): void {\n if (defaultsComputed) {\n return;\n }\n defaultsComputed = true;\n\n const defaults = computeSummarizationDefaults(resolvedModel);\n\n trigger = defaults.trigger;\n keep = options.keep ?? defaults.keep;\n\n if (!options.truncateArgsSettings) {\n truncateArgsSettings = defaults.truncateArgsSettings;\n truncateTrigger = defaults.truncateArgsSettings.trigger;\n truncateKeep = defaults.truncateArgsSettings.keep ?? {\n type: \"messages\" as const,\n value: 20,\n };\n maxArgLength = defaults.truncateArgsSettings.maxLength ?? 2000;\n truncationText =\n defaults.truncateArgsSettings.truncationText ??\n \"...(argument truncated)\";\n }\n }\n\n // Session ID for this middleware instance (fallback if no thread_id)\n let sessionId: string | null = null;\n\n // Calibration multiplier for token estimation. countTokensApproximately\n // can significantly undercount (e.g. it ignores tool_use content blocks,\n // JSON structural overhead). After a ContextOverflowError we learn the\n // gap between estimated and actual tokens and adjust future comparisons\n // so proactive summarization fires before the hard limit is hit.\n let tokenEstimationMultiplier = 1.0;\n\n /**\n * Resolve backend from instance or factory.\n */\n function getBackend(state: unknown): BackendProtocol {\n if (typeof backend === \"function\") {\n return backend({ state }) as BackendProtocol;\n }\n return backend;\n }\n\n /**\n * Get or create session ID for history file naming.\n */\n function getSessionId(state: Record<string, unknown>): string {\n if (state._summarizationSessionId) {\n return state._summarizationSessionId as string;\n }\n if (!sessionId) {\n sessionId = `session_${uuidv4().substring(0, 8)}`;\n }\n return sessionId;\n }\n\n /**\n * Get the history file path.\n */\n function getHistoryPath(state: Record<string, unknown>): string {\n const id = getSessionId(state);\n return `${historyPathPrefix}/${id}.md`;\n }\n\n /**\n * Cached resolved model to avoid repeated initChatModel calls\n */\n let cachedModel: BaseChatModel | undefined = undefined;\n\n /**\n * Resolve the chat model.\n * Uses initChatModel to support any model provider from a string name.\n * The resolved model is cached for subsequent calls.\n */\n async function getChatModel(): Promise<BaseChatModel> {\n if (cachedModel) {\n return cachedModel;\n }\n\n if (typeof model === \"string\") {\n cachedModel = await initChatModel(model);\n } else {\n cachedModel = model as BaseChatModel;\n }\n return cachedModel;\n }\n\n /**\n * Get the max input tokens from the model's profile.\n * Similar to Python's _get_profile_limits.\n *\n * When the profile is unavailable, returns undefined. In that case the\n * middleware uses fixed token/message-count fallback defaults for\n * trigger/keep, and relies on the ContextOverflowError catch as a\n * safety net if the prompt still exceeds the model's actual limit.\n */\n function getMaxInputTokens(resolvedModel: BaseChatModel): number | undefined {\n const profile = resolvedModel.profile;\n if (\n profile &&\n typeof profile === \"object\" &&\n \"maxInputTokens\" in profile &&\n typeof profile.maxInputTokens === \"number\"\n ) {\n return profile.maxInputTokens;\n }\n return undefined;\n }\n\n /**\n * Check if summarization should be triggered.\n */\n function shouldSummarize(\n messages: BaseMessage[],\n totalTokens: number,\n maxInputTokens?: number,\n ): boolean {\n if (!trigger) {\n return false;\n }\n\n const adjustedTokens = totalTokens * tokenEstimationMultiplier;\n const triggers = Array.isArray(trigger) ? trigger : [trigger];\n\n for (const t of triggers) {\n if (t.type === \"messages\" && messages.length >= t.value) {\n return true;\n }\n if (t.type === \"tokens\" && adjustedTokens >= t.value) {\n return true;\n }\n if (t.type === \"fraction\" && maxInputTokens) {\n const threshold = Math.floor(maxInputTokens * t.value);\n if (adjustedTokens >= threshold) {\n return true;\n }\n }\n }\n\n return false;\n }\n\n /**\n * Find a safe cutoff point that doesn't split AI/Tool message pairs.\n *\n * If the message at `cutoffIndex` is a ToolMessage, this adjusts the boundary\n * so that related AI and Tool messages stay together. Two strategies are used:\n *\n * 1. **Move backward** to include the AIMessage that produced the tool calls,\n * keeping the pair in the preserved set. Preferred when it doesn't move\n * the cutoff too far back.\n *\n * 2. **Advance forward** past all consecutive ToolMessages, putting the entire\n * pair into the summarized set. Used when moving backward would preserve\n * too many messages (e.g., a single AIMessage made 20+ tool calls).\n */\n function findSafeCutoffPoint(\n messages: BaseMessage[],\n cutoffIndex: number,\n ): number {\n if (\n cutoffIndex >= messages.length ||\n !ToolMessage.isInstance(messages[cutoffIndex])\n ) {\n return cutoffIndex;\n }\n\n // Advance past all consecutive ToolMessages at the cutoff point\n let forwardIdx = cutoffIndex;\n while (\n forwardIdx < messages.length &&\n ToolMessage.isInstance(messages[forwardIdx])\n ) {\n forwardIdx++;\n }\n\n // Collect tool_call_ids from the ToolMessages at the cutoff boundary\n const toolCallIds = new Set<string>();\n for (let i = cutoffIndex; i < forwardIdx; i++) {\n const toolMsg = messages[i] as InstanceType<typeof ToolMessage>;\n if (toolMsg.tool_call_id) {\n toolCallIds.add(toolMsg.tool_call_id);\n }\n }\n\n // Search backward for AIMessage with matching tool_calls\n let backwardIdx: number | null = null;\n for (let i = cutoffIndex - 1; i >= 0; i--) {\n const msg = messages[i];\n if (AIMessage.isInstance(msg) && msg.tool_calls) {\n const aiToolCallIds = new Set(\n msg.tool_calls\n .map((tc) => tc.id)\n .filter((id): id is string => id != null),\n );\n for (const id of toolCallIds) {\n if (aiToolCallIds.has(id)) {\n backwardIdx = i;\n break;\n }\n }\n if (backwardIdx !== null) break;\n }\n }\n\n if (backwardIdx === null) {\n // No matching AIMessage found - advance forward past ToolMessages\n return forwardIdx;\n }\n\n // Choose strategy: prefer backward (preserves more context) unless it\n // would move the cutoff back by more than half the original position,\n // which indicates a single AIMessage with many tool calls that would\n // defeat the purpose of summarization.\n const backwardDistance = cutoffIndex - backwardIdx;\n if (backwardDistance > cutoffIndex / 2 && cutoffIndex > 2) {\n return forwardIdx;\n }\n\n return backwardIdx;\n }\n\n /**\n * Determine cutoff index for messages to summarize.\n * Messages at index < cutoff will be summarized.\n * Messages at index >= cutoff will be preserved.\n *\n * Uses findSafeCutoffPoint to ensure tool call/result pairs stay together.\n */\n function determineCutoffIndex(\n messages: BaseMessage[],\n maxInputTokens?: number,\n ): number {\n let rawCutoff: number;\n\n if (keep.type === \"messages\") {\n if (messages.length <= keep.value) {\n return 0;\n }\n rawCutoff = messages.length - keep.value;\n } else if (keep.type === \"tokens\" || keep.type === \"fraction\") {\n const targetTokenCount =\n keep.type === \"fraction\" && maxInputTokens\n ? Math.floor(maxInputTokens * keep.value)\n : keep.value;\n\n let tokensKept = 0;\n rawCutoff = 0;\n for (let i = messages.length - 1; i >= 0; i--) {\n const msgTokens = countTokensApproximately([messages[i]]);\n if (tokensKept + msgTokens > targetTokenCount) {\n rawCutoff = i + 1;\n break;\n }\n tokensKept += msgTokens;\n }\n } else {\n return 0;\n }\n\n return findSafeCutoffPoint(messages, rawCutoff);\n }\n\n /**\n * Check if argument truncation should be triggered.\n */\n function shouldTruncateArgs(\n messages: BaseMessage[],\n totalTokens: number,\n maxInputTokens?: number,\n ): boolean {\n if (!truncateTrigger) {\n return false;\n }\n\n const adjustedTokens = totalTokens * tokenEstimationMultiplier;\n if (truncateTrigger.type === \"messages\") {\n return messages.length >= truncateTrigger.value;\n }\n if (truncateTrigger.type === \"tokens\") {\n return adjustedTokens >= truncateTrigger.value;\n }\n if (truncateTrigger.type === \"fraction\" && maxInputTokens) {\n const threshold = Math.floor(maxInputTokens * truncateTrigger.value);\n return adjustedTokens >= threshold;\n }\n\n return false;\n }\n\n /**\n * Determine cutoff index for argument truncation.\n * Uses findSafeCutoffPoint to ensure tool call/result pairs stay together.\n */\n function determineTruncateCutoffIndex(\n messages: BaseMessage[],\n maxInputTokens?: number,\n ): number {\n let rawCutoff: number;\n\n if (truncateKeep.type === \"messages\") {\n if (messages.length <= truncateKeep.value) {\n return messages.length;\n }\n rawCutoff = messages.length - truncateKeep.value;\n } else if (\n truncateKeep.type === \"tokens\" ||\n truncateKeep.type === \"fraction\"\n ) {\n const targetTokenCount =\n truncateKeep.type === \"fraction\" && maxInputTokens\n ? Math.floor(maxInputTokens * truncateKeep.value)\n : truncateKeep.value;\n\n let tokensKept = 0;\n rawCutoff = 0;\n for (let i = messages.length - 1; i >= 0; i--) {\n const msgTokens = countTokensApproximately([messages[i]]);\n if (tokensKept + msgTokens > targetTokenCount) {\n rawCutoff = i + 1;\n break;\n }\n tokensKept += msgTokens;\n }\n } else {\n return messages.length;\n }\n\n return findSafeCutoffPoint(messages, rawCutoff);\n }\n\n /**\n * Count tokens including system message and tools, matching Python's approach.\n * This gives a more accurate picture of what actually gets sent to the model.\n */\n function countTotalTokens(\n messages: BaseMessage[],\n systemMessage?: SystemMessage | unknown,\n tools?: (ServerTool | ClientTool)[] | unknown[],\n ): number {\n const countedMessages: BaseMessage[] =\n systemMessage && SystemMessage.isInstance(systemMessage)\n ? [systemMessage as SystemMessage, ...messages]\n : [...messages];\n\n const toolsArray =\n tools && Array.isArray(tools) && tools.length > 0\n ? (tools as Array<Record<string, unknown>>)\n : null;\n\n return countTokensApproximately(countedMessages, toolsArray);\n }\n\n /**\n * Truncate ToolMessage content so that the total payload fits within the\n * model's context window. Each ToolMessage gets an equal share of the\n * remaining token budget after accounting for non-tool messages, system\n * message, and tool schemas.\n *\n * This is critical for conversations where a single AIMessage triggers\n * many tool calls whose results collectively exceed the context window.\n * Without this, findSafeCutoffPoint cannot split the AI/Tool group and\n * summarization would discard everything, causing the model to re-call\n * the same tools in an infinite loop.\n */\n function compactToolResults(\n messages: BaseMessage[],\n maxInputTokens: number,\n systemMessage?: SystemMessage | unknown,\n tools?: (ServerTool | ClientTool)[] | unknown[],\n ): { messages: BaseMessage[]; modified: boolean } {\n const toolMessageIndices: number[] = [];\n for (let i = 0; i < messages.length; i++) {\n if (ToolMessage.isInstance(messages[i])) {\n toolMessageIndices.push(i);\n }\n }\n if (toolMessageIndices.length === 0) {\n return { messages, modified: false };\n }\n\n const nonToolMessages = messages.filter((m) => !ToolMessage.isInstance(m));\n const overheadTokens = countTotalTokens(\n nonToolMessages,\n systemMessage,\n tools,\n );\n\n // Target: fit within maxInputTokens / multiplier, leaving 30% headroom\n const adjustedMax = maxInputTokens / tokenEstimationMultiplier;\n const budgetForTools = Math.max(adjustedMax * 0.7 - overheadTokens, 1000);\n const perToolBudgetTokens = Math.floor(\n budgetForTools / toolMessageIndices.length,\n );\n const perToolBudgetChars = perToolBudgetTokens * 4;\n\n let modified = false;\n const result = [...messages];\n\n for (const idx of toolMessageIndices) {\n const msg = messages[idx] as InstanceType<typeof ToolMessage>;\n const content =\n typeof msg.content === \"string\"\n ? msg.content\n : JSON.stringify(msg.content);\n\n if (content.length > perToolBudgetChars) {\n result[idx] = new ToolMessage({\n content:\n content.substring(0, perToolBudgetChars) +\n \"\\n...(result truncated)\",\n tool_call_id: msg.tool_call_id,\n name: msg.name,\n });\n modified = true;\n }\n }\n\n return { messages: result, modified };\n }\n\n /**\n * Truncate large tool arguments in old messages.\n */\n function truncateArgs(\n messages: BaseMessage[],\n maxInputTokens?: number,\n systemMessage?: SystemMessage | unknown,\n tools?: (ServerTool | ClientTool)[] | unknown[],\n ): { messages: BaseMessage[]; modified: boolean } {\n const totalTokens = countTotalTokens(messages, systemMessage, tools);\n if (!shouldTruncateArgs(messages, totalTokens, maxInputTokens)) {\n return { messages, modified: false };\n }\n\n const cutoffIndex = determineTruncateCutoffIndex(messages, maxInputTokens);\n if (cutoffIndex >= messages.length) {\n return { messages, modified: false };\n }\n\n const truncatedMessages: BaseMessage[] = [];\n let modified = false;\n\n for (let i = 0; i < messages.length; i++) {\n const msg = messages[i];\n\n if (i < cutoffIndex && AIMessage.isInstance(msg) && msg.tool_calls) {\n const truncatedToolCalls = msg.tool_calls.map((toolCall) => {\n const args = toolCall.args || {};\n const truncatedArgs: Record<string, unknown> = {};\n let toolModified = false;\n\n for (const [key, value] of Object.entries(args)) {\n if (\n typeof value === \"string\" &&\n value.length > maxArgLength &&\n (toolCall.name === \"write_file\" || toolCall.name === \"edit_file\")\n ) {\n truncatedArgs[key] = value.substring(0, 20) + truncationText;\n toolModified = true;\n } else {\n truncatedArgs[key] = value;\n }\n }\n\n if (toolModified) {\n modified = true;\n return { ...toolCall, args: truncatedArgs };\n }\n return toolCall;\n });\n\n if (modified) {\n const truncatedMsg = new AIMessage({\n content: msg.content,\n tool_calls: truncatedToolCalls,\n additional_kwargs: msg.additional_kwargs,\n });\n truncatedMessages.push(truncatedMsg);\n } else {\n truncatedMessages.push(msg);\n }\n } else {\n truncatedMessages.push(msg);\n }\n }\n\n return { messages: truncatedMessages, modified };\n }\n\n /**\n * Filter out previous summary messages.\n */\n function filterSummaryMessages(messages: BaseMessage[]): BaseMessage[] {\n return messages.filter((msg) => !isSummaryMessage(msg));\n }\n\n /**\n * Offload messages to backend by appending to the history file.\n *\n * Uses uploadFiles() directly with raw byte concatenation instead of\n * edit() to avoid downloading the file twice and performing a full\n * string search-and-replace. This keeps peak memory at ~2x file size\n * (existing bytes + combined bytes) instead of ~6x with the old\n * download → edit(oldContent, newContent) approach.\n */\n async function offloadToBackend(\n resolvedBackend: BackendProtocol,\n messages: BaseMessage[],\n state: Record<string, unknown>,\n ): Promise<string | null> {\n const filePath = getHistoryPath(state);\n const filteredMessages = filterSummaryMessages(messages);\n\n const timestamp = new Date().toISOString();\n const newSection = `## Summarized at ${timestamp}\\n\\n${getBufferString(filteredMessages)}\\n\\n`;\n const sectionBytes = new TextEncoder().encode(newSection);\n\n try {\n // Read existing content as raw bytes (no string decode needed)\n let existingBytes: Uint8Array | null = null;\n if (resolvedBackend.downloadFiles) {\n try {\n const responses = await resolvedBackend.downloadFiles([filePath]);\n if (\n responses.length > 0 &&\n responses[0].content &&\n !responses[0].error\n ) {\n existingBytes = responses[0].content;\n }\n } catch {\n // File doesn't exist yet, that's fine\n }\n }\n\n let result: { error?: string; path?: string };\n if (existingBytes && resolvedBackend.uploadFiles) {\n // Append: concatenate raw bytes and upload directly\n const combined = new Uint8Array(\n existingBytes.byteLength + sectionBytes.byteLength,\n );\n combined.set(existingBytes, 0);\n combined.set(sectionBytes, existingBytes.byteLength);\n\n const uploadResults = await resolvedBackend.uploadFiles([\n [filePath, combined],\n ]);\n result = uploadResults[0].error\n ? { error: uploadResults[0].error }\n : { path: filePath };\n } else if (!existingBytes) {\n result = await resolvedBackend.write(filePath, newSection);\n } else {\n // Fallback: uploadFiles unavailable, use edit()\n const existingContent = new TextDecoder().decode(existingBytes);\n result = await resolvedBackend.edit(\n filePath,\n existingContent,\n existingContent + newSection,\n );\n }\n\n if (result.error) {\n // eslint-disable-next-line no-console\n console.warn(\n `Failed to offload conversation history to ${filePath}: ${result.error}`,\n );\n return null;\n }\n\n return filePath;\n } catch (e) {\n // eslint-disable-next-line no-console\n console.warn(\n `Exception offloading conversation history to ${filePath}:`,\n e,\n );\n return null;\n }\n }\n\n /**\n * Create summary of messages.\n */\n async function createSummary(\n messages: BaseMessage[],\n chatModel: BaseChatModel,\n ): Promise<string> {\n // Trim messages if too long\n let messagesToSummarize = messages;\n const tokens = countTokensApproximately(messages);\n if (tokens > trimTokensToSummarize) {\n // Keep only recent messages that fit\n let kept = 0;\n const trimmedMessages: BaseMessage[] = [];\n for (let i = messages.length - 1; i >= 0; i--) {\n const msgTokens = countTokensApproximately([messages[i]]);\n if (kept + msgTokens > trimTokensToSummarize) {\n break;\n }\n trimmedMessages.unshift(messages[i]);\n kept += msgTokens;\n }\n messagesToSummarize = trimmedMessages;\n }\n\n const conversation = getBufferString(messagesToSummarize);\n const prompt = summaryPrompt.replace(\"{conversation}\", conversation);\n\n const response = await chatModel.invoke([\n new HumanMessage({ content: prompt }),\n ]);\n\n return typeof response.content === \"string\"\n ? response.content\n : JSON.stringify(response.content);\n }\n\n /**\n * Build the summary message with file path reference.\n */\n function buildSummaryMessage(\n summary: string,\n filePath: string | null,\n ): HumanMessage {\n let content: string;\n if (filePath) {\n content = `You are in the middle of a conversation that has been summarized.\n\nThe full conversation history has been saved to ${filePath} should you need to refer back to it for details.\n\nA condensed summary follows:\n\n<summary>\n${summary}\n</summary>`;\n } else {\n content = `Here is a summary of the conversation to date:\\n\\n${summary}`;\n }\n\n return new HumanMessage({\n content,\n additional_kwargs: { lc_source: \"summarization\" },\n });\n }\n\n /**\n * Reconstruct the effective message list based on any previous summarization event.\n *\n * After summarization, instead of using all messages from state, we use the summary\n * message plus messages after the cutoff index. This avoids full state rewrites.\n */\n function getEffectiveMessages(\n messages: BaseMessage[],\n state: Record<string, unknown>,\n ): BaseMessage[] {\n const event = state._summarizationEvent as SummarizationEvent | undefined;\n\n // If no summarization event, return all messages as-is\n if (!event) {\n return messages;\n }\n\n // Build effective messages: summary message, then messages from cutoff onward\n const result: BaseMessage[] = [event.summaryMessage];\n result.push(...messages.slice(event.cutoffIndex));\n\n return result;\n }\n\n /**\n * Summarize a set of messages using the given model and build the\n * summary message + backend offload. Returns the summary message,\n * the file path, and the state cutoff index.\n */\n async function summarizeMessages(\n messagesToSummarize: BaseMessage[],\n resolvedModel: BaseChatModel,\n state: Record<string, unknown>,\n previousCutoffIndex: number | undefined,\n cutoffIndex: number,\n ): Promise<{\n summaryMessage: HumanMessage;\n filePath: string | null;\n stateCutoffIndex: number;\n }> {\n const resolvedBackend = getBackend(state);\n const filePath = await offloadToBackend(\n resolvedBackend,\n messagesToSummarize,\n state,\n );\n\n if (filePath === null) {\n // eslint-disable-next-line no-console\n console.warn(\n `[SummarizationMiddleware] Backend offload failed during summarization. Proceeding with summary generation.`,\n );\n }\n\n const summary = await createSummary(messagesToSummarize, resolvedModel);\n const summaryMessage = buildSummaryMessage(summary, filePath);\n\n const stateCutoffIndex =\n previousCutoffIndex != null\n ? previousCutoffIndex + cutoffIndex - 1\n : cutoffIndex;\n\n return { summaryMessage, filePath, stateCutoffIndex };\n }\n\n /**\n * Check if an error (possibly wrapped in MiddlewareError layers) is a\n * ContextOverflowError by walking the `cause` chain.\n */\n function isContextOverflow(err: unknown): boolean {\n let cause: unknown = err;\n for (;;) {\n if (!cause) {\n break;\n }\n if (ContextOverflowError.isInstance(cause)) {\n return true;\n }\n cause =\n typeof cause === \"object\" && \"cause\" in cause\n ? (cause as { cause?: unknown }).cause\n : undefined;\n }\n return false;\n }\n\n async function performSummarization(\n request: {\n messages: BaseMessage[];\n state: Record<string, unknown>;\n systemMessage?: SystemMessage | unknown;\n tools?: (ServerTool | ClientTool)[] | unknown[];\n [key: string]: unknown;\n },\n handler: (req: any) => any,\n truncatedMessages: BaseMessage[],\n resolvedModel: BaseChatModel,\n maxInputTokens: number | undefined,\n ): Promise<any> {\n const cutoffIndex = determineCutoffIndex(truncatedMessages, maxInputTokens);\n if (cutoffIndex <= 0) {\n return handler({ ...request, messages: truncatedMessages });\n }\n\n const messagesToSummarize = truncatedMessages.slice(0, cutoffIndex);\n const preservedMessages = truncatedMessages.slice(cutoffIndex);\n\n // When ALL messages would be summarized (preserving 0), the model loses\n // all tool call context and re-invokes the same tools, creating an\n // infinite loop. Instead, try truncating ToolMessage content so the\n // entire AI/Tool group fits in context without summarization.\n if (preservedMessages.length === 0 && maxInputTokens) {\n const compact = compactToolResults(\n truncatedMessages,\n maxInputTokens,\n request.systemMessage,\n request.tools,\n );\n\n if (compact.modified) {\n try {\n return await handler({\n ...request,\n messages: compact.messages,\n });\n } catch (err: unknown) {\n if (!isContextOverflow(err)) {\n throw err;\n }\n }\n }\n }\n\n const previousEvent = request.state._summarizationEvent;\n const previousCutoffIndex =\n previousEvent != null\n ? (previousEvent as SummarizationEvent).cutoffIndex\n : undefined;\n\n const { summaryMessage, filePath, stateCutoffIndex } =\n await summarizeMessages(\n messagesToSummarize,\n resolvedModel,\n request.state,\n previousCutoffIndex,\n cutoffIndex,\n );\n\n let modifiedMessages = [summaryMessage, ...preservedMessages];\n const modifiedTokens = countTotalTokens(\n modifiedMessages,\n request.systemMessage,\n request.tools,\n );\n\n let finalStateCutoffIndex = stateCutoffIndex;\n let finalSummaryMessage = summaryMessage;\n let finalFilePath = filePath;\n\n try {\n await handler({ ...request, messages: modifiedMessages });\n } catch (err: unknown) {\n if (!isContextOverflow(err)) {\n throw err;\n }\n\n if (maxInputTokens && modifiedTokens > 0) {\n const observedRatio = maxInputTokens / modifiedTokens;\n if (observedRatio > tokenEstimationMultiplier) {\n tokenEstimationMultiplier = observedRatio * 1.1;\n }\n }\n\n const allMessages = [...messagesToSummarize, ...preservedMessages];\n const reSumResult = await summarizeMessages(\n allMessages,\n resolvedModel,\n request.state,\n previousCutoffIndex,\n truncatedMessages.length,\n );\n\n finalSummaryMessage = reSumResult.summaryMessage;\n finalFilePath = reSumResult.filePath;\n finalStateCutoffIndex = reSumResult.stateCutoffIndex;\n\n modifiedMessages = [reSumResult.summaryMessage];\n\n await handler({ ...request, messages: modifiedMessages });\n }\n\n return new Command({\n update: {\n _summarizationEvent: {\n cutoffIndex: finalStateCutoffIndex,\n summaryMessage: finalSummaryMessage,\n filePath: finalFilePath,\n } satisfies SummarizationEvent,\n _summarizationSessionId: getSessionId(request.state),\n },\n });\n }\n\n return createMiddleware({\n name: \"SummarizationMiddleware\",\n stateSchema: SummarizationStateSchema,\n\n async wrapModelCall(request, handler) {\n // Get effective messages based on previous summarization events\n const effectiveMessages = getEffectiveMessages(\n request.messages ?? [],\n request.state,\n );\n\n if (effectiveMessages.length === 0) {\n return handler(request);\n }\n\n /**\n * Resolve the chat model and get max input tokens from its profile.\n */\n const resolvedModel = await getChatModel();\n const maxInputTokens = getMaxInputTokens(resolvedModel);\n applyModelDefaults(resolvedModel);\n\n /**\n * Step 1: Truncate args if configured\n */\n const { messages: truncatedMessages } = truncateArgs(\n effectiveMessages,\n maxInputTokens,\n request.systemMessage,\n request.tools,\n );\n\n /**\n * Step 2: Check if summarization should happen.\n * Count tokens including system message and tools to match what's\n * actually sent to the model (matching Python implementation).\n */\n const totalTokens = countTotalTokens(\n truncatedMessages,\n request.systemMessage,\n request.tools,\n );\n\n const shouldDoSummarization = shouldSummarize(\n truncatedMessages,\n totalTokens,\n maxInputTokens,\n );\n\n /**\n * If no summarization needed, try passing through.\n * If the handler throws a ContextOverflowError, fall back to\n * emergency summarization (matching Python's behavior).\n */\n if (!shouldDoSummarization) {\n try {\n return await handler({\n ...request,\n messages: truncatedMessages,\n });\n } catch (err: unknown) {\n if (!isContextOverflow(err)) {\n throw err;\n }\n\n if (maxInputTokens && totalTokens > 0) {\n const observedRatio = maxInputTokens / totalTokens;\n if (observedRatio > tokenEstimationMultiplier) {\n tokenEstimationMultiplier = observedRatio * 1.1;\n }\n }\n // Fall through to summarization below\n }\n }\n\n /**\n * Step 3: Perform summarization\n */\n return performSummarization(\n request as any,\n handler,\n truncatedMessages,\n resolvedModel,\n maxInputTokens,\n );\n },\n });\n}\n","/**\n * StoreBackend: Adapter for LangGraph's BaseStore (persistent, cross-thread).\n */\n\nimport type { Item } from \"@langchain/langgraph\";\nimport type {\n BackendProtocol,\n EditResult,\n FileData,\n FileDownloadResponse,\n FileInfo,\n FileUploadResponse,\n GrepMatch,\n StateAndStore,\n WriteResult,\n} from \"./protocol.js\";\nimport {\n createFileData,\n fileDataToString,\n formatReadResponse,\n globSearchFiles,\n grepMatchesFromFiles,\n performStringReplacement,\n updateFileData,\n} from \"./utils.js\";\n\nconst NAMESPACE_COMPONENT_RE = /^[A-Za-z0-9\\-_.@+:~]+$/;\n\n/**\n * Validate a namespace array.\n *\n * Each component must be a non-empty string containing only safe characters:\n * alphanumeric (a-z, A-Z, 0-9), hyphen (-), underscore (_), dot (.),\n * at sign (@), plus (+), colon (:), and tilde (~).\n *\n * Characters like *, ?, [, ], {, } etc. are rejected to prevent\n * wildcard or glob injection in store lookups.\n */\nfunction validateNamespace(namespace: string[]): string[] {\n if (namespace.length === 0) {\n throw new Error(\"Namespace array must not be empty.\");\n }\n for (let i = 0; i < namespace.length; i++) {\n const component = namespace[i];\n if (typeof component !== \"string\") {\n throw new TypeError(\n `Namespace component at index ${i} must be a string, got ${typeof component}.`,\n );\n }\n if (!component) {\n throw new Error(`Namespace component at index ${i} must not be empty.`);\n }\n if (!NAMESPACE_COMPONENT_RE.test(component)) {\n throw new Error(\n `Namespace component at index ${i} contains disallowed characters: \"${component}\". ` +\n `Only alphanumeric characters, hyphens, underscores, dots, @, +, colons, and tildes are allowed.`,\n );\n }\n }\n return namespace;\n}\n\n/**\n * Options for StoreBackend constructor.\n */\nexport interface StoreBackendOptions {\n /**\n * Custom namespace for store operations.\n *\n * Determines where files are stored in the LangGraph store, enabling\n * user-scoped, org-scoped, or any custom isolation pattern.\n *\n * If not provided, falls back to legacy behavior using assistantId from StateAndStore.\n *\n * @example\n * ```typescript\n * // User-scoped storage\n * new StoreBackend(stateAndStore, {\n * namespace: [\"memories\", orgId, userId, \"filesystem\"],\n * });\n *\n * // Org-scoped storage\n * new StoreBackend(stateAndStore, {\n * namespace: [\"memories\", orgId, \"filesystem\"],\n * });\n * ```\n */\n namespace?: string[];\n}\n\n/**\n * Backend that stores files in LangGraph's BaseStore (persistent).\n *\n * Uses LangGraph's Store for persistent, cross-conversation storage.\n * Files are organized via namespaces and persist across all threads.\n *\n * The namespace can be customized via a factory function for flexible\n * isolation patterns (user-scoped, org-scoped, etc.), or falls back\n * to legacy assistant_id-based isolation.\n */\nexport class StoreBackend implements BackendProtocol {\n private stateAndStore: StateAndStore;\n private _namespace: string[] | undefined;\n\n constructor(stateAndStore: StateAndStore, options?: StoreBackendOptions) {\n this.stateAndStore = stateAndStore;\n if (options?.namespace) {\n this._namespace = validateNamespace(options.namespace);\n }\n }\n\n /**\n * Get the store instance.\n *\n * @returns BaseStore instance\n * @throws Error if no store is available\n */\n private getStore() {\n const store = this.stateAndStore.store;\n if (!store) {\n throw new Error(\"Store is required but not available in StateAndStore\");\n }\n return store;\n }\n\n /**\n * Get the namespace for store operations.\n *\n * If a custom namespace was provided, returns it directly.\n *\n * Otherwise, falls back to legacy behavior:\n * - If assistantId is set: [assistantId, \"filesystem\"]\n * - Otherwise: [\"filesystem\"]\n */\n protected getNamespace(): string[] {\n if (this._namespace) {\n return this._namespace;\n }\n const assistantId = this.stateAndStore.assistantId;\n if (assistantId) {\n return [assistantId, \"filesystem\"];\n }\n return [\"filesystem\"];\n }\n\n /**\n * Convert a store Item to FileData format.\n *\n * @param storeItem - The store Item containing file data\n * @returns FileData object\n * @throws Error if required fields are missing or have incorrect types\n */\n private convertStoreItemToFileData(storeItem: Item): FileData {\n const value = storeItem.value as any;\n\n if (\n !value.content ||\n !Array.isArray(value.content) ||\n typeof value.created_at !== \"string\" ||\n typeof value.modified_at !== \"string\"\n ) {\n throw new Error(\n `Store item does not contain valid FileData fields. Got keys: ${Object.keys(value).join(\", \")}`,\n );\n }\n\n return {\n content: value.content,\n created_at: value.created_at,\n modified_at: value.modified_at,\n };\n }\n\n /**\n * Convert FileData to a value suitable for store.put().\n *\n * @param fileData - The FileData to convert\n * @returns Object with content, created_at, and modified_at fields\n */\n private convertFileDataToStoreValue(fileData: FileData): Record<string, any> {\n return {\n content: fileData.content,\n created_at: fileData.created_at,\n modified_at: fileData.modified_at,\n };\n }\n\n /**\n * Search store with automatic pagination to retrieve all results.\n *\n * @param store - The store to search\n * @param namespace - Hierarchical path prefix to search within\n * @param options - Optional query, filter, and page_size\n * @returns List of all items matching the search criteria\n */\n private async searchStorePaginated(\n store: any,\n namespace: string[],\n options: {\n query?: string;\n filter?: Record<string, any>;\n pageSize?: number;\n } = {},\n ): Promise<Item[]> {\n const { query, filter, pageSize = 100 } = options;\n const allItems: Item[] = [];\n let offset = 0;\n\n while (true) {\n const pageItems = await store.search(namespace, {\n query,\n filter,\n limit: pageSize,\n offset,\n });\n\n if (!pageItems || pageItems.length === 0) {\n break;\n }\n\n allItems.push(...pageItems);\n\n if (pageItems.length < pageSize) {\n break;\n }\n\n offset += pageSize;\n }\n\n return allItems;\n }\n\n /**\n * List files and directories in the specified directory (non-recursive).\n *\n * @param path - Absolute path to directory\n * @returns List of FileInfo objects for files and directories directly in the directory.\n * Directories have a trailing / in their path and is_dir=true.\n */\n async lsInfo(path: string): Promise<FileInfo[]> {\n const store = this.getStore();\n const namespace = this.getNamespace();\n\n // Retrieve all items and filter by path prefix locally to avoid\n // coupling to store-specific filter semantics\n const items = await this.searchStorePaginated(store, namespace);\n const infos: FileInfo[] = [];\n const subdirs = new Set<string>();\n\n // Normalize path to have trailing slash for proper prefix matching\n const normalizedPath = path.endsWith(\"/\") ? path : path + \"/\";\n\n for (const item of items) {\n const itemKey = String(item.key);\n\n // Check if file is in the specified directory or a subdirectory\n if (!itemKey.startsWith(normalizedPath)) {\n continue;\n }\n\n // Get the relative path after the directory\n const relative = itemKey.substring(normalizedPath.length);\n\n // If relative path contains '/', it's in a subdirectory\n if (relative.includes(\"/\")) {\n // Extract the immediate subdirectory name\n const subdirName = relative.split(\"/\")[0];\n subdirs.add(normalizedPath + subdirName + \"/\");\n continue;\n }\n\n // This is a file directly in the current directory\n try {\n const fd = this.convertStoreItemToFileData(item);\n const size = fd.content.join(\"\\n\").length;\n infos.push({\n path: itemKey,\n is_dir: false,\n size: size,\n modified_at: fd.modified_at,\n });\n } catch {\n // Skip invalid items\n continue;\n }\n }\n\n // Add directories to the results\n for (const subdir of Array.from(subdirs).sort()) {\n infos.push({\n path: subdir,\n is_dir: true,\n size: 0,\n modified_at: \"\",\n });\n }\n\n infos.sort((a, b) => a.path.localeCompare(b.path));\n return infos;\n }\n\n /**\n * Read file content with line numbers.\n *\n * @param filePath - Absolute file path\n * @param offset - Line offset to start reading from (0-indexed)\n * @param limit - Maximum number of lines to read\n * @returns Formatted file content with line numbers, or error message\n */\n async read(\n filePath: string,\n offset: number = 0,\n limit: number = 500,\n ): Promise<string> {\n try {\n const fileData = await this.readRaw(filePath);\n return formatReadResponse(fileData, offset, limit);\n } catch (e: any) {\n return `Error: ${e.message}`;\n }\n }\n\n /**\n * Read file content as raw FileData.\n *\n * @param filePath - Absolute file path\n * @returns Raw file content as FileData\n */\n async readRaw(filePath: string): Promise<FileData> {\n const store = this.getStore();\n const namespace = this.getNamespace();\n const item = await store.get(namespace, filePath);\n\n if (!item) throw new Error(`File '${filePath}' not found`);\n return this.convertStoreItemToFileData(item);\n }\n\n /**\n * Create a new file with content.\n * Returns WriteResult. External storage sets filesUpdate=null.\n */\n async write(filePath: string, content: string): Promise<WriteResult> {\n const store = this.getStore();\n const namespace = this.getNamespace();\n\n // Check if file exists\n const existing = await store.get(namespace, filePath);\n if (existing) {\n return {\n error: `Cannot write to ${filePath} because it already exists. Read and then make an edit, or write to a new path.`,\n };\n }\n\n // Create new file\n const fileData = createFileData(content);\n const storeValue = this.convertFileDataToStoreValue(fileData);\n await store.put(namespace, filePath, storeValue);\n return { path: filePath, filesUpdate: null };\n }\n\n /**\n * Edit a file by replacing string occurrences.\n * Returns EditResult. External storage sets filesUpdate=null.\n */\n async edit(\n filePath: string,\n oldString: string,\n newString: string,\n replaceAll: boolean = false,\n ): Promise<EditResult> {\n const store = this.getStore();\n const namespace = this.getNamespace();\n\n // Get existing file\n const item = await store.get(namespace, filePath);\n if (!item) {\n return { error: `Error: File '${filePath}' not found` };\n }\n\n try {\n const fileData = this.convertStoreItemToFileData(item);\n const content = fileDataToString(fileData);\n const result = performStringReplacement(\n content,\n oldString,\n newString,\n replaceAll,\n );\n\n if (typeof result === \"string\") {\n return { error: result };\n }\n\n const [newContent, occurrences] = result;\n const newFileData = updateFileData(fileData, newContent);\n\n // Update file in store\n const storeValue = this.convertFileDataToStoreValue(newFileData);\n await store.put(namespace, filePath, storeValue);\n return { path: filePath, filesUpdate: null, occurrences: occurrences };\n } catch (e: any) {\n return { error: `Error: ${e.message}` };\n }\n }\n\n /**\n * Structured search results or error string for invalid input.\n */\n async grepRaw(\n pattern: string,\n path: string = \"/\",\n glob: string | null = null,\n ): Promise<GrepMatch[] | string> {\n const store = this.getStore();\n const namespace = this.getNamespace();\n const items = await this.searchStorePaginated(store, namespace);\n\n const files: Record<string, FileData> = {};\n for (const item of items) {\n try {\n files[item.key] = this.convertStoreItemToFileData(item);\n } catch {\n // Skip invalid items\n continue;\n }\n }\n\n return grepMatchesFromFiles(files, pattern, path, glob);\n }\n\n /**\n * Structured glob matching returning FileInfo objects.\n */\n async globInfo(pattern: string, path: string = \"/\"): Promise<FileInfo[]> {\n const store = this.getStore();\n const namespace = this.getNamespace();\n const items = await this.searchStorePaginated(store, namespace);\n\n const files: Record<string, FileData> = {};\n for (const item of items) {\n try {\n files[item.key] = this.convertStoreItemToFileData(item);\n } catch {\n // Skip invalid items\n continue;\n }\n }\n\n const result = globSearchFiles(files, pattern, path);\n if (result === \"No files found\") {\n return [];\n }\n\n const paths = result.split(\"\\n\");\n const infos: FileInfo[] = [];\n for (const p of paths) {\n const fd = files[p];\n const size = fd ? fd.content.join(\"\\n\").length : 0;\n infos.push({\n path: p,\n is_dir: false,\n size: size,\n modified_at: fd?.modified_at || \"\",\n });\n }\n return infos;\n }\n\n /**\n * Upload multiple files.\n *\n * @param files - List of [path, content] tuples to upload\n * @returns List of FileUploadResponse objects, one per input file\n */\n async uploadFiles(\n files: Array<[string, Uint8Array]>,\n ): Promise<FileUploadResponse[]> {\n const store = this.getStore();\n const namespace = this.getNamespace();\n const responses: FileUploadResponse[] = [];\n\n for (const [path, content] of files) {\n try {\n const contentStr = new TextDecoder().decode(content);\n const fileData = createFileData(contentStr);\n const storeValue = this.convertFileDataToStoreValue(fileData);\n await store.put(namespace, path, storeValue);\n responses.push({ path, error: null });\n } catch {\n responses.push({ path, error: \"invalid_path\" });\n }\n }\n\n return responses;\n }\n\n /**\n * Download multiple files.\n *\n * @param paths - List of file paths to download\n * @returns List of FileDownloadResponse objects, one per input path\n */\n async downloadFiles(paths: string[]): Promise<FileDownloadResponse[]> {\n const store = this.getStore();\n const namespace = this.getNamespace();\n const responses: FileDownloadResponse[] = [];\n\n for (const path of paths) {\n try {\n const item = await store.get(namespace, path);\n if (!item) {\n responses.push({ path, content: null, error: \"file_not_found\" });\n continue;\n }\n\n const fileData = this.convertStoreItemToFileData(item);\n const contentStr = fileDataToString(fileData);\n const content = new TextEncoder().encode(contentStr);\n responses.push({ path, content, error: null });\n } catch {\n responses.push({ path, content: null, error: \"file_not_found\" });\n }\n }\n\n return responses;\n }\n}\n","/**\n * FilesystemBackend: Read and write files directly from the filesystem.\n *\n * Security and search upgrades:\n * - Secure path resolution with root containment when in virtual_mode (sandboxed to cwd)\n * - Prevent symlink-following on file I/O using O_NOFOLLOW when available\n * - Ripgrep-powered grep with literal (fixed-string) search, plus substring fallback\n * and optional glob include filtering, while preserving virtual path behavior\n */\n\nimport fs from \"node:fs/promises\";\nimport fsSync from \"node:fs\";\nimport path from \"node:path\";\nimport { spawn } from \"node:child_process\";\n\nimport fg from \"fast-glob\";\nimport micromatch from \"micromatch\";\nimport type {\n BackendProtocol,\n EditResult,\n FileData,\n FileDownloadResponse,\n FileInfo,\n FileUploadResponse,\n GrepMatch,\n WriteResult,\n} from \"./protocol.js\";\nimport {\n checkEmptyContent,\n formatContentWithLineNumbers,\n performStringReplacement,\n} from \"./utils.js\";\n\nconst SUPPORTS_NOFOLLOW = fsSync.constants.O_NOFOLLOW !== undefined;\n\n/**\n * Backend that reads and writes files directly from the filesystem.\n *\n * Files are accessed using their actual filesystem paths. Relative paths are\n * resolved relative to the current working directory. Content is read/written\n * as plain text, and metadata (timestamps) are derived from filesystem stats.\n */\nexport class FilesystemBackend implements BackendProtocol {\n protected cwd: string;\n protected virtualMode: boolean;\n private maxFileSizeBytes: number;\n\n constructor(\n options: {\n rootDir?: string;\n virtualMode?: boolean;\n maxFileSizeMb?: number;\n } = {},\n ) {\n const { rootDir, virtualMode = false, maxFileSizeMb = 10 } = options;\n this.cwd = rootDir ? path.resolve(rootDir) : process.cwd();\n this.virtualMode = virtualMode;\n this.maxFileSizeBytes = maxFileSizeMb * 1024 * 1024;\n }\n\n /**\n * Resolve a file path with security checks.\n *\n * When virtualMode=true, treat incoming paths as virtual absolute paths under\n * this.cwd, disallow traversal (.., ~) and ensure resolved path stays within root.\n * When virtualMode=false, preserve legacy behavior: absolute paths are allowed\n * as-is; relative paths resolve under cwd.\n *\n * @param key - File path (absolute, relative, or virtual when virtualMode=true)\n * @returns Resolved absolute path string\n * @throws Error if path traversal detected or path outside root\n */\n private resolvePath(key: string): string {\n if (this.virtualMode) {\n const vpath = key.startsWith(\"/\") ? key : \"/\" + key;\n if (vpath.includes(\"..\") || vpath.startsWith(\"~\")) {\n throw new Error(\"Path traversal not allowed\");\n }\n const full = path.resolve(this.cwd, vpath.substring(1));\n const relative = path.relative(this.cwd, full);\n if (relative.startsWith(\"..\") || path.isAbsolute(relative)) {\n throw new Error(`Path: ${full} outside root directory: ${this.cwd}`);\n }\n return full;\n }\n\n if (path.isAbsolute(key)) {\n return key;\n }\n return path.resolve(this.cwd, key);\n }\n\n /**\n * List files and directories in the specified directory (non-recursive).\n *\n * @param dirPath - Absolute directory path to list files from\n * @returns List of FileInfo objects for files and directories directly in the directory.\n * Directories have a trailing / in their path and is_dir=true.\n */\n async lsInfo(dirPath: string): Promise<FileInfo[]> {\n try {\n const resolvedPath = this.resolvePath(dirPath);\n const stat = await fs.stat(resolvedPath);\n\n if (!stat.isDirectory()) {\n return [];\n }\n\n const entries = await fs.readdir(resolvedPath, { withFileTypes: true });\n const results: FileInfo[] = [];\n\n const cwdStr = this.cwd.endsWith(path.sep)\n ? this.cwd\n : this.cwd + path.sep;\n\n for (const entry of entries) {\n const fullPath = path.join(resolvedPath, entry.name);\n\n try {\n const entryStat = await fs.stat(fullPath);\n const isFile = entryStat.isFile();\n const isDir = entryStat.isDirectory();\n\n if (!this.virtualMode) {\n // Non-virtual mode: use absolute paths\n if (isFile) {\n results.push({\n path: fullPath,\n is_dir: false,\n size: entryStat.size,\n modified_at: entryStat.mtime.toISOString(),\n });\n } else if (isDir) {\n results.push({\n path: fullPath + path.sep,\n is_dir: true,\n size: 0,\n modified_at: entryStat.mtime.toISOString(),\n });\n }\n } else {\n let relativePath: string;\n if (fullPath.startsWith(cwdStr)) {\n relativePath = fullPath.substring(cwdStr.length);\n } else if (fullPath.startsWith(this.cwd)) {\n relativePath = fullPath\n .substring(this.cwd.length)\n .replace(/^[/\\\\]/, \"\");\n } else {\n relativePath = fullPath;\n }\n\n relativePath = relativePath.split(path.sep).join(\"/\");\n const virtPath = \"/\" + relativePath;\n\n if (isFile) {\n results.push({\n path: virtPath,\n is_dir: false,\n size: entryStat.size,\n modified_at: entryStat.mtime.toISOString(),\n });\n } else if (isDir) {\n results.push({\n path: virtPath + \"/\",\n is_dir: true,\n size: 0,\n modified_at: entryStat.mtime.toISOString(),\n });\n }\n }\n } catch {\n // Skip entries we can't stat\n continue;\n }\n }\n\n results.sort((a, b) => a.path.localeCompare(b.path));\n return results;\n } catch {\n return [];\n }\n }\n\n /**\n * Read file content with line numbers.\n *\n * @param filePath - Absolute or relative file path\n * @param offset - Line offset to start reading from (0-indexed)\n * @param limit - Maximum number of lines to read\n * @returns Formatted file content with line numbers, or error message\n */\n async read(\n filePath: string,\n offset: number = 0,\n limit: number = 500,\n ): Promise<string> {\n try {\n const resolvedPath = this.resolvePath(filePath);\n\n let content: string;\n\n if (SUPPORTS_NOFOLLOW) {\n const stat = await fs.stat(resolvedPath);\n if (!stat.isFile()) {\n return `Error: File '${filePath}' not found`;\n }\n const fd = await fs.open(\n resolvedPath,\n fsSync.constants.O_RDONLY | fsSync.constants.O_NOFOLLOW,\n );\n try {\n content = await fd.readFile({ encoding: \"utf-8\" });\n } finally {\n await fd.close();\n }\n } else {\n const stat = await fs.lstat(resolvedPath);\n if (stat.isSymbolicLink()) {\n return `Error: Symlinks are not allowed: ${filePath}`;\n }\n if (!stat.isFile()) {\n return `Error: File '${filePath}' not found`;\n }\n content = await fs.readFile(resolvedPath, \"utf-8\");\n }\n\n const emptyMsg = checkEmptyContent(content);\n if (emptyMsg) {\n return emptyMsg;\n }\n\n const lines = content.split(\"\\n\");\n const startIdx = offset;\n const endIdx = Math.min(startIdx + limit, lines.length);\n\n if (startIdx >= lines.length) {\n return `Error: Line offset ${offset} exceeds file length (${lines.length} lines)`;\n }\n\n const selectedLines = lines.slice(startIdx, endIdx);\n return formatContentWithLineNumbers(selectedLines, startIdx + 1);\n } catch (e: any) {\n return `Error reading file '${filePath}': ${e.message}`;\n }\n }\n\n /**\n * Read file content as raw FileData.\n *\n * @param filePath - Absolute file path\n * @returns Raw file content as FileData\n */\n async readRaw(filePath: string): Promise<FileData> {\n const resolvedPath = this.resolvePath(filePath);\n\n let content: string;\n let stat: fsSync.Stats;\n\n if (SUPPORTS_NOFOLLOW) {\n stat = await fs.stat(resolvedPath);\n if (!stat.isFile()) throw new Error(`File '${filePath}' not found`);\n const fd = await fs.open(\n resolvedPath,\n fsSync.constants.O_RDONLY | fsSync.constants.O_NOFOLLOW,\n );\n try {\n content = await fd.readFile({ encoding: \"utf-8\" });\n } finally {\n await fd.close();\n }\n } else {\n stat = await fs.lstat(resolvedPath);\n if (stat.isSymbolicLink()) {\n throw new Error(`Symlinks are not allowed: ${filePath}`);\n }\n if (!stat.isFile()) throw new Error(`File '${filePath}' not found`);\n content = await fs.readFile(resolvedPath, \"utf-8\");\n }\n\n return {\n content: content.split(\"\\n\"),\n created_at: stat.ctime.toISOString(),\n modified_at: stat.mtime.toISOString(),\n };\n }\n\n /**\n * Create a new file with content.\n * Returns WriteResult. External storage sets filesUpdate=null.\n */\n async write(filePath: string, content: string): Promise<WriteResult> {\n try {\n const resolvedPath = this.resolvePath(filePath);\n\n try {\n const stat = await fs.lstat(resolvedPath);\n if (stat.isSymbolicLink()) {\n return {\n error: `Cannot write to ${filePath} because it is a symlink. Symlinks are not allowed.`,\n };\n }\n return {\n error: `Cannot write to ${filePath} because it already exists. Read and then make an edit, or write to a new path.`,\n };\n } catch {\n // File doesn't exist, good to proceed\n }\n\n await fs.mkdir(path.dirname(resolvedPath), { recursive: true });\n\n if (SUPPORTS_NOFOLLOW) {\n const flags =\n fsSync.constants.O_WRONLY |\n fsSync.constants.O_CREAT |\n fsSync.constants.O_TRUNC |\n fsSync.constants.O_NOFOLLOW;\n\n const fd = await fs.open(resolvedPath, flags, 0o644);\n try {\n await fd.writeFile(content, \"utf-8\");\n } finally {\n await fd.close();\n }\n } else {\n await fs.writeFile(resolvedPath, content, \"utf-8\");\n }\n\n return { path: filePath, filesUpdate: null };\n } catch (e: any) {\n return { error: `Error writing file '${filePath}': ${e.message}` };\n }\n }\n\n /**\n * Edit a file by replacing string occurrences.\n * Returns EditResult. External storage sets filesUpdate=null.\n */\n async edit(\n filePath: string,\n oldString: string,\n newString: string,\n replaceAll: boolean = false,\n ): Promise<EditResult> {\n try {\n const resolvedPath = this.resolvePath(filePath);\n\n let content: string;\n\n if (SUPPORTS_NOFOLLOW) {\n const stat = await fs.stat(resolvedPath);\n if (!stat.isFile()) {\n return { error: `Error: File '${filePath}' not found` };\n }\n\n const fd = await fs.open(\n resolvedPath,\n fsSync.constants.O_RDONLY | fsSync.constants.O_NOFOLLOW,\n );\n try {\n content = await fd.readFile({ encoding: \"utf-8\" });\n } finally {\n await fd.close();\n }\n } else {\n const stat = await fs.lstat(resolvedPath);\n if (stat.isSymbolicLink()) {\n return { error: `Error: Symlinks are not allowed: ${filePath}` };\n }\n if (!stat.isFile()) {\n return { error: `Error: File '${filePath}' not found` };\n }\n content = await fs.readFile(resolvedPath, \"utf-8\");\n }\n\n const result = performStringReplacement(\n content,\n oldString,\n newString,\n replaceAll,\n );\n\n if (typeof result === \"string\") {\n return { error: result };\n }\n\n const [newContent, occurrences] = result;\n\n // Write securely\n if (SUPPORTS_NOFOLLOW) {\n const flags =\n fsSync.constants.O_WRONLY |\n fsSync.constants.O_TRUNC |\n fsSync.constants.O_NOFOLLOW;\n\n const fd = await fs.open(resolvedPath, flags);\n try {\n await fd.writeFile(newContent, \"utf-8\");\n } finally {\n await fd.close();\n }\n } else {\n await fs.writeFile(resolvedPath, newContent, \"utf-8\");\n }\n\n return { path: filePath, filesUpdate: null, occurrences: occurrences };\n } catch (e: any) {\n return { error: `Error editing file '${filePath}': ${e.message}` };\n }\n }\n\n /**\n * Search for a literal text pattern in files.\n *\n * Uses ripgrep if available, falling back to substring search.\n *\n * @param pattern - Literal string to search for (NOT regex).\n * @param dirPath - Directory or file path to search in. Defaults to current directory.\n * @param glob - Optional glob pattern to filter which files to search.\n * @returns List of GrepMatch dicts containing path, line number, and matched text.\n */\n async grepRaw(\n pattern: string,\n dirPath: string = \"/\",\n glob: string | null = null,\n ): Promise<GrepMatch[] | string> {\n // Resolve base path\n let baseFull: string;\n try {\n baseFull = this.resolvePath(dirPath || \".\");\n } catch {\n return [];\n }\n\n try {\n await fs.stat(baseFull);\n } catch {\n return [];\n }\n\n // Try ripgrep first (with -F flag for literal search), fallback to substring search\n let results = await this.ripgrepSearch(pattern, baseFull, glob);\n if (results === null) {\n results = await this.literalSearch(pattern, baseFull, glob);\n }\n\n const matches: GrepMatch[] = [];\n for (const [fpath, items] of Object.entries(results)) {\n for (const [lineNum, lineText] of items) {\n matches.push({ path: fpath, line: lineNum, text: lineText });\n }\n }\n return matches;\n }\n\n /**\n * Search using ripgrep with fixed-string (literal) mode.\n *\n * @param pattern - Literal string to search for (unescaped).\n * @param baseFull - Resolved base path to search in.\n * @param includeGlob - Optional glob pattern to filter files.\n * @returns Dict mapping file paths to list of (line_number, line_text) tuples.\n * Returns null if ripgrep is unavailable or times out.\n */\n private async ripgrepSearch(\n pattern: string,\n baseFull: string,\n includeGlob: string | null,\n ): Promise<Record<string, Array<[number, string]>> | null> {\n return new Promise((resolve) => {\n // -F enables fixed-string (literal) mode\n const args = [\"--json\", \"-F\"];\n if (includeGlob) {\n args.push(\"--glob\", includeGlob);\n }\n args.push(\"--\", pattern, baseFull);\n\n const proc = spawn(\"rg\", args, { timeout: 30000 });\n const results: Record<string, Array<[number, string]>> = {};\n let output = \"\";\n\n proc.stdout.on(\"data\", (data) => {\n output += data.toString();\n });\n\n proc.on(\"close\", (code) => {\n if (code !== 0 && code !== 1) {\n // Error (code 1 means no matches, which is ok)\n resolve(null);\n return;\n }\n\n for (const line of output.split(\"\\n\")) {\n if (!line.trim()) continue;\n try {\n const data = JSON.parse(line);\n if (data.type !== \"match\") continue;\n\n const pdata = data.data || {};\n const ftext = pdata.path?.text;\n if (!ftext) continue;\n\n let virtPath: string;\n if (this.virtualMode) {\n try {\n const resolved = path.resolve(ftext);\n const relative = path.relative(this.cwd, resolved);\n if (relative.startsWith(\"..\")) continue;\n const normalizedRelative = relative.split(path.sep).join(\"/\");\n virtPath = \"/\" + normalizedRelative;\n } catch {\n continue;\n }\n } else {\n virtPath = ftext;\n }\n\n const ln = pdata.line_number;\n const lt = pdata.lines?.text?.replace(/\\n$/, \"\") || \"\";\n if (ln === undefined) continue;\n\n if (!results[virtPath]) {\n results[virtPath] = [];\n }\n results[virtPath].push([ln, lt]);\n } catch {\n // Skip invalid JSON\n continue;\n }\n }\n\n resolve(results);\n });\n\n proc.on(\"error\", () => {\n resolve(null);\n });\n });\n }\n\n /**\n * Fallback search using literal substring matching when ripgrep is unavailable.\n *\n * Recursively searches files, respecting maxFileSizeBytes limit.\n *\n * @param pattern - Literal string to search for.\n * @param baseFull - Resolved base path to search in.\n * @param includeGlob - Optional glob pattern to filter files by name.\n * @returns Dict mapping file paths to list of (line_number, line_text) tuples.\n */\n private async literalSearch(\n pattern: string,\n baseFull: string,\n includeGlob: string | null,\n ): Promise<Record<string, Array<[number, string]>>> {\n const results: Record<string, Array<[number, string]>> = {};\n const stat = await fs.stat(baseFull);\n const root = stat.isDirectory() ? baseFull : path.dirname(baseFull);\n\n // Use fast-glob to recursively find all files\n const files = await fg(\"**/*\", {\n cwd: root,\n absolute: true,\n onlyFiles: true,\n dot: true,\n });\n\n for (const fp of files) {\n try {\n // Filter by glob if provided\n if (\n includeGlob &&\n !micromatch.isMatch(path.basename(fp), includeGlob)\n ) {\n continue;\n }\n\n // Check file size\n const stat = await fs.stat(fp);\n if (stat.size > this.maxFileSizeBytes) {\n continue;\n }\n\n // Read and search using literal substring matching\n const content = await fs.readFile(fp, \"utf-8\");\n const lines = content.split(\"\\n\");\n\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i];\n // Simple substring search for literal matching\n if (line.includes(pattern)) {\n let virtPath: string;\n if (this.virtualMode) {\n try {\n const relative = path.relative(this.cwd, fp);\n if (relative.startsWith(\"..\")) continue;\n const normalizedRelative = relative.split(path.sep).join(\"/\");\n virtPath = \"/\" + normalizedRelative;\n } catch {\n continue;\n }\n } else {\n virtPath = fp;\n }\n\n if (!results[virtPath]) {\n results[virtPath] = [];\n }\n results[virtPath].push([i + 1, line]);\n }\n }\n } catch {\n // Skip files we can't read\n continue;\n }\n }\n\n return results;\n }\n\n /**\n * Structured glob matching returning FileInfo objects.\n */\n async globInfo(\n pattern: string,\n searchPath: string = \"/\",\n ): Promise<FileInfo[]> {\n if (pattern.startsWith(\"/\")) {\n pattern = pattern.substring(1);\n }\n\n const resolvedSearchPath =\n searchPath === \"/\" ? this.cwd : this.resolvePath(searchPath);\n\n try {\n const stat = await fs.stat(resolvedSearchPath);\n if (!stat.isDirectory()) {\n return [];\n }\n } catch {\n return [];\n }\n\n const results: FileInfo[] = [];\n\n try {\n // Use fast-glob for pattern matching\n const matches = await fg(pattern, {\n cwd: resolvedSearchPath,\n absolute: true,\n onlyFiles: true,\n dot: true,\n });\n\n for (const matchedPath of matches) {\n try {\n const stat = await fs.stat(matchedPath);\n if (!stat.isFile()) continue;\n\n // Normalize fast-glob paths to platform separators\n // fast-glob returns forward slashes on all platforms, but we need\n // platform-native separators for path comparisons on Windows\n const normalizedPath = matchedPath.split(\"/\").join(path.sep);\n\n if (!this.virtualMode) {\n results.push({\n path: normalizedPath,\n is_dir: false,\n size: stat.size,\n modified_at: stat.mtime.toISOString(),\n });\n } else {\n const cwdStr = this.cwd.endsWith(path.sep)\n ? this.cwd\n : this.cwd + path.sep;\n let relativePath: string;\n\n if (normalizedPath.startsWith(cwdStr)) {\n relativePath = normalizedPath.substring(cwdStr.length);\n } else if (normalizedPath.startsWith(this.cwd)) {\n relativePath = normalizedPath\n .substring(this.cwd.length)\n .replace(/^[/\\\\]/, \"\");\n } else {\n relativePath = normalizedPath;\n }\n\n relativePath = relativePath.split(path.sep).join(\"/\");\n const virt = \"/\" + relativePath;\n results.push({\n path: virt,\n is_dir: false,\n size: stat.size,\n modified_at: stat.mtime.toISOString(),\n });\n }\n } catch {\n // Skip files we can't stat\n continue;\n }\n }\n } catch {\n // Ignore glob errors\n }\n\n results.sort((a, b) => a.path.localeCompare(b.path));\n return results;\n }\n\n /**\n * Upload multiple files to the filesystem.\n *\n * @param files - List of [path, content] tuples to upload\n * @returns List of FileUploadResponse objects, one per input file\n */\n async uploadFiles(\n files: Array<[string, Uint8Array]>,\n ): Promise<FileUploadResponse[]> {\n const responses: FileUploadResponse[] = [];\n\n for (const [filePath, content] of files) {\n try {\n const resolvedPath = this.resolvePath(filePath);\n\n // Ensure parent directory exists\n await fs.mkdir(path.dirname(resolvedPath), { recursive: true });\n\n // Write file\n await fs.writeFile(resolvedPath, content);\n responses.push({ path: filePath, error: null });\n } catch (e: any) {\n if (e.code === \"ENOENT\") {\n responses.push({ path: filePath, error: \"file_not_found\" });\n } else if (e.code === \"EACCES\") {\n responses.push({ path: filePath, error: \"permission_denied\" });\n } else if (e.code === \"EISDIR\") {\n responses.push({ path: filePath, error: \"is_directory\" });\n } else {\n responses.push({ path: filePath, error: \"invalid_path\" });\n }\n }\n }\n\n return responses;\n }\n\n /**\n * Download multiple files from the filesystem.\n *\n * @param paths - List of file paths to download\n * @returns List of FileDownloadResponse objects, one per input path\n */\n async downloadFiles(paths: string[]): Promise<FileDownloadResponse[]> {\n const responses: FileDownloadResponse[] = [];\n\n for (const filePath of paths) {\n try {\n const resolvedPath = this.resolvePath(filePath);\n const content = await fs.readFile(resolvedPath);\n responses.push({ path: filePath, content, error: null });\n } catch (e: any) {\n if (e.code === \"ENOENT\") {\n responses.push({\n path: filePath,\n content: null,\n error: \"file_not_found\",\n });\n } else if (e.code === \"EACCES\") {\n responses.push({\n path: filePath,\n content: null,\n error: \"permission_denied\",\n });\n } else if (e.code === \"EISDIR\") {\n responses.push({\n path: filePath,\n content: null,\n error: \"is_directory\",\n });\n } else {\n responses.push({\n path: filePath,\n content: null,\n error: \"invalid_path\",\n });\n }\n }\n }\n\n return responses;\n }\n}\n","/**\n * CompositeBackend: Route operations to different backends based on path prefix.\n */\n\nimport type {\n BackendProtocol,\n EditResult,\n ExecuteResponse,\n FileData,\n FileDownloadResponse,\n FileInfo,\n FileUploadResponse,\n GrepMatch,\n WriteResult,\n} from \"./protocol.js\";\nimport { isSandboxBackend } from \"./protocol.js\";\n\n/**\n * Backend that routes file operations to different backends based on path prefix.\n *\n * This enables hybrid storage strategies like:\n * - `/memories/` → StoreBackend (persistent, cross-thread)\n * - Everything else → StateBackend (ephemeral, per-thread)\n *\n * The CompositeBackend handles path prefix stripping/re-adding transparently.\n */\nexport class CompositeBackend implements BackendProtocol {\n private default: BackendProtocol;\n private routes: Record<string, BackendProtocol>;\n private sortedRoutes: Array<[string, BackendProtocol]>;\n\n constructor(\n defaultBackend: BackendProtocol,\n routes: Record<string, BackendProtocol>,\n ) {\n this.default = defaultBackend;\n this.routes = routes;\n\n // Sort routes by length (longest first) for correct prefix matching\n this.sortedRoutes = Object.entries(routes).sort(\n (a, b) => b[0].length - a[0].length,\n );\n }\n\n /** Delegates to default backend's id if it is a sandbox, otherwise empty string. */\n get id(): string {\n return isSandboxBackend(this.default) ? this.default.id : \"\";\n }\n\n /**\n * Determine which backend handles this key and strip prefix.\n *\n * @param key - Original file path\n * @returns Tuple of [backend, stripped_key] where stripped_key has the route\n * prefix removed (but keeps leading slash).\n */\n private getBackendAndKey(key: string): [BackendProtocol, string] {\n // Check routes in order of length (longest first)\n for (const [prefix, backend] of this.sortedRoutes) {\n if (key.startsWith(prefix)) {\n // Strip full prefix and ensure a leading slash remains\n // e.g., \"/memories/notes.txt\" → \"/notes.txt\"; \"/memories/\" → \"/\"\n const suffix = key.substring(prefix.length);\n const strippedKey = suffix ? \"/\" + suffix : \"/\";\n return [backend, strippedKey];\n }\n }\n\n return [this.default, key];\n }\n\n /**\n * List files and directories in the specified directory (non-recursive).\n *\n * @param path - Absolute path to directory\n * @returns List of FileInfo objects with route prefixes added, for files and directories\n * directly in the directory. Directories have a trailing / in their path and is_dir=true.\n */\n async lsInfo(path: string): Promise<FileInfo[]> {\n // Check if path matches a specific route\n for (const [routePrefix, backend] of this.sortedRoutes) {\n if (path.startsWith(routePrefix.replace(/\\/$/, \"\"))) {\n // Query only the matching routed backend\n const suffix = path.substring(routePrefix.length);\n const searchPath = suffix ? \"/\" + suffix : \"/\";\n const infos = await backend.lsInfo(searchPath);\n\n // Add route prefix back to paths\n const prefixed: FileInfo[] = [];\n for (const fi of infos) {\n prefixed.push({\n ...fi,\n path: routePrefix.slice(0, -1) + fi.path,\n });\n }\n return prefixed;\n }\n }\n\n // At root, aggregate default and all routed backends\n if (path === \"/\") {\n const results: FileInfo[] = [];\n const defaultInfos = await this.default.lsInfo(path);\n results.push(...defaultInfos);\n\n // Add the route itself as a directory (e.g., /memories/)\n for (const [routePrefix] of this.sortedRoutes) {\n results.push({\n path: routePrefix,\n is_dir: true,\n size: 0,\n modified_at: \"\",\n });\n }\n\n results.sort((a, b) => a.path.localeCompare(b.path));\n return results;\n }\n\n // Path doesn't match a route: query only default backend\n return await this.default.lsInfo(path);\n }\n\n /**\n * Read file content, routing to appropriate backend.\n *\n * @param filePath - Absolute file path\n * @param offset - Line offset to start reading from (0-indexed)\n * @param limit - Maximum number of lines to read\n * @returns Formatted file content with line numbers, or error message\n */\n async read(\n filePath: string,\n offset: number = 0,\n limit: number = 500,\n ): Promise<string> {\n const [backend, strippedKey] = this.getBackendAndKey(filePath);\n return await backend.read(strippedKey, offset, limit);\n }\n\n /**\n * Read file content as raw FileData.\n *\n * @param filePath - Absolute file path\n * @returns Raw file content as FileData\n */\n async readRaw(filePath: string): Promise<FileData> {\n const [backend, strippedKey] = this.getBackendAndKey(filePath);\n return await backend.readRaw(strippedKey);\n }\n\n /**\n * Structured search results or error string for invalid input.\n */\n async grepRaw(\n pattern: string,\n path: string = \"/\",\n glob: string | null = null,\n ): Promise<GrepMatch[] | string> {\n // If path targets a specific route, search only that backend\n for (const [routePrefix, backend] of this.sortedRoutes) {\n if (path.startsWith(routePrefix.replace(/\\/$/, \"\"))) {\n const searchPath = path.substring(routePrefix.length - 1);\n const raw = await backend.grepRaw(pattern, searchPath || \"/\", glob);\n\n if (typeof raw === \"string\") {\n return raw;\n }\n\n // Add route prefix back\n return raw.map((m) => ({\n ...m,\n path: routePrefix.slice(0, -1) + m.path,\n }));\n }\n }\n\n // Otherwise, search default and all routed backends and merge\n const allMatches: GrepMatch[] = [];\n const rawDefault = await this.default.grepRaw(pattern, path, glob);\n\n if (typeof rawDefault === \"string\") {\n return rawDefault;\n }\n\n allMatches.push(...rawDefault);\n\n // Search all routes\n for (const [routePrefix, backend] of Object.entries(this.routes)) {\n const raw = await backend.grepRaw(pattern, \"/\", glob);\n\n if (typeof raw === \"string\") {\n return raw;\n }\n\n // Add route prefix back\n allMatches.push(\n ...raw.map((m) => ({\n ...m,\n path: routePrefix.slice(0, -1) + m.path,\n })),\n );\n }\n\n return allMatches;\n }\n\n /**\n * Structured glob matching returning FileInfo objects.\n */\n async globInfo(pattern: string, path: string = \"/\"): Promise<FileInfo[]> {\n const results: FileInfo[] = [];\n\n // Route based on path, not pattern\n for (const [routePrefix, backend] of this.sortedRoutes) {\n if (path.startsWith(routePrefix.replace(/\\/$/, \"\"))) {\n const searchPath = path.substring(routePrefix.length - 1);\n const infos = await backend.globInfo(pattern, searchPath || \"/\");\n\n // Add route prefix back\n return infos.map((fi) => ({\n ...fi,\n path: routePrefix.slice(0, -1) + fi.path,\n }));\n }\n }\n\n // Path doesn't match any specific route - search default backend AND all routed backends\n const defaultInfos = await this.default.globInfo(pattern, path);\n results.push(...defaultInfos);\n\n for (const [routePrefix, backend] of Object.entries(this.routes)) {\n const infos = await backend.globInfo(pattern, \"/\");\n results.push(\n ...infos.map((fi) => ({\n ...fi,\n path: routePrefix.slice(0, -1) + fi.path,\n })),\n );\n }\n\n // Deterministic ordering\n results.sort((a, b) => a.path.localeCompare(b.path));\n return results;\n }\n\n /**\n * Create a new file, routing to appropriate backend.\n *\n * @param filePath - Absolute file path\n * @param content - File content as string\n * @returns WriteResult with path or error\n */\n async write(filePath: string, content: string): Promise<WriteResult> {\n const [backend, strippedKey] = this.getBackendAndKey(filePath);\n return await backend.write(strippedKey, content);\n }\n\n /**\n * Edit a file, routing to appropriate backend.\n *\n * @param filePath - Absolute file path\n * @param oldString - String to find and replace\n * @param newString - Replacement string\n * @param replaceAll - If true, replace all occurrences\n * @returns EditResult with path, occurrences, or error\n */\n async edit(\n filePath: string,\n oldString: string,\n newString: string,\n replaceAll: boolean = false,\n ): Promise<EditResult> {\n const [backend, strippedKey] = this.getBackendAndKey(filePath);\n return await backend.edit(strippedKey, oldString, newString, replaceAll);\n }\n\n /**\n * Execute a command via the default backend.\n * Execution is not path-specific, so it always delegates to the default backend.\n *\n * @param command - Full shell command string to execute\n * @returns ExecuteResponse with combined output, exit code, and truncation flag\n * @throws Error if the default backend doesn't support command execution\n */\n execute(command: string): Promise<ExecuteResponse> {\n if (!isSandboxBackend(this.default)) {\n throw new Error(\n \"Default backend doesn't support command execution (SandboxBackendProtocol). \" +\n \"To enable execution, provide a default backend that implements SandboxBackendProtocol.\",\n );\n }\n return Promise.resolve(this.default.execute(command));\n }\n\n /**\n * Upload multiple files, batching by backend for efficiency.\n *\n * @param files - List of [path, content] tuples to upload\n * @returns List of FileUploadResponse objects, one per input file\n */\n async uploadFiles(\n files: Array<[string, Uint8Array]>,\n ): Promise<FileUploadResponse[]> {\n const results: Array<FileUploadResponse | null> = Array.from(\n { length: files.length },\n () => null,\n );\n const batchesByBackend = new Map<\n BackendProtocol,\n Array<{ idx: number; path: string; content: Uint8Array }>\n >();\n\n for (let idx = 0; idx < files.length; idx++) {\n const [path, content] = files[idx];\n const [backend, strippedPath] = this.getBackendAndKey(path);\n\n if (!batchesByBackend.has(backend)) {\n batchesByBackend.set(backend, []);\n }\n batchesByBackend.get(backend)!.push({ idx, path: strippedPath, content });\n }\n\n for (const [backend, batch] of batchesByBackend) {\n if (!backend.uploadFiles) {\n throw new Error(\"Backend does not support uploadFiles\");\n }\n\n const batchFiles = batch.map(\n (b) => [b.path, b.content] as [string, Uint8Array],\n );\n const batchResponses = await backend.uploadFiles(batchFiles);\n\n for (let i = 0; i < batch.length; i++) {\n const originalIdx = batch[i].idx;\n results[originalIdx] = {\n path: files[originalIdx][0], // Original path\n error: batchResponses[i]?.error ?? null,\n };\n }\n }\n\n return results as FileUploadResponse[];\n }\n\n /**\n * Download multiple files, batching by backend for efficiency.\n *\n * @param paths - List of file paths to download\n * @returns List of FileDownloadResponse objects, one per input path\n */\n async downloadFiles(paths: string[]): Promise<FileDownloadResponse[]> {\n const results: Array<FileDownloadResponse | null> = Array.from(\n { length: paths.length },\n () => null,\n );\n const batchesByBackend = new Map<\n BackendProtocol,\n Array<{ idx: number; path: string }>\n >();\n\n for (let idx = 0; idx < paths.length; idx++) {\n const path = paths[idx];\n const [backend, strippedPath] = this.getBackendAndKey(path);\n\n if (!batchesByBackend.has(backend)) {\n batchesByBackend.set(backend, []);\n }\n batchesByBackend.get(backend)!.push({ idx, path: strippedPath });\n }\n\n for (const [backend, batch] of batchesByBackend) {\n if (!backend.downloadFiles) {\n throw new Error(\"Backend does not support downloadFiles\");\n }\n\n const batchPaths = batch.map((b) => b.path);\n const batchResponses = await backend.downloadFiles(batchPaths);\n\n for (let i = 0; i < batch.length; i++) {\n const originalIdx = batch[i].idx;\n results[originalIdx] = {\n path: paths[originalIdx], // Original path\n content: batchResponses[i]?.content ?? null,\n error: batchResponses[i]?.error ?? null,\n };\n }\n }\n\n return results as FileDownloadResponse[];\n }\n}\n","/**\n * LocalShellBackend: Node.js implementation of the filesystem backend with unrestricted local shell execution.\n *\n * This backend extends FilesystemBackend to add shell command execution on the local\n * host system. It provides NO sandboxing or isolation - all operations run directly\n * on the host machine with full system access.\n *\n * @module\n */\n\nimport cp from \"node:child_process\";\nimport fs from \"node:fs/promises\";\nimport path from \"node:path\";\n\nimport fg from \"fast-glob\";\n\nimport { FilesystemBackend } from \"./filesystem.js\";\nimport type {\n EditResult,\n ExecuteResponse,\n FileInfo,\n SandboxBackendProtocol,\n} from \"./protocol.js\";\nimport { SandboxError } from \"./protocol.js\";\n\n/**\n * Options for creating a LocalShellBackend instance.\n */\nexport interface LocalShellBackendOptions {\n /**\n * Working directory for both filesystem operations and shell commands.\n * @defaultValue `process.cwd()`\n */\n rootDir?: string;\n\n /**\n * Enable virtual path mode for filesystem operations.\n * When true, treats rootDir as a virtual root filesystem.\n * Does NOT restrict shell commands.\n * @defaultValue `false`\n */\n virtualMode?: boolean;\n\n /**\n * Maximum time in seconds to wait for shell command execution.\n * Commands exceeding this timeout will be terminated.\n * @defaultValue `120`\n */\n timeout?: number;\n\n /**\n * Maximum number of bytes to capture from command output.\n * Output exceeding this limit will be truncated.\n * @defaultValue `100_000`\n */\n maxOutputBytes?: number;\n\n /**\n * Environment variables for shell commands. If undefined, starts with an empty\n * environment (unless inheritEnv is true).\n * @defaultValue `undefined`\n */\n env?: Record<string, string>;\n\n /**\n * Whether to inherit the parent process's environment variables.\n * When false, only variables in env dict are available.\n * When true, inherits all process.env variables and applies env overrides.\n * @defaultValue `false`\n */\n inheritEnv?: boolean;\n\n /**\n * Files to create on disk during `create()`.\n * Keys are file paths (resolved via the backend's path handling),\n * values are string content.\n * @defaultValue `undefined`\n */\n initialFiles?: Record<string, string>;\n}\n\n/**\n * Filesystem backend with unrestricted local shell command execution.\n *\n * This backend extends FilesystemBackend to add shell command execution\n * capabilities. Commands are executed directly on the host system without any\n * sandboxing, process isolation, or security restrictions.\n *\n * **Security Warning:**\n * This backend grants agents BOTH direct filesystem access AND unrestricted\n * shell execution on your local machine. Use with extreme caution and only in\n * appropriate environments.\n *\n * **Appropriate use cases:**\n * - Local development CLIs (coding assistants, development tools)\n * - Personal development environments where you trust the agent's code\n * - CI/CD pipelines with proper secret management\n *\n * **Inappropriate use cases:**\n * - Production environments (e.g., web servers, APIs, multi-tenant systems)\n * - Processing untrusted user input or executing untrusted code\n *\n * Use StateBackend, StoreBackend, or extend BaseSandbox for production.\n *\n * @example\n * ```typescript\n * import { LocalShellBackend } from \"@langchain/deepagents\";\n *\n * // Create backend with explicit environment\n * const backend = new LocalShellBackend({\n * rootDir: \"/home/user/project\",\n * env: { PATH: \"/usr/bin:/bin\" },\n * });\n *\n * // Execute shell commands (runs directly on host)\n * const result = await backend.execute(\"ls -la\");\n * console.log(result.output);\n * console.log(result.exitCode);\n *\n * // Use filesystem operations (inherited from FilesystemBackend)\n * const content = await backend.read(\"/README.md\");\n * await backend.write(\"/output.txt\", \"Hello world\");\n *\n * // Inherit all environment variables\n * const backend2 = new LocalShellBackend({\n * rootDir: \"/home/user/project\",\n * inheritEnv: true,\n * });\n * ```\n */\nexport class LocalShellBackend\n extends FilesystemBackend\n implements SandboxBackendProtocol\n{\n #timeout: number;\n #maxOutputBytes: number;\n #env: Record<string, string>;\n #sandboxId: string;\n #initialized = false;\n\n constructor(options: LocalShellBackendOptions = {}) {\n const {\n rootDir,\n virtualMode = false,\n timeout = 120,\n maxOutputBytes = 100_000,\n env,\n inheritEnv = false,\n } = options;\n\n super({ rootDir, virtualMode, maxFileSizeMb: 10 });\n\n this.#timeout = timeout;\n this.#maxOutputBytes = maxOutputBytes;\n const bytes = new Uint8Array(4);\n crypto.getRandomValues(bytes);\n this.#sandboxId = `local-${[...bytes].map((b) => b.toString(16).padStart(2, \"0\")).join(\"\")}`;\n\n if (inheritEnv) {\n this.#env = { ...process.env } as Record<string, string>;\n if (env) {\n Object.assign(this.#env, env);\n }\n } else {\n this.#env = env ?? {};\n }\n }\n\n /** Unique identifier for this backend instance (format: \"local-{random_hex}\"). */\n get id(): string {\n return this.#sandboxId;\n }\n\n /** Whether the backend has been initialized and is ready to use. */\n get isInitialized(): boolean {\n return this.#initialized;\n }\n\n /** Alias for `isInitialized`, matching the standard sandbox interface. */\n get isRunning(): boolean {\n return this.#initialized;\n }\n\n /**\n * Initialize the backend by ensuring the rootDir exists.\n *\n * Creates the rootDir (and any parent directories) if it does not already\n * exist. Safe to call on an existing directory. Must be called before\n * `execute()`, or use the static `LocalShellBackend.create()` factory.\n *\n * @throws {SandboxError} If already initialized (`ALREADY_INITIALIZED`)\n */\n async initialize(): Promise<void> {\n if (this.#initialized) {\n throw new SandboxError(\n \"Backend is already initialized. Each LocalShellBackend instance can only be initialized once.\",\n \"ALREADY_INITIALIZED\",\n );\n }\n await fs.mkdir(this.cwd, { recursive: true });\n this.#initialized = true;\n }\n\n /**\n * Mark the backend as no longer running.\n *\n * For local shell backends there is no remote resource to tear down,\n * so this simply flips the `isRunning` / `isInitialized` flag.\n */\n async close(): Promise<void> {\n this.#initialized = false;\n }\n\n /**\n * Read a file, adapting error messages to the standard sandbox format.\n */\n override async read(\n filePath: string,\n offset: number = 0,\n limit: number = 500,\n ): Promise<string> {\n const result = await super.read(filePath, offset, limit);\n if (\n typeof result === \"string\" &&\n result.startsWith(\"Error reading file\") &&\n result.includes(\"ENOENT\")\n ) {\n return `Error: File '${filePath}' not found`;\n }\n return result;\n }\n\n /**\n * Edit a file, adapting error messages to the standard sandbox format.\n */\n override async edit(\n filePath: string,\n oldString: string,\n newString: string,\n replaceAll: boolean = false,\n ): Promise<EditResult> {\n const result = await super.edit(filePath, oldString, newString, replaceAll);\n if (result.error?.includes(\"ENOENT\")) {\n return { ...result, error: `Error: File '${filePath}' not found` };\n }\n return result;\n }\n\n /**\n * List directory contents, returning paths relative to rootDir.\n */\n override async lsInfo(dirPath: string): Promise<FileInfo[]> {\n const results = await super.lsInfo(dirPath);\n if (this.virtualMode) {\n return results;\n }\n const cwdPrefix = this.cwd.endsWith(path.sep)\n ? this.cwd\n : this.cwd + path.sep;\n return results.map((info) => ({\n ...info,\n path: info.path.startsWith(cwdPrefix)\n ? info.path.slice(cwdPrefix.length)\n : info.path,\n }));\n }\n\n /**\n * Glob matching that returns relative paths and includes directories.\n */\n override async globInfo(\n pattern: string,\n searchPath: string = \"/\",\n ): Promise<FileInfo[]> {\n if (pattern.startsWith(\"/\")) {\n pattern = pattern.substring(1);\n }\n\n const resolvedSearchPath =\n searchPath === \"/\" || searchPath === \"\"\n ? this.cwd\n : this.virtualMode\n ? path.resolve(this.cwd, searchPath.replace(/^\\//, \"\"))\n : path.resolve(this.cwd, searchPath);\n\n try {\n const stat = await fs.stat(resolvedSearchPath);\n if (!stat.isDirectory()) return [];\n } catch {\n return [];\n }\n\n const formatPath = (rel: string) => (this.virtualMode ? `/${rel}` : rel);\n\n const globOpts = { cwd: resolvedSearchPath, absolute: false, dot: true };\n const [fileMatches, dirMatches] = await Promise.all([\n fg(pattern, { ...globOpts, onlyFiles: true }),\n fg(pattern, { ...globOpts, onlyDirectories: true }),\n ]);\n\n const statFile = async (match: string): Promise<FileInfo | null> => {\n try {\n const entryStat = await fs.stat(path.join(resolvedSearchPath, match));\n if (entryStat.isFile()) {\n return {\n path: formatPath(match),\n is_dir: false,\n size: entryStat.size,\n modified_at: entryStat.mtime.toISOString(),\n };\n }\n } catch {\n /* skip unstatable entries */\n }\n return null;\n };\n\n const statDir = async (match: string): Promise<FileInfo | null> => {\n try {\n const entryStat = await fs.stat(path.join(resolvedSearchPath, match));\n if (entryStat.isDirectory()) {\n return {\n path: formatPath(match),\n is_dir: true,\n size: 0,\n modified_at: entryStat.mtime.toISOString(),\n };\n }\n } catch {\n /* skip unstatable entries */\n }\n return null;\n };\n\n const [fileInfos, dirInfos] = await Promise.all([\n Promise.all(fileMatches.map(statFile)),\n Promise.all(dirMatches.map(statDir)),\n ]);\n\n const results = [...fileInfos, ...dirInfos].filter(\n (info): info is FileInfo => info !== null,\n );\n results.sort((a, b) => a.path.localeCompare(b.path));\n return results;\n }\n\n /**\n * Execute a shell command directly on the host system.\n *\n * Commands are executed directly on your host system using `spawn()`\n * with `shell: true`. There is NO sandboxing, isolation, or security\n * restrictions. The command runs with your user's full permissions.\n *\n * The command is executed using the system shell with the working directory\n * set to the backend's rootDir. Stdout and stderr are combined into a single\n * output stream, with stderr lines prefixed with `[stderr]`.\n *\n * @param command - Shell command string to execute\n * @returns ExecuteResponse containing output, exit code, and truncation flag\n */\n async execute(command: string): Promise<ExecuteResponse> {\n if (!command || typeof command !== \"string\") {\n return {\n output: \"Error: Command must be a non-empty string.\",\n exitCode: 1,\n truncated: false,\n };\n }\n\n return new Promise<ExecuteResponse>((resolve) => {\n let stdout = \"\";\n let stderr = \"\";\n let timedOut = false;\n\n const child = cp.spawn(command, {\n shell: true,\n env: this.#env,\n cwd: this.cwd,\n });\n\n const timer = setTimeout(() => {\n timedOut = true;\n child.kill(\"SIGTERM\");\n }, this.#timeout * 1000);\n\n child.stdout.on(\"data\", (data: Buffer) => {\n stdout += data.toString();\n });\n\n child.stderr.on(\"data\", (data: Buffer) => {\n stderr += data.toString();\n });\n\n child.on(\"error\", (err) => {\n clearTimeout(timer);\n resolve({\n output: `Error executing command: ${err.message}`,\n exitCode: 1,\n truncated: false,\n });\n });\n\n child.on(\"close\", (code, signal) => {\n clearTimeout(timer);\n\n if (timedOut || signal === \"SIGTERM\") {\n resolve({\n output: `Error: Command timed out after ${this.#timeout.toFixed(1)} seconds.`,\n exitCode: 124,\n truncated: false,\n });\n return;\n }\n\n const outputParts: string[] = [];\n if (stdout) {\n outputParts.push(stdout);\n }\n if (stderr) {\n const stderrLines = stderr.trim().split(\"\\n\");\n outputParts.push(\n ...stderrLines.map((line: string) => `[stderr] ${line}`),\n );\n }\n\n let output =\n outputParts.length > 0 ? outputParts.join(\"\\n\") : \"<no output>\";\n\n let truncated = false;\n if (output.length > this.#maxOutputBytes) {\n output = output.slice(0, this.#maxOutputBytes);\n output += `\\n\\n... Output truncated at ${this.#maxOutputBytes} bytes.`;\n truncated = true;\n }\n\n const exitCode = code ?? 1;\n\n if (exitCode !== 0) {\n output = `${output.trimEnd()}\\n\\nExit code: ${exitCode}`;\n }\n\n resolve({\n output,\n exitCode,\n truncated,\n });\n });\n });\n }\n\n /**\n * Create and initialize a new LocalShellBackend in one step.\n *\n * This is the recommended way to create a backend when the rootDir may\n * not exist yet. It combines construction and initialization (ensuring\n * rootDir exists) into a single async operation.\n *\n * @param options - Configuration options for the backend\n * @returns An initialized and ready-to-use backend\n */\n static async create(\n options: LocalShellBackendOptions = {},\n ): Promise<LocalShellBackend> {\n const { initialFiles, ...backendOptions } = options;\n const backend = new LocalShellBackend(backendOptions);\n await backend.initialize();\n\n if (initialFiles) {\n const encoder = new TextEncoder();\n const files: Array<[string, Uint8Array]> = Object.entries(\n initialFiles,\n ).map(([filePath, content]) => [filePath, encoder.encode(content)]);\n await backend.uploadFiles(files);\n }\n\n return backend;\n }\n}\n","/**\n * BaseSandbox: Abstract base class for sandbox backends with command execution.\n *\n * This class provides default implementations for all SandboxBackendProtocol\n * methods. Concrete implementations only need to implement execute(),\n * uploadFiles(), and downloadFiles().\n *\n * Runtime requirements on the sandbox host:\n * - read, grep: Pure POSIX shell (awk, grep) — works on any Linux including Alpine\n * - write, edit, readRaw: No runtime needed — uses uploadFiles/downloadFiles directly\n * - ls, glob: Pure POSIX shell (find, stat) — works on any Linux including Alpine\n *\n * No Python, Node.js, or other runtime required.\n */\n\nimport type {\n EditResult,\n ExecuteResponse,\n FileData,\n FileDownloadResponse,\n FileInfo,\n FileUploadResponse,\n GrepMatch,\n MaybePromise,\n SandboxBackendProtocol,\n WriteResult,\n} from \"./protocol.js\";\n\n/**\n * Shell-quote a string using single quotes (POSIX).\n * Escapes embedded single quotes with the '\\'' technique.\n */\nfunction shellQuote(s: string): string {\n return \"'\" + s.replace(/'/g, \"'\\\\''\") + \"'\";\n}\n\n/**\n * Convert a glob pattern to a path-aware RegExp.\n *\n * Inspired by the just-bash project's glob utilities:\n * - `*` matches any characters except `/`\n * - `**` matches any characters including `/` (recursive)\n * - `?` matches a single character except `/`\n * - `[...]` character classes\n */\nfunction globToPathRegex(pattern: string): RegExp {\n let regex = \"^\";\n let i = 0;\n\n while (i < pattern.length) {\n const c = pattern[i];\n\n if (c === \"*\") {\n if (i + 1 < pattern.length && pattern[i + 1] === \"*\") {\n // ** (globstar) matches everything including /\n i += 2;\n if (i < pattern.length && pattern[i] === \"/\") {\n // **/ matches zero or more directory segments\n regex += \"(.*/)?\";\n i++;\n } else {\n // ** at end matches anything\n regex += \".*\";\n }\n } else {\n // * matches anything except /\n regex += \"[^/]*\";\n i++;\n }\n } else if (c === \"?\") {\n regex += \"[^/]\";\n i++;\n } else if (c === \"[\") {\n // Character class — find closing bracket\n let j = i + 1;\n while (j < pattern.length && pattern[j] !== \"]\") j++;\n regex += pattern.slice(i, j + 1);\n i = j + 1;\n } else if (\n c === \".\" ||\n c === \"+\" ||\n c === \"^\" ||\n c === \"$\" ||\n c === \"{\" ||\n c === \"}\" ||\n c === \"(\" ||\n c === \")\" ||\n c === \"|\" ||\n c === \"\\\\\"\n ) {\n regex += `\\\\${c}`;\n i++;\n } else {\n regex += c;\n i++;\n }\n }\n\n regex += \"$\";\n return new RegExp(regex);\n}\n\n/**\n * Parse a single line of stat/find output in the format: size\\tmtime\\ttype\\tpath\n *\n * The first three tab-delimited fields are always fixed (number, number, string),\n * so we safely take everything after the third tab as the file path — even if the\n * path itself contains tabs.\n *\n * The type field varies by platform / tool:\n * - GNU find -printf %y: single letter \"d\", \"f\", \"l\"\n * - BSD stat -f %Sp: permission strings like \"drwxr-xr-x\", \"-rw-r--r--\"\n *\n * The mtime field may be a float (GNU find %T@ → \"1234567890.0000000000\")\n * or an integer (BSD stat %m → \"1234567890\"); parseInt handles both.\n */\nfunction parseStatLine(\n line: string,\n): { size: number; mtime: number; isDir: boolean; fullPath: string } | null {\n const firstTab = line.indexOf(\"\\t\");\n if (firstTab === -1) return null;\n\n const secondTab = line.indexOf(\"\\t\", firstTab + 1);\n if (secondTab === -1) return null;\n\n const thirdTab = line.indexOf(\"\\t\", secondTab + 1);\n if (thirdTab === -1) return null;\n\n const size = parseInt(line.slice(0, firstTab), 10);\n const mtime = parseInt(line.slice(firstTab + 1, secondTab), 10);\n const fileType = line.slice(secondTab + 1, thirdTab);\n const fullPath = line.slice(thirdTab + 1);\n\n if (isNaN(size) || isNaN(mtime)) return null;\n\n return {\n size,\n mtime,\n // GNU find %y outputs \"d\"; BSD stat %Sp outputs \"drwxr-xr-x\"\n isDir:\n fileType === \"d\" || fileType === \"directory\" || fileType.startsWith(\"d\"),\n fullPath,\n };\n}\n\n/**\n * BusyBox/Alpine fallback script for stat -c.\n *\n * Determines file type with POSIX test builtins, then uses stat -c\n * (supported by both GNU coreutils and BusyBox) for size and mtime.\n * printf handles tab-delimited output formatting.\n */\nconst STAT_C_SCRIPT =\n \"for f; do \" +\n 'if [ -d \"$f\" ]; then t=d; elif [ -L \"$f\" ]; then t=l; else t=f; fi; ' +\n 'sz=$(stat -c %s \"$f\" 2>/dev/null) || continue; ' +\n 'mt=$(stat -c %Y \"$f\" 2>/dev/null) || continue; ' +\n 'printf \"%s\\\\t%s\\\\t%s\\\\t%s\\\\n\" \"$sz\" \"$mt\" \"$t\" \"$f\"; ' +\n \"done\";\n\n/**\n * Shell command for listing directory contents with metadata.\n *\n * Detects the environment at runtime with three-way probing:\n * 1. GNU find (full Linux): uses built-in `-printf` (most efficient)\n * 2. BusyBox / Alpine: uses `find -exec sh -c` with `stat -c` fallback\n * 3. BSD / macOS: uses `find -exec stat -f`\n *\n * Output format per line: size\\tmtime\\ttype\\tpath\n */\nfunction buildLsCommand(dirPath: string): string {\n const quotedPath = shellQuote(dirPath);\n const findBase = `find ${quotedPath} -maxdepth 1 -not -path ${quotedPath}`;\n return (\n `if find /dev/null -maxdepth 0 -printf '' 2>/dev/null; then ` +\n `${findBase} -printf '%s\\\\t%T@\\\\t%y\\\\t%p\\\\n' 2>/dev/null; ` +\n `elif stat -c %s /dev/null >/dev/null 2>&1; then ` +\n `${findBase} -exec sh -c '${STAT_C_SCRIPT}' _ {} +; ` +\n `else ` +\n `${findBase} -exec stat -f '%z\\t%m\\t%Sp\\t%N' {} + 2>/dev/null; ` +\n `fi || true`\n );\n}\n\n/**\n * Shell command for listing files recursively with metadata.\n * Same three-way detection as buildLsCommand (GNU -printf / stat -c / BSD stat -f).\n *\n * Output format per line: size\\tmtime\\ttype\\tpath\n */\nfunction buildFindCommand(searchPath: string): string {\n const quotedPath = shellQuote(searchPath);\n const findBase = `find ${quotedPath} -not -path ${quotedPath}`;\n return (\n `if find /dev/null -maxdepth 0 -printf '' 2>/dev/null; then ` +\n `${findBase} -printf '%s\\\\t%T@\\\\t%y\\\\t%p\\\\n' 2>/dev/null; ` +\n `elif stat -c %s /dev/null >/dev/null 2>&1; then ` +\n `${findBase} -exec sh -c '${STAT_C_SCRIPT}' _ {} +; ` +\n `else ` +\n `${findBase} -exec stat -f '%z\\t%m\\t%Sp\\t%N' {} + 2>/dev/null; ` +\n `fi || true`\n );\n}\n\n/**\n * Pure POSIX shell command for reading files with line numbers.\n * Uses awk for line numbering with offset/limit — works on any Linux including Alpine.\n */\nfunction buildReadCommand(\n filePath: string,\n offset: number,\n limit: number,\n): string {\n const quotedPath = shellQuote(filePath);\n // Coerce offset and limit to safe non-negative integers.\n const safeOffset =\n Number.isFinite(offset) && offset > 0 ? Math.floor(offset) : 0;\n const safeLimit =\n Number.isFinite(limit) && limit > 0\n ? Math.min(Math.floor(limit), 999_999_999)\n : 999_999_999;\n // awk NR is 1-based, our offset is 0-based\n const start = safeOffset + 1;\n const end = safeOffset + safeLimit;\n\n return [\n `if [ ! -f ${quotedPath} ]; then echo \"Error: File not found\"; exit 1; fi`,\n `if [ ! -s ${quotedPath} ]; then echo \"System reminder: File exists but has empty contents\"; exit 0; fi`,\n `awk 'NR >= ${start} && NR <= ${end} { printf \"%6d\\\\t%s\\\\n\", NR, $0 }' ${quotedPath}`,\n ].join(\"; \");\n}\n\n/**\n * Build a grep command for literal (fixed-string) search.\n * Uses grep -rHnF for recursive, with-filename, with-line-number, fixed-string search.\n *\n * When a glob pattern is provided, uses `find -name GLOB -exec grep` instead of\n * `grep --include=GLOB` for universal compatibility (BusyBox grep lacks --include).\n *\n * @param pattern - Literal string to search for (NOT regex).\n * @param searchPath - Base path to search in.\n * @param globPattern - Optional glob pattern to filter files.\n */\nfunction buildGrepCommand(\n pattern: string,\n searchPath: string,\n globPattern: string | null,\n): string {\n const patternEscaped = shellQuote(pattern);\n const searchPathQuoted = shellQuote(searchPath);\n\n if (globPattern) {\n // Use find + grep for BusyBox compatibility (BusyBox grep lacks --include)\n const globEscaped = shellQuote(globPattern);\n return `find ${searchPathQuoted} -type f -name ${globEscaped} -exec grep -HnF -e ${patternEscaped} {} + 2>/dev/null || true`;\n }\n\n return `grep -rHnF -e ${patternEscaped} ${searchPathQuoted} 2>/dev/null || true`;\n}\n\n/**\n * Base sandbox implementation with execute() as the only abstract method.\n *\n * This class provides default implementations for all SandboxBackendProtocol\n * methods using shell commands executed via execute(). Concrete implementations\n * only need to implement execute(), uploadFiles(), and downloadFiles().\n *\n * All shell commands use pure POSIX utilities (awk, grep, find, stat) that are\n * available on any Linux including Alpine/busybox. No Python, Node.js, or\n * other runtime is required on the sandbox host.\n */\nexport abstract class BaseSandbox implements SandboxBackendProtocol {\n /** Unique identifier for the sandbox backend */\n abstract readonly id: string;\n\n /**\n * Execute a command in the sandbox.\n * This is the only method concrete implementations must provide.\n */\n abstract execute(command: string): MaybePromise<ExecuteResponse>;\n\n /**\n * Upload multiple files to the sandbox.\n * Implementations must support partial success.\n */\n abstract uploadFiles(\n files: Array<[string, Uint8Array]>,\n ): MaybePromise<FileUploadResponse[]>;\n\n /**\n * Download multiple files from the sandbox.\n * Implementations must support partial success.\n */\n abstract downloadFiles(paths: string[]): MaybePromise<FileDownloadResponse[]>;\n\n /**\n * List files and directories in the specified directory (non-recursive).\n *\n * Uses pure POSIX shell (find + stat) via execute() — works on any Linux\n * including Alpine. No Python or Node.js needed.\n *\n * @param path - Absolute path to directory\n * @returns List of FileInfo objects for files and directories directly in the directory.\n */\n async lsInfo(path: string): Promise<FileInfo[]> {\n const command = buildLsCommand(path);\n const result = await this.execute(command);\n\n const infos: FileInfo[] = [];\n const lines = result.output.trim().split(\"\\n\").filter(Boolean);\n\n for (const line of lines) {\n const parsed = parseStatLine(line);\n if (!parsed) continue;\n\n infos.push({\n path: parsed.isDir ? parsed.fullPath + \"/\" : parsed.fullPath,\n is_dir: parsed.isDir,\n size: parsed.size,\n modified_at: new Date(parsed.mtime * 1000).toISOString(),\n });\n }\n\n return infos;\n }\n\n /**\n * Read file content with line numbers.\n *\n * Uses pure POSIX shell (awk) via execute() — only the requested slice\n * is returned over the wire, making this efficient for large files.\n * Works on any Linux including Alpine (no Python or Node.js needed).\n *\n * @param filePath - Absolute file path\n * @param offset - Line offset to start reading from (0-indexed)\n * @param limit - Maximum number of lines to read\n * @returns Formatted file content with line numbers, or error message\n */\n async read(\n filePath: string,\n offset: number = 0,\n limit: number = 500,\n ): Promise<string> {\n // limit=0 means return nothing\n if (limit === 0) return \"\";\n\n const command = buildReadCommand(filePath, offset, limit);\n const result = await this.execute(command);\n\n if (result.exitCode !== 0) {\n return `Error: File '${filePath}' not found`;\n }\n\n return result.output;\n }\n\n /**\n * Read file content as raw FileData.\n *\n * Uses downloadFiles() directly — no runtime needed on the sandbox host.\n *\n * @param filePath - Absolute file path\n * @returns Raw file content as FileData\n */\n async readRaw(filePath: string): Promise<FileData> {\n const results = await this.downloadFiles([filePath]);\n if (results[0].error || !results[0].content) {\n throw new Error(`File '${filePath}' not found`);\n }\n\n const content = new TextDecoder().decode(results[0].content);\n const lines = content.split(\"\\n\");\n\n const now = new Date().toISOString();\n return {\n content: lines,\n created_at: now,\n modified_at: now,\n };\n }\n\n /**\n * Search for a literal text pattern in files using grep.\n *\n * @param pattern - Literal string to search for (NOT regex).\n * @param path - Directory or file path to search in.\n * @param glob - Optional glob pattern to filter which files to search.\n * @returns List of GrepMatch dicts containing path, line number, and matched text.\n */\n async grepRaw(\n pattern: string,\n path: string = \"/\",\n glob: string | null = null,\n ): Promise<GrepMatch[] | string> {\n const command = buildGrepCommand(pattern, path, glob);\n const result = await this.execute(command);\n\n const output = result.output.trim();\n if (!output) {\n return [];\n }\n\n // Parse grep output format: path:line_number:text\n const matches: GrepMatch[] = [];\n for (const line of output.split(\"\\n\")) {\n const parts = line.split(\":\");\n if (parts.length >= 3) {\n const lineNum = parseInt(parts[1], 10);\n if (!isNaN(lineNum)) {\n matches.push({\n path: parts[0],\n line: lineNum,\n text: parts.slice(2).join(\":\"),\n });\n }\n }\n }\n\n return matches;\n }\n\n /**\n * Structured glob matching returning FileInfo objects.\n *\n * Uses pure POSIX shell (find + stat) via execute() to list all files,\n * then applies glob-to-regex matching in TypeScript. No Python or Node.js\n * needed on the sandbox host.\n *\n * Glob patterns are matched against paths relative to the search base:\n * - `*` matches any characters except `/`\n * - `**` matches any characters including `/` (recursive)\n * - `?` matches a single character except `/`\n * - `[...]` character classes\n */\n async globInfo(pattern: string, path: string = \"/\"): Promise<FileInfo[]> {\n const command = buildFindCommand(path);\n const result = await this.execute(command);\n\n const regex = globToPathRegex(pattern);\n const infos: FileInfo[] = [];\n const lines = result.output.trim().split(\"\\n\").filter(Boolean);\n\n // Normalise base path (strip trailing /)\n const basePath = path.endsWith(\"/\") ? path.slice(0, -1) : path;\n\n for (const line of lines) {\n const parsed = parseStatLine(line);\n if (!parsed) continue;\n\n // Compute path relative to the search base\n const relPath = parsed.fullPath.startsWith(basePath + \"/\")\n ? parsed.fullPath.slice(basePath.length + 1)\n : parsed.fullPath;\n\n if (regex.test(relPath)) {\n infos.push({\n path: relPath,\n is_dir: parsed.isDir,\n size: parsed.size,\n modified_at: new Date(parsed.mtime * 1000).toISOString(),\n });\n }\n }\n\n return infos;\n }\n\n /**\n * Create a new file with content.\n *\n * Uses downloadFiles() to check existence and uploadFiles() to write.\n * No runtime needed on the sandbox host.\n */\n async write(filePath: string, content: string): Promise<WriteResult> {\n // Check if file already exists\n try {\n const existCheck = await this.downloadFiles([filePath]);\n if (existCheck[0].content !== null && existCheck[0].error === null) {\n return {\n error: `Cannot write to ${filePath} because it already exists. Read and then make an edit, or write to a new path.`,\n };\n }\n } catch {\n // File doesn't exist, which is what we want for write\n }\n\n const encoder = new TextEncoder();\n const results = await this.uploadFiles([\n [filePath, encoder.encode(content)],\n ]);\n\n if (results[0].error) {\n return {\n error: `Failed to write to ${filePath}: ${results[0].error}`,\n };\n }\n\n return { path: filePath, filesUpdate: null };\n }\n\n /**\n * Edit a file by replacing string occurrences.\n *\n * Uses downloadFiles() to read, performs string replacement in TypeScript,\n * then uploadFiles() to write back. No runtime needed on the sandbox host.\n *\n * Memory-conscious: releases intermediate references early so the GC can\n * reclaim buffers before the next large allocation is made.\n */\n async edit(\n filePath: string,\n oldString: string,\n newString: string,\n replaceAll: boolean = false,\n ): Promise<EditResult> {\n const results = await this.downloadFiles([filePath]);\n if (results[0].error || !results[0].content) {\n return { error: `Error: File '${filePath}' not found` };\n }\n\n const text = new TextDecoder().decode(results[0].content);\n results[0].content = null as unknown as Uint8Array;\n\n /**\n * are we editing an empty file?\n */\n if (oldString.length === 0) {\n /**\n * if the file is not empty, we cannot edit it with an empty oldString\n */\n if (text.length !== 0) {\n return {\n error: \"oldString must not be empty unless the file is empty\",\n };\n }\n /**\n * if the newString is empty, we can just return the file as is\n */\n if (newString.length === 0) {\n return { path: filePath, filesUpdate: null, occurrences: 0 };\n }\n\n /**\n * if the newString is not empty, we can edit the file\n */\n const encoded = new TextEncoder().encode(newString);\n const uploadResults = await this.uploadFiles([[filePath, encoded]]);\n /**\n * if the upload fails, we return an error\n */\n if (uploadResults[0].error) {\n return {\n error: `Failed to write edited file '${filePath}': ${uploadResults[0].error}`,\n };\n }\n return { path: filePath, filesUpdate: null, occurrences: 1 };\n }\n\n const firstIdx = text.indexOf(oldString);\n if (firstIdx === -1) {\n return { error: `String not found in file '${filePath}'` };\n }\n\n if (oldString === newString) {\n return { path: filePath, filesUpdate: null, occurrences: 1 };\n }\n\n let newText: string;\n let count: number;\n\n if (replaceAll) {\n newText = text.replaceAll(oldString, newString);\n /**\n * Derive count from the length delta to avoid a separate O(n) counting pass\n */\n const lenDiff = oldString.length - newString.length;\n if (lenDiff !== 0) {\n count = (text.length - newText.length) / lenDiff;\n } else {\n /**\n * Lengths are equal — count via indexOf (we already found the first)\n */\n count = 1;\n let pos = firstIdx + oldString.length;\n while (pos <= text.length) {\n const idx = text.indexOf(oldString, pos);\n if (idx === -1) break;\n count++;\n pos = idx + oldString.length;\n }\n }\n } else {\n const secondIdx = text.indexOf(oldString, firstIdx + oldString.length);\n if (secondIdx !== -1) {\n return {\n error: `Multiple occurrences found in '${filePath}'. Use replaceAll=true to replace all.`,\n };\n }\n count = 1;\n /**\n * Build result from the known index — avoids a redundant search by .replace()\n */\n newText =\n text.slice(0, firstIdx) +\n newString +\n text.slice(firstIdx + oldString.length);\n }\n\n const encoded = new TextEncoder().encode(newText);\n const uploadResults = await this.uploadFiles([[filePath, encoded]]);\n\n if (uploadResults[0].error) {\n return {\n error: `Failed to write edited file '${filePath}': ${uploadResults[0].error}`,\n };\n }\n\n return { path: filePath, filesUpdate: null, occurrences: count };\n }\n}\n","import { createMiddleware, SystemMessage } from \"langchain\";\n\n/**\n * Creates a middleware that places a cache breakpoint at the end of the static\n * system prompt content.\n *\n * This middleware tags the last block of the system message with\n * `cache_control: { type: \"ephemeral\" }` at the time it runs, capturing all\n * static content injected by preceding middleware (e.g. todo list instructions,\n * filesystem tools, subagent instructions) in a single cache breakpoint.\n *\n * This should run after all static system prompt middleware and before any\n * dynamic middleware (e.g. memory) so the breakpoint sits at the boundary\n * between stable and changing content.\n *\n * When used alongside memory middleware (which adds its own breakpoint on the\n * memory block), the result is two separate cache breakpoints:\n * - One covering all static content\n * - One covering the memory block\n *\n * This is a no-op when the system message has no content blocks.\n */\nexport function createCacheBreakpointMiddleware() {\n return createMiddleware({\n name: \"CacheBreakpointMiddleware\",\n\n wrapModelCall(request, handler) {\n const existingContent = request.systemMessage.content;\n const existingBlocks =\n typeof existingContent === \"string\"\n ? [{ type: \"text\" as const, text: existingContent }]\n : Array.isArray(existingContent)\n ? [...existingContent]\n : [];\n\n if (existingBlocks.length === 0) return handler(request);\n\n existingBlocks[existingBlocks.length - 1] = {\n ...existingBlocks[existingBlocks.length - 1],\n cache_control: { type: \"ephemeral\" },\n };\n\n return handler({\n ...request,\n systemMessage: new SystemMessage({ content: existingBlocks }),\n });\n },\n });\n}\n","import {\n createAgent,\n humanInTheLoopMiddleware,\n anthropicPromptCachingMiddleware,\n todoListMiddleware,\n SystemMessage,\n type AgentMiddleware,\n} from \"langchain\";\nimport type {\n ClientTool,\n ServerTool,\n StructuredTool,\n} from \"@langchain/core/tools\";\nimport { Runnable } from \"@langchain/core/runnables\";\nimport type { BaseStore } from \"@langchain/langgraph-checkpoint\";\n\nimport {\n createFilesystemMiddleware,\n createSubAgentMiddleware,\n createPatchToolCallsMiddleware,\n createSummarizationMiddleware,\n createMemoryMiddleware,\n createSkillsMiddleware,\n type SubAgent,\n} from \"./middleware/index.js\";\nimport { StateBackend } from \"./backends/index.js\";\nimport { InteropZodObject } from \"@langchain/core/utils/types\";\nimport { CompiledSubAgent } from \"./middleware/subagents.js\";\nimport type {\n CreateDeepAgentParams,\n DeepAgent,\n DeepAgentTypeConfig,\n FlattenSubAgentMiddleware,\n InferStructuredResponse,\n SupportedResponseFormat,\n} from \"./types.js\";\n\n/**\n * required for type inference\n */\nimport type * as _messages from \"@langchain/core/messages\";\nimport type * as _Command from \"@langchain/langgraph\";\nimport type { BaseLanguageModel } from \"@langchain/core/language_models/base\";\nimport { createCacheBreakpointMiddleware } from \"./middleware/cache.js\";\n\nconst BASE_PROMPT = `In order to complete the objective that the user asks of you, you have access to a number of standard tools.`;\n\n/**\n * Detect whether a model is an Anthropic model.\n * Used to gate Anthropic-specific prompt caching optimizations (cache_control breakpoints).\n */\nexport function isAnthropicModel(model: BaseLanguageModel | string): boolean {\n if (typeof model === \"string\") {\n if (model.includes(\":\")) return model.split(\":\")[0] === \"anthropic\";\n return model.startsWith(\"claude\");\n }\n if (model.getName() === \"ConfigurableModel\") {\n return (model as any)._defaultConfig?.modelProvider === \"anthropic\";\n }\n return model.getName() === \"ChatAnthropic\";\n}\n\n/**\n * Create a Deep Agent with middleware-based architecture.\n *\n * Matches Python's create_deep_agent function, using middleware for all features:\n * - Todo management (todoListMiddleware)\n * - Filesystem tools (createFilesystemMiddleware)\n * - Subagent delegation (createSubAgentMiddleware)\n * - Conversation summarization (createSummarizationMiddleware) with backend offloading\n * - Prompt caching (anthropicPromptCachingMiddleware)\n * - Tool call patching (createPatchToolCallsMiddleware)\n * - Human-in-the-loop (humanInTheLoopMiddleware) - optional\n *\n * @param params Configuration parameters for the agent\n * @returns ReactAgent instance ready for invocation with properly inferred state types\n *\n * @example\n * ```typescript\n * // Middleware with custom state\n * const ResearchMiddleware = createMiddleware({\n * name: \"ResearchMiddleware\",\n * stateSchema: z.object({ research: z.string().default(\"\") }),\n * });\n *\n * const agent = createDeepAgent({\n * middleware: [ResearchMiddleware],\n * });\n *\n * const result = await agent.invoke({ messages: [...] });\n * // result.research is properly typed as string\n * ```\n */\nexport function createDeepAgent<\n TResponse extends SupportedResponseFormat = SupportedResponseFormat,\n ContextSchema extends InteropZodObject = InteropZodObject,\n const TMiddleware extends readonly AgentMiddleware[] = readonly [],\n const TSubagents extends readonly (SubAgent | CompiledSubAgent)[] =\n readonly [],\n const TTools extends readonly (ClientTool | ServerTool)[] = readonly [],\n>(\n params: CreateDeepAgentParams<\n TResponse,\n ContextSchema,\n TMiddleware,\n TSubagents,\n TTools\n > = {} as CreateDeepAgentParams<\n TResponse,\n ContextSchema,\n TMiddleware,\n TSubagents,\n TTools\n >,\n) {\n const {\n model = \"claude-sonnet-4-5-20250929\",\n tools = [],\n systemPrompt,\n middleware: customMiddleware = [],\n subagents = [],\n responseFormat,\n contextSchema,\n checkpointer,\n store,\n backend,\n interruptOn,\n name,\n memory,\n skills,\n } = params;\n\n const anthropicModel = isAnthropicModel(model);\n\n /**\n * Combine system prompt with base prompt like Python implementation\n */\n const systemPromptBlocks: _messages.ContentBlock[] = systemPrompt\n ? typeof systemPrompt === \"string\"\n ? [{ type: \"text\", text: `${systemPrompt}\\n\\n${BASE_PROMPT}` }]\n : [\n { type: \"text\", text: BASE_PROMPT },\n ...(typeof systemPrompt.content === \"string\"\n ? [{ type: \"text\", text: systemPrompt.content }]\n : systemPrompt.content),\n ]\n : [{ type: \"text\", text: BASE_PROMPT }];\n\n const finalSystemPrompt = new SystemMessage({ content: systemPromptBlocks });\n\n /**\n * Create backend configuration for filesystem middleware\n * If no backend is provided, use a factory that creates a StateBackend\n */\n const filesystemBackend = backend\n ? backend\n : (config: { state: unknown; store?: BaseStore }) =>\n new StateBackend(config);\n\n /**\n * Skills middleware (created conditionally for runtime use)\n */\n const skillsMiddlewareArray =\n skills != null && skills.length > 0\n ? [\n createSkillsMiddleware({\n backend: filesystemBackend,\n sources: skills,\n }),\n ]\n : [];\n\n /**\n * Memory middleware (created conditionally for runtime use)\n */\n const memoryMiddlewareArray =\n memory != null && memory.length > 0\n ? [\n createMemoryMiddleware({\n backend: filesystemBackend,\n sources: memory,\n addCacheControl: anthropicModel,\n }),\n ]\n : [];\n\n /**\n * Process subagents to add SkillsMiddleware for those with their own skills.\n *\n * Custom subagents do NOT inherit skills from the main agent by default.\n * Only the general-purpose subagent inherits the main agent's skills (via defaultMiddleware).\n * If a custom subagent needs skills, it must specify its own `skills` array.\n */\n const processedSubagents = subagents.map((subagent) => {\n /**\n * CompiledSubAgent - use as-is (already has its own middleware baked in)\n */\n if (Runnable.isRunnable(subagent)) {\n return subagent;\n }\n\n /**\n * SubAgent without skills - use as-is\n */\n if (!(\"skills\" in subagent) || subagent.skills?.length === 0) {\n return subagent;\n }\n\n /**\n * SubAgent with skills - add SkillsMiddleware BEFORE user's middleware\n * Order: base middleware (via defaultMiddleware) → skills → user's middleware\n * This matches Python's ordering in create_deep_agent\n */\n const subagentSkillsMiddleware = createSkillsMiddleware({\n backend: filesystemBackend,\n sources: subagent.skills ?? [],\n });\n\n return {\n ...subagent,\n middleware: [\n subagentSkillsMiddleware,\n ...(subagent.middleware || []),\n ] as readonly AgentMiddleware[],\n };\n });\n\n /**\n * Middleware for custom subagents (does NOT include skills from main agent).\n * Custom subagents must define their own `skills` property to get skills.\n *\n * Uses createSummarizationMiddleware (deepagents version) with backend support\n * and auto-computed defaults from model profile, matching Python's create_deep_agent.\n * When trigger is not provided, defaults are lazily computed:\n * - With model profile: fraction-based (trigger=0.85, keep=0.10)\n * - Without profile: fixed (trigger=170k tokens, keep=6 messages)\n */\n const subagentMiddleware = [\n todoListMiddleware(),\n createFilesystemMiddleware({\n backend: filesystemBackend,\n }),\n createSummarizationMiddleware({\n model,\n backend: filesystemBackend,\n }),\n anthropicPromptCachingMiddleware({\n unsupportedModelBehavior: \"ignore\",\n minMessagesToCache: 1,\n }),\n createPatchToolCallsMiddleware(),\n ];\n\n /**\n * Built-in middleware array - core middleware with known types\n * This tuple is typed without conditional spreads to preserve TypeScript's tuple inference.\n * Optional middleware (skills, memory, HITL) are handled at runtime but typed explicitly.\n */\n const builtInMiddleware = [\n /**\n * Provides todo list management capabilities for tracking tasks\n */\n todoListMiddleware(),\n /**\n * Enables filesystem operations and optional long-term memory storage\n */\n createFilesystemMiddleware({ backend: filesystemBackend }),\n /**\n * Enables delegation to specialized subagents for complex tasks\n */\n createSubAgentMiddleware({\n defaultModel: model,\n defaultTools: tools as StructuredTool[],\n /**\n * Custom subagents must define their own `skills` property to get skills.\n */\n defaultMiddleware: [\n ...subagentMiddleware,\n ...((anthropicModel\n ? [createCacheBreakpointMiddleware()]\n : []) as AgentMiddleware[]),\n ],\n /**\n * Middleware for the general-purpose subagent (inherits skills from main agent).\n */\n generalPurposeMiddleware: [\n ...subagentMiddleware,\n ...skillsMiddlewareArray,\n ...((anthropicModel\n ? [createCacheBreakpointMiddleware()]\n : []) as AgentMiddleware[]),\n ],\n defaultInterruptOn: interruptOn,\n subagents: processedSubagents,\n generalPurposeAgent: true,\n }),\n /**\n * Automatically summarizes conversation history when token limits are approached.\n * Uses createSummarizationMiddleware (deepagents version) with backend support\n * for conversation history offloading and auto-computed defaults from model profile.\n */\n createSummarizationMiddleware({\n model,\n backend: filesystemBackend,\n }),\n /**\n * Enables Anthropic prompt caching for improved performance and reduced costs\n */\n anthropicPromptCachingMiddleware({\n unsupportedModelBehavior: \"ignore\",\n minMessagesToCache: 1,\n }),\n /**\n * Patches tool calls to ensure compatibility across different model providers\n */\n createPatchToolCallsMiddleware(),\n ] as const;\n\n /**\n * Runtime middleware array: combine built-in + optional middleware\n * Note: The type is handled separately via AllMiddleware type alias\n */\n const runtimeMiddleware: AgentMiddleware[] = [\n ...builtInMiddleware,\n ...skillsMiddlewareArray,\n ...(anthropicModel ? [createCacheBreakpointMiddleware()] : []),\n ...memoryMiddlewareArray,\n ...(interruptOn ? [humanInTheLoopMiddleware({ interruptOn })] : []),\n ...(customMiddleware as unknown as AgentMiddleware[]),\n ];\n\n const agent = createAgent({\n model,\n systemPrompt: finalSystemPrompt,\n tools: tools as StructuredTool[],\n middleware: runtimeMiddleware,\n ...(responseFormat != null && { responseFormat }),\n contextSchema,\n checkpointer,\n store,\n name,\n }).withConfig({ recursionLimit: 10_000 });\n\n /**\n * Combine custom middleware with flattened subagent middleware for complete type inference\n * This ensures InferMiddlewareStates captures state from both sources\n */\n type AllMiddleware = readonly [\n ...typeof builtInMiddleware,\n ...TMiddleware,\n ...FlattenSubAgentMiddleware<TSubagents>,\n ];\n\n /**\n * Return as DeepAgent with proper DeepAgentTypeConfig\n * - Response: InferStructuredResponse<TResponse> (unwraps ToolStrategy<T>/ProviderStrategy<T> → T)\n * - State: undefined (state comes from middleware)\n * - Context: ContextSchema\n * - Middleware: AllMiddleware (built-in + custom + subagent middleware for state inference)\n * - Tools: TTools\n * - Subagents: TSubagents (for type-safe streaming)\n */\n return agent as unknown as DeepAgent<\n DeepAgentTypeConfig<\n InferStructuredResponse<TResponse>,\n undefined,\n ContextSchema,\n AllMiddleware,\n TTools,\n TSubagents\n >\n >;\n}\n","/**\n * Configuration and settings for deepagents.\n *\n * Provides project detection, path management, and environment configuration\n * for skills and agent memory middleware.\n */\n\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport os from \"node:os\";\n\n/**\n * Options for creating a Settings instance.\n */\nexport interface SettingsOptions {\n /** Starting directory for project detection (defaults to cwd) */\n startPath?: string;\n}\n\n/**\n * Settings interface for project detection and path management.\n *\n * Provides access to:\n * - Project root detection (via .git directory)\n * - User-level deepagents directory (~/.deepagents)\n * - Agent-specific directories and files\n * - Skills directories (user and project level)\n */\nexport interface Settings {\n /** Detected project root directory, or null if not in a git project */\n readonly projectRoot: string | null;\n\n /** Base user-level .deepagents directory (~/.deepagents) */\n readonly userDeepagentsDir: string;\n\n /** Check if currently in a git project */\n readonly hasProject: boolean;\n\n /**\n * Get the agent directory path.\n * @param agentName - Name of the agent\n * @returns Path to ~/.deepagents/{agentName}\n * @throws Error if agent name is invalid\n */\n getAgentDir(agentName: string): string;\n\n /**\n * Ensure agent directory exists and return path.\n * @param agentName - Name of the agent\n * @returns Path to ~/.deepagents/{agentName}\n * @throws Error if agent name is invalid\n */\n ensureAgentDir(agentName: string): string;\n\n /**\n * Get user-level agent.md path for a specific agent.\n * @param agentName - Name of the agent\n * @returns Path to ~/.deepagents/{agentName}/agent.md\n */\n getUserAgentMdPath(agentName: string): string;\n\n /**\n * Get project-level agent.md path.\n * @returns Path to {projectRoot}/.deepagents/agent.md, or null if not in a project\n */\n getProjectAgentMdPath(): string | null;\n\n /**\n * Get user-level skills directory path for a specific agent.\n * @param agentName - Name of the agent\n * @returns Path to ~/.deepagents/{agentName}/skills/\n */\n getUserSkillsDir(agentName: string): string;\n\n /**\n * Ensure user-level skills directory exists and return path.\n * @param agentName - Name of the agent\n * @returns Path to ~/.deepagents/{agentName}/skills/\n */\n ensureUserSkillsDir(agentName: string): string;\n\n /**\n * Get project-level skills directory path.\n * @returns Path to {projectRoot}/.deepagents/skills/, or null if not in a project\n */\n getProjectSkillsDir(): string | null;\n\n /**\n * Ensure project-level skills directory exists and return path.\n * @returns Path to {projectRoot}/.deepagents/skills/, or null if not in a project\n */\n ensureProjectSkillsDir(): string | null;\n\n /**\n * Ensure project .deepagents directory exists.\n * @returns Path to {projectRoot}/.deepagents/, or null if not in a project\n */\n ensureProjectDeepagentsDir(): string | null;\n}\n\n/**\n * Find the project root by looking for .git directory.\n *\n * Walks up the directory tree from startPath (or cwd) looking for a .git\n * directory, which indicates the project root.\n *\n * @param startPath - Directory to start searching from. Defaults to current working directory.\n * @returns Path to the project root if found, null otherwise.\n */\nexport function findProjectRoot(startPath?: string): string | null {\n let current = path.resolve(startPath || process.cwd());\n\n // Walk up the directory tree\n while (current !== path.dirname(current)) {\n const gitDir = path.join(current, \".git\");\n if (fs.existsSync(gitDir)) {\n return current;\n }\n current = path.dirname(current);\n }\n\n // Check root directory as well\n const rootGitDir = path.join(current, \".git\");\n if (fs.existsSync(rootGitDir)) {\n return current;\n }\n\n return null;\n}\n\n/**\n * Validate agent name to prevent invalid filesystem paths and security issues.\n *\n * @param agentName - The agent name to validate\n * @returns True if valid, false otherwise\n */\nfunction isValidAgentName(agentName: string): boolean {\n if (!agentName || !agentName.trim()) {\n return false;\n }\n // Allow only alphanumeric, hyphens, underscores, and whitespace\n return /^[a-zA-Z0-9_\\-\\s]+$/.test(agentName);\n}\n\n/**\n * Create a Settings instance with detected environment.\n *\n * @param options - Configuration options\n * @returns Settings instance with project detection and path management\n */\nexport function createSettings(options: SettingsOptions = {}): Settings {\n const projectRoot = findProjectRoot(options.startPath);\n const userDeepagentsDir = path.join(os.homedir(), \".deepagents\");\n\n return {\n projectRoot,\n userDeepagentsDir,\n hasProject: projectRoot !== null,\n\n getAgentDir(agentName: string): string {\n if (!isValidAgentName(agentName)) {\n throw new Error(\n `Invalid agent name: ${JSON.stringify(agentName)}. ` +\n \"Agent names can only contain letters, numbers, hyphens, underscores, and spaces.\",\n );\n }\n return path.join(userDeepagentsDir, agentName);\n },\n\n ensureAgentDir(agentName: string): string {\n const agentDir = this.getAgentDir(agentName);\n fs.mkdirSync(agentDir, { recursive: true });\n return agentDir;\n },\n\n getUserAgentMdPath(agentName: string): string {\n return path.join(this.getAgentDir(agentName), \"agent.md\");\n },\n\n getProjectAgentMdPath(): string | null {\n if (!projectRoot) {\n return null;\n }\n return path.join(projectRoot, \".deepagents\", \"agent.md\");\n },\n\n getUserSkillsDir(agentName: string): string {\n return path.join(this.getAgentDir(agentName), \"skills\");\n },\n\n ensureUserSkillsDir(agentName: string): string {\n const skillsDir = this.getUserSkillsDir(agentName);\n fs.mkdirSync(skillsDir, { recursive: true });\n return skillsDir;\n },\n\n getProjectSkillsDir(): string | null {\n if (!projectRoot) {\n return null;\n }\n return path.join(projectRoot, \".deepagents\", \"skills\");\n },\n\n ensureProjectSkillsDir(): string | null {\n const skillsDir = this.getProjectSkillsDir();\n if (!skillsDir) {\n return null;\n }\n fs.mkdirSync(skillsDir, { recursive: true });\n return skillsDir;\n },\n\n ensureProjectDeepagentsDir(): string | null {\n if (!projectRoot) {\n return null;\n }\n const deepagentsDir = path.join(projectRoot, \".deepagents\");\n fs.mkdirSync(deepagentsDir, { recursive: true });\n return deepagentsDir;\n },\n };\n}\n","/**\n * Middleware for loading agent-specific long-term memory into the system prompt.\n *\n * This middleware loads the agent's long-term memory from agent.md files\n * and injects it into the system prompt. Memory is loaded from:\n * - User memory: ~/.deepagents/{agent_name}/agent.md\n * - Project memory: {project_root}/.deepagents/agent.md\n *\n * @deprecated Use `createMemoryMiddleware` from `./memory.js` instead.\n * This middleware uses direct filesystem access (Node.js fs module) which is not\n * portable across backends. The `createMemoryMiddleware` function uses the\n * `BackendProtocol` abstraction and follows the AGENTS.md specification.\n *\n * Migration example:\n * ```typescript\n * // Before (deprecated):\n * import { createAgentMemoryMiddleware } from \"./agent-memory.js\";\n * const middleware = createAgentMemoryMiddleware({ settings, assistantId });\n *\n * // After (recommended):\n * import { createMemoryMiddleware } from \"./memory.js\";\n * import { FilesystemBackend } from \"../backends/filesystem.js\";\n *\n * const middleware = createMemoryMiddleware({\n * backend: new FilesystemBackend({ rootDir: \"/\" }),\n * sources: [\n * `~/.deepagents/${assistantId}/AGENTS.md`,\n * `${projectRoot}/.deepagents/AGENTS.md`,\n * ],\n * });\n * ```\n */\n\nimport fs from \"node:fs\";\nimport { z } from \"zod\";\nimport {\n createMiddleware,\n /**\n * required for type inference\n */\n type AgentMiddleware as _AgentMiddleware,\n} from \"langchain\";\n\nimport type { Settings } from \"../config.js\";\n\n/**\n * Options for the agent memory middleware.\n */\nexport interface AgentMemoryMiddlewareOptions {\n /** Settings instance with project detection and paths */\n settings: Settings;\n\n /** The agent identifier */\n assistantId: string;\n\n /** Optional custom template for injecting agent memory into system prompt */\n systemPromptTemplate?: string;\n}\n\n/**\n * State schema for agent memory middleware.\n */\nconst AgentMemoryStateSchema = z.object({\n /** Personal preferences from ~/.deepagents/{agent}/ (applies everywhere) */\n userMemory: z.string().optional(),\n\n /** Project-specific context (loaded from project root) */\n projectMemory: z.string().optional(),\n});\n\n/**\n * Default template for memory injection.\n */\nconst DEFAULT_MEMORY_TEMPLATE = `<user_memory>\n{user_memory}\n</user_memory>\n\n<project_memory>\n{project_memory}\n</project_memory>`;\n\n/**\n * Long-term Memory Documentation system prompt.\n */\nconst LONGTERM_MEMORY_SYSTEM_PROMPT = `\n\n## Long-term Memory\n\nYour long-term memory is stored in files on the filesystem and persists across sessions.\n\n**User Memory Location**: \\`{agent_dir_absolute}\\` (displays as \\`{agent_dir_display}\\`)\n**Project Memory Location**: {project_memory_info}\n\nYour system prompt is loaded from TWO sources at startup:\n1. **User agent.md**: \\`{agent_dir_absolute}/agent.md\\` - Your personal preferences across all projects\n2. **Project agent.md**: Loaded from project root if available - Project-specific instructions\n\nProject-specific agent.md is loaded from these locations (both combined if both exist):\n- \\`[project-root]/.deepagents/agent.md\\` (preferred)\n- \\`[project-root]/agent.md\\` (fallback, but also included if both exist)\n\n**When to CHECK/READ memories (CRITICAL - do this FIRST):**\n- **At the start of ANY new session**: Check both user and project memories\n - User: \\`ls {agent_dir_absolute}\\`\n - Project: \\`ls {project_deepagents_dir}\\` (if in a project)\n- **BEFORE answering questions**: If asked \"what do you know about X?\" or \"how do I do Y?\", check project memories FIRST, then user\n- **When user asks you to do something**: Check if you have project-specific guides or examples\n- **When user references past work**: Search project memory files for related context\n\n**Memory-first response pattern:**\n1. User asks a question → Check project directory first: \\`ls {project_deepagents_dir}\\`\n2. If relevant files exist → Read them with \\`read_file '{project_deepagents_dir}/[filename]'\\`\n3. Check user memory if needed → \\`ls {agent_dir_absolute}\\`\n4. Base your answer on saved knowledge supplemented by general knowledge\n\n**When to update memories:**\n- **IMMEDIATELY when the user describes your role or how you should behave**\n- **IMMEDIATELY when the user gives feedback on your work** - Update memories to capture what was wrong and how to do it better\n- When the user explicitly asks you to remember something\n- When patterns or preferences emerge (coding styles, conventions, workflows)\n- After significant work where context would help in future sessions\n\n**Learning from feedback:**\n- When user says something is better/worse, capture WHY and encode it as a pattern\n- Each correction is a chance to improve permanently - don't just fix the immediate issue, update your instructions\n- When user says \"you should remember X\" or \"be careful about Y\", treat this as HIGH PRIORITY - update memories IMMEDIATELY\n- Look for the underlying principle behind corrections, not just the specific mistake\n\n## Deciding Where to Store Memory\n\nWhen writing or updating agent memory, decide whether each fact, configuration, or behavior belongs in:\n\n### User Agent File: \\`{agent_dir_absolute}/agent.md\\`\n→ Describes the agent's **personality, style, and universal behavior** across all projects.\n\n**Store here:**\n- Your general tone and communication style\n- Universal coding preferences (formatting, comment style, etc.)\n- General workflows and methodologies you follow\n- Tool usage patterns that apply everywhere\n- Personal preferences that don't change per-project\n\n**Examples:**\n- \"Be concise and direct in responses\"\n- \"Always use type hints in Python\"\n- \"Prefer functional programming patterns\"\n\n### Project Agent File: \\`{project_deepagents_dir}/agent.md\\`\n→ Describes **how this specific project works** and **how the agent should behave here only.**\n\n**Store here:**\n- Project-specific architecture and design patterns\n- Coding conventions specific to this codebase\n- Project structure and organization\n- Testing strategies for this project\n- Deployment processes and workflows\n- Team conventions and guidelines\n\n**Examples:**\n- \"This project uses FastAPI with SQLAlchemy\"\n- \"Tests go in tests/ directory mirroring src/ structure\"\n- \"All API changes require updating OpenAPI spec\"\n\n### Project Memory Files: \\`{project_deepagents_dir}/*.md\\`\n→ Use for **project-specific reference information** and structured notes.\n\n**Store here:**\n- API design documentation\n- Architecture decisions and rationale\n- Deployment procedures\n- Common debugging patterns\n- Onboarding information\n\n**Examples:**\n- \\`{project_deepagents_dir}/api-design.md\\` - REST API patterns used\n- \\`{project_deepagents_dir}/architecture.md\\` - System architecture overview\n- \\`{project_deepagents_dir}/deployment.md\\` - How to deploy this project\n\n### File Operations:\n\n**User memory:**\n\\`\\`\\`\nls {agent_dir_absolute} # List user memory files\nread_file '{agent_dir_absolute}/agent.md' # Read user preferences\nedit_file '{agent_dir_absolute}/agent.md' ... # Update user preferences\n\\`\\`\\`\n\n**Project memory (preferred for project-specific information):**\n\\`\\`\\`\nls {project_deepagents_dir} # List project memory files\nread_file '{project_deepagents_dir}/agent.md' # Read project instructions\nedit_file '{project_deepagents_dir}/agent.md' ... # Update project instructions\nwrite_file '{project_deepagents_dir}/agent.md' ... # Create project memory file\n\\`\\`\\`\n\n**Important**:\n- Project memory files are stored in \\`.deepagents/\\` inside the project root\n- Always use absolute paths for file operations\n- Check project memories BEFORE user when answering project-specific questions`;\n\n/**\n * Create middleware for loading agent-specific long-term memory.\n *\n * This middleware loads the agent's long-term memory from a file (agent.md)\n * and injects it into the system prompt. The memory is loaded once at the\n * start of the conversation and stored in state.\n *\n * @param options - Configuration options\n * @returns AgentMiddleware for memory loading and injection\n *\n * @deprecated Use `createMemoryMiddleware` from `./memory.js` instead.\n * This function uses direct filesystem access which limits portability.\n */\nexport function createAgentMemoryMiddleware(\n options: AgentMemoryMiddlewareOptions,\n) {\n const { settings, assistantId, systemPromptTemplate } = options;\n\n // Compute paths\n const agentDir = settings.getAgentDir(assistantId);\n const agentDirDisplay = `~/.deepagents/${assistantId}`;\n const agentDirAbsolute = agentDir;\n const projectRoot = settings.projectRoot;\n\n // Build project memory info for documentation\n const projectMemoryInfo = projectRoot\n ? `\\`${projectRoot}\\` (detected)`\n : \"None (not in a git project)\";\n\n // Build project deepagents directory path\n const projectDeepagentsDir = projectRoot\n ? `${projectRoot}/.deepagents`\n : \"[project-root]/.deepagents (not in a project)\";\n\n const template = systemPromptTemplate || DEFAULT_MEMORY_TEMPLATE;\n\n return createMiddleware({\n name: \"AgentMemoryMiddleware\",\n stateSchema: AgentMemoryStateSchema as any,\n\n beforeAgent(state: any) {\n const result: Record<string, string> = {};\n\n // Load user memory if not already in state\n if (!(\"userMemory\" in state)) {\n const userPath = settings.getUserAgentMdPath(assistantId);\n if (fs.existsSync(userPath)) {\n try {\n result.userMemory = fs.readFileSync(userPath, \"utf-8\");\n } catch {\n // Ignore read errors\n }\n }\n }\n\n // Load project memory if not already in state\n if (!(\"projectMemory\" in state)) {\n const projectPath = settings.getProjectAgentMdPath();\n if (projectPath && fs.existsSync(projectPath)) {\n try {\n result.projectMemory = fs.readFileSync(projectPath, \"utf-8\");\n } catch {\n // Ignore read errors\n }\n }\n }\n\n return Object.keys(result).length > 0 ? result : undefined;\n },\n\n wrapModelCall(request: any, handler: any) {\n // Extract memory from state\n const userMemory = request.state?.userMemory;\n const projectMemory = request.state?.projectMemory;\n const baseSystemPrompt = request.systemPrompt || \"\";\n\n // Format memory section with both memories\n const memorySection = template\n .replace(\"{user_memory}\", userMemory || \"(No user agent.md)\")\n .replace(\"{project_memory}\", projectMemory || \"(No project agent.md)\");\n\n // Format long-term memory documentation\n const memoryDocs = LONGTERM_MEMORY_SYSTEM_PROMPT.replaceAll(\n \"{agent_dir_absolute}\",\n agentDirAbsolute,\n )\n .replaceAll(\"{agent_dir_display}\", agentDirDisplay)\n .replaceAll(\"{project_memory_info}\", projectMemoryInfo)\n .replaceAll(\"{project_deepagents_dir}\", projectDeepagentsDir);\n\n // Memory content at start, base prompt in middle, documentation at end\n let systemPrompt = memorySection;\n if (baseSystemPrompt) {\n systemPrompt += \"\\n\\n\" + baseSystemPrompt;\n }\n systemPrompt += \"\\n\\n\" + memoryDocs;\n\n return handler({ ...request, systemPrompt });\n },\n });\n}\n","/**\n * Skill loader for parsing and loading agent skills from SKILL.md files.\n *\n * This module implements Anthropic's agent skills pattern with YAML frontmatter parsing.\n * Each skill is a directory containing a SKILL.md file with:\n * - YAML frontmatter (name, description required)\n * - Markdown instructions for the agent\n * - Optional supporting files (scripts, configs, etc.)\n *\n * @example\n * ```markdown\n * ---\n * name: web-research\n * description: Structured approach to conducting thorough web research\n * ---\n *\n * # Web Research Skill\n *\n * ## When to Use\n * - User asks you to research a topic\n * ...\n * ```\n *\n * @see https://agentskills.io/specification\n */\n\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport yaml from \"yaml\";\n\n/** Maximum size for SKILL.md files (10MB) */\nexport const MAX_SKILL_FILE_SIZE = 10 * 1024 * 1024;\n\n/** Agent Skills spec constraints */\nexport const MAX_SKILL_NAME_LENGTH = 64;\nexport const MAX_SKILL_DESCRIPTION_LENGTH = 1024;\n\n/** Pattern for validating skill names per Agent Skills spec */\nconst SKILL_NAME_PATTERN = /^[a-z0-9]+(-[a-z0-9]+)*$/;\n\n/** Pattern for extracting YAML frontmatter */\nconst FRONTMATTER_PATTERN = /^---\\s*\\n([\\s\\S]*?)\\n---\\s*\\n/;\n\n/**\n * Metadata for a skill per Agent Skills spec.\n * @see https://agentskills.io/specification\n */\nexport interface SkillMetadata {\n /** Name of the skill (max 64 chars, lowercase alphanumeric and hyphens) */\n name: string;\n\n /** Description of what the skill does (max 1024 chars) */\n description: string;\n\n /** Absolute path to the SKILL.md file */\n path: string;\n\n /** Source of the skill ('user' or 'project') */\n source: \"user\" | \"project\";\n\n /** Optional: License name or reference to bundled license file */\n license?: string;\n\n /** Optional: Environment requirements (max 500 chars) */\n compatibility?: string;\n\n /** Optional: Arbitrary key-value mapping for additional metadata */\n metadata?: Record<string, string>;\n\n /** Optional: Space-delimited list of pre-approved tools */\n allowedTools?: string;\n}\n\n/**\n * Options for listing skills.\n */\nexport interface ListSkillsOptions {\n /** Path to user-level skills directory */\n userSkillsDir?: string | null;\n\n /** Path to project-level skills directory */\n projectSkillsDir?: string | null;\n}\n\n/**\n * Result of skill name validation.\n */\ninterface ValidationResult {\n valid: boolean;\n error?: string;\n}\n\n/**\n * Check if a path is safely contained within base_dir.\n *\n * This prevents directory traversal attacks via symlinks or path manipulation.\n * The function resolves both paths to their canonical form (following symlinks)\n * and verifies that the target path is within the base directory.\n *\n * @param targetPath - The path to validate\n * @param baseDir - The base directory that should contain the path\n * @returns True if the path is safely within baseDir, false otherwise\n */\nfunction isSafePath(targetPath: string, baseDir: string): boolean {\n try {\n // Resolve both paths to their canonical form (follows symlinks)\n const resolvedPath = fs.realpathSync(targetPath);\n const resolvedBase = fs.realpathSync(baseDir);\n\n // Check if the resolved path is within the base directory\n return (\n resolvedPath.startsWith(resolvedBase + path.sep) ||\n resolvedPath === resolvedBase\n );\n } catch {\n // Error resolving paths (e.g., circular symlinks, too many levels)\n return false;\n }\n}\n\n/**\n * Validate skill name per Agent Skills spec.\n *\n * Requirements:\n * - Max 64 characters\n * - Lowercase alphanumeric and hyphens only (a-z, 0-9, -)\n * - Cannot start or end with hyphen\n * - No consecutive hyphens\n * - Must match parent directory name\n *\n * @param name - The skill name from YAML frontmatter\n * @param directoryName - The parent directory name\n * @returns Validation result with error message if invalid\n */\nfunction validateSkillName(\n name: string,\n directoryName: string,\n): ValidationResult {\n if (!name) {\n return { valid: false, error: \"name is required\" };\n }\n if (name.length > MAX_SKILL_NAME_LENGTH) {\n return { valid: false, error: \"name exceeds 64 characters\" };\n }\n // Pattern: lowercase alphanumeric, single hyphens between segments, no start/end hyphen\n if (!SKILL_NAME_PATTERN.test(name)) {\n return {\n valid: false,\n error: \"name must be lowercase alphanumeric with single hyphens only\",\n };\n }\n if (name !== directoryName) {\n return {\n valid: false,\n error: `name '${name}' must match directory name '${directoryName}'`,\n };\n }\n return { valid: true };\n}\n\n/**\n * Parse YAML frontmatter from content.\n *\n * @param content - The file content\n * @returns Parsed frontmatter object, or null if parsing fails\n */\nfunction parseFrontmatter(content: string): Record<string, unknown> | null {\n const match = content.match(FRONTMATTER_PATTERN);\n if (!match) {\n return null;\n }\n\n try {\n const parsed = yaml.parse(match[1]);\n return typeof parsed === \"object\" && parsed !== null ? parsed : null;\n } catch {\n return null;\n }\n}\n\n/**\n * Parse YAML frontmatter from a SKILL.md file per Agent Skills spec.\n *\n * @param skillMdPath - Path to the SKILL.md file\n * @param source - Source of the skill ('user' or 'project')\n * @returns SkillMetadata with all fields, or null if parsing fails\n */\nexport function parseSkillMetadata(\n skillMdPath: string,\n source: \"user\" | \"project\",\n): SkillMetadata | null {\n try {\n // Security: Check file size to prevent DoS attacks\n const stats = fs.statSync(skillMdPath);\n if (stats.size > MAX_SKILL_FILE_SIZE) {\n // eslint-disable-next-line no-console\n console.warn(\n `Skipping ${skillMdPath}: file too large (${stats.size} bytes)`,\n );\n return null;\n }\n\n const content = fs.readFileSync(skillMdPath, \"utf-8\");\n const frontmatter = parseFrontmatter(content);\n\n if (!frontmatter) {\n // eslint-disable-next-line no-console\n console.warn(`Skipping ${skillMdPath}: no valid YAML frontmatter found`);\n return null;\n }\n\n // Validate required fields\n const name = frontmatter.name;\n const description = frontmatter.description;\n\n if (!name || !description) {\n // eslint-disable-next-line no-console\n console.warn(\n `Skipping ${skillMdPath}: missing required 'name' or 'description'`,\n );\n return null;\n }\n\n // Validate name format per spec (warn but still load for backwards compatibility)\n const directoryName = path.basename(path.dirname(skillMdPath));\n const validation = validateSkillName(String(name), directoryName);\n if (!validation.valid) {\n // eslint-disable-next-line no-console\n console.warn(\n `Skill '${name}' in ${skillMdPath} does not follow Agent Skills spec: ${validation.error}. ` +\n \"Consider renaming to be spec-compliant.\",\n );\n }\n\n // Truncate description if too long (spec: max 1024 chars)\n let descriptionStr = String(description);\n if (descriptionStr.length > MAX_SKILL_DESCRIPTION_LENGTH) {\n // eslint-disable-next-line no-console\n console.warn(\n `Description exceeds ${MAX_SKILL_DESCRIPTION_LENGTH} chars in ${skillMdPath}, truncating`,\n );\n descriptionStr = descriptionStr.slice(0, MAX_SKILL_DESCRIPTION_LENGTH);\n }\n\n return {\n name: String(name),\n description: descriptionStr,\n path: skillMdPath,\n source,\n license: frontmatter.license ? String(frontmatter.license) : undefined,\n compatibility: frontmatter.compatibility\n ? String(frontmatter.compatibility)\n : undefined,\n metadata:\n frontmatter.metadata && typeof frontmatter.metadata === \"object\"\n ? (frontmatter.metadata as Record<string, string>)\n : undefined,\n allowedTools: frontmatter[\"allowed-tools\"]\n ? String(frontmatter[\"allowed-tools\"])\n : undefined,\n };\n } catch (error) {\n // eslint-disable-next-line no-console\n console.warn(`Error reading ${skillMdPath}: ${error}`);\n return null;\n }\n}\n\n/**\n * List all skills from a single skills directory (internal helper).\n *\n * Scans the skills directory for subdirectories containing SKILL.md files,\n * parses YAML frontmatter, and returns skill metadata.\n *\n * Skills are organized as:\n * ```\n * skills/\n * ├── skill-name/\n * │ ├── SKILL.md # Required: instructions with YAML frontmatter\n * │ ├── script.py # Optional: supporting files\n * │ └── config.json # Optional: supporting files\n * ```\n *\n * @param skillsDir - Path to the skills directory\n * @param source - Source of the skills ('user' or 'project')\n * @returns List of skill metadata\n */\nfunction listSkillsFromDir(\n skillsDir: string,\n source: \"user\" | \"project\",\n): SkillMetadata[] {\n // Check if skills directory exists\n const expandedDir = skillsDir.startsWith(\"~\")\n ? path.join(\n process.env.HOME || process.env.USERPROFILE || \"\",\n skillsDir.slice(1),\n )\n : skillsDir;\n\n if (!fs.existsSync(expandedDir)) {\n return [];\n }\n\n // Resolve base directory to canonical path for security checks\n let resolvedBase: string;\n try {\n resolvedBase = fs.realpathSync(expandedDir);\n } catch {\n // Can't resolve base directory, fail safe\n return [];\n }\n\n const skills: SkillMetadata[] = [];\n\n // Iterate through subdirectories\n let entries: fs.Dirent[];\n try {\n entries = fs.readdirSync(resolvedBase, { withFileTypes: true });\n } catch {\n return [];\n }\n\n for (const entry of entries) {\n const skillDir = path.join(resolvedBase, entry.name);\n\n // Security: Catch symlinks pointing outside the skills directory\n if (!isSafePath(skillDir, resolvedBase)) {\n continue;\n }\n\n if (!entry.isDirectory()) {\n continue;\n }\n\n // Look for SKILL.md file\n const skillMdPath = path.join(skillDir, \"SKILL.md\");\n if (!fs.existsSync(skillMdPath)) {\n continue;\n }\n\n // Security: Validate SKILL.md path is safe before reading\n if (!isSafePath(skillMdPath, resolvedBase)) {\n continue;\n }\n\n // Parse metadata\n const metadata = parseSkillMetadata(skillMdPath, source);\n if (metadata) {\n skills.push(metadata);\n }\n }\n\n return skills;\n}\n\n/**\n * List skills from user and/or project directories.\n *\n * When both directories are provided, project skills with the same name as\n * user skills will override them.\n *\n * @param options - Options specifying which directories to search\n * @returns Merged list of skill metadata from both sources, with project skills\n * taking precedence over user skills when names conflict\n */\nexport function listSkills(options: ListSkillsOptions): SkillMetadata[] {\n const allSkills: Map<string, SkillMetadata> = new Map();\n\n // Load user skills first (foundation)\n if (options.userSkillsDir) {\n const userSkills = listSkillsFromDir(options.userSkillsDir, \"user\");\n for (const skill of userSkills) {\n allSkills.set(skill.name, skill);\n }\n }\n\n // Load project skills second (override/augment)\n if (options.projectSkillsDir) {\n const projectSkills = listSkillsFromDir(\n options.projectSkillsDir,\n \"project\",\n );\n for (const skill of projectSkills) {\n // Project skills override user skills with the same name\n allSkills.set(skill.name, skill);\n }\n }\n\n return Array.from(allSkills.values());\n}\n"],"mappings":"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"}
|