@langchain/modal 0.1.0 → 0.1.1
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/dist/index.cjs +82 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +83 -4
- package/dist/index.d.ts +83 -4
- package/dist/index.js +82 -4
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
package/dist/index.cjs
CHANGED
|
@@ -97,7 +97,7 @@ const MODAL_SANDBOX_ERROR_SYMBOL = Symbol.for("modal.sandbox.error");
|
|
|
97
97
|
* }
|
|
98
98
|
* ```
|
|
99
99
|
*/
|
|
100
|
-
var ModalSandboxError = class ModalSandboxError extends
|
|
100
|
+
var ModalSandboxError = class ModalSandboxError extends deepagents.SandboxError {
|
|
101
101
|
[MODAL_SANDBOX_ERROR_SYMBOL];
|
|
102
102
|
/** Error name for instanceof checks and logging */
|
|
103
103
|
name = "ModalSandboxError";
|
|
@@ -109,7 +109,7 @@ var ModalSandboxError = class ModalSandboxError extends Error {
|
|
|
109
109
|
* @param cause - Original error that caused this error (for debugging)
|
|
110
110
|
*/
|
|
111
111
|
constructor(message, code, cause) {
|
|
112
|
-
super(message);
|
|
112
|
+
super(message, code, cause);
|
|
113
113
|
this.code = code;
|
|
114
114
|
this.cause = cause;
|
|
115
115
|
Object.setPrototypeOf(this, ModalSandboxError.prototype);
|
|
@@ -628,9 +628,89 @@ var ModalSandbox = class ModalSandbox extends deepagents.BaseSandbox {
|
|
|
628
628
|
}
|
|
629
629
|
}
|
|
630
630
|
};
|
|
631
|
+
/**
|
|
632
|
+
* Create an async factory function that creates a new Modal Sandbox per invocation.
|
|
633
|
+
*
|
|
634
|
+
* Each call to the factory will create and initialize a new sandbox.
|
|
635
|
+
* This is useful when you want fresh, isolated environments for each
|
|
636
|
+
* agent invocation.
|
|
637
|
+
*
|
|
638
|
+
* **Important**: This returns an async factory. For use with middleware that
|
|
639
|
+
* requires synchronous BackendFactory, use `createModalSandboxFactoryFromSandbox()`
|
|
640
|
+
* with a pre-created sandbox instead.
|
|
641
|
+
*
|
|
642
|
+
* @param options - Optional configuration for sandbox creation
|
|
643
|
+
* @returns An async factory function that creates new sandboxes
|
|
644
|
+
*
|
|
645
|
+
* @example
|
|
646
|
+
* ```typescript
|
|
647
|
+
* import { ModalSandbox, createModalSandboxFactory } from "@langchain/modal";
|
|
648
|
+
*
|
|
649
|
+
* // Create a factory for new sandboxes
|
|
650
|
+
* const factory = createModalSandboxFactory({ imageName: "python:3.12-slim" });
|
|
651
|
+
*
|
|
652
|
+
* // Each call creates a new sandbox
|
|
653
|
+
* const sandbox1 = await factory();
|
|
654
|
+
* const sandbox2 = await factory();
|
|
655
|
+
*
|
|
656
|
+
* try {
|
|
657
|
+
* // Use sandboxes...
|
|
658
|
+
* } finally {
|
|
659
|
+
* await sandbox1.close();
|
|
660
|
+
* await sandbox2.close();
|
|
661
|
+
* }
|
|
662
|
+
* ```
|
|
663
|
+
*/
|
|
664
|
+
function createModalSandboxFactory(options) {
|
|
665
|
+
return async () => {
|
|
666
|
+
return await ModalSandbox.create(options);
|
|
667
|
+
};
|
|
668
|
+
}
|
|
669
|
+
/**
|
|
670
|
+
* Create a backend factory that reuses an existing Modal Sandbox.
|
|
671
|
+
*
|
|
672
|
+
* This allows multiple agent invocations to share the same sandbox,
|
|
673
|
+
* avoiding the startup overhead of creating new sandboxes.
|
|
674
|
+
*
|
|
675
|
+
* Important: You are responsible for managing the sandbox lifecycle
|
|
676
|
+
* (calling `close()` when done).
|
|
677
|
+
*
|
|
678
|
+
* @param sandbox - An existing ModalSandbox instance (must be initialized)
|
|
679
|
+
* @returns A BackendFactory that returns the provided sandbox
|
|
680
|
+
*
|
|
681
|
+
* @example
|
|
682
|
+
* ```typescript
|
|
683
|
+
* import { createDeepAgent, createFilesystemMiddleware } from "deepagents";
|
|
684
|
+
* import { ModalSandbox, createModalSandboxFactoryFromSandbox } from "@langchain/modal";
|
|
685
|
+
*
|
|
686
|
+
* // Create and initialize a sandbox
|
|
687
|
+
* const sandbox = await ModalSandbox.create({ imageName: "python:3.12-slim" });
|
|
688
|
+
*
|
|
689
|
+
* try {
|
|
690
|
+
* const agent = createDeepAgent({
|
|
691
|
+
* model: new ChatAnthropic({ model: "claude-sonnet-4-20250514" }),
|
|
692
|
+
* systemPrompt: "You are a coding assistant.",
|
|
693
|
+
* middlewares: [
|
|
694
|
+
* createFilesystemMiddleware({
|
|
695
|
+
* backend: createModalSandboxFactoryFromSandbox(sandbox),
|
|
696
|
+
* }),
|
|
697
|
+
* ],
|
|
698
|
+
* });
|
|
699
|
+
*
|
|
700
|
+
* await agent.invoke({ messages: [...] });
|
|
701
|
+
* } finally {
|
|
702
|
+
* await sandbox.close();
|
|
703
|
+
* }
|
|
704
|
+
* ```
|
|
705
|
+
*/
|
|
706
|
+
function createModalSandboxFactoryFromSandbox(sandbox) {
|
|
707
|
+
return () => sandbox;
|
|
708
|
+
}
|
|
631
709
|
|
|
632
710
|
//#endregion
|
|
633
711
|
exports.ModalSandbox = ModalSandbox;
|
|
634
712
|
exports.ModalSandboxError = ModalSandboxError;
|
|
713
|
+
exports.createModalSandboxFactory = createModalSandboxFactory;
|
|
714
|
+
exports.createModalSandboxFactoryFromSandbox = createModalSandboxFactoryFromSandbox;
|
|
635
715
|
exports.getAuthCredentials = getAuthCredentials;
|
|
636
716
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":["BaseSandbox","#id","#sandbox","#client","#options","ModalClient","#app","#uploadInitialFiles","#mapError","#setFromExisting"],"sources":["../src/auth.ts","../src/types.ts","../src/sandbox.ts"],"sourcesContent":["/**\n * Authentication utilities for Modal Sandbox.\n *\n * This module provides authentication credential resolution for the Modal SDK.\n *\n * @packageDocumentation\n */\n\nimport type { ModalSandboxOptions } from \"./types.js\";\n\n/**\n * Authentication credentials for Modal API.\n */\nexport interface ModalCredentials {\n /** Modal token ID */\n tokenId: string;\n /** Modal token secret */\n tokenSecret: string;\n}\n\n/**\n * Get authentication credentials for Modal API.\n *\n * Credentials are resolved in the following priority order:\n *\n * 1. **Explicit options**: If `options.tokenId` and/or `options.tokenSecret` are provided,\n * they are used directly.\n * 2. **Environment variables**: `MODAL_TOKEN_ID` and `MODAL_TOKEN_SECRET` are used as fallbacks.\n *\n * ## Environment Variable Setup\n *\n * ```bash\n * # Go to https://modal.com/settings/tokens\n * # Create a new token and set the environment variables\n * export MODAL_TOKEN_ID=your_token_id\n * export MODAL_TOKEN_SECRET=your_token_secret\n * ```\n *\n * @param options - Optional authentication configuration from ModalSandboxOptions\n * @returns Complete authentication credentials\n * @throws {Error} If any credentials are missing\n *\n * @example\n * ```typescript\n * // With explicit credentials\n * const creds = getAuthCredentials({ tokenId: \"...\", tokenSecret: \"...\" });\n *\n * // Using environment variables (auto-detected)\n * const creds = getAuthCredentials();\n *\n * // From ModalSandboxOptions\n * const options: ModalSandboxOptions = {\n * auth: { tokenId: \"...\", tokenSecret: \"...\" }\n * };\n * const creds = getAuthCredentials(options.auth);\n * ```\n */\nexport function getAuthCredentials(\n options?: ModalSandboxOptions[\"auth\"],\n): ModalCredentials {\n // Resolve token ID: explicit option first, then environment variable\n const tokenId = options?.tokenId || process.env.MODAL_TOKEN_ID;\n\n // Resolve token secret: explicit option first, then environment variable\n const tokenSecret = options?.tokenSecret || process.env.MODAL_TOKEN_SECRET;\n\n // Check what's missing and build appropriate error message\n const missingTokenId = !tokenId;\n const missingTokenSecret = !tokenSecret;\n\n if (missingTokenId || missingTokenSecret) {\n const missing: string[] = [];\n if (missingTokenId) missing.push(\"MODAL_TOKEN_ID\");\n if (missingTokenSecret) missing.push(\"MODAL_TOKEN_SECRET\");\n\n throw new Error(\n `Modal authentication required. Missing: ${missing.join(\", \")}.\\n\\n` +\n \"Provide credentials using one of these methods:\\n\\n\" +\n \"1. Set environment variables:\\n\" +\n \" Go to https://modal.com/settings/tokens\\n\" +\n \" Create a new token and run:\\n\" +\n \" export MODAL_TOKEN_ID=your_token_id\\n\" +\n \" export MODAL_TOKEN_SECRET=your_token_secret\\n\\n\" +\n \"2. Pass credentials directly in options:\\n\" +\n \" new ModalSandbox({ auth: { tokenId: '...', tokenSecret: '...' } })\",\n );\n }\n\n return { tokenId, tokenSecret };\n}\n","/**\n * Type definitions for the Modal Sandbox backend.\n *\n * This module contains all type definitions for the @langchain/modal package,\n * including options and error types.\n */\n\nimport type { SandboxCreateParams } from \"modal\";\n\n/**\n * Fields from SandboxCreateParams that we wrap with a different API:\n * - `volumes` -> we accept volume names (strings), SDK needs Volume objects\n * - `secrets` -> we accept secret names (strings), SDK needs Secret objects\n *\n * Fields not exposed yet:\n * - `cloudBucketMounts`, `proxy`, `experimentalOptions`, `customDomain`\n * - `command`, `pty`, `encryptedPorts`, `h2Ports`, `unencryptedPorts`, `cloud`\n */\ntype WrappedSdkFields =\n | \"secrets\"\n | \"volumes\"\n | \"cloudBucketMounts\"\n | \"proxy\"\n | \"experimentalOptions\"\n | \"customDomain\"\n | \"command\"\n | \"pty\"\n | \"encryptedPorts\"\n | \"h2Ports\"\n | \"unencryptedPorts\"\n | \"cloud\";\n\n/**\n * SDK options that pass through directly.\n */\ntype BaseSdkOptions = Omit<SandboxCreateParams, WrappedSdkFields>;\n\n/**\n * Configuration options for creating a Modal Sandbox.\n *\n * Extends the Modal SDK's SandboxCreateParams with additional options\n * for app/image configuration and a simplified volumes/secrets API.\n *\n * @example\n * ```typescript\n * const options: ModalSandboxOptions = {\n * appName: \"my-sandbox-app\",\n * imageName: \"python:3.12-slim\",\n * timeoutMs: 600_000, // 10 minutes\n * memoryMiB: 2048, // 2GB\n * initialFiles: {\n * \"/app/index.js\": \"console.log('Hello')\",\n * },\n * };\n * ```\n */\nexport interface ModalSandboxOptions extends BaseSdkOptions {\n /**\n * Name of the Modal App to associate the sandbox with.\n * If not provided, a default app name will be used.\n * The app will be created if it doesn't exist.\n *\n * @default \"deepagents-sandbox\"\n */\n appName?: string;\n\n /**\n * Docker image to use for the sandbox container.\n * Can be any public Docker image or a Modal Image reference.\n *\n * @default \"alpine:3.21\"\n *\n * @example\n * ```typescript\n * // Use Python image\n * imageName: \"python:3.12-slim\"\n *\n * // Use Node.js image\n * imageName: \"node:20-slim\"\n * ```\n */\n imageName?: string;\n\n /**\n * Modal Volume names to mount, mapped to their mount paths.\n * Volumes must be created beforehand in Modal.\n *\n * Unlike the SDK which requires Volume objects, we accept volume names\n * and look them up automatically.\n *\n * @example\n * ```typescript\n * volumes: {\n * \"/data\": \"my-data-volume\",\n * \"/cache\": \"my-cache-volume\"\n * }\n * ```\n */\n volumes?: Record<string, string>;\n\n /**\n * Modal Secret names to inject into the sandbox environment.\n * Secrets must be created beforehand in Modal.\n *\n * Unlike the SDK which requires Secret objects, we accept secret names\n * and look them up automatically.\n *\n * @example\n * ```typescript\n * secrets: [\"my-api-keys\", \"database-credentials\"]\n * ```\n */\n secrets?: string[];\n\n /**\n * Initial files to populate the sandbox with.\n *\n * Keys are file paths (relative to the working directory), values are file contents.\n * Parent directories will be created automatically if they don't exist.\n *\n * @example\n * ```typescript\n * initialFiles: {\n * \"/src/index.js\": \"console.log('Hello')\",\n * \"/package.json\": '{\"name\": \"my-app\"}',\n * }\n * ```\n */\n initialFiles?: Record<string, string | Uint8Array>;\n\n /**\n * Authentication configuration for Modal API.\n *\n * ### Environment Variable Setup\n *\n * ```bash\n * # Create a token at https://modal.com/settings/tokens\n * export MODAL_TOKEN_ID=your_token_id\n * export MODAL_TOKEN_SECRET=your_token_secret\n * ```\n *\n * Or pass the credentials directly in this auth configuration.\n */\n auth?: {\n /**\n * Modal token ID.\n * If not provided, reads from `MODAL_TOKEN_ID` environment variable.\n */\n tokenId?: string;\n\n /**\n * Modal token secret.\n * If not provided, reads from `MODAL_TOKEN_SECRET` environment variable.\n */\n tokenSecret?: string;\n };\n}\n\n/**\n * Error codes for Modal Sandbox operations.\n *\n * Used to identify specific error conditions and handle them appropriately.\n */\nexport type ModalSandboxErrorCode =\n /** Sandbox has not been initialized - call initialize() first */\n | \"NOT_INITIALIZED\"\n /** Sandbox is already initialized - cannot initialize twice */\n | \"ALREADY_INITIALIZED\"\n /** Authentication failed - check token configuration */\n | \"AUTHENTICATION_FAILED\"\n /** Failed to create sandbox - check options and quotas */\n | \"SANDBOX_CREATION_FAILED\"\n /** Sandbox not found - may have been stopped or expired */\n | \"SANDBOX_NOT_FOUND\"\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 /** Resource limits exceeded (CPU, memory, storage) */\n | \"RESOURCE_LIMIT_EXCEEDED\"\n /** Volume operation failed */\n | \"VOLUME_ERROR\";\n\nconst MODAL_SANDBOX_ERROR_SYMBOL = Symbol.for(\"modal.sandbox.error\");\n\n/**\n * Custom error class for Modal Sandbox operations.\n *\n * Provides structured error information including:\n * - Human-readable message\n * - Error code for programmatic handling\n * - Original cause for debugging\n *\n * @example\n * ```typescript\n * try {\n * await sandbox.execute(\"some command\");\n * } catch (error) {\n * if (error instanceof ModalSandboxError) {\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 ModalSandboxError extends Error {\n [MODAL_SANDBOX_ERROR_SYMBOL]: true;\n\n /** Error name for instanceof checks and logging */\n override readonly name = \"ModalSandboxError\";\n\n /**\n * Creates a new ModalSandboxError.\n *\n * @param message - Human-readable error description\n * @param code - Structured error code for programmatic handling\n * @param cause - Original error that caused this error (for debugging)\n */\n constructor(\n message: string,\n public readonly code: ModalSandboxErrorCode,\n public override readonly cause?: Error,\n ) {\n super(message);\n // Maintain proper prototype chain for instanceof checks\n Object.setPrototypeOf(this, ModalSandboxError.prototype);\n }\n\n /**\n * Checks if the error is an instance of ModalSandboxError.\n *\n * @param error - The error to check\n * @returns True if the error is an instance of ModalSandboxError, false otherwise\n */\n static isInstance(error: unknown): error is ModalSandboxError {\n return (\n typeof error === \"object\" &&\n error !== null &&\n (error as Record<symbol, unknown>)[MODAL_SANDBOX_ERROR_SYMBOL] === true\n );\n }\n}\n","/* eslint-disable no-instanceof/no-instanceof */\n/**\n * Modal Sandbox implementation of the SandboxBackendProtocol.\n *\n * This module provides a Modal Sandbox backend for deepagents, enabling agents\n * to execute commands, read/write files, and manage isolated container\n * environments using Modal's serverless infrastructure.\n *\n * @packageDocumentation\n */\n\nimport { ModalClient } from \"modal\";\nimport type { App, Sandbox, Image, SandboxCreateParams } from \"modal\";\nimport {\n BaseSandbox,\n type ExecuteResponse,\n type FileDownloadResponse,\n type FileOperationError,\n type FileUploadResponse,\n} from \"deepagents\";\n\nimport { getAuthCredentials } from \"./auth.js\";\nimport { ModalSandboxError, type ModalSandboxOptions } from \"./types.js\";\n\n/**\n * Modal Sandbox backend for deepagents.\n *\n * Extends `BaseSandbox` to provide command execution, file operations, and\n * sandbox lifecycle management using Modal's serverless infrastructure.\n *\n * ## Basic Usage\n *\n * ```typescript\n * import { ModalSandbox } from \"@langchain/modal\";\n *\n * // Create and initialize a sandbox\n * const sandbox = await ModalSandbox.create({\n * imageName: \"python:3.12-slim\",\n * timeout: 600,\n * });\n *\n * try {\n * // Execute commands\n * const result = await sandbox.execute(\"python --version\");\n * console.log(result.output);\n * } finally {\n * // Always cleanup\n * await sandbox.close();\n * }\n * ```\n *\n * ## Using with DeepAgent\n *\n * ```typescript\n * import { createDeepAgent } from \"deepagents\";\n * import { ModalSandbox } from \"@langchain/modal\";\n *\n * const sandbox = await ModalSandbox.create();\n *\n * const agent = createDeepAgent({\n * model: new ChatAnthropic({ model: \"claude-sonnet-4-20250514\" }),\n * systemPrompt: \"You are a coding assistant with sandbox access.\",\n * backend: sandbox,\n * });\n * ```\n */\nexport class ModalSandbox extends BaseSandbox {\n /** Private reference to the Modal client */\n #client: ModalClient | null = null;\n\n /** Private reference to the Modal App */\n #app: App | null = null;\n\n /** Private reference to the underlying Modal Sandbox instance */\n #sandbox: Sandbox | null = null;\n\n /** Configuration options for this sandbox */\n #options: ModalSandboxOptions;\n\n /** Unique identifier for this sandbox instance */\n #id: string;\n\n /**\n * Get the unique identifier for this sandbox.\n *\n * Before initialization, returns a temporary ID.\n * After initialization, returns the actual Modal sandbox ID.\n */\n get id(): string {\n return this.#id;\n }\n\n /**\n * Get the underlying Modal Sandbox instance.\n *\n * @throws {ModalSandboxError} If the sandbox is not initialized\n *\n * @example\n * ```typescript\n * const sandbox = await ModalSandbox.create();\n * const modalInstance = sandbox.instance; // Access the raw Modal Sandbox\n * ```\n */\n get instance(): Sandbox {\n if (!this.#sandbox) {\n throw new ModalSandboxError(\n \"Sandbox not initialized. Call initialize() or use ModalSandbox.create()\",\n \"NOT_INITIALIZED\",\n );\n }\n return this.#sandbox;\n }\n\n /**\n * Get the underlying Modal client instance.\n *\n * @throws {ModalSandboxError} If the sandbox is not initialized\n *\n * @example\n * ```typescript\n * const sandbox = await ModalSandbox.create();\n * const modalClient = sandbox.client; // Access the raw Modal client\n * ```\n */\n get client(): ModalClient {\n if (!this.#client) {\n throw new ModalSandboxError(\n \"Sandbox not initialized. Call initialize() or use ModalSandbox.create()\",\n \"NOT_INITIALIZED\",\n );\n }\n return this.#client;\n }\n\n /**\n * Check if the sandbox is initialized and running.\n */\n get isRunning(): boolean {\n return this.#sandbox !== null;\n }\n\n /**\n * Create a new ModalSandbox instance.\n *\n * Note: This only creates the instance. Call `initialize()` to actually\n * create the Modal Sandbox, or use the static `ModalSandbox.create()` method.\n *\n * @param options - Configuration options for the sandbox\n *\n * @example\n * ```typescript\n * // Two-step initialization\n * const sandbox = new ModalSandbox({ imageName: \"python:3.12-slim\" });\n * await sandbox.initialize();\n *\n * // Or use the factory method\n * const sandbox = await ModalSandbox.create({ imageName: \"python:3.12-slim\" });\n * ```\n */\n constructor(options: ModalSandboxOptions = {}) {\n super();\n\n // Set defaults for our custom options only\n // SDK options (timeoutMs, etc.) use SDK defaults\n this.#options = {\n appName: \"deepagents-sandbox\",\n imageName: \"alpine:3.21\",\n ...options,\n };\n\n // Generate temporary ID until initialized\n this.#id = `modal-sandbox-${Date.now()}`;\n }\n\n /**\n * Initialize the sandbox by creating a new Modal Sandbox instance.\n *\n * This method authenticates with Modal and provisions a new sandbox container.\n * After initialization, the `id` property will reflect the actual Modal sandbox ID.\n *\n * @throws {ModalSandboxError} If already initialized (`ALREADY_INITIALIZED`)\n * @throws {ModalSandboxError} If authentication fails (`AUTHENTICATION_FAILED`)\n * @throws {ModalSandboxError} If sandbox creation fails (`SANDBOX_CREATION_FAILED`)\n *\n * @example\n * ```typescript\n * const sandbox = new ModalSandbox();\n * await sandbox.initialize();\n * console.log(`Sandbox ID: ${sandbox.id}`);\n * ```\n */\n async initialize(): Promise<void> {\n // Prevent double initialization\n if (this.#sandbox) {\n throw new ModalSandboxError(\n \"Sandbox is already initialized. Each ModalSandbox instance can only be initialized once.\",\n \"ALREADY_INITIALIZED\",\n );\n }\n\n // Validate authentication credentials exist\n try {\n getAuthCredentials(this.#options.auth);\n } catch (error) {\n throw new ModalSandboxError(\n \"Failed to authenticate with Modal. Check your token configuration.\",\n \"AUTHENTICATION_FAILED\",\n error instanceof Error ? error : undefined,\n );\n }\n\n try {\n // Create Modal client\n this.#client = new ModalClient();\n\n // Get or create the app\n this.#app = await this.#client.apps.fromName(\n this.#options.appName ?? \"deepagents-sandbox\",\n { createIfMissing: true },\n );\n\n // Create the image\n const image: Image = this.#client.images.fromRegistry(\n this.#options.imageName ?? \"alpine:3.21\",\n );\n\n // Build sandbox creation options\n // Extract our custom fields, pass everything else through to SDK\n const {\n appName: _appName,\n imageName: _imageName,\n initialFiles: _initialFiles,\n auth: _auth,\n volumes: volumeNames,\n secrets: secretNames,\n ...sdkOptions\n } = this.#options;\n\n const createOptions: SandboxCreateParams = { ...sdkOptions };\n\n // Handle volumes - look up Volume objects from names\n if (volumeNames !== undefined) {\n const volumeObjects: SandboxCreateParams[\"volumes\"] = {};\n for (const [mountPath, volumeName] of Object.entries(volumeNames)) {\n const volume = await this.#client.volumes.fromName(volumeName, {\n createIfMissing: false,\n });\n volumeObjects[mountPath] = volume;\n }\n createOptions.volumes = volumeObjects;\n }\n\n // Handle secrets - look up Secret objects from names\n if (secretNames !== undefined && secretNames.length > 0) {\n const secretObjects: SandboxCreateParams[\"secrets\"] = [];\n for (const secretName of secretNames) {\n const secret = await this.#client.secrets.fromName(secretName);\n secretObjects.push(secret);\n }\n createOptions.secrets = secretObjects;\n }\n\n // Create the sandbox\n this.#sandbox = await this.#client.sandboxes.create(\n this.#app,\n image,\n createOptions,\n );\n\n // Update ID to the actual sandbox ID\n this.#id = this.#sandbox.sandboxId;\n\n // Upload initial files if provided\n if (this.#options.initialFiles) {\n await this.#uploadInitialFiles(this.#options.initialFiles);\n }\n } catch (error) {\n // If it's already a ModalSandboxError, re-throw it\n if (ModalSandboxError.isInstance(error)) {\n throw error;\n }\n\n throw new ModalSandboxError(\n `Failed to create Modal Sandbox: ${error instanceof Error ? error.message : String(error)}`,\n \"SANDBOX_CREATION_FAILED\",\n error instanceof Error ? error : undefined,\n );\n }\n }\n\n /**\n * Upload initial files to the sandbox during initialization.\n * This is a private helper method used by initialize().\n *\n * @param files - Record of file paths to contents\n */\n async #uploadInitialFiles(\n files: Record<string, string | Uint8Array>,\n ): Promise<void> {\n const encoder = new TextEncoder();\n const filesToUpload: Array<[string, Uint8Array]> = [];\n\n for (const [filePath, content] of Object.entries(files)) {\n // Normalize the path - remove leading slash if present for consistency\n const normalizedPath = filePath.startsWith(\"/\")\n ? filePath.slice(1)\n : filePath;\n\n // Convert string content to Uint8Array\n const data =\n typeof content === \"string\" ? encoder.encode(content) : content;\n\n filesToUpload.push([normalizedPath, data]);\n }\n\n // Use the existing uploadFiles method\n const results = await this.uploadFiles(filesToUpload);\n\n // Check for errors\n const errors = results.filter((r) => r.error !== null);\n if (errors.length > 0) {\n const errorPaths = errors.map((e) => e.path).join(\", \");\n throw new ModalSandboxError(\n `Failed to upload initial files: ${errorPaths}`,\n \"FILE_OPERATION_FAILED\",\n );\n }\n }\n\n /**\n * Execute a command in the sandbox.\n *\n * Commands are run using bash -c to execute the command string.\n *\n * @param command - The shell command to execute\n * @returns Execution result with output, exit code, and truncation flag\n * @throws {ModalSandboxError} If the sandbox is not initialized\n *\n * @example\n * ```typescript\n * const result = await sandbox.execute(\"echo 'Hello World'\");\n * console.log(result.output); // \"Hello World\\n\"\n * console.log(result.exitCode); // 0\n * ```\n */\n async execute(command: string): Promise<ExecuteResponse> {\n const sandbox = this.instance; // Throws if not initialized\n\n try {\n // Execute using bash -c to handle shell features\n const process = await sandbox.exec([\"bash\", \"-c\", command], {\n stdout: \"pipe\",\n stderr: \"pipe\",\n });\n\n // Read both stdout and stderr\n const [stdout, stderr] = await Promise.all([\n process.stdout.readText(),\n process.stderr.readText(),\n ]);\n\n // Wait for the process to complete and get exit code\n const exitCode = await process.wait();\n\n return {\n output: stdout + stderr,\n exitCode: exitCode ?? 0,\n truncated: false,\n };\n } catch (error) {\n // Check for timeout\n if (error instanceof Error && error.message.includes(\"timeout\")) {\n throw new ModalSandboxError(\n `Command timed out: ${command}`,\n \"COMMAND_TIMEOUT\",\n error,\n );\n }\n\n throw new ModalSandboxError(\n `Command execution failed: ${error instanceof Error ? error.message : String(error)}`,\n \"COMMAND_FAILED\",\n error instanceof Error ? error : undefined,\n );\n }\n }\n\n /**\n * Upload files to the sandbox.\n *\n * Files are written to the sandbox filesystem using Modal's file API.\n * Parent directories are created automatically if they don't exist.\n *\n * @param files - Array of [path, content] tuples to upload\n * @returns Upload result for each file, with success or error status\n *\n * @example\n * ```typescript\n * const encoder = new TextEncoder();\n * const results = await sandbox.uploadFiles([\n * [\"src/index.js\", encoder.encode(\"console.log('Hello')\")],\n * [\"package.json\", encoder.encode('{\"name\": \"test\"}')],\n * ]);\n * ```\n */\n async uploadFiles(\n files: Array<[string, Uint8Array]>,\n ): Promise<FileUploadResponse[]> {\n const sandbox = this.instance; // Throws if not initialized\n const results: FileUploadResponse[] = [];\n\n for (const [path, content] of files) {\n try {\n // Ensure parent directory exists\n const parentDir = path.substring(0, path.lastIndexOf(\"/\"));\n if (parentDir) {\n await sandbox\n .exec([\"mkdir\", \"-p\", parentDir], {\n stdout: \"pipe\",\n stderr: \"pipe\",\n })\n .then((p) => p.wait());\n }\n\n // Write the file content using Modal's file API\n const writeHandle = await sandbox.open(path, \"w\");\n await writeHandle.write(content);\n await writeHandle.close();\n\n results.push({ path, error: null });\n } catch (error) {\n results.push({ path, error: this.#mapError(error) });\n }\n }\n\n return results;\n }\n\n /**\n * Download files from the sandbox.\n *\n * Each file is read individually using Modal's file API, allowing\n * partial success when some files exist and others don't.\n *\n * @param paths - Array of file paths to download\n * @returns Download result for each file, with content or error\n *\n * @example\n * ```typescript\n * const results = await sandbox.downloadFiles([\"src/index.js\", \"missing.txt\"]);\n * for (const result of results) {\n * if (result.content) {\n * console.log(new TextDecoder().decode(result.content));\n * } else {\n * console.error(`Error: ${result.error}`);\n * }\n * }\n * ```\n */\n async downloadFiles(paths: string[]): Promise<FileDownloadResponse[]> {\n const sandbox = this.instance; // Throws if not initialized\n const results: FileDownloadResponse[] = [];\n\n for (const path of paths) {\n try {\n // Read the file content using Modal's file API\n const readHandle = await sandbox.open(path, \"r\");\n const content = await readHandle.read();\n await readHandle.close();\n\n results.push({\n path,\n content: new Uint8Array(content),\n error: null,\n });\n } catch (error) {\n results.push({\n path,\n content: null,\n error: this.#mapError(error),\n });\n }\n }\n\n return results;\n }\n\n /**\n * Close the sandbox and release all resources.\n *\n * After closing, the sandbox cannot be used again. This terminates\n * the sandbox container on Modal.\n *\n * @example\n * ```typescript\n * try {\n * await sandbox.execute(\"npm run build\");\n * } finally {\n * await sandbox.close();\n * }\n * ```\n */\n async close(): Promise<void> {\n if (this.#sandbox) {\n try {\n await this.#sandbox.terminate();\n } finally {\n this.#sandbox = null;\n this.#app = null;\n this.#client = null;\n }\n }\n }\n\n /**\n * Terminate the sandbox.\n *\n * Alias for close() for Modal SDK compatibility.\n *\n * @example\n * ```typescript\n * await sandbox.terminate();\n * ```\n */\n async terminate(): Promise<void> {\n await this.close();\n }\n\n /**\n * Alias for close() to maintain compatibility with other sandbox implementations.\n */\n async stop(): Promise<void> {\n await this.close();\n }\n\n /**\n * Poll the sandbox status to check if it has finished running.\n *\n * @returns The exit code if the sandbox has finished, or null if still running\n */\n async poll(): Promise<number | null> {\n if (!this.#sandbox) {\n return null;\n }\n return this.#sandbox.poll();\n }\n\n /**\n * Wait for the sandbox to finish running.\n *\n * @returns The exit code of the sandbox\n * @throws {ModalSandboxError} If the sandbox is not initialized\n */\n async wait(): Promise<number> {\n return this.instance.wait();\n }\n\n /**\n * Set the sandbox from an existing Modal Sandbox instance.\n * Used internally by the static `fromId()` and `fromName()` methods.\n */\n #setFromExisting(\n client: ModalClient,\n existingSandbox: Sandbox,\n sandboxId: string,\n ): void {\n this.#client = client;\n this.#sandbox = existingSandbox;\n this.#id = sandboxId;\n }\n\n /**\n * Map Modal SDK errors to standardized FileOperationError codes.\n *\n * @param error - The error from the Modal SDK\n * @returns A standardized error code\n */\n #mapError(error: unknown): FileOperationError {\n if (error instanceof Error) {\n const msg = error.message.toLowerCase();\n\n if (msg.includes(\"not found\") || msg.includes(\"enoent\")) {\n return \"file_not_found\";\n }\n if (msg.includes(\"permission\") || msg.includes(\"eacces\")) {\n return \"permission_denied\";\n }\n if (msg.includes(\"directory\") || msg.includes(\"eisdir\")) {\n return \"is_directory\";\n }\n }\n\n return \"invalid_path\";\n }\n\n // ============================================================================\n // Static Factory Methods\n // ============================================================================\n\n /**\n * Create and initialize a new ModalSandbox in one step.\n *\n * This is the recommended way to create a sandbox. It combines\n * construction and initialization into a single async operation.\n *\n * @param options - Configuration options for the sandbox\n * @returns An initialized and ready-to-use sandbox\n *\n * @example\n * ```typescript\n * const sandbox = await ModalSandbox.create({\n * imageName: \"python:3.12-slim\",\n * timeout: 600,\n * memory: 2048,\n * });\n * ```\n */\n static async create(options?: ModalSandboxOptions): Promise<ModalSandbox> {\n const sandbox = new ModalSandbox(options);\n await sandbox.initialize();\n return sandbox;\n }\n\n /**\n * Reconnect to an existing sandbox by ID.\n *\n * This allows you to resume working with a sandbox that was created\n * earlier and is still running.\n *\n * @param sandboxId - The ID of the sandbox to reconnect to\n * @param options - Optional auth configuration\n * @returns A connected sandbox instance\n *\n * @example\n * ```typescript\n * // Resume a sandbox from a stored ID\n * const sandbox = await ModalSandbox.fromId(\"sb-abc123\");\n * const result = await sandbox.execute(\"ls -la\");\n * ```\n */\n static async fromId(\n sandboxId: string,\n options?: Pick<ModalSandboxOptions, \"auth\">,\n ): Promise<ModalSandbox> {\n // Validate authentication credentials exist\n try {\n getAuthCredentials(options?.auth);\n } catch (error) {\n throw new ModalSandboxError(\n \"Failed to authenticate with Modal. Check your token configuration.\",\n \"AUTHENTICATION_FAILED\",\n error instanceof Error ? error : undefined,\n );\n }\n\n try {\n const client = new ModalClient();\n const existingSandbox = await client.sandboxes.fromId(sandboxId);\n\n const modalSandbox = new ModalSandbox(options);\n modalSandbox.#setFromExisting(client, existingSandbox, sandboxId);\n\n return modalSandbox;\n } catch (error) {\n throw new ModalSandboxError(\n `Sandbox not found: ${sandboxId}`,\n \"SANDBOX_NOT_FOUND\",\n error instanceof Error ? error : undefined,\n );\n }\n }\n\n /**\n * Get a running sandbox by name from a deployed app.\n *\n * @param appName - The name of the Modal app\n * @param sandboxName - The name of the sandbox\n * @param options - Optional auth configuration\n * @returns A connected sandbox instance\n *\n * @example\n * ```typescript\n * const sandbox = await ModalSandbox.fromName(\"my-app\", \"my-sandbox\");\n * const result = await sandbox.execute(\"ls -la\");\n * ```\n */\n static async fromName(\n appName: string,\n sandboxName: string,\n options?: Pick<ModalSandboxOptions, \"auth\">,\n ): Promise<ModalSandbox> {\n // Validate authentication credentials exist\n try {\n getAuthCredentials(options?.auth);\n } catch (error) {\n throw new ModalSandboxError(\n \"Failed to authenticate with Modal. Check your token configuration.\",\n \"AUTHENTICATION_FAILED\",\n error instanceof Error ? error : undefined,\n );\n }\n\n try {\n const client = new ModalClient();\n const existingSandbox = await client.sandboxes.fromName(\n appName,\n sandboxName,\n );\n\n const modalSandbox = new ModalSandbox(options);\n modalSandbox.#setFromExisting(\n client,\n existingSandbox,\n existingSandbox.sandboxId,\n );\n\n return modalSandbox;\n } catch (error) {\n throw new ModalSandboxError(\n `Sandbox not found: ${appName}/${sandboxName}`,\n \"SANDBOX_NOT_FOUND\",\n error instanceof Error ? error : undefined,\n );\n }\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["SandboxError","BaseSandbox","#id","#sandbox","#client","#options","ModalClient","#app","#uploadInitialFiles","#mapError","#setFromExisting"],"sources":["../src/auth.ts","../src/types.ts","../src/sandbox.ts"],"sourcesContent":["/**\n * Authentication utilities for Modal Sandbox.\n *\n * This module provides authentication credential resolution for the Modal SDK.\n *\n * @packageDocumentation\n */\n\nimport type { ModalSandboxOptions } from \"./types.js\";\n\n/**\n * Authentication credentials for Modal API.\n */\nexport interface ModalCredentials {\n /** Modal token ID */\n tokenId: string;\n /** Modal token secret */\n tokenSecret: string;\n}\n\n/**\n * Get authentication credentials for Modal API.\n *\n * Credentials are resolved in the following priority order:\n *\n * 1. **Explicit options**: If `options.tokenId` and/or `options.tokenSecret` are provided,\n * they are used directly.\n * 2. **Environment variables**: `MODAL_TOKEN_ID` and `MODAL_TOKEN_SECRET` are used as fallbacks.\n *\n * ## Environment Variable Setup\n *\n * ```bash\n * # Go to https://modal.com/settings/tokens\n * # Create a new token and set the environment variables\n * export MODAL_TOKEN_ID=your_token_id\n * export MODAL_TOKEN_SECRET=your_token_secret\n * ```\n *\n * @param options - Optional authentication configuration from ModalSandboxOptions\n * @returns Complete authentication credentials\n * @throws {Error} If any credentials are missing\n *\n * @example\n * ```typescript\n * // With explicit credentials\n * const creds = getAuthCredentials({ tokenId: \"...\", tokenSecret: \"...\" });\n *\n * // Using environment variables (auto-detected)\n * const creds = getAuthCredentials();\n *\n * // From ModalSandboxOptions\n * const options: ModalSandboxOptions = {\n * auth: { tokenId: \"...\", tokenSecret: \"...\" }\n * };\n * const creds = getAuthCredentials(options.auth);\n * ```\n */\nexport function getAuthCredentials(\n options?: ModalSandboxOptions[\"auth\"],\n): ModalCredentials {\n // Resolve token ID: explicit option first, then environment variable\n const tokenId = options?.tokenId || process.env.MODAL_TOKEN_ID;\n\n // Resolve token secret: explicit option first, then environment variable\n const tokenSecret = options?.tokenSecret || process.env.MODAL_TOKEN_SECRET;\n\n // Check what's missing and build appropriate error message\n const missingTokenId = !tokenId;\n const missingTokenSecret = !tokenSecret;\n\n if (missingTokenId || missingTokenSecret) {\n const missing: string[] = [];\n if (missingTokenId) missing.push(\"MODAL_TOKEN_ID\");\n if (missingTokenSecret) missing.push(\"MODAL_TOKEN_SECRET\");\n\n throw new Error(\n `Modal authentication required. Missing: ${missing.join(\", \")}.\\n\\n` +\n \"Provide credentials using one of these methods:\\n\\n\" +\n \"1. Set environment variables:\\n\" +\n \" Go to https://modal.com/settings/tokens\\n\" +\n \" Create a new token and run:\\n\" +\n \" export MODAL_TOKEN_ID=your_token_id\\n\" +\n \" export MODAL_TOKEN_SECRET=your_token_secret\\n\\n\" +\n \"2. Pass credentials directly in options:\\n\" +\n \" new ModalSandbox({ auth: { tokenId: '...', tokenSecret: '...' } })\",\n );\n }\n\n return { tokenId, tokenSecret };\n}\n","/**\n * Type definitions for the Modal Sandbox backend.\n *\n * This module contains all type definitions for the @langchain/modal package,\n * including options and error types.\n */\n\nimport type { SandboxCreateParams } from \"modal\";\nimport { type SandboxErrorCode, SandboxError } from \"deepagents\";\n\n/**\n * Fields from SandboxCreateParams that we wrap with a different API:\n * - `volumes` -> we accept volume names (strings), SDK needs Volume objects\n * - `secrets` -> we accept secret names (strings), SDK needs Secret objects\n *\n * Fields not exposed yet:\n * - `cloudBucketMounts`, `proxy`, `experimentalOptions`, `customDomain`\n * - `command`, `pty`, `encryptedPorts`, `h2Ports`, `unencryptedPorts`, `cloud`\n */\ntype WrappedSdkFields =\n | \"secrets\"\n | \"volumes\"\n | \"cloudBucketMounts\"\n | \"proxy\"\n | \"experimentalOptions\"\n | \"customDomain\"\n | \"command\"\n | \"pty\"\n | \"encryptedPorts\"\n | \"h2Ports\"\n | \"unencryptedPorts\"\n | \"cloud\";\n\n/**\n * SDK options that pass through directly.\n */\ntype BaseSdkOptions = Omit<SandboxCreateParams, WrappedSdkFields>;\n\n/**\n * Configuration options for creating a Modal Sandbox.\n *\n * Extends the Modal SDK's SandboxCreateParams with additional options\n * for app/image configuration and a simplified volumes/secrets API.\n *\n * @example\n * ```typescript\n * const options: ModalSandboxOptions = {\n * appName: \"my-sandbox-app\",\n * imageName: \"python:3.12-slim\",\n * timeoutMs: 600_000, // 10 minutes\n * memoryMiB: 2048, // 2GB\n * initialFiles: {\n * \"/app/index.js\": \"console.log('Hello')\",\n * },\n * };\n * ```\n */\nexport interface ModalSandboxOptions extends BaseSdkOptions {\n /**\n * Name of the Modal App to associate the sandbox with.\n * If not provided, a default app name will be used.\n * The app will be created if it doesn't exist.\n *\n * @default \"deepagents-sandbox\"\n */\n appName?: string;\n\n /**\n * Docker image to use for the sandbox container.\n * Can be any public Docker image or a Modal Image reference.\n *\n * @default \"alpine:3.21\"\n *\n * @example\n * ```typescript\n * // Use Python image\n * imageName: \"python:3.12-slim\"\n *\n * // Use Node.js image\n * imageName: \"node:20-slim\"\n * ```\n */\n imageName?: string;\n\n /**\n * Modal Volume names to mount, mapped to their mount paths.\n * Volumes must be created beforehand in Modal.\n *\n * Unlike the SDK which requires Volume objects, we accept volume names\n * and look them up automatically.\n *\n * @example\n * ```typescript\n * volumes: {\n * \"/data\": \"my-data-volume\",\n * \"/cache\": \"my-cache-volume\"\n * }\n * ```\n */\n volumes?: Record<string, string>;\n\n /**\n * Modal Secret names to inject into the sandbox environment.\n * Secrets must be created beforehand in Modal.\n *\n * Unlike the SDK which requires Secret objects, we accept secret names\n * and look them up automatically.\n *\n * @example\n * ```typescript\n * secrets: [\"my-api-keys\", \"database-credentials\"]\n * ```\n */\n secrets?: string[];\n\n /**\n * Initial files to populate the sandbox with.\n *\n * Keys are file paths (relative to the working directory), values are file contents.\n * Parent directories will be created automatically if they don't exist.\n *\n * @example\n * ```typescript\n * initialFiles: {\n * \"/src/index.js\": \"console.log('Hello')\",\n * \"/package.json\": '{\"name\": \"my-app\"}',\n * }\n * ```\n */\n initialFiles?: Record<string, string | Uint8Array>;\n\n /**\n * Authentication configuration for Modal API.\n *\n * ### Environment Variable Setup\n *\n * ```bash\n * # Create a token at https://modal.com/settings/tokens\n * export MODAL_TOKEN_ID=your_token_id\n * export MODAL_TOKEN_SECRET=your_token_secret\n * ```\n *\n * Or pass the credentials directly in this auth configuration.\n */\n auth?: {\n /**\n * Modal token ID.\n * If not provided, reads from `MODAL_TOKEN_ID` environment variable.\n */\n tokenId?: string;\n\n /**\n * Modal token secret.\n * If not provided, reads from `MODAL_TOKEN_SECRET` environment variable.\n */\n tokenSecret?: string;\n };\n}\n\n/**\n * Error codes for Modal Sandbox operations.\n *\n * Used to identify specific error conditions and handle them appropriately.\n */\nexport type ModalSandboxErrorCode =\n | SandboxErrorCode\n /** Authentication failed - check token configuration */\n | \"AUTHENTICATION_FAILED\"\n /** Failed to create sandbox - check options and quotas */\n | \"SANDBOX_CREATION_FAILED\"\n /** Sandbox not found - may have been stopped or expired */\n | \"SANDBOX_NOT_FOUND\"\n /** Resource limits exceeded (CPU, memory, storage) */\n | \"RESOURCE_LIMIT_EXCEEDED\"\n /** Volume operation failed */\n | \"VOLUME_ERROR\";\n\nconst MODAL_SANDBOX_ERROR_SYMBOL = Symbol.for(\"modal.sandbox.error\");\n\n/**\n * Custom error class for Modal Sandbox operations.\n *\n * Provides structured error information including:\n * - Human-readable message\n * - Error code for programmatic handling\n * - Original cause for debugging\n *\n * @example\n * ```typescript\n * try {\n * await sandbox.execute(\"some command\");\n * } catch (error) {\n * if (error instanceof ModalSandboxError) {\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 ModalSandboxError extends SandboxError {\n [MODAL_SANDBOX_ERROR_SYMBOL]: true;\n\n /** Error name for instanceof checks and logging */\n override readonly name = \"ModalSandboxError\";\n\n /**\n * Creates a new ModalSandboxError.\n *\n * @param message - Human-readable error description\n * @param code - Structured error code for programmatic handling\n * @param cause - Original error that caused this error (for debugging)\n */\n constructor(\n message: string,\n public readonly code: ModalSandboxErrorCode,\n public override readonly cause?: Error,\n ) {\n super(message, code as SandboxErrorCode, cause);\n // Maintain proper prototype chain for instanceof checks\n Object.setPrototypeOf(this, ModalSandboxError.prototype);\n }\n\n /**\n * Checks if the error is an instance of ModalSandboxError.\n *\n * @param error - The error to check\n * @returns True if the error is an instance of ModalSandboxError, false otherwise\n */\n static isInstance(error: unknown): error is ModalSandboxError {\n return (\n typeof error === \"object\" &&\n error !== null &&\n (error as Record<symbol, unknown>)[MODAL_SANDBOX_ERROR_SYMBOL] === true\n );\n }\n}\n","/* eslint-disable no-instanceof/no-instanceof */\n/**\n * Modal Sandbox implementation of the SandboxBackendProtocol.\n *\n * This module provides a Modal Sandbox backend for deepagents, enabling agents\n * to execute commands, read/write files, and manage isolated container\n * environments using Modal's serverless infrastructure.\n *\n * @packageDocumentation\n */\n\nimport { ModalClient } from \"modal\";\nimport type { App, Sandbox, Image, SandboxCreateParams } from \"modal\";\nimport {\n BaseSandbox,\n type BackendFactory,\n type ExecuteResponse,\n type FileDownloadResponse,\n type FileOperationError,\n type FileUploadResponse,\n} from \"deepagents\";\n\nimport { getAuthCredentials } from \"./auth.js\";\nimport { ModalSandboxError, type ModalSandboxOptions } from \"./types.js\";\n\n/**\n * Modal Sandbox backend for deepagents.\n *\n * Extends `BaseSandbox` to provide command execution, file operations, and\n * sandbox lifecycle management using Modal's serverless infrastructure.\n *\n * ## Basic Usage\n *\n * ```typescript\n * import { ModalSandbox } from \"@langchain/modal\";\n *\n * // Create and initialize a sandbox\n * const sandbox = await ModalSandbox.create({\n * imageName: \"python:3.12-slim\",\n * timeout: 600,\n * });\n *\n * try {\n * // Execute commands\n * const result = await sandbox.execute(\"python --version\");\n * console.log(result.output);\n * } finally {\n * // Always cleanup\n * await sandbox.close();\n * }\n * ```\n *\n * ## Using with DeepAgent\n *\n * ```typescript\n * import { createDeepAgent } from \"deepagents\";\n * import { ModalSandbox } from \"@langchain/modal\";\n *\n * const sandbox = await ModalSandbox.create();\n *\n * const agent = createDeepAgent({\n * model: new ChatAnthropic({ model: \"claude-sonnet-4-20250514\" }),\n * systemPrompt: \"You are a coding assistant with sandbox access.\",\n * backend: sandbox,\n * });\n * ```\n */\nexport class ModalSandbox extends BaseSandbox {\n /** Private reference to the Modal client */\n #client: ModalClient | null = null;\n\n /** Private reference to the Modal App */\n #app: App | null = null;\n\n /** Private reference to the underlying Modal Sandbox instance */\n #sandbox: Sandbox | null = null;\n\n /** Configuration options for this sandbox */\n #options: ModalSandboxOptions;\n\n /** Unique identifier for this sandbox instance */\n #id: string;\n\n /**\n * Get the unique identifier for this sandbox.\n *\n * Before initialization, returns a temporary ID.\n * After initialization, returns the actual Modal sandbox ID.\n */\n get id(): string {\n return this.#id;\n }\n\n /**\n * Get the underlying Modal Sandbox instance.\n *\n * @throws {ModalSandboxError} If the sandbox is not initialized\n *\n * @example\n * ```typescript\n * const sandbox = await ModalSandbox.create();\n * const modalInstance = sandbox.instance; // Access the raw Modal Sandbox\n * ```\n */\n get instance(): Sandbox {\n if (!this.#sandbox) {\n throw new ModalSandboxError(\n \"Sandbox not initialized. Call initialize() or use ModalSandbox.create()\",\n \"NOT_INITIALIZED\",\n );\n }\n return this.#sandbox;\n }\n\n /**\n * Get the underlying Modal client instance.\n *\n * @throws {ModalSandboxError} If the sandbox is not initialized\n *\n * @example\n * ```typescript\n * const sandbox = await ModalSandbox.create();\n * const modalClient = sandbox.client; // Access the raw Modal client\n * ```\n */\n get client(): ModalClient {\n if (!this.#client) {\n throw new ModalSandboxError(\n \"Sandbox not initialized. Call initialize() or use ModalSandbox.create()\",\n \"NOT_INITIALIZED\",\n );\n }\n return this.#client;\n }\n\n /**\n * Check if the sandbox is initialized and running.\n */\n get isRunning(): boolean {\n return this.#sandbox !== null;\n }\n\n /**\n * Create a new ModalSandbox instance.\n *\n * Note: This only creates the instance. Call `initialize()` to actually\n * create the Modal Sandbox, or use the static `ModalSandbox.create()` method.\n *\n * @param options - Configuration options for the sandbox\n *\n * @example\n * ```typescript\n * // Two-step initialization\n * const sandbox = new ModalSandbox({ imageName: \"python:3.12-slim\" });\n * await sandbox.initialize();\n *\n * // Or use the factory method\n * const sandbox = await ModalSandbox.create({ imageName: \"python:3.12-slim\" });\n * ```\n */\n constructor(options: ModalSandboxOptions = {}) {\n super();\n\n // Set defaults for our custom options only\n // SDK options (timeoutMs, etc.) use SDK defaults\n this.#options = {\n appName: \"deepagents-sandbox\",\n imageName: \"alpine:3.21\",\n ...options,\n };\n\n // Generate temporary ID until initialized\n this.#id = `modal-sandbox-${Date.now()}`;\n }\n\n /**\n * Initialize the sandbox by creating a new Modal Sandbox instance.\n *\n * This method authenticates with Modal and provisions a new sandbox container.\n * After initialization, the `id` property will reflect the actual Modal sandbox ID.\n *\n * @throws {ModalSandboxError} If already initialized (`ALREADY_INITIALIZED`)\n * @throws {ModalSandboxError} If authentication fails (`AUTHENTICATION_FAILED`)\n * @throws {ModalSandboxError} If sandbox creation fails (`SANDBOX_CREATION_FAILED`)\n *\n * @example\n * ```typescript\n * const sandbox = new ModalSandbox();\n * await sandbox.initialize();\n * console.log(`Sandbox ID: ${sandbox.id}`);\n * ```\n */\n async initialize(): Promise<void> {\n // Prevent double initialization\n if (this.#sandbox) {\n throw new ModalSandboxError(\n \"Sandbox is already initialized. Each ModalSandbox instance can only be initialized once.\",\n \"ALREADY_INITIALIZED\",\n );\n }\n\n // Validate authentication credentials exist\n try {\n getAuthCredentials(this.#options.auth);\n } catch (error) {\n throw new ModalSandboxError(\n \"Failed to authenticate with Modal. Check your token configuration.\",\n \"AUTHENTICATION_FAILED\",\n error instanceof Error ? error : undefined,\n );\n }\n\n try {\n // Create Modal client\n this.#client = new ModalClient();\n\n // Get or create the app\n this.#app = await this.#client.apps.fromName(\n this.#options.appName ?? \"deepagents-sandbox\",\n { createIfMissing: true },\n );\n\n // Create the image\n const image: Image = this.#client.images.fromRegistry(\n this.#options.imageName ?? \"alpine:3.21\",\n );\n\n // Build sandbox creation options\n // Extract our custom fields, pass everything else through to SDK\n const {\n appName: _appName,\n imageName: _imageName,\n initialFiles: _initialFiles,\n auth: _auth,\n volumes: volumeNames,\n secrets: secretNames,\n ...sdkOptions\n } = this.#options;\n\n const createOptions: SandboxCreateParams = { ...sdkOptions };\n\n // Handle volumes - look up Volume objects from names\n if (volumeNames !== undefined) {\n const volumeObjects: SandboxCreateParams[\"volumes\"] = {};\n for (const [mountPath, volumeName] of Object.entries(volumeNames)) {\n const volume = await this.#client.volumes.fromName(volumeName, {\n createIfMissing: false,\n });\n volumeObjects[mountPath] = volume;\n }\n createOptions.volumes = volumeObjects;\n }\n\n // Handle secrets - look up Secret objects from names\n if (secretNames !== undefined && secretNames.length > 0) {\n const secretObjects: SandboxCreateParams[\"secrets\"] = [];\n for (const secretName of secretNames) {\n const secret = await this.#client.secrets.fromName(secretName);\n secretObjects.push(secret);\n }\n createOptions.secrets = secretObjects;\n }\n\n // Create the sandbox\n this.#sandbox = await this.#client.sandboxes.create(\n this.#app,\n image,\n createOptions,\n );\n\n // Update ID to the actual sandbox ID\n this.#id = this.#sandbox.sandboxId;\n\n // Upload initial files if provided\n if (this.#options.initialFiles) {\n await this.#uploadInitialFiles(this.#options.initialFiles);\n }\n } catch (error) {\n // If it's already a ModalSandboxError, re-throw it\n if (ModalSandboxError.isInstance(error)) {\n throw error;\n }\n\n throw new ModalSandboxError(\n `Failed to create Modal Sandbox: ${error instanceof Error ? error.message : String(error)}`,\n \"SANDBOX_CREATION_FAILED\",\n error instanceof Error ? error : undefined,\n );\n }\n }\n\n /**\n * Upload initial files to the sandbox during initialization.\n * This is a private helper method used by initialize().\n *\n * @param files - Record of file paths to contents\n */\n async #uploadInitialFiles(\n files: Record<string, string | Uint8Array>,\n ): Promise<void> {\n const encoder = new TextEncoder();\n const filesToUpload: Array<[string, Uint8Array]> = [];\n\n for (const [filePath, content] of Object.entries(files)) {\n // Normalize the path - remove leading slash if present for consistency\n const normalizedPath = filePath.startsWith(\"/\")\n ? filePath.slice(1)\n : filePath;\n\n // Convert string content to Uint8Array\n const data =\n typeof content === \"string\" ? encoder.encode(content) : content;\n\n filesToUpload.push([normalizedPath, data]);\n }\n\n // Use the existing uploadFiles method\n const results = await this.uploadFiles(filesToUpload);\n\n // Check for errors\n const errors = results.filter((r) => r.error !== null);\n if (errors.length > 0) {\n const errorPaths = errors.map((e) => e.path).join(\", \");\n throw new ModalSandboxError(\n `Failed to upload initial files: ${errorPaths}`,\n \"FILE_OPERATION_FAILED\",\n );\n }\n }\n\n /**\n * Execute a command in the sandbox.\n *\n * Commands are run using bash -c to execute the command string.\n *\n * @param command - The shell command to execute\n * @returns Execution result with output, exit code, and truncation flag\n * @throws {ModalSandboxError} If the sandbox is not initialized\n *\n * @example\n * ```typescript\n * const result = await sandbox.execute(\"echo 'Hello World'\");\n * console.log(result.output); // \"Hello World\\n\"\n * console.log(result.exitCode); // 0\n * ```\n */\n async execute(command: string): Promise<ExecuteResponse> {\n const sandbox = this.instance; // Throws if not initialized\n\n try {\n // Execute using bash -c to handle shell features\n const process = await sandbox.exec([\"bash\", \"-c\", command], {\n stdout: \"pipe\",\n stderr: \"pipe\",\n });\n\n // Read both stdout and stderr\n const [stdout, stderr] = await Promise.all([\n process.stdout.readText(),\n process.stderr.readText(),\n ]);\n\n // Wait for the process to complete and get exit code\n const exitCode = await process.wait();\n\n return {\n output: stdout + stderr,\n exitCode: exitCode ?? 0,\n truncated: false,\n };\n } catch (error) {\n // Check for timeout\n if (error instanceof Error && error.message.includes(\"timeout\")) {\n throw new ModalSandboxError(\n `Command timed out: ${command}`,\n \"COMMAND_TIMEOUT\",\n error,\n );\n }\n\n throw new ModalSandboxError(\n `Command execution failed: ${error instanceof Error ? error.message : String(error)}`,\n \"COMMAND_FAILED\",\n error instanceof Error ? error : undefined,\n );\n }\n }\n\n /**\n * Upload files to the sandbox.\n *\n * Files are written to the sandbox filesystem using Modal's file API.\n * Parent directories are created automatically if they don't exist.\n *\n * @param files - Array of [path, content] tuples to upload\n * @returns Upload result for each file, with success or error status\n *\n * @example\n * ```typescript\n * const encoder = new TextEncoder();\n * const results = await sandbox.uploadFiles([\n * [\"src/index.js\", encoder.encode(\"console.log('Hello')\")],\n * [\"package.json\", encoder.encode('{\"name\": \"test\"}')],\n * ]);\n * ```\n */\n async uploadFiles(\n files: Array<[string, Uint8Array]>,\n ): Promise<FileUploadResponse[]> {\n const sandbox = this.instance; // Throws if not initialized\n const results: FileUploadResponse[] = [];\n\n for (const [path, content] of files) {\n try {\n // Ensure parent directory exists\n const parentDir = path.substring(0, path.lastIndexOf(\"/\"));\n if (parentDir) {\n await sandbox\n .exec([\"mkdir\", \"-p\", parentDir], {\n stdout: \"pipe\",\n stderr: \"pipe\",\n })\n .then((p) => p.wait());\n }\n\n // Write the file content using Modal's file API\n const writeHandle = await sandbox.open(path, \"w\");\n await writeHandle.write(content);\n await writeHandle.close();\n\n results.push({ path, error: null });\n } catch (error) {\n results.push({ path, error: this.#mapError(error) });\n }\n }\n\n return results;\n }\n\n /**\n * Download files from the sandbox.\n *\n * Each file is read individually using Modal's file API, allowing\n * partial success when some files exist and others don't.\n *\n * @param paths - Array of file paths to download\n * @returns Download result for each file, with content or error\n *\n * @example\n * ```typescript\n * const results = await sandbox.downloadFiles([\"src/index.js\", \"missing.txt\"]);\n * for (const result of results) {\n * if (result.content) {\n * console.log(new TextDecoder().decode(result.content));\n * } else {\n * console.error(`Error: ${result.error}`);\n * }\n * }\n * ```\n */\n async downloadFiles(paths: string[]): Promise<FileDownloadResponse[]> {\n const sandbox = this.instance; // Throws if not initialized\n const results: FileDownloadResponse[] = [];\n\n for (const path of paths) {\n try {\n // Read the file content using Modal's file API\n const readHandle = await sandbox.open(path, \"r\");\n const content = await readHandle.read();\n await readHandle.close();\n\n results.push({\n path,\n content: new Uint8Array(content),\n error: null,\n });\n } catch (error) {\n results.push({\n path,\n content: null,\n error: this.#mapError(error),\n });\n }\n }\n\n return results;\n }\n\n /**\n * Close the sandbox and release all resources.\n *\n * After closing, the sandbox cannot be used again. This terminates\n * the sandbox container on Modal.\n *\n * @example\n * ```typescript\n * try {\n * await sandbox.execute(\"npm run build\");\n * } finally {\n * await sandbox.close();\n * }\n * ```\n */\n async close(): Promise<void> {\n if (this.#sandbox) {\n try {\n await this.#sandbox.terminate();\n } finally {\n this.#sandbox = null;\n this.#app = null;\n this.#client = null;\n }\n }\n }\n\n /**\n * Terminate the sandbox.\n *\n * Alias for close() for Modal SDK compatibility.\n *\n * @example\n * ```typescript\n * await sandbox.terminate();\n * ```\n */\n async terminate(): Promise<void> {\n await this.close();\n }\n\n /**\n * Alias for close() to maintain compatibility with other sandbox implementations.\n */\n async stop(): Promise<void> {\n await this.close();\n }\n\n /**\n * Poll the sandbox status to check if it has finished running.\n *\n * @returns The exit code if the sandbox has finished, or null if still running\n */\n async poll(): Promise<number | null> {\n if (!this.#sandbox) {\n return null;\n }\n return this.#sandbox.poll();\n }\n\n /**\n * Wait for the sandbox to finish running.\n *\n * @returns The exit code of the sandbox\n * @throws {ModalSandboxError} If the sandbox is not initialized\n */\n async wait(): Promise<number> {\n return this.instance.wait();\n }\n\n /**\n * Set the sandbox from an existing Modal Sandbox instance.\n * Used internally by the static `fromId()` and `fromName()` methods.\n */\n #setFromExisting(\n client: ModalClient,\n existingSandbox: Sandbox,\n sandboxId: string,\n ): void {\n this.#client = client;\n this.#sandbox = existingSandbox;\n this.#id = sandboxId;\n }\n\n /**\n * Map Modal SDK errors to standardized FileOperationError codes.\n *\n * @param error - The error from the Modal SDK\n * @returns A standardized error code\n */\n #mapError(error: unknown): FileOperationError {\n if (error instanceof Error) {\n const msg = error.message.toLowerCase();\n\n if (msg.includes(\"not found\") || msg.includes(\"enoent\")) {\n return \"file_not_found\";\n }\n if (msg.includes(\"permission\") || msg.includes(\"eacces\")) {\n return \"permission_denied\";\n }\n if (msg.includes(\"directory\") || msg.includes(\"eisdir\")) {\n return \"is_directory\";\n }\n }\n\n return \"invalid_path\";\n }\n\n /**\n * Create and initialize a new ModalSandbox in one step.\n *\n * This is the recommended way to create a sandbox. It combines\n * construction and initialization into a single async operation.\n *\n * @param options - Configuration options for the sandbox\n * @returns An initialized and ready-to-use sandbox\n *\n * @example\n * ```typescript\n * const sandbox = await ModalSandbox.create({\n * imageName: \"python:3.12-slim\",\n * timeout: 600,\n * memory: 2048,\n * });\n * ```\n */\n static async create(options?: ModalSandboxOptions): Promise<ModalSandbox> {\n const sandbox = new ModalSandbox(options);\n await sandbox.initialize();\n return sandbox;\n }\n\n /**\n * Reconnect to an existing sandbox by ID.\n *\n * This allows you to resume working with a sandbox that was created\n * earlier and is still running.\n *\n * @param sandboxId - The ID of the sandbox to reconnect to\n * @param options - Optional auth configuration\n * @returns A connected sandbox instance\n *\n * @example\n * ```typescript\n * // Resume a sandbox from a stored ID\n * const sandbox = await ModalSandbox.fromId(\"sb-abc123\");\n * const result = await sandbox.execute(\"ls -la\");\n * ```\n */\n static async fromId(\n sandboxId: string,\n options?: Pick<ModalSandboxOptions, \"auth\">,\n ): Promise<ModalSandbox> {\n // Validate authentication credentials exist\n try {\n getAuthCredentials(options?.auth);\n } catch (error) {\n throw new ModalSandboxError(\n \"Failed to authenticate with Modal. Check your token configuration.\",\n \"AUTHENTICATION_FAILED\",\n error instanceof Error ? error : undefined,\n );\n }\n\n try {\n const client = new ModalClient();\n const existingSandbox = await client.sandboxes.fromId(sandboxId);\n\n const modalSandbox = new ModalSandbox(options);\n modalSandbox.#setFromExisting(client, existingSandbox, sandboxId);\n\n return modalSandbox;\n } catch (error) {\n throw new ModalSandboxError(\n `Sandbox not found: ${sandboxId}`,\n \"SANDBOX_NOT_FOUND\",\n error instanceof Error ? error : undefined,\n );\n }\n }\n\n /**\n * Get a running sandbox by name from a deployed app.\n *\n * @param appName - The name of the Modal app\n * @param sandboxName - The name of the sandbox\n * @param options - Optional auth configuration\n * @returns A connected sandbox instance\n *\n * @example\n * ```typescript\n * const sandbox = await ModalSandbox.fromName(\"my-app\", \"my-sandbox\");\n * const result = await sandbox.execute(\"ls -la\");\n * ```\n */\n static async fromName(\n appName: string,\n sandboxName: string,\n options?: Pick<ModalSandboxOptions, \"auth\">,\n ): Promise<ModalSandbox> {\n // Validate authentication credentials exist\n try {\n getAuthCredentials(options?.auth);\n } catch (error) {\n throw new ModalSandboxError(\n \"Failed to authenticate with Modal. Check your token configuration.\",\n \"AUTHENTICATION_FAILED\",\n error instanceof Error ? error : undefined,\n );\n }\n\n try {\n const client = new ModalClient();\n const existingSandbox = await client.sandboxes.fromName(\n appName,\n sandboxName,\n );\n\n const modalSandbox = new ModalSandbox(options);\n modalSandbox.#setFromExisting(\n client,\n existingSandbox,\n existingSandbox.sandboxId,\n );\n\n return modalSandbox;\n } catch (error) {\n throw new ModalSandboxError(\n `Sandbox not found: ${appName}/${sandboxName}`,\n \"SANDBOX_NOT_FOUND\",\n error instanceof Error ? error : undefined,\n );\n }\n }\n}\n\n/**\n * Async factory function type for creating Modal Sandbox instances.\n *\n * This is similar to BackendFactory but supports async creation,\n * which is required for Modal Sandbox since initialization is async.\n */\nexport type AsyncModalSandboxFactory = () => Promise<ModalSandbox>;\n\n/**\n * Create an async factory function that creates a new Modal Sandbox per invocation.\n *\n * Each call to the factory will create and initialize a new sandbox.\n * This is useful when you want fresh, isolated environments for each\n * agent invocation.\n *\n * **Important**: This returns an async factory. For use with middleware that\n * requires synchronous BackendFactory, use `createModalSandboxFactoryFromSandbox()`\n * with a pre-created sandbox instead.\n *\n * @param options - Optional configuration for sandbox creation\n * @returns An async factory function that creates new sandboxes\n *\n * @example\n * ```typescript\n * import { ModalSandbox, createModalSandboxFactory } from \"@langchain/modal\";\n *\n * // Create a factory for new sandboxes\n * const factory = createModalSandboxFactory({ imageName: \"python:3.12-slim\" });\n *\n * // Each call creates a new sandbox\n * const sandbox1 = await factory();\n * const sandbox2 = await factory();\n *\n * try {\n * // Use sandboxes...\n * } finally {\n * await sandbox1.close();\n * await sandbox2.close();\n * }\n * ```\n */\nexport function createModalSandboxFactory(\n options?: ModalSandboxOptions,\n): AsyncModalSandboxFactory {\n return async () => {\n return await ModalSandbox.create(options);\n };\n}\n\n/**\n * Create a backend factory that reuses an existing Modal Sandbox.\n *\n * This allows multiple agent invocations to share the same sandbox,\n * avoiding the startup overhead of creating new sandboxes.\n *\n * Important: You are responsible for managing the sandbox lifecycle\n * (calling `close()` when done).\n *\n * @param sandbox - An existing ModalSandbox instance (must be initialized)\n * @returns A BackendFactory that returns the provided sandbox\n *\n * @example\n * ```typescript\n * import { createDeepAgent, createFilesystemMiddleware } from \"deepagents\";\n * import { ModalSandbox, createModalSandboxFactoryFromSandbox } from \"@langchain/modal\";\n *\n * // Create and initialize a sandbox\n * const sandbox = await ModalSandbox.create({ imageName: \"python:3.12-slim\" });\n *\n * try {\n * const agent = createDeepAgent({\n * model: new ChatAnthropic({ model: \"claude-sonnet-4-20250514\" }),\n * systemPrompt: \"You are a coding assistant.\",\n * middlewares: [\n * createFilesystemMiddleware({\n * backend: createModalSandboxFactoryFromSandbox(sandbox),\n * }),\n * ],\n * });\n *\n * await agent.invoke({ messages: [...] });\n * } finally {\n * await sandbox.close();\n * }\n * ```\n */\nexport function createModalSandboxFactoryFromSandbox(\n sandbox: ModalSandbox,\n): BackendFactory {\n return () => sandbox;\n}\n"],"mappings":"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"}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ModalClient, Sandbox, SandboxCreateParams } from "modal";
|
|
2
|
-
import { BaseSandbox, ExecuteResponse, FileDownloadResponse, FileUploadResponse } from "deepagents";
|
|
2
|
+
import { BackendFactory, BaseSandbox, ExecuteResponse, FileDownloadResponse, FileUploadResponse, SandboxError, SandboxErrorCode } from "deepagents";
|
|
3
3
|
|
|
4
4
|
//#region src/types.d.ts
|
|
5
5
|
/**
|
|
@@ -135,7 +135,7 @@ interface ModalSandboxOptions extends BaseSdkOptions {
|
|
|
135
135
|
*
|
|
136
136
|
* Used to identify specific error conditions and handle them appropriately.
|
|
137
137
|
*/
|
|
138
|
-
type ModalSandboxErrorCode =
|
|
138
|
+
type ModalSandboxErrorCode = SandboxErrorCode /** Authentication failed - check token configuration */ | "AUTHENTICATION_FAILED" /** Failed to create sandbox - check options and quotas */ | "SANDBOX_CREATION_FAILED" /** Sandbox not found - may have been stopped or expired */ | "SANDBOX_NOT_FOUND" /** Resource limits exceeded (CPU, memory, storage) */ | "RESOURCE_LIMIT_EXCEEDED" /** Volume operation failed */ | "VOLUME_ERROR";
|
|
139
139
|
declare const MODAL_SANDBOX_ERROR_SYMBOL: unique symbol;
|
|
140
140
|
/**
|
|
141
141
|
* Custom error class for Modal Sandbox operations.
|
|
@@ -165,7 +165,7 @@ declare const MODAL_SANDBOX_ERROR_SYMBOL: unique symbol;
|
|
|
165
165
|
* }
|
|
166
166
|
* ```
|
|
167
167
|
*/
|
|
168
|
-
declare class ModalSandboxError extends
|
|
168
|
+
declare class ModalSandboxError extends SandboxError {
|
|
169
169
|
readonly code: ModalSandboxErrorCode;
|
|
170
170
|
readonly cause?: Error | undefined;
|
|
171
171
|
[MODAL_SANDBOX_ERROR_SYMBOL]: true;
|
|
@@ -460,6 +460,85 @@ declare class ModalSandbox extends BaseSandbox {
|
|
|
460
460
|
*/
|
|
461
461
|
static fromName(appName: string, sandboxName: string, options?: Pick<ModalSandboxOptions, "auth">): Promise<ModalSandbox>;
|
|
462
462
|
}
|
|
463
|
+
/**
|
|
464
|
+
* Async factory function type for creating Modal Sandbox instances.
|
|
465
|
+
*
|
|
466
|
+
* This is similar to BackendFactory but supports async creation,
|
|
467
|
+
* which is required for Modal Sandbox since initialization is async.
|
|
468
|
+
*/
|
|
469
|
+
type AsyncModalSandboxFactory = () => Promise<ModalSandbox>;
|
|
470
|
+
/**
|
|
471
|
+
* Create an async factory function that creates a new Modal Sandbox per invocation.
|
|
472
|
+
*
|
|
473
|
+
* Each call to the factory will create and initialize a new sandbox.
|
|
474
|
+
* This is useful when you want fresh, isolated environments for each
|
|
475
|
+
* agent invocation.
|
|
476
|
+
*
|
|
477
|
+
* **Important**: This returns an async factory. For use with middleware that
|
|
478
|
+
* requires synchronous BackendFactory, use `createModalSandboxFactoryFromSandbox()`
|
|
479
|
+
* with a pre-created sandbox instead.
|
|
480
|
+
*
|
|
481
|
+
* @param options - Optional configuration for sandbox creation
|
|
482
|
+
* @returns An async factory function that creates new sandboxes
|
|
483
|
+
*
|
|
484
|
+
* @example
|
|
485
|
+
* ```typescript
|
|
486
|
+
* import { ModalSandbox, createModalSandboxFactory } from "@langchain/modal";
|
|
487
|
+
*
|
|
488
|
+
* // Create a factory for new sandboxes
|
|
489
|
+
* const factory = createModalSandboxFactory({ imageName: "python:3.12-slim" });
|
|
490
|
+
*
|
|
491
|
+
* // Each call creates a new sandbox
|
|
492
|
+
* const sandbox1 = await factory();
|
|
493
|
+
* const sandbox2 = await factory();
|
|
494
|
+
*
|
|
495
|
+
* try {
|
|
496
|
+
* // Use sandboxes...
|
|
497
|
+
* } finally {
|
|
498
|
+
* await sandbox1.close();
|
|
499
|
+
* await sandbox2.close();
|
|
500
|
+
* }
|
|
501
|
+
* ```
|
|
502
|
+
*/
|
|
503
|
+
declare function createModalSandboxFactory(options?: ModalSandboxOptions): AsyncModalSandboxFactory;
|
|
504
|
+
/**
|
|
505
|
+
* Create a backend factory that reuses an existing Modal Sandbox.
|
|
506
|
+
*
|
|
507
|
+
* This allows multiple agent invocations to share the same sandbox,
|
|
508
|
+
* avoiding the startup overhead of creating new sandboxes.
|
|
509
|
+
*
|
|
510
|
+
* Important: You are responsible for managing the sandbox lifecycle
|
|
511
|
+
* (calling `close()` when done).
|
|
512
|
+
*
|
|
513
|
+
* @param sandbox - An existing ModalSandbox instance (must be initialized)
|
|
514
|
+
* @returns A BackendFactory that returns the provided sandbox
|
|
515
|
+
*
|
|
516
|
+
* @example
|
|
517
|
+
* ```typescript
|
|
518
|
+
* import { createDeepAgent, createFilesystemMiddleware } from "deepagents";
|
|
519
|
+
* import { ModalSandbox, createModalSandboxFactoryFromSandbox } from "@langchain/modal";
|
|
520
|
+
*
|
|
521
|
+
* // Create and initialize a sandbox
|
|
522
|
+
* const sandbox = await ModalSandbox.create({ imageName: "python:3.12-slim" });
|
|
523
|
+
*
|
|
524
|
+
* try {
|
|
525
|
+
* const agent = createDeepAgent({
|
|
526
|
+
* model: new ChatAnthropic({ model: "claude-sonnet-4-20250514" }),
|
|
527
|
+
* systemPrompt: "You are a coding assistant.",
|
|
528
|
+
* middlewares: [
|
|
529
|
+
* createFilesystemMiddleware({
|
|
530
|
+
* backend: createModalSandboxFactoryFromSandbox(sandbox),
|
|
531
|
+
* }),
|
|
532
|
+
* ],
|
|
533
|
+
* });
|
|
534
|
+
*
|
|
535
|
+
* await agent.invoke({ messages: [...] });
|
|
536
|
+
* } finally {
|
|
537
|
+
* await sandbox.close();
|
|
538
|
+
* }
|
|
539
|
+
* ```
|
|
540
|
+
*/
|
|
541
|
+
declare function createModalSandboxFactoryFromSandbox(sandbox: ModalSandbox): BackendFactory;
|
|
463
542
|
//#endregion
|
|
464
543
|
//#region src/auth.d.ts
|
|
465
544
|
/**
|
|
@@ -510,5 +589,5 @@ interface ModalCredentials {
|
|
|
510
589
|
*/
|
|
511
590
|
declare function getAuthCredentials(options?: ModalSandboxOptions["auth"]): ModalCredentials;
|
|
512
591
|
//#endregion
|
|
513
|
-
export { type ModalCredentials, ModalSandbox, ModalSandboxError, type ModalSandboxErrorCode, type ModalSandboxOptions, getAuthCredentials };
|
|
592
|
+
export { type AsyncModalSandboxFactory, type ModalCredentials, ModalSandbox, ModalSandboxError, type ModalSandboxErrorCode, type ModalSandboxOptions, createModalSandboxFactory, createModalSandboxFactoryFromSandbox, getAuthCredentials };
|
|
514
593
|
//# sourceMappingURL=index.d.cts.map
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ModalClient, Sandbox, SandboxCreateParams } from "modal";
|
|
2
|
-
import { BaseSandbox, ExecuteResponse, FileDownloadResponse, FileUploadResponse } from "deepagents";
|
|
2
|
+
import { BackendFactory, BaseSandbox, ExecuteResponse, FileDownloadResponse, FileUploadResponse, SandboxError, SandboxErrorCode } from "deepagents";
|
|
3
3
|
|
|
4
4
|
//#region src/types.d.ts
|
|
5
5
|
/**
|
|
@@ -135,7 +135,7 @@ interface ModalSandboxOptions extends BaseSdkOptions {
|
|
|
135
135
|
*
|
|
136
136
|
* Used to identify specific error conditions and handle them appropriately.
|
|
137
137
|
*/
|
|
138
|
-
type ModalSandboxErrorCode =
|
|
138
|
+
type ModalSandboxErrorCode = SandboxErrorCode /** Authentication failed - check token configuration */ | "AUTHENTICATION_FAILED" /** Failed to create sandbox - check options and quotas */ | "SANDBOX_CREATION_FAILED" /** Sandbox not found - may have been stopped or expired */ | "SANDBOX_NOT_FOUND" /** Resource limits exceeded (CPU, memory, storage) */ | "RESOURCE_LIMIT_EXCEEDED" /** Volume operation failed */ | "VOLUME_ERROR";
|
|
139
139
|
declare const MODAL_SANDBOX_ERROR_SYMBOL: unique symbol;
|
|
140
140
|
/**
|
|
141
141
|
* Custom error class for Modal Sandbox operations.
|
|
@@ -165,7 +165,7 @@ declare const MODAL_SANDBOX_ERROR_SYMBOL: unique symbol;
|
|
|
165
165
|
* }
|
|
166
166
|
* ```
|
|
167
167
|
*/
|
|
168
|
-
declare class ModalSandboxError extends
|
|
168
|
+
declare class ModalSandboxError extends SandboxError {
|
|
169
169
|
readonly code: ModalSandboxErrorCode;
|
|
170
170
|
readonly cause?: Error | undefined;
|
|
171
171
|
[MODAL_SANDBOX_ERROR_SYMBOL]: true;
|
|
@@ -460,6 +460,85 @@ declare class ModalSandbox extends BaseSandbox {
|
|
|
460
460
|
*/
|
|
461
461
|
static fromName(appName: string, sandboxName: string, options?: Pick<ModalSandboxOptions, "auth">): Promise<ModalSandbox>;
|
|
462
462
|
}
|
|
463
|
+
/**
|
|
464
|
+
* Async factory function type for creating Modal Sandbox instances.
|
|
465
|
+
*
|
|
466
|
+
* This is similar to BackendFactory but supports async creation,
|
|
467
|
+
* which is required for Modal Sandbox since initialization is async.
|
|
468
|
+
*/
|
|
469
|
+
type AsyncModalSandboxFactory = () => Promise<ModalSandbox>;
|
|
470
|
+
/**
|
|
471
|
+
* Create an async factory function that creates a new Modal Sandbox per invocation.
|
|
472
|
+
*
|
|
473
|
+
* Each call to the factory will create and initialize a new sandbox.
|
|
474
|
+
* This is useful when you want fresh, isolated environments for each
|
|
475
|
+
* agent invocation.
|
|
476
|
+
*
|
|
477
|
+
* **Important**: This returns an async factory. For use with middleware that
|
|
478
|
+
* requires synchronous BackendFactory, use `createModalSandboxFactoryFromSandbox()`
|
|
479
|
+
* with a pre-created sandbox instead.
|
|
480
|
+
*
|
|
481
|
+
* @param options - Optional configuration for sandbox creation
|
|
482
|
+
* @returns An async factory function that creates new sandboxes
|
|
483
|
+
*
|
|
484
|
+
* @example
|
|
485
|
+
* ```typescript
|
|
486
|
+
* import { ModalSandbox, createModalSandboxFactory } from "@langchain/modal";
|
|
487
|
+
*
|
|
488
|
+
* // Create a factory for new sandboxes
|
|
489
|
+
* const factory = createModalSandboxFactory({ imageName: "python:3.12-slim" });
|
|
490
|
+
*
|
|
491
|
+
* // Each call creates a new sandbox
|
|
492
|
+
* const sandbox1 = await factory();
|
|
493
|
+
* const sandbox2 = await factory();
|
|
494
|
+
*
|
|
495
|
+
* try {
|
|
496
|
+
* // Use sandboxes...
|
|
497
|
+
* } finally {
|
|
498
|
+
* await sandbox1.close();
|
|
499
|
+
* await sandbox2.close();
|
|
500
|
+
* }
|
|
501
|
+
* ```
|
|
502
|
+
*/
|
|
503
|
+
declare function createModalSandboxFactory(options?: ModalSandboxOptions): AsyncModalSandboxFactory;
|
|
504
|
+
/**
|
|
505
|
+
* Create a backend factory that reuses an existing Modal Sandbox.
|
|
506
|
+
*
|
|
507
|
+
* This allows multiple agent invocations to share the same sandbox,
|
|
508
|
+
* avoiding the startup overhead of creating new sandboxes.
|
|
509
|
+
*
|
|
510
|
+
* Important: You are responsible for managing the sandbox lifecycle
|
|
511
|
+
* (calling `close()` when done).
|
|
512
|
+
*
|
|
513
|
+
* @param sandbox - An existing ModalSandbox instance (must be initialized)
|
|
514
|
+
* @returns A BackendFactory that returns the provided sandbox
|
|
515
|
+
*
|
|
516
|
+
* @example
|
|
517
|
+
* ```typescript
|
|
518
|
+
* import { createDeepAgent, createFilesystemMiddleware } from "deepagents";
|
|
519
|
+
* import { ModalSandbox, createModalSandboxFactoryFromSandbox } from "@langchain/modal";
|
|
520
|
+
*
|
|
521
|
+
* // Create and initialize a sandbox
|
|
522
|
+
* const sandbox = await ModalSandbox.create({ imageName: "python:3.12-slim" });
|
|
523
|
+
*
|
|
524
|
+
* try {
|
|
525
|
+
* const agent = createDeepAgent({
|
|
526
|
+
* model: new ChatAnthropic({ model: "claude-sonnet-4-20250514" }),
|
|
527
|
+
* systemPrompt: "You are a coding assistant.",
|
|
528
|
+
* middlewares: [
|
|
529
|
+
* createFilesystemMiddleware({
|
|
530
|
+
* backend: createModalSandboxFactoryFromSandbox(sandbox),
|
|
531
|
+
* }),
|
|
532
|
+
* ],
|
|
533
|
+
* });
|
|
534
|
+
*
|
|
535
|
+
* await agent.invoke({ messages: [...] });
|
|
536
|
+
* } finally {
|
|
537
|
+
* await sandbox.close();
|
|
538
|
+
* }
|
|
539
|
+
* ```
|
|
540
|
+
*/
|
|
541
|
+
declare function createModalSandboxFactoryFromSandbox(sandbox: ModalSandbox): BackendFactory;
|
|
463
542
|
//#endregion
|
|
464
543
|
//#region src/auth.d.ts
|
|
465
544
|
/**
|
|
@@ -510,5 +589,5 @@ interface ModalCredentials {
|
|
|
510
589
|
*/
|
|
511
590
|
declare function getAuthCredentials(options?: ModalSandboxOptions["auth"]): ModalCredentials;
|
|
512
591
|
//#endregion
|
|
513
|
-
export { type ModalCredentials, ModalSandbox, ModalSandboxError, type ModalSandboxErrorCode, type ModalSandboxOptions, getAuthCredentials };
|
|
592
|
+
export { type AsyncModalSandboxFactory, type ModalCredentials, ModalSandbox, ModalSandboxError, type ModalSandboxErrorCode, type ModalSandboxOptions, createModalSandboxFactory, createModalSandboxFactoryFromSandbox, getAuthCredentials };
|
|
514
593
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ModalClient } from "modal";
|
|
2
|
-
import { BaseSandbox } from "deepagents";
|
|
2
|
+
import { BaseSandbox, SandboxError } from "deepagents";
|
|
3
3
|
|
|
4
4
|
//#region src/auth.ts
|
|
5
5
|
/**
|
|
@@ -96,7 +96,7 @@ const MODAL_SANDBOX_ERROR_SYMBOL = Symbol.for("modal.sandbox.error");
|
|
|
96
96
|
* }
|
|
97
97
|
* ```
|
|
98
98
|
*/
|
|
99
|
-
var ModalSandboxError = class ModalSandboxError extends
|
|
99
|
+
var ModalSandboxError = class ModalSandboxError extends SandboxError {
|
|
100
100
|
[MODAL_SANDBOX_ERROR_SYMBOL];
|
|
101
101
|
/** Error name for instanceof checks and logging */
|
|
102
102
|
name = "ModalSandboxError";
|
|
@@ -108,7 +108,7 @@ var ModalSandboxError = class ModalSandboxError extends Error {
|
|
|
108
108
|
* @param cause - Original error that caused this error (for debugging)
|
|
109
109
|
*/
|
|
110
110
|
constructor(message, code, cause) {
|
|
111
|
-
super(message);
|
|
111
|
+
super(message, code, cause);
|
|
112
112
|
this.code = code;
|
|
113
113
|
this.cause = cause;
|
|
114
114
|
Object.setPrototypeOf(this, ModalSandboxError.prototype);
|
|
@@ -627,7 +627,85 @@ var ModalSandbox = class ModalSandbox extends BaseSandbox {
|
|
|
627
627
|
}
|
|
628
628
|
}
|
|
629
629
|
};
|
|
630
|
+
/**
|
|
631
|
+
* Create an async factory function that creates a new Modal Sandbox per invocation.
|
|
632
|
+
*
|
|
633
|
+
* Each call to the factory will create and initialize a new sandbox.
|
|
634
|
+
* This is useful when you want fresh, isolated environments for each
|
|
635
|
+
* agent invocation.
|
|
636
|
+
*
|
|
637
|
+
* **Important**: This returns an async factory. For use with middleware that
|
|
638
|
+
* requires synchronous BackendFactory, use `createModalSandboxFactoryFromSandbox()`
|
|
639
|
+
* with a pre-created sandbox instead.
|
|
640
|
+
*
|
|
641
|
+
* @param options - Optional configuration for sandbox creation
|
|
642
|
+
* @returns An async factory function that creates new sandboxes
|
|
643
|
+
*
|
|
644
|
+
* @example
|
|
645
|
+
* ```typescript
|
|
646
|
+
* import { ModalSandbox, createModalSandboxFactory } from "@langchain/modal";
|
|
647
|
+
*
|
|
648
|
+
* // Create a factory for new sandboxes
|
|
649
|
+
* const factory = createModalSandboxFactory({ imageName: "python:3.12-slim" });
|
|
650
|
+
*
|
|
651
|
+
* // Each call creates a new sandbox
|
|
652
|
+
* const sandbox1 = await factory();
|
|
653
|
+
* const sandbox2 = await factory();
|
|
654
|
+
*
|
|
655
|
+
* try {
|
|
656
|
+
* // Use sandboxes...
|
|
657
|
+
* } finally {
|
|
658
|
+
* await sandbox1.close();
|
|
659
|
+
* await sandbox2.close();
|
|
660
|
+
* }
|
|
661
|
+
* ```
|
|
662
|
+
*/
|
|
663
|
+
function createModalSandboxFactory(options) {
|
|
664
|
+
return async () => {
|
|
665
|
+
return await ModalSandbox.create(options);
|
|
666
|
+
};
|
|
667
|
+
}
|
|
668
|
+
/**
|
|
669
|
+
* Create a backend factory that reuses an existing Modal Sandbox.
|
|
670
|
+
*
|
|
671
|
+
* This allows multiple agent invocations to share the same sandbox,
|
|
672
|
+
* avoiding the startup overhead of creating new sandboxes.
|
|
673
|
+
*
|
|
674
|
+
* Important: You are responsible for managing the sandbox lifecycle
|
|
675
|
+
* (calling `close()` when done).
|
|
676
|
+
*
|
|
677
|
+
* @param sandbox - An existing ModalSandbox instance (must be initialized)
|
|
678
|
+
* @returns A BackendFactory that returns the provided sandbox
|
|
679
|
+
*
|
|
680
|
+
* @example
|
|
681
|
+
* ```typescript
|
|
682
|
+
* import { createDeepAgent, createFilesystemMiddleware } from "deepagents";
|
|
683
|
+
* import { ModalSandbox, createModalSandboxFactoryFromSandbox } from "@langchain/modal";
|
|
684
|
+
*
|
|
685
|
+
* // Create and initialize a sandbox
|
|
686
|
+
* const sandbox = await ModalSandbox.create({ imageName: "python:3.12-slim" });
|
|
687
|
+
*
|
|
688
|
+
* try {
|
|
689
|
+
* const agent = createDeepAgent({
|
|
690
|
+
* model: new ChatAnthropic({ model: "claude-sonnet-4-20250514" }),
|
|
691
|
+
* systemPrompt: "You are a coding assistant.",
|
|
692
|
+
* middlewares: [
|
|
693
|
+
* createFilesystemMiddleware({
|
|
694
|
+
* backend: createModalSandboxFactoryFromSandbox(sandbox),
|
|
695
|
+
* }),
|
|
696
|
+
* ],
|
|
697
|
+
* });
|
|
698
|
+
*
|
|
699
|
+
* await agent.invoke({ messages: [...] });
|
|
700
|
+
* } finally {
|
|
701
|
+
* await sandbox.close();
|
|
702
|
+
* }
|
|
703
|
+
* ```
|
|
704
|
+
*/
|
|
705
|
+
function createModalSandboxFactoryFromSandbox(sandbox) {
|
|
706
|
+
return () => sandbox;
|
|
707
|
+
}
|
|
630
708
|
|
|
631
709
|
//#endregion
|
|
632
|
-
export { ModalSandbox, ModalSandboxError, getAuthCredentials };
|
|
710
|
+
export { ModalSandbox, ModalSandboxError, createModalSandboxFactory, createModalSandboxFactoryFromSandbox, getAuthCredentials };
|
|
633
711
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["#id","#sandbox","#client","#options","#app","#uploadInitialFiles","#mapError","#setFromExisting"],"sources":["../src/auth.ts","../src/types.ts","../src/sandbox.ts"],"sourcesContent":["/**\n * Authentication utilities for Modal Sandbox.\n *\n * This module provides authentication credential resolution for the Modal SDK.\n *\n * @packageDocumentation\n */\n\nimport type { ModalSandboxOptions } from \"./types.js\";\n\n/**\n * Authentication credentials for Modal API.\n */\nexport interface ModalCredentials {\n /** Modal token ID */\n tokenId: string;\n /** Modal token secret */\n tokenSecret: string;\n}\n\n/**\n * Get authentication credentials for Modal API.\n *\n * Credentials are resolved in the following priority order:\n *\n * 1. **Explicit options**: If `options.tokenId` and/or `options.tokenSecret` are provided,\n * they are used directly.\n * 2. **Environment variables**: `MODAL_TOKEN_ID` and `MODAL_TOKEN_SECRET` are used as fallbacks.\n *\n * ## Environment Variable Setup\n *\n * ```bash\n * # Go to https://modal.com/settings/tokens\n * # Create a new token and set the environment variables\n * export MODAL_TOKEN_ID=your_token_id\n * export MODAL_TOKEN_SECRET=your_token_secret\n * ```\n *\n * @param options - Optional authentication configuration from ModalSandboxOptions\n * @returns Complete authentication credentials\n * @throws {Error} If any credentials are missing\n *\n * @example\n * ```typescript\n * // With explicit credentials\n * const creds = getAuthCredentials({ tokenId: \"...\", tokenSecret: \"...\" });\n *\n * // Using environment variables (auto-detected)\n * const creds = getAuthCredentials();\n *\n * // From ModalSandboxOptions\n * const options: ModalSandboxOptions = {\n * auth: { tokenId: \"...\", tokenSecret: \"...\" }\n * };\n * const creds = getAuthCredentials(options.auth);\n * ```\n */\nexport function getAuthCredentials(\n options?: ModalSandboxOptions[\"auth\"],\n): ModalCredentials {\n // Resolve token ID: explicit option first, then environment variable\n const tokenId = options?.tokenId || process.env.MODAL_TOKEN_ID;\n\n // Resolve token secret: explicit option first, then environment variable\n const tokenSecret = options?.tokenSecret || process.env.MODAL_TOKEN_SECRET;\n\n // Check what's missing and build appropriate error message\n const missingTokenId = !tokenId;\n const missingTokenSecret = !tokenSecret;\n\n if (missingTokenId || missingTokenSecret) {\n const missing: string[] = [];\n if (missingTokenId) missing.push(\"MODAL_TOKEN_ID\");\n if (missingTokenSecret) missing.push(\"MODAL_TOKEN_SECRET\");\n\n throw new Error(\n `Modal authentication required. Missing: ${missing.join(\", \")}.\\n\\n` +\n \"Provide credentials using one of these methods:\\n\\n\" +\n \"1. Set environment variables:\\n\" +\n \" Go to https://modal.com/settings/tokens\\n\" +\n \" Create a new token and run:\\n\" +\n \" export MODAL_TOKEN_ID=your_token_id\\n\" +\n \" export MODAL_TOKEN_SECRET=your_token_secret\\n\\n\" +\n \"2. Pass credentials directly in options:\\n\" +\n \" new ModalSandbox({ auth: { tokenId: '...', tokenSecret: '...' } })\",\n );\n }\n\n return { tokenId, tokenSecret };\n}\n","/**\n * Type definitions for the Modal Sandbox backend.\n *\n * This module contains all type definitions for the @langchain/modal package,\n * including options and error types.\n */\n\nimport type { SandboxCreateParams } from \"modal\";\n\n/**\n * Fields from SandboxCreateParams that we wrap with a different API:\n * - `volumes` -> we accept volume names (strings), SDK needs Volume objects\n * - `secrets` -> we accept secret names (strings), SDK needs Secret objects\n *\n * Fields not exposed yet:\n * - `cloudBucketMounts`, `proxy`, `experimentalOptions`, `customDomain`\n * - `command`, `pty`, `encryptedPorts`, `h2Ports`, `unencryptedPorts`, `cloud`\n */\ntype WrappedSdkFields =\n | \"secrets\"\n | \"volumes\"\n | \"cloudBucketMounts\"\n | \"proxy\"\n | \"experimentalOptions\"\n | \"customDomain\"\n | \"command\"\n | \"pty\"\n | \"encryptedPorts\"\n | \"h2Ports\"\n | \"unencryptedPorts\"\n | \"cloud\";\n\n/**\n * SDK options that pass through directly.\n */\ntype BaseSdkOptions = Omit<SandboxCreateParams, WrappedSdkFields>;\n\n/**\n * Configuration options for creating a Modal Sandbox.\n *\n * Extends the Modal SDK's SandboxCreateParams with additional options\n * for app/image configuration and a simplified volumes/secrets API.\n *\n * @example\n * ```typescript\n * const options: ModalSandboxOptions = {\n * appName: \"my-sandbox-app\",\n * imageName: \"python:3.12-slim\",\n * timeoutMs: 600_000, // 10 minutes\n * memoryMiB: 2048, // 2GB\n * initialFiles: {\n * \"/app/index.js\": \"console.log('Hello')\",\n * },\n * };\n * ```\n */\nexport interface ModalSandboxOptions extends BaseSdkOptions {\n /**\n * Name of the Modal App to associate the sandbox with.\n * If not provided, a default app name will be used.\n * The app will be created if it doesn't exist.\n *\n * @default \"deepagents-sandbox\"\n */\n appName?: string;\n\n /**\n * Docker image to use for the sandbox container.\n * Can be any public Docker image or a Modal Image reference.\n *\n * @default \"alpine:3.21\"\n *\n * @example\n * ```typescript\n * // Use Python image\n * imageName: \"python:3.12-slim\"\n *\n * // Use Node.js image\n * imageName: \"node:20-slim\"\n * ```\n */\n imageName?: string;\n\n /**\n * Modal Volume names to mount, mapped to their mount paths.\n * Volumes must be created beforehand in Modal.\n *\n * Unlike the SDK which requires Volume objects, we accept volume names\n * and look them up automatically.\n *\n * @example\n * ```typescript\n * volumes: {\n * \"/data\": \"my-data-volume\",\n * \"/cache\": \"my-cache-volume\"\n * }\n * ```\n */\n volumes?: Record<string, string>;\n\n /**\n * Modal Secret names to inject into the sandbox environment.\n * Secrets must be created beforehand in Modal.\n *\n * Unlike the SDK which requires Secret objects, we accept secret names\n * and look them up automatically.\n *\n * @example\n * ```typescript\n * secrets: [\"my-api-keys\", \"database-credentials\"]\n * ```\n */\n secrets?: string[];\n\n /**\n * Initial files to populate the sandbox with.\n *\n * Keys are file paths (relative to the working directory), values are file contents.\n * Parent directories will be created automatically if they don't exist.\n *\n * @example\n * ```typescript\n * initialFiles: {\n * \"/src/index.js\": \"console.log('Hello')\",\n * \"/package.json\": '{\"name\": \"my-app\"}',\n * }\n * ```\n */\n initialFiles?: Record<string, string | Uint8Array>;\n\n /**\n * Authentication configuration for Modal API.\n *\n * ### Environment Variable Setup\n *\n * ```bash\n * # Create a token at https://modal.com/settings/tokens\n * export MODAL_TOKEN_ID=your_token_id\n * export MODAL_TOKEN_SECRET=your_token_secret\n * ```\n *\n * Or pass the credentials directly in this auth configuration.\n */\n auth?: {\n /**\n * Modal token ID.\n * If not provided, reads from `MODAL_TOKEN_ID` environment variable.\n */\n tokenId?: string;\n\n /**\n * Modal token secret.\n * If not provided, reads from `MODAL_TOKEN_SECRET` environment variable.\n */\n tokenSecret?: string;\n };\n}\n\n/**\n * Error codes for Modal Sandbox operations.\n *\n * Used to identify specific error conditions and handle them appropriately.\n */\nexport type ModalSandboxErrorCode =\n /** Sandbox has not been initialized - call initialize() first */\n | \"NOT_INITIALIZED\"\n /** Sandbox is already initialized - cannot initialize twice */\n | \"ALREADY_INITIALIZED\"\n /** Authentication failed - check token configuration */\n | \"AUTHENTICATION_FAILED\"\n /** Failed to create sandbox - check options and quotas */\n | \"SANDBOX_CREATION_FAILED\"\n /** Sandbox not found - may have been stopped or expired */\n | \"SANDBOX_NOT_FOUND\"\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 /** Resource limits exceeded (CPU, memory, storage) */\n | \"RESOURCE_LIMIT_EXCEEDED\"\n /** Volume operation failed */\n | \"VOLUME_ERROR\";\n\nconst MODAL_SANDBOX_ERROR_SYMBOL = Symbol.for(\"modal.sandbox.error\");\n\n/**\n * Custom error class for Modal Sandbox operations.\n *\n * Provides structured error information including:\n * - Human-readable message\n * - Error code for programmatic handling\n * - Original cause for debugging\n *\n * @example\n * ```typescript\n * try {\n * await sandbox.execute(\"some command\");\n * } catch (error) {\n * if (error instanceof ModalSandboxError) {\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 ModalSandboxError extends Error {\n [MODAL_SANDBOX_ERROR_SYMBOL]: true;\n\n /** Error name for instanceof checks and logging */\n override readonly name = \"ModalSandboxError\";\n\n /**\n * Creates a new ModalSandboxError.\n *\n * @param message - Human-readable error description\n * @param code - Structured error code for programmatic handling\n * @param cause - Original error that caused this error (for debugging)\n */\n constructor(\n message: string,\n public readonly code: ModalSandboxErrorCode,\n public override readonly cause?: Error,\n ) {\n super(message);\n // Maintain proper prototype chain for instanceof checks\n Object.setPrototypeOf(this, ModalSandboxError.prototype);\n }\n\n /**\n * Checks if the error is an instance of ModalSandboxError.\n *\n * @param error - The error to check\n * @returns True if the error is an instance of ModalSandboxError, false otherwise\n */\n static isInstance(error: unknown): error is ModalSandboxError {\n return (\n typeof error === \"object\" &&\n error !== null &&\n (error as Record<symbol, unknown>)[MODAL_SANDBOX_ERROR_SYMBOL] === true\n );\n }\n}\n","/* eslint-disable no-instanceof/no-instanceof */\n/**\n * Modal Sandbox implementation of the SandboxBackendProtocol.\n *\n * This module provides a Modal Sandbox backend for deepagents, enabling agents\n * to execute commands, read/write files, and manage isolated container\n * environments using Modal's serverless infrastructure.\n *\n * @packageDocumentation\n */\n\nimport { ModalClient } from \"modal\";\nimport type { App, Sandbox, Image, SandboxCreateParams } from \"modal\";\nimport {\n BaseSandbox,\n type ExecuteResponse,\n type FileDownloadResponse,\n type FileOperationError,\n type FileUploadResponse,\n} from \"deepagents\";\n\nimport { getAuthCredentials } from \"./auth.js\";\nimport { ModalSandboxError, type ModalSandboxOptions } from \"./types.js\";\n\n/**\n * Modal Sandbox backend for deepagents.\n *\n * Extends `BaseSandbox` to provide command execution, file operations, and\n * sandbox lifecycle management using Modal's serverless infrastructure.\n *\n * ## Basic Usage\n *\n * ```typescript\n * import { ModalSandbox } from \"@langchain/modal\";\n *\n * // Create and initialize a sandbox\n * const sandbox = await ModalSandbox.create({\n * imageName: \"python:3.12-slim\",\n * timeout: 600,\n * });\n *\n * try {\n * // Execute commands\n * const result = await sandbox.execute(\"python --version\");\n * console.log(result.output);\n * } finally {\n * // Always cleanup\n * await sandbox.close();\n * }\n * ```\n *\n * ## Using with DeepAgent\n *\n * ```typescript\n * import { createDeepAgent } from \"deepagents\";\n * import { ModalSandbox } from \"@langchain/modal\";\n *\n * const sandbox = await ModalSandbox.create();\n *\n * const agent = createDeepAgent({\n * model: new ChatAnthropic({ model: \"claude-sonnet-4-20250514\" }),\n * systemPrompt: \"You are a coding assistant with sandbox access.\",\n * backend: sandbox,\n * });\n * ```\n */\nexport class ModalSandbox extends BaseSandbox {\n /** Private reference to the Modal client */\n #client: ModalClient | null = null;\n\n /** Private reference to the Modal App */\n #app: App | null = null;\n\n /** Private reference to the underlying Modal Sandbox instance */\n #sandbox: Sandbox | null = null;\n\n /** Configuration options for this sandbox */\n #options: ModalSandboxOptions;\n\n /** Unique identifier for this sandbox instance */\n #id: string;\n\n /**\n * Get the unique identifier for this sandbox.\n *\n * Before initialization, returns a temporary ID.\n * After initialization, returns the actual Modal sandbox ID.\n */\n get id(): string {\n return this.#id;\n }\n\n /**\n * Get the underlying Modal Sandbox instance.\n *\n * @throws {ModalSandboxError} If the sandbox is not initialized\n *\n * @example\n * ```typescript\n * const sandbox = await ModalSandbox.create();\n * const modalInstance = sandbox.instance; // Access the raw Modal Sandbox\n * ```\n */\n get instance(): Sandbox {\n if (!this.#sandbox) {\n throw new ModalSandboxError(\n \"Sandbox not initialized. Call initialize() or use ModalSandbox.create()\",\n \"NOT_INITIALIZED\",\n );\n }\n return this.#sandbox;\n }\n\n /**\n * Get the underlying Modal client instance.\n *\n * @throws {ModalSandboxError} If the sandbox is not initialized\n *\n * @example\n * ```typescript\n * const sandbox = await ModalSandbox.create();\n * const modalClient = sandbox.client; // Access the raw Modal client\n * ```\n */\n get client(): ModalClient {\n if (!this.#client) {\n throw new ModalSandboxError(\n \"Sandbox not initialized. Call initialize() or use ModalSandbox.create()\",\n \"NOT_INITIALIZED\",\n );\n }\n return this.#client;\n }\n\n /**\n * Check if the sandbox is initialized and running.\n */\n get isRunning(): boolean {\n return this.#sandbox !== null;\n }\n\n /**\n * Create a new ModalSandbox instance.\n *\n * Note: This only creates the instance. Call `initialize()` to actually\n * create the Modal Sandbox, or use the static `ModalSandbox.create()` method.\n *\n * @param options - Configuration options for the sandbox\n *\n * @example\n * ```typescript\n * // Two-step initialization\n * const sandbox = new ModalSandbox({ imageName: \"python:3.12-slim\" });\n * await sandbox.initialize();\n *\n * // Or use the factory method\n * const sandbox = await ModalSandbox.create({ imageName: \"python:3.12-slim\" });\n * ```\n */\n constructor(options: ModalSandboxOptions = {}) {\n super();\n\n // Set defaults for our custom options only\n // SDK options (timeoutMs, etc.) use SDK defaults\n this.#options = {\n appName: \"deepagents-sandbox\",\n imageName: \"alpine:3.21\",\n ...options,\n };\n\n // Generate temporary ID until initialized\n this.#id = `modal-sandbox-${Date.now()}`;\n }\n\n /**\n * Initialize the sandbox by creating a new Modal Sandbox instance.\n *\n * This method authenticates with Modal and provisions a new sandbox container.\n * After initialization, the `id` property will reflect the actual Modal sandbox ID.\n *\n * @throws {ModalSandboxError} If already initialized (`ALREADY_INITIALIZED`)\n * @throws {ModalSandboxError} If authentication fails (`AUTHENTICATION_FAILED`)\n * @throws {ModalSandboxError} If sandbox creation fails (`SANDBOX_CREATION_FAILED`)\n *\n * @example\n * ```typescript\n * const sandbox = new ModalSandbox();\n * await sandbox.initialize();\n * console.log(`Sandbox ID: ${sandbox.id}`);\n * ```\n */\n async initialize(): Promise<void> {\n // Prevent double initialization\n if (this.#sandbox) {\n throw new ModalSandboxError(\n \"Sandbox is already initialized. Each ModalSandbox instance can only be initialized once.\",\n \"ALREADY_INITIALIZED\",\n );\n }\n\n // Validate authentication credentials exist\n try {\n getAuthCredentials(this.#options.auth);\n } catch (error) {\n throw new ModalSandboxError(\n \"Failed to authenticate with Modal. Check your token configuration.\",\n \"AUTHENTICATION_FAILED\",\n error instanceof Error ? error : undefined,\n );\n }\n\n try {\n // Create Modal client\n this.#client = new ModalClient();\n\n // Get or create the app\n this.#app = await this.#client.apps.fromName(\n this.#options.appName ?? \"deepagents-sandbox\",\n { createIfMissing: true },\n );\n\n // Create the image\n const image: Image = this.#client.images.fromRegistry(\n this.#options.imageName ?? \"alpine:3.21\",\n );\n\n // Build sandbox creation options\n // Extract our custom fields, pass everything else through to SDK\n const {\n appName: _appName,\n imageName: _imageName,\n initialFiles: _initialFiles,\n auth: _auth,\n volumes: volumeNames,\n secrets: secretNames,\n ...sdkOptions\n } = this.#options;\n\n const createOptions: SandboxCreateParams = { ...sdkOptions };\n\n // Handle volumes - look up Volume objects from names\n if (volumeNames !== undefined) {\n const volumeObjects: SandboxCreateParams[\"volumes\"] = {};\n for (const [mountPath, volumeName] of Object.entries(volumeNames)) {\n const volume = await this.#client.volumes.fromName(volumeName, {\n createIfMissing: false,\n });\n volumeObjects[mountPath] = volume;\n }\n createOptions.volumes = volumeObjects;\n }\n\n // Handle secrets - look up Secret objects from names\n if (secretNames !== undefined && secretNames.length > 0) {\n const secretObjects: SandboxCreateParams[\"secrets\"] = [];\n for (const secretName of secretNames) {\n const secret = await this.#client.secrets.fromName(secretName);\n secretObjects.push(secret);\n }\n createOptions.secrets = secretObjects;\n }\n\n // Create the sandbox\n this.#sandbox = await this.#client.sandboxes.create(\n this.#app,\n image,\n createOptions,\n );\n\n // Update ID to the actual sandbox ID\n this.#id = this.#sandbox.sandboxId;\n\n // Upload initial files if provided\n if (this.#options.initialFiles) {\n await this.#uploadInitialFiles(this.#options.initialFiles);\n }\n } catch (error) {\n // If it's already a ModalSandboxError, re-throw it\n if (ModalSandboxError.isInstance(error)) {\n throw error;\n }\n\n throw new ModalSandboxError(\n `Failed to create Modal Sandbox: ${error instanceof Error ? error.message : String(error)}`,\n \"SANDBOX_CREATION_FAILED\",\n error instanceof Error ? error : undefined,\n );\n }\n }\n\n /**\n * Upload initial files to the sandbox during initialization.\n * This is a private helper method used by initialize().\n *\n * @param files - Record of file paths to contents\n */\n async #uploadInitialFiles(\n files: Record<string, string | Uint8Array>,\n ): Promise<void> {\n const encoder = new TextEncoder();\n const filesToUpload: Array<[string, Uint8Array]> = [];\n\n for (const [filePath, content] of Object.entries(files)) {\n // Normalize the path - remove leading slash if present for consistency\n const normalizedPath = filePath.startsWith(\"/\")\n ? filePath.slice(1)\n : filePath;\n\n // Convert string content to Uint8Array\n const data =\n typeof content === \"string\" ? encoder.encode(content) : content;\n\n filesToUpload.push([normalizedPath, data]);\n }\n\n // Use the existing uploadFiles method\n const results = await this.uploadFiles(filesToUpload);\n\n // Check for errors\n const errors = results.filter((r) => r.error !== null);\n if (errors.length > 0) {\n const errorPaths = errors.map((e) => e.path).join(\", \");\n throw new ModalSandboxError(\n `Failed to upload initial files: ${errorPaths}`,\n \"FILE_OPERATION_FAILED\",\n );\n }\n }\n\n /**\n * Execute a command in the sandbox.\n *\n * Commands are run using bash -c to execute the command string.\n *\n * @param command - The shell command to execute\n * @returns Execution result with output, exit code, and truncation flag\n * @throws {ModalSandboxError} If the sandbox is not initialized\n *\n * @example\n * ```typescript\n * const result = await sandbox.execute(\"echo 'Hello World'\");\n * console.log(result.output); // \"Hello World\\n\"\n * console.log(result.exitCode); // 0\n * ```\n */\n async execute(command: string): Promise<ExecuteResponse> {\n const sandbox = this.instance; // Throws if not initialized\n\n try {\n // Execute using bash -c to handle shell features\n const process = await sandbox.exec([\"bash\", \"-c\", command], {\n stdout: \"pipe\",\n stderr: \"pipe\",\n });\n\n // Read both stdout and stderr\n const [stdout, stderr] = await Promise.all([\n process.stdout.readText(),\n process.stderr.readText(),\n ]);\n\n // Wait for the process to complete and get exit code\n const exitCode = await process.wait();\n\n return {\n output: stdout + stderr,\n exitCode: exitCode ?? 0,\n truncated: false,\n };\n } catch (error) {\n // Check for timeout\n if (error instanceof Error && error.message.includes(\"timeout\")) {\n throw new ModalSandboxError(\n `Command timed out: ${command}`,\n \"COMMAND_TIMEOUT\",\n error,\n );\n }\n\n throw new ModalSandboxError(\n `Command execution failed: ${error instanceof Error ? error.message : String(error)}`,\n \"COMMAND_FAILED\",\n error instanceof Error ? error : undefined,\n );\n }\n }\n\n /**\n * Upload files to the sandbox.\n *\n * Files are written to the sandbox filesystem using Modal's file API.\n * Parent directories are created automatically if they don't exist.\n *\n * @param files - Array of [path, content] tuples to upload\n * @returns Upload result for each file, with success or error status\n *\n * @example\n * ```typescript\n * const encoder = new TextEncoder();\n * const results = await sandbox.uploadFiles([\n * [\"src/index.js\", encoder.encode(\"console.log('Hello')\")],\n * [\"package.json\", encoder.encode('{\"name\": \"test\"}')],\n * ]);\n * ```\n */\n async uploadFiles(\n files: Array<[string, Uint8Array]>,\n ): Promise<FileUploadResponse[]> {\n const sandbox = this.instance; // Throws if not initialized\n const results: FileUploadResponse[] = [];\n\n for (const [path, content] of files) {\n try {\n // Ensure parent directory exists\n const parentDir = path.substring(0, path.lastIndexOf(\"/\"));\n if (parentDir) {\n await sandbox\n .exec([\"mkdir\", \"-p\", parentDir], {\n stdout: \"pipe\",\n stderr: \"pipe\",\n })\n .then((p) => p.wait());\n }\n\n // Write the file content using Modal's file API\n const writeHandle = await sandbox.open(path, \"w\");\n await writeHandle.write(content);\n await writeHandle.close();\n\n results.push({ path, error: null });\n } catch (error) {\n results.push({ path, error: this.#mapError(error) });\n }\n }\n\n return results;\n }\n\n /**\n * Download files from the sandbox.\n *\n * Each file is read individually using Modal's file API, allowing\n * partial success when some files exist and others don't.\n *\n * @param paths - Array of file paths to download\n * @returns Download result for each file, with content or error\n *\n * @example\n * ```typescript\n * const results = await sandbox.downloadFiles([\"src/index.js\", \"missing.txt\"]);\n * for (const result of results) {\n * if (result.content) {\n * console.log(new TextDecoder().decode(result.content));\n * } else {\n * console.error(`Error: ${result.error}`);\n * }\n * }\n * ```\n */\n async downloadFiles(paths: string[]): Promise<FileDownloadResponse[]> {\n const sandbox = this.instance; // Throws if not initialized\n const results: FileDownloadResponse[] = [];\n\n for (const path of paths) {\n try {\n // Read the file content using Modal's file API\n const readHandle = await sandbox.open(path, \"r\");\n const content = await readHandle.read();\n await readHandle.close();\n\n results.push({\n path,\n content: new Uint8Array(content),\n error: null,\n });\n } catch (error) {\n results.push({\n path,\n content: null,\n error: this.#mapError(error),\n });\n }\n }\n\n return results;\n }\n\n /**\n * Close the sandbox and release all resources.\n *\n * After closing, the sandbox cannot be used again. This terminates\n * the sandbox container on Modal.\n *\n * @example\n * ```typescript\n * try {\n * await sandbox.execute(\"npm run build\");\n * } finally {\n * await sandbox.close();\n * }\n * ```\n */\n async close(): Promise<void> {\n if (this.#sandbox) {\n try {\n await this.#sandbox.terminate();\n } finally {\n this.#sandbox = null;\n this.#app = null;\n this.#client = null;\n }\n }\n }\n\n /**\n * Terminate the sandbox.\n *\n * Alias for close() for Modal SDK compatibility.\n *\n * @example\n * ```typescript\n * await sandbox.terminate();\n * ```\n */\n async terminate(): Promise<void> {\n await this.close();\n }\n\n /**\n * Alias for close() to maintain compatibility with other sandbox implementations.\n */\n async stop(): Promise<void> {\n await this.close();\n }\n\n /**\n * Poll the sandbox status to check if it has finished running.\n *\n * @returns The exit code if the sandbox has finished, or null if still running\n */\n async poll(): Promise<number | null> {\n if (!this.#sandbox) {\n return null;\n }\n return this.#sandbox.poll();\n }\n\n /**\n * Wait for the sandbox to finish running.\n *\n * @returns The exit code of the sandbox\n * @throws {ModalSandboxError} If the sandbox is not initialized\n */\n async wait(): Promise<number> {\n return this.instance.wait();\n }\n\n /**\n * Set the sandbox from an existing Modal Sandbox instance.\n * Used internally by the static `fromId()` and `fromName()` methods.\n */\n #setFromExisting(\n client: ModalClient,\n existingSandbox: Sandbox,\n sandboxId: string,\n ): void {\n this.#client = client;\n this.#sandbox = existingSandbox;\n this.#id = sandboxId;\n }\n\n /**\n * Map Modal SDK errors to standardized FileOperationError codes.\n *\n * @param error - The error from the Modal SDK\n * @returns A standardized error code\n */\n #mapError(error: unknown): FileOperationError {\n if (error instanceof Error) {\n const msg = error.message.toLowerCase();\n\n if (msg.includes(\"not found\") || msg.includes(\"enoent\")) {\n return \"file_not_found\";\n }\n if (msg.includes(\"permission\") || msg.includes(\"eacces\")) {\n return \"permission_denied\";\n }\n if (msg.includes(\"directory\") || msg.includes(\"eisdir\")) {\n return \"is_directory\";\n }\n }\n\n return \"invalid_path\";\n }\n\n // ============================================================================\n // Static Factory Methods\n // ============================================================================\n\n /**\n * Create and initialize a new ModalSandbox in one step.\n *\n * This is the recommended way to create a sandbox. It combines\n * construction and initialization into a single async operation.\n *\n * @param options - Configuration options for the sandbox\n * @returns An initialized and ready-to-use sandbox\n *\n * @example\n * ```typescript\n * const sandbox = await ModalSandbox.create({\n * imageName: \"python:3.12-slim\",\n * timeout: 600,\n * memory: 2048,\n * });\n * ```\n */\n static async create(options?: ModalSandboxOptions): Promise<ModalSandbox> {\n const sandbox = new ModalSandbox(options);\n await sandbox.initialize();\n return sandbox;\n }\n\n /**\n * Reconnect to an existing sandbox by ID.\n *\n * This allows you to resume working with a sandbox that was created\n * earlier and is still running.\n *\n * @param sandboxId - The ID of the sandbox to reconnect to\n * @param options - Optional auth configuration\n * @returns A connected sandbox instance\n *\n * @example\n * ```typescript\n * // Resume a sandbox from a stored ID\n * const sandbox = await ModalSandbox.fromId(\"sb-abc123\");\n * const result = await sandbox.execute(\"ls -la\");\n * ```\n */\n static async fromId(\n sandboxId: string,\n options?: Pick<ModalSandboxOptions, \"auth\">,\n ): Promise<ModalSandbox> {\n // Validate authentication credentials exist\n try {\n getAuthCredentials(options?.auth);\n } catch (error) {\n throw new ModalSandboxError(\n \"Failed to authenticate with Modal. Check your token configuration.\",\n \"AUTHENTICATION_FAILED\",\n error instanceof Error ? error : undefined,\n );\n }\n\n try {\n const client = new ModalClient();\n const existingSandbox = await client.sandboxes.fromId(sandboxId);\n\n const modalSandbox = new ModalSandbox(options);\n modalSandbox.#setFromExisting(client, existingSandbox, sandboxId);\n\n return modalSandbox;\n } catch (error) {\n throw new ModalSandboxError(\n `Sandbox not found: ${sandboxId}`,\n \"SANDBOX_NOT_FOUND\",\n error instanceof Error ? error : undefined,\n );\n }\n }\n\n /**\n * Get a running sandbox by name from a deployed app.\n *\n * @param appName - The name of the Modal app\n * @param sandboxName - The name of the sandbox\n * @param options - Optional auth configuration\n * @returns A connected sandbox instance\n *\n * @example\n * ```typescript\n * const sandbox = await ModalSandbox.fromName(\"my-app\", \"my-sandbox\");\n * const result = await sandbox.execute(\"ls -la\");\n * ```\n */\n static async fromName(\n appName: string,\n sandboxName: string,\n options?: Pick<ModalSandboxOptions, \"auth\">,\n ): Promise<ModalSandbox> {\n // Validate authentication credentials exist\n try {\n getAuthCredentials(options?.auth);\n } catch (error) {\n throw new ModalSandboxError(\n \"Failed to authenticate with Modal. Check your token configuration.\",\n \"AUTHENTICATION_FAILED\",\n error instanceof Error ? error : undefined,\n );\n }\n\n try {\n const client = new ModalClient();\n const existingSandbox = await client.sandboxes.fromName(\n appName,\n sandboxName,\n );\n\n const modalSandbox = new ModalSandbox(options);\n modalSandbox.#setFromExisting(\n client,\n existingSandbox,\n existingSandbox.sandboxId,\n );\n\n return modalSandbox;\n } catch (error) {\n throw new ModalSandboxError(\n `Sandbox not found: ${appName}/${sandboxName}`,\n \"SANDBOX_NOT_FOUND\",\n error instanceof Error ? error : undefined,\n );\n }\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["#id","#sandbox","#client","#options","#app","#uploadInitialFiles","#mapError","#setFromExisting"],"sources":["../src/auth.ts","../src/types.ts","../src/sandbox.ts"],"sourcesContent":["/**\n * Authentication utilities for Modal Sandbox.\n *\n * This module provides authentication credential resolution for the Modal SDK.\n *\n * @packageDocumentation\n */\n\nimport type { ModalSandboxOptions } from \"./types.js\";\n\n/**\n * Authentication credentials for Modal API.\n */\nexport interface ModalCredentials {\n /** Modal token ID */\n tokenId: string;\n /** Modal token secret */\n tokenSecret: string;\n}\n\n/**\n * Get authentication credentials for Modal API.\n *\n * Credentials are resolved in the following priority order:\n *\n * 1. **Explicit options**: If `options.tokenId` and/or `options.tokenSecret` are provided,\n * they are used directly.\n * 2. **Environment variables**: `MODAL_TOKEN_ID` and `MODAL_TOKEN_SECRET` are used as fallbacks.\n *\n * ## Environment Variable Setup\n *\n * ```bash\n * # Go to https://modal.com/settings/tokens\n * # Create a new token and set the environment variables\n * export MODAL_TOKEN_ID=your_token_id\n * export MODAL_TOKEN_SECRET=your_token_secret\n * ```\n *\n * @param options - Optional authentication configuration from ModalSandboxOptions\n * @returns Complete authentication credentials\n * @throws {Error} If any credentials are missing\n *\n * @example\n * ```typescript\n * // With explicit credentials\n * const creds = getAuthCredentials({ tokenId: \"...\", tokenSecret: \"...\" });\n *\n * // Using environment variables (auto-detected)\n * const creds = getAuthCredentials();\n *\n * // From ModalSandboxOptions\n * const options: ModalSandboxOptions = {\n * auth: { tokenId: \"...\", tokenSecret: \"...\" }\n * };\n * const creds = getAuthCredentials(options.auth);\n * ```\n */\nexport function getAuthCredentials(\n options?: ModalSandboxOptions[\"auth\"],\n): ModalCredentials {\n // Resolve token ID: explicit option first, then environment variable\n const tokenId = options?.tokenId || process.env.MODAL_TOKEN_ID;\n\n // Resolve token secret: explicit option first, then environment variable\n const tokenSecret = options?.tokenSecret || process.env.MODAL_TOKEN_SECRET;\n\n // Check what's missing and build appropriate error message\n const missingTokenId = !tokenId;\n const missingTokenSecret = !tokenSecret;\n\n if (missingTokenId || missingTokenSecret) {\n const missing: string[] = [];\n if (missingTokenId) missing.push(\"MODAL_TOKEN_ID\");\n if (missingTokenSecret) missing.push(\"MODAL_TOKEN_SECRET\");\n\n throw new Error(\n `Modal authentication required. Missing: ${missing.join(\", \")}.\\n\\n` +\n \"Provide credentials using one of these methods:\\n\\n\" +\n \"1. Set environment variables:\\n\" +\n \" Go to https://modal.com/settings/tokens\\n\" +\n \" Create a new token and run:\\n\" +\n \" export MODAL_TOKEN_ID=your_token_id\\n\" +\n \" export MODAL_TOKEN_SECRET=your_token_secret\\n\\n\" +\n \"2. Pass credentials directly in options:\\n\" +\n \" new ModalSandbox({ auth: { tokenId: '...', tokenSecret: '...' } })\",\n );\n }\n\n return { tokenId, tokenSecret };\n}\n","/**\n * Type definitions for the Modal Sandbox backend.\n *\n * This module contains all type definitions for the @langchain/modal package,\n * including options and error types.\n */\n\nimport type { SandboxCreateParams } from \"modal\";\nimport { type SandboxErrorCode, SandboxError } from \"deepagents\";\n\n/**\n * Fields from SandboxCreateParams that we wrap with a different API:\n * - `volumes` -> we accept volume names (strings), SDK needs Volume objects\n * - `secrets` -> we accept secret names (strings), SDK needs Secret objects\n *\n * Fields not exposed yet:\n * - `cloudBucketMounts`, `proxy`, `experimentalOptions`, `customDomain`\n * - `command`, `pty`, `encryptedPorts`, `h2Ports`, `unencryptedPorts`, `cloud`\n */\ntype WrappedSdkFields =\n | \"secrets\"\n | \"volumes\"\n | \"cloudBucketMounts\"\n | \"proxy\"\n | \"experimentalOptions\"\n | \"customDomain\"\n | \"command\"\n | \"pty\"\n | \"encryptedPorts\"\n | \"h2Ports\"\n | \"unencryptedPorts\"\n | \"cloud\";\n\n/**\n * SDK options that pass through directly.\n */\ntype BaseSdkOptions = Omit<SandboxCreateParams, WrappedSdkFields>;\n\n/**\n * Configuration options for creating a Modal Sandbox.\n *\n * Extends the Modal SDK's SandboxCreateParams with additional options\n * for app/image configuration and a simplified volumes/secrets API.\n *\n * @example\n * ```typescript\n * const options: ModalSandboxOptions = {\n * appName: \"my-sandbox-app\",\n * imageName: \"python:3.12-slim\",\n * timeoutMs: 600_000, // 10 minutes\n * memoryMiB: 2048, // 2GB\n * initialFiles: {\n * \"/app/index.js\": \"console.log('Hello')\",\n * },\n * };\n * ```\n */\nexport interface ModalSandboxOptions extends BaseSdkOptions {\n /**\n * Name of the Modal App to associate the sandbox with.\n * If not provided, a default app name will be used.\n * The app will be created if it doesn't exist.\n *\n * @default \"deepagents-sandbox\"\n */\n appName?: string;\n\n /**\n * Docker image to use for the sandbox container.\n * Can be any public Docker image or a Modal Image reference.\n *\n * @default \"alpine:3.21\"\n *\n * @example\n * ```typescript\n * // Use Python image\n * imageName: \"python:3.12-slim\"\n *\n * // Use Node.js image\n * imageName: \"node:20-slim\"\n * ```\n */\n imageName?: string;\n\n /**\n * Modal Volume names to mount, mapped to their mount paths.\n * Volumes must be created beforehand in Modal.\n *\n * Unlike the SDK which requires Volume objects, we accept volume names\n * and look them up automatically.\n *\n * @example\n * ```typescript\n * volumes: {\n * \"/data\": \"my-data-volume\",\n * \"/cache\": \"my-cache-volume\"\n * }\n * ```\n */\n volumes?: Record<string, string>;\n\n /**\n * Modal Secret names to inject into the sandbox environment.\n * Secrets must be created beforehand in Modal.\n *\n * Unlike the SDK which requires Secret objects, we accept secret names\n * and look them up automatically.\n *\n * @example\n * ```typescript\n * secrets: [\"my-api-keys\", \"database-credentials\"]\n * ```\n */\n secrets?: string[];\n\n /**\n * Initial files to populate the sandbox with.\n *\n * Keys are file paths (relative to the working directory), values are file contents.\n * Parent directories will be created automatically if they don't exist.\n *\n * @example\n * ```typescript\n * initialFiles: {\n * \"/src/index.js\": \"console.log('Hello')\",\n * \"/package.json\": '{\"name\": \"my-app\"}',\n * }\n * ```\n */\n initialFiles?: Record<string, string | Uint8Array>;\n\n /**\n * Authentication configuration for Modal API.\n *\n * ### Environment Variable Setup\n *\n * ```bash\n * # Create a token at https://modal.com/settings/tokens\n * export MODAL_TOKEN_ID=your_token_id\n * export MODAL_TOKEN_SECRET=your_token_secret\n * ```\n *\n * Or pass the credentials directly in this auth configuration.\n */\n auth?: {\n /**\n * Modal token ID.\n * If not provided, reads from `MODAL_TOKEN_ID` environment variable.\n */\n tokenId?: string;\n\n /**\n * Modal token secret.\n * If not provided, reads from `MODAL_TOKEN_SECRET` environment variable.\n */\n tokenSecret?: string;\n };\n}\n\n/**\n * Error codes for Modal Sandbox operations.\n *\n * Used to identify specific error conditions and handle them appropriately.\n */\nexport type ModalSandboxErrorCode =\n | SandboxErrorCode\n /** Authentication failed - check token configuration */\n | \"AUTHENTICATION_FAILED\"\n /** Failed to create sandbox - check options and quotas */\n | \"SANDBOX_CREATION_FAILED\"\n /** Sandbox not found - may have been stopped or expired */\n | \"SANDBOX_NOT_FOUND\"\n /** Resource limits exceeded (CPU, memory, storage) */\n | \"RESOURCE_LIMIT_EXCEEDED\"\n /** Volume operation failed */\n | \"VOLUME_ERROR\";\n\nconst MODAL_SANDBOX_ERROR_SYMBOL = Symbol.for(\"modal.sandbox.error\");\n\n/**\n * Custom error class for Modal Sandbox operations.\n *\n * Provides structured error information including:\n * - Human-readable message\n * - Error code for programmatic handling\n * - Original cause for debugging\n *\n * @example\n * ```typescript\n * try {\n * await sandbox.execute(\"some command\");\n * } catch (error) {\n * if (error instanceof ModalSandboxError) {\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 ModalSandboxError extends SandboxError {\n [MODAL_SANDBOX_ERROR_SYMBOL]: true;\n\n /** Error name for instanceof checks and logging */\n override readonly name = \"ModalSandboxError\";\n\n /**\n * Creates a new ModalSandboxError.\n *\n * @param message - Human-readable error description\n * @param code - Structured error code for programmatic handling\n * @param cause - Original error that caused this error (for debugging)\n */\n constructor(\n message: string,\n public readonly code: ModalSandboxErrorCode,\n public override readonly cause?: Error,\n ) {\n super(message, code as SandboxErrorCode, cause);\n // Maintain proper prototype chain for instanceof checks\n Object.setPrototypeOf(this, ModalSandboxError.prototype);\n }\n\n /**\n * Checks if the error is an instance of ModalSandboxError.\n *\n * @param error - The error to check\n * @returns True if the error is an instance of ModalSandboxError, false otherwise\n */\n static isInstance(error: unknown): error is ModalSandboxError {\n return (\n typeof error === \"object\" &&\n error !== null &&\n (error as Record<symbol, unknown>)[MODAL_SANDBOX_ERROR_SYMBOL] === true\n );\n }\n}\n","/* eslint-disable no-instanceof/no-instanceof */\n/**\n * Modal Sandbox implementation of the SandboxBackendProtocol.\n *\n * This module provides a Modal Sandbox backend for deepagents, enabling agents\n * to execute commands, read/write files, and manage isolated container\n * environments using Modal's serverless infrastructure.\n *\n * @packageDocumentation\n */\n\nimport { ModalClient } from \"modal\";\nimport type { App, Sandbox, Image, SandboxCreateParams } from \"modal\";\nimport {\n BaseSandbox,\n type BackendFactory,\n type ExecuteResponse,\n type FileDownloadResponse,\n type FileOperationError,\n type FileUploadResponse,\n} from \"deepagents\";\n\nimport { getAuthCredentials } from \"./auth.js\";\nimport { ModalSandboxError, type ModalSandboxOptions } from \"./types.js\";\n\n/**\n * Modal Sandbox backend for deepagents.\n *\n * Extends `BaseSandbox` to provide command execution, file operations, and\n * sandbox lifecycle management using Modal's serverless infrastructure.\n *\n * ## Basic Usage\n *\n * ```typescript\n * import { ModalSandbox } from \"@langchain/modal\";\n *\n * // Create and initialize a sandbox\n * const sandbox = await ModalSandbox.create({\n * imageName: \"python:3.12-slim\",\n * timeout: 600,\n * });\n *\n * try {\n * // Execute commands\n * const result = await sandbox.execute(\"python --version\");\n * console.log(result.output);\n * } finally {\n * // Always cleanup\n * await sandbox.close();\n * }\n * ```\n *\n * ## Using with DeepAgent\n *\n * ```typescript\n * import { createDeepAgent } from \"deepagents\";\n * import { ModalSandbox } from \"@langchain/modal\";\n *\n * const sandbox = await ModalSandbox.create();\n *\n * const agent = createDeepAgent({\n * model: new ChatAnthropic({ model: \"claude-sonnet-4-20250514\" }),\n * systemPrompt: \"You are a coding assistant with sandbox access.\",\n * backend: sandbox,\n * });\n * ```\n */\nexport class ModalSandbox extends BaseSandbox {\n /** Private reference to the Modal client */\n #client: ModalClient | null = null;\n\n /** Private reference to the Modal App */\n #app: App | null = null;\n\n /** Private reference to the underlying Modal Sandbox instance */\n #sandbox: Sandbox | null = null;\n\n /** Configuration options for this sandbox */\n #options: ModalSandboxOptions;\n\n /** Unique identifier for this sandbox instance */\n #id: string;\n\n /**\n * Get the unique identifier for this sandbox.\n *\n * Before initialization, returns a temporary ID.\n * After initialization, returns the actual Modal sandbox ID.\n */\n get id(): string {\n return this.#id;\n }\n\n /**\n * Get the underlying Modal Sandbox instance.\n *\n * @throws {ModalSandboxError} If the sandbox is not initialized\n *\n * @example\n * ```typescript\n * const sandbox = await ModalSandbox.create();\n * const modalInstance = sandbox.instance; // Access the raw Modal Sandbox\n * ```\n */\n get instance(): Sandbox {\n if (!this.#sandbox) {\n throw new ModalSandboxError(\n \"Sandbox not initialized. Call initialize() or use ModalSandbox.create()\",\n \"NOT_INITIALIZED\",\n );\n }\n return this.#sandbox;\n }\n\n /**\n * Get the underlying Modal client instance.\n *\n * @throws {ModalSandboxError} If the sandbox is not initialized\n *\n * @example\n * ```typescript\n * const sandbox = await ModalSandbox.create();\n * const modalClient = sandbox.client; // Access the raw Modal client\n * ```\n */\n get client(): ModalClient {\n if (!this.#client) {\n throw new ModalSandboxError(\n \"Sandbox not initialized. Call initialize() or use ModalSandbox.create()\",\n \"NOT_INITIALIZED\",\n );\n }\n return this.#client;\n }\n\n /**\n * Check if the sandbox is initialized and running.\n */\n get isRunning(): boolean {\n return this.#sandbox !== null;\n }\n\n /**\n * Create a new ModalSandbox instance.\n *\n * Note: This only creates the instance. Call `initialize()` to actually\n * create the Modal Sandbox, or use the static `ModalSandbox.create()` method.\n *\n * @param options - Configuration options for the sandbox\n *\n * @example\n * ```typescript\n * // Two-step initialization\n * const sandbox = new ModalSandbox({ imageName: \"python:3.12-slim\" });\n * await sandbox.initialize();\n *\n * // Or use the factory method\n * const sandbox = await ModalSandbox.create({ imageName: \"python:3.12-slim\" });\n * ```\n */\n constructor(options: ModalSandboxOptions = {}) {\n super();\n\n // Set defaults for our custom options only\n // SDK options (timeoutMs, etc.) use SDK defaults\n this.#options = {\n appName: \"deepagents-sandbox\",\n imageName: \"alpine:3.21\",\n ...options,\n };\n\n // Generate temporary ID until initialized\n this.#id = `modal-sandbox-${Date.now()}`;\n }\n\n /**\n * Initialize the sandbox by creating a new Modal Sandbox instance.\n *\n * This method authenticates with Modal and provisions a new sandbox container.\n * After initialization, the `id` property will reflect the actual Modal sandbox ID.\n *\n * @throws {ModalSandboxError} If already initialized (`ALREADY_INITIALIZED`)\n * @throws {ModalSandboxError} If authentication fails (`AUTHENTICATION_FAILED`)\n * @throws {ModalSandboxError} If sandbox creation fails (`SANDBOX_CREATION_FAILED`)\n *\n * @example\n * ```typescript\n * const sandbox = new ModalSandbox();\n * await sandbox.initialize();\n * console.log(`Sandbox ID: ${sandbox.id}`);\n * ```\n */\n async initialize(): Promise<void> {\n // Prevent double initialization\n if (this.#sandbox) {\n throw new ModalSandboxError(\n \"Sandbox is already initialized. Each ModalSandbox instance can only be initialized once.\",\n \"ALREADY_INITIALIZED\",\n );\n }\n\n // Validate authentication credentials exist\n try {\n getAuthCredentials(this.#options.auth);\n } catch (error) {\n throw new ModalSandboxError(\n \"Failed to authenticate with Modal. Check your token configuration.\",\n \"AUTHENTICATION_FAILED\",\n error instanceof Error ? error : undefined,\n );\n }\n\n try {\n // Create Modal client\n this.#client = new ModalClient();\n\n // Get or create the app\n this.#app = await this.#client.apps.fromName(\n this.#options.appName ?? \"deepagents-sandbox\",\n { createIfMissing: true },\n );\n\n // Create the image\n const image: Image = this.#client.images.fromRegistry(\n this.#options.imageName ?? \"alpine:3.21\",\n );\n\n // Build sandbox creation options\n // Extract our custom fields, pass everything else through to SDK\n const {\n appName: _appName,\n imageName: _imageName,\n initialFiles: _initialFiles,\n auth: _auth,\n volumes: volumeNames,\n secrets: secretNames,\n ...sdkOptions\n } = this.#options;\n\n const createOptions: SandboxCreateParams = { ...sdkOptions };\n\n // Handle volumes - look up Volume objects from names\n if (volumeNames !== undefined) {\n const volumeObjects: SandboxCreateParams[\"volumes\"] = {};\n for (const [mountPath, volumeName] of Object.entries(volumeNames)) {\n const volume = await this.#client.volumes.fromName(volumeName, {\n createIfMissing: false,\n });\n volumeObjects[mountPath] = volume;\n }\n createOptions.volumes = volumeObjects;\n }\n\n // Handle secrets - look up Secret objects from names\n if (secretNames !== undefined && secretNames.length > 0) {\n const secretObjects: SandboxCreateParams[\"secrets\"] = [];\n for (const secretName of secretNames) {\n const secret = await this.#client.secrets.fromName(secretName);\n secretObjects.push(secret);\n }\n createOptions.secrets = secretObjects;\n }\n\n // Create the sandbox\n this.#sandbox = await this.#client.sandboxes.create(\n this.#app,\n image,\n createOptions,\n );\n\n // Update ID to the actual sandbox ID\n this.#id = this.#sandbox.sandboxId;\n\n // Upload initial files if provided\n if (this.#options.initialFiles) {\n await this.#uploadInitialFiles(this.#options.initialFiles);\n }\n } catch (error) {\n // If it's already a ModalSandboxError, re-throw it\n if (ModalSandboxError.isInstance(error)) {\n throw error;\n }\n\n throw new ModalSandboxError(\n `Failed to create Modal Sandbox: ${error instanceof Error ? error.message : String(error)}`,\n \"SANDBOX_CREATION_FAILED\",\n error instanceof Error ? error : undefined,\n );\n }\n }\n\n /**\n * Upload initial files to the sandbox during initialization.\n * This is a private helper method used by initialize().\n *\n * @param files - Record of file paths to contents\n */\n async #uploadInitialFiles(\n files: Record<string, string | Uint8Array>,\n ): Promise<void> {\n const encoder = new TextEncoder();\n const filesToUpload: Array<[string, Uint8Array]> = [];\n\n for (const [filePath, content] of Object.entries(files)) {\n // Normalize the path - remove leading slash if present for consistency\n const normalizedPath = filePath.startsWith(\"/\")\n ? filePath.slice(1)\n : filePath;\n\n // Convert string content to Uint8Array\n const data =\n typeof content === \"string\" ? encoder.encode(content) : content;\n\n filesToUpload.push([normalizedPath, data]);\n }\n\n // Use the existing uploadFiles method\n const results = await this.uploadFiles(filesToUpload);\n\n // Check for errors\n const errors = results.filter((r) => r.error !== null);\n if (errors.length > 0) {\n const errorPaths = errors.map((e) => e.path).join(\", \");\n throw new ModalSandboxError(\n `Failed to upload initial files: ${errorPaths}`,\n \"FILE_OPERATION_FAILED\",\n );\n }\n }\n\n /**\n * Execute a command in the sandbox.\n *\n * Commands are run using bash -c to execute the command string.\n *\n * @param command - The shell command to execute\n * @returns Execution result with output, exit code, and truncation flag\n * @throws {ModalSandboxError} If the sandbox is not initialized\n *\n * @example\n * ```typescript\n * const result = await sandbox.execute(\"echo 'Hello World'\");\n * console.log(result.output); // \"Hello World\\n\"\n * console.log(result.exitCode); // 0\n * ```\n */\n async execute(command: string): Promise<ExecuteResponse> {\n const sandbox = this.instance; // Throws if not initialized\n\n try {\n // Execute using bash -c to handle shell features\n const process = await sandbox.exec([\"bash\", \"-c\", command], {\n stdout: \"pipe\",\n stderr: \"pipe\",\n });\n\n // Read both stdout and stderr\n const [stdout, stderr] = await Promise.all([\n process.stdout.readText(),\n process.stderr.readText(),\n ]);\n\n // Wait for the process to complete and get exit code\n const exitCode = await process.wait();\n\n return {\n output: stdout + stderr,\n exitCode: exitCode ?? 0,\n truncated: false,\n };\n } catch (error) {\n // Check for timeout\n if (error instanceof Error && error.message.includes(\"timeout\")) {\n throw new ModalSandboxError(\n `Command timed out: ${command}`,\n \"COMMAND_TIMEOUT\",\n error,\n );\n }\n\n throw new ModalSandboxError(\n `Command execution failed: ${error instanceof Error ? error.message : String(error)}`,\n \"COMMAND_FAILED\",\n error instanceof Error ? error : undefined,\n );\n }\n }\n\n /**\n * Upload files to the sandbox.\n *\n * Files are written to the sandbox filesystem using Modal's file API.\n * Parent directories are created automatically if they don't exist.\n *\n * @param files - Array of [path, content] tuples to upload\n * @returns Upload result for each file, with success or error status\n *\n * @example\n * ```typescript\n * const encoder = new TextEncoder();\n * const results = await sandbox.uploadFiles([\n * [\"src/index.js\", encoder.encode(\"console.log('Hello')\")],\n * [\"package.json\", encoder.encode('{\"name\": \"test\"}')],\n * ]);\n * ```\n */\n async uploadFiles(\n files: Array<[string, Uint8Array]>,\n ): Promise<FileUploadResponse[]> {\n const sandbox = this.instance; // Throws if not initialized\n const results: FileUploadResponse[] = [];\n\n for (const [path, content] of files) {\n try {\n // Ensure parent directory exists\n const parentDir = path.substring(0, path.lastIndexOf(\"/\"));\n if (parentDir) {\n await sandbox\n .exec([\"mkdir\", \"-p\", parentDir], {\n stdout: \"pipe\",\n stderr: \"pipe\",\n })\n .then((p) => p.wait());\n }\n\n // Write the file content using Modal's file API\n const writeHandle = await sandbox.open(path, \"w\");\n await writeHandle.write(content);\n await writeHandle.close();\n\n results.push({ path, error: null });\n } catch (error) {\n results.push({ path, error: this.#mapError(error) });\n }\n }\n\n return results;\n }\n\n /**\n * Download files from the sandbox.\n *\n * Each file is read individually using Modal's file API, allowing\n * partial success when some files exist and others don't.\n *\n * @param paths - Array of file paths to download\n * @returns Download result for each file, with content or error\n *\n * @example\n * ```typescript\n * const results = await sandbox.downloadFiles([\"src/index.js\", \"missing.txt\"]);\n * for (const result of results) {\n * if (result.content) {\n * console.log(new TextDecoder().decode(result.content));\n * } else {\n * console.error(`Error: ${result.error}`);\n * }\n * }\n * ```\n */\n async downloadFiles(paths: string[]): Promise<FileDownloadResponse[]> {\n const sandbox = this.instance; // Throws if not initialized\n const results: FileDownloadResponse[] = [];\n\n for (const path of paths) {\n try {\n // Read the file content using Modal's file API\n const readHandle = await sandbox.open(path, \"r\");\n const content = await readHandle.read();\n await readHandle.close();\n\n results.push({\n path,\n content: new Uint8Array(content),\n error: null,\n });\n } catch (error) {\n results.push({\n path,\n content: null,\n error: this.#mapError(error),\n });\n }\n }\n\n return results;\n }\n\n /**\n * Close the sandbox and release all resources.\n *\n * After closing, the sandbox cannot be used again. This terminates\n * the sandbox container on Modal.\n *\n * @example\n * ```typescript\n * try {\n * await sandbox.execute(\"npm run build\");\n * } finally {\n * await sandbox.close();\n * }\n * ```\n */\n async close(): Promise<void> {\n if (this.#sandbox) {\n try {\n await this.#sandbox.terminate();\n } finally {\n this.#sandbox = null;\n this.#app = null;\n this.#client = null;\n }\n }\n }\n\n /**\n * Terminate the sandbox.\n *\n * Alias for close() for Modal SDK compatibility.\n *\n * @example\n * ```typescript\n * await sandbox.terminate();\n * ```\n */\n async terminate(): Promise<void> {\n await this.close();\n }\n\n /**\n * Alias for close() to maintain compatibility with other sandbox implementations.\n */\n async stop(): Promise<void> {\n await this.close();\n }\n\n /**\n * Poll the sandbox status to check if it has finished running.\n *\n * @returns The exit code if the sandbox has finished, or null if still running\n */\n async poll(): Promise<number | null> {\n if (!this.#sandbox) {\n return null;\n }\n return this.#sandbox.poll();\n }\n\n /**\n * Wait for the sandbox to finish running.\n *\n * @returns The exit code of the sandbox\n * @throws {ModalSandboxError} If the sandbox is not initialized\n */\n async wait(): Promise<number> {\n return this.instance.wait();\n }\n\n /**\n * Set the sandbox from an existing Modal Sandbox instance.\n * Used internally by the static `fromId()` and `fromName()` methods.\n */\n #setFromExisting(\n client: ModalClient,\n existingSandbox: Sandbox,\n sandboxId: string,\n ): void {\n this.#client = client;\n this.#sandbox = existingSandbox;\n this.#id = sandboxId;\n }\n\n /**\n * Map Modal SDK errors to standardized FileOperationError codes.\n *\n * @param error - The error from the Modal SDK\n * @returns A standardized error code\n */\n #mapError(error: unknown): FileOperationError {\n if (error instanceof Error) {\n const msg = error.message.toLowerCase();\n\n if (msg.includes(\"not found\") || msg.includes(\"enoent\")) {\n return \"file_not_found\";\n }\n if (msg.includes(\"permission\") || msg.includes(\"eacces\")) {\n return \"permission_denied\";\n }\n if (msg.includes(\"directory\") || msg.includes(\"eisdir\")) {\n return \"is_directory\";\n }\n }\n\n return \"invalid_path\";\n }\n\n /**\n * Create and initialize a new ModalSandbox in one step.\n *\n * This is the recommended way to create a sandbox. It combines\n * construction and initialization into a single async operation.\n *\n * @param options - Configuration options for the sandbox\n * @returns An initialized and ready-to-use sandbox\n *\n * @example\n * ```typescript\n * const sandbox = await ModalSandbox.create({\n * imageName: \"python:3.12-slim\",\n * timeout: 600,\n * memory: 2048,\n * });\n * ```\n */\n static async create(options?: ModalSandboxOptions): Promise<ModalSandbox> {\n const sandbox = new ModalSandbox(options);\n await sandbox.initialize();\n return sandbox;\n }\n\n /**\n * Reconnect to an existing sandbox by ID.\n *\n * This allows you to resume working with a sandbox that was created\n * earlier and is still running.\n *\n * @param sandboxId - The ID of the sandbox to reconnect to\n * @param options - Optional auth configuration\n * @returns A connected sandbox instance\n *\n * @example\n * ```typescript\n * // Resume a sandbox from a stored ID\n * const sandbox = await ModalSandbox.fromId(\"sb-abc123\");\n * const result = await sandbox.execute(\"ls -la\");\n * ```\n */\n static async fromId(\n sandboxId: string,\n options?: Pick<ModalSandboxOptions, \"auth\">,\n ): Promise<ModalSandbox> {\n // Validate authentication credentials exist\n try {\n getAuthCredentials(options?.auth);\n } catch (error) {\n throw new ModalSandboxError(\n \"Failed to authenticate with Modal. Check your token configuration.\",\n \"AUTHENTICATION_FAILED\",\n error instanceof Error ? error : undefined,\n );\n }\n\n try {\n const client = new ModalClient();\n const existingSandbox = await client.sandboxes.fromId(sandboxId);\n\n const modalSandbox = new ModalSandbox(options);\n modalSandbox.#setFromExisting(client, existingSandbox, sandboxId);\n\n return modalSandbox;\n } catch (error) {\n throw new ModalSandboxError(\n `Sandbox not found: ${sandboxId}`,\n \"SANDBOX_NOT_FOUND\",\n error instanceof Error ? error : undefined,\n );\n }\n }\n\n /**\n * Get a running sandbox by name from a deployed app.\n *\n * @param appName - The name of the Modal app\n * @param sandboxName - The name of the sandbox\n * @param options - Optional auth configuration\n * @returns A connected sandbox instance\n *\n * @example\n * ```typescript\n * const sandbox = await ModalSandbox.fromName(\"my-app\", \"my-sandbox\");\n * const result = await sandbox.execute(\"ls -la\");\n * ```\n */\n static async fromName(\n appName: string,\n sandboxName: string,\n options?: Pick<ModalSandboxOptions, \"auth\">,\n ): Promise<ModalSandbox> {\n // Validate authentication credentials exist\n try {\n getAuthCredentials(options?.auth);\n } catch (error) {\n throw new ModalSandboxError(\n \"Failed to authenticate with Modal. Check your token configuration.\",\n \"AUTHENTICATION_FAILED\",\n error instanceof Error ? error : undefined,\n );\n }\n\n try {\n const client = new ModalClient();\n const existingSandbox = await client.sandboxes.fromName(\n appName,\n sandboxName,\n );\n\n const modalSandbox = new ModalSandbox(options);\n modalSandbox.#setFromExisting(\n client,\n existingSandbox,\n existingSandbox.sandboxId,\n );\n\n return modalSandbox;\n } catch (error) {\n throw new ModalSandboxError(\n `Sandbox not found: ${appName}/${sandboxName}`,\n \"SANDBOX_NOT_FOUND\",\n error instanceof Error ? error : undefined,\n );\n }\n }\n}\n\n/**\n * Async factory function type for creating Modal Sandbox instances.\n *\n * This is similar to BackendFactory but supports async creation,\n * which is required for Modal Sandbox since initialization is async.\n */\nexport type AsyncModalSandboxFactory = () => Promise<ModalSandbox>;\n\n/**\n * Create an async factory function that creates a new Modal Sandbox per invocation.\n *\n * Each call to the factory will create and initialize a new sandbox.\n * This is useful when you want fresh, isolated environments for each\n * agent invocation.\n *\n * **Important**: This returns an async factory. For use with middleware that\n * requires synchronous BackendFactory, use `createModalSandboxFactoryFromSandbox()`\n * with a pre-created sandbox instead.\n *\n * @param options - Optional configuration for sandbox creation\n * @returns An async factory function that creates new sandboxes\n *\n * @example\n * ```typescript\n * import { ModalSandbox, createModalSandboxFactory } from \"@langchain/modal\";\n *\n * // Create a factory for new sandboxes\n * const factory = createModalSandboxFactory({ imageName: \"python:3.12-slim\" });\n *\n * // Each call creates a new sandbox\n * const sandbox1 = await factory();\n * const sandbox2 = await factory();\n *\n * try {\n * // Use sandboxes...\n * } finally {\n * await sandbox1.close();\n * await sandbox2.close();\n * }\n * ```\n */\nexport function createModalSandboxFactory(\n options?: ModalSandboxOptions,\n): AsyncModalSandboxFactory {\n return async () => {\n return await ModalSandbox.create(options);\n };\n}\n\n/**\n * Create a backend factory that reuses an existing Modal Sandbox.\n *\n * This allows multiple agent invocations to share the same sandbox,\n * avoiding the startup overhead of creating new sandboxes.\n *\n * Important: You are responsible for managing the sandbox lifecycle\n * (calling `close()` when done).\n *\n * @param sandbox - An existing ModalSandbox instance (must be initialized)\n * @returns A BackendFactory that returns the provided sandbox\n *\n * @example\n * ```typescript\n * import { createDeepAgent, createFilesystemMiddleware } from \"deepagents\";\n * import { ModalSandbox, createModalSandboxFactoryFromSandbox } from \"@langchain/modal\";\n *\n * // Create and initialize a sandbox\n * const sandbox = await ModalSandbox.create({ imageName: \"python:3.12-slim\" });\n *\n * try {\n * const agent = createDeepAgent({\n * model: new ChatAnthropic({ model: \"claude-sonnet-4-20250514\" }),\n * systemPrompt: \"You are a coding assistant.\",\n * middlewares: [\n * createFilesystemMiddleware({\n * backend: createModalSandboxFactoryFromSandbox(sandbox),\n * }),\n * ],\n * });\n *\n * await agent.invoke({ messages: [...] });\n * } finally {\n * await sandbox.close();\n * }\n * ```\n */\nexport function createModalSandboxFactoryFromSandbox(\n sandbox: ModalSandbox,\n): BackendFactory {\n return () => sandbox;\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@langchain/modal",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Modal Sandbox backend for deepagents",
|
|
5
5
|
"main": "./dist/index.cjs",
|
|
6
6
|
"module": "./dist/index.js",
|
|
@@ -41,7 +41,8 @@
|
|
|
41
41
|
"tsx": "^4.21.0",
|
|
42
42
|
"typescript": "^5.9.3",
|
|
43
43
|
"vitest": "^4.0.18",
|
|
44
|
-
"deepagents": "1.7.
|
|
44
|
+
"deepagents": "1.7.1",
|
|
45
|
+
"@langchain/standard-tests": "0.0.1"
|
|
45
46
|
},
|
|
46
47
|
"exports": {
|
|
47
48
|
".": {
|