@langchain/modal 0.0.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/README.md ADDED
@@ -0,0 +1,344 @@
1
+ # @langchain/modal
2
+
3
+ Modal Sandbox backend for [deepagents](https://www.npmjs.com/package/deepagents). This package provides a `ModalSandbox` implementation of the `SandboxBackendProtocol`, enabling agents to execute commands, read/write files, and manage isolated container environments using Modal's serverless infrastructure.
4
+
5
+ [![npm version](https://img.shields.io/npm/v/@langchain/modal.svg)](https://www.npmjs.com/package/@langchain/modal)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
+
8
+ ## Features
9
+
10
+ - **Isolated Execution**: Run commands in secure, isolated containers on Modal's serverless infrastructure
11
+ - **GPU Support**: Access NVIDIA GPUs (T4, L4, A10G, A100, H100) for ML/AI workloads
12
+ - **Custom Images**: Use any Docker image from public registries
13
+ - **File Operations**: Upload and download files with full filesystem access
14
+ - **Volume Mounts**: Mount Modal Volumes for persistent storage
15
+ - **Secrets Injection**: Securely inject Modal Secrets as environment variables
16
+ - **BaseSandbox Integration**: All inherited methods (`read`, `write`, `edit`, `ls`, `grep`, `glob`) work out of the box
17
+ - **Factory Pattern**: Compatible with deepagents' middleware architecture
18
+ - **Full SDK Access**: Access the underlying Modal SDK via the `sandbox` property for advanced features
19
+
20
+ ## Installation
21
+
22
+ ```bash
23
+ # npm
24
+ npm install @langchain/modal
25
+
26
+ # yarn
27
+ yarn add @langchain/modal
28
+
29
+ # pnpm
30
+ pnpm add @langchain/modal
31
+ ```
32
+
33
+ ## Authentication Setup
34
+
35
+ The package requires Modal authentication:
36
+
37
+ ### Environment Variables (Recommended)
38
+
39
+ 1. Go to [https://modal.com/settings/tokens](https://modal.com/settings/tokens)
40
+ 2. Create a new token and set the environment variables:
41
+
42
+ ```bash
43
+ export MODAL_TOKEN_ID=your_token_id
44
+ export MODAL_TOKEN_SECRET=your_token_secret
45
+ ```
46
+
47
+ ### Explicit Credentials in Code
48
+
49
+ ```typescript
50
+ const sandbox = await ModalSandbox.create({
51
+ auth: {
52
+ tokenId: "your-token-id",
53
+ tokenSecret: "your-token-secret",
54
+ },
55
+ });
56
+ ```
57
+
58
+ ## Basic Usage
59
+
60
+ ```typescript
61
+ import { createDeepAgent } from "deepagents";
62
+ import { ChatAnthropic } from "@langchain/anthropic";
63
+ import { ModalSandbox } from "@langchain/modal";
64
+
65
+ // Create and initialize the sandbox
66
+ const sandbox = await ModalSandbox.create({
67
+ imageName: "python:3.12-slim",
68
+ timeoutMs: 600_000, // 10 minutes
69
+ });
70
+
71
+ try {
72
+ const agent = createDeepAgent({
73
+ model: new ChatAnthropic({ model: "claude-sonnet-4-20250514" }),
74
+ systemPrompt: "You are a coding assistant with access to a sandbox.",
75
+ backend: sandbox,
76
+ });
77
+
78
+ const result = await agent.invoke({
79
+ messages: [
80
+ { role: "user", content: "Create a hello world Python app and run it" },
81
+ ],
82
+ });
83
+ } finally {
84
+ await sandbox.close();
85
+ }
86
+ ```
87
+
88
+ ## Configuration Options
89
+
90
+ `ModalSandboxOptions` extends the Modal SDK's `SandboxCreateParams` directly, so you can use any SDK option. We only wrap `volumes` and `secrets` to accept names instead of objects.
91
+
92
+ ```typescript
93
+ interface ModalSandboxOptions extends SandboxCreateParams {
94
+ /** Modal App name. @default "deepagents-sandbox" */
95
+ appName?: string;
96
+
97
+ /** Docker image to use. @default "alpine:3.21" */
98
+ imageName?: string;
99
+
100
+ /** Initial files to populate the sandbox with. */
101
+ initialFiles?: Record<string, string | Uint8Array>;
102
+
103
+ /** Authentication credentials (or use env vars). */
104
+ auth?: { tokenId?: string; tokenSecret?: string };
105
+
106
+ /** Modal Volume names to mount (keys are mount paths). */
107
+ volumes?: Record<string, string>;
108
+
109
+ /** Modal Secret names to inject. */
110
+ secrets?: string[];
111
+
112
+ // All SandboxCreateParams options are available:
113
+ timeoutMs?: number; // Max lifetime in milliseconds
114
+ idleTimeoutMs?: number; // Idle timeout in milliseconds
115
+ workdir?: string; // Working directory
116
+ gpu?: string; // GPU type (e.g., "T4", "A100")
117
+ cpu?: number; // CPU cores (fractional allowed)
118
+ memoryMiB?: number; // Memory in MiB
119
+ regions?: string[]; // Regions to run in
120
+ env?: Record<string, string>; // Environment variables
121
+ blockNetwork?: boolean; // Block network access
122
+ cidrAllowlist?: string[]; // Allowed CIDRs
123
+ verbose?: boolean; // Enable verbose logging
124
+ name?: string; // Sandbox name (unique within app)
125
+ }
126
+ ```
127
+
128
+ ## GPU Support
129
+
130
+ Modal supports various NVIDIA GPUs for ML/AI workloads:
131
+
132
+ ```typescript
133
+ const sandbox = await ModalSandbox.create({
134
+ imageName: "python:3.12-slim",
135
+ gpu: "T4", // NVIDIA T4 (16GB VRAM)
136
+ // gpu: "L4", // NVIDIA L4 (24GB VRAM)
137
+ // gpu: "A10G", // NVIDIA A10G (24GB VRAM)
138
+ // gpu: "A100", // NVIDIA A100 (40/80GB VRAM)
139
+ // gpu: "H100", // NVIDIA H100 (80GB VRAM)
140
+ });
141
+ ```
142
+
143
+ ## Using Volumes
144
+
145
+ Mount Modal Volumes for persistent storage:
146
+
147
+ ```typescript
148
+ // Volume must be created in Modal first
149
+ const sandbox = await ModalSandbox.create({
150
+ imageName: "python:3.12-slim",
151
+ volumes: {
152
+ "/data": "my-data-volume",
153
+ "/models": "my-models-volume",
154
+ },
155
+ });
156
+
157
+ // Files in /data and /models persist across sandbox restarts
158
+ await sandbox.execute("echo 'Hello' > /data/test.txt");
159
+ ```
160
+
161
+ ## Using Secrets
162
+
163
+ Inject Modal Secrets as environment variables:
164
+
165
+ ```typescript
166
+ // Secrets must be created in Modal first
167
+ const sandbox = await ModalSandbox.create({
168
+ imageName: "python:3.12-slim",
169
+ secrets: ["my-api-keys", "database-credentials"],
170
+ });
171
+
172
+ // Secrets are available as environment variables
173
+ await sandbox.execute("echo $API_KEY");
174
+ ```
175
+
176
+ ## Initial Files
177
+
178
+ Pre-populate the sandbox with files during creation:
179
+
180
+ ```typescript
181
+ const sandbox = await ModalSandbox.create({
182
+ imageName: "python:3.12-slim",
183
+ initialFiles: {
184
+ // String content
185
+ "/app/main.py": `
186
+ import json
187
+
188
+ def main():
189
+ print("Hello from Python!")
190
+
191
+ if __name__ == "__main__":
192
+ main()
193
+ `,
194
+ // JSON configuration
195
+ "/app/config.json": JSON.stringify(
196
+ { name: "my-app", version: "1.0.0" },
197
+ null,
198
+ 2,
199
+ ),
200
+
201
+ // Uint8Array content also supported
202
+ "/app/data.bin": new Uint8Array([0x00, 0x01, 0x02]),
203
+ },
204
+ });
205
+
206
+ // Files are ready to use immediately
207
+ const result = await sandbox.execute("python /app/main.py");
208
+ console.log(result.output); // "Hello from Python!"
209
+ ```
210
+
211
+ This is especially useful for:
212
+
213
+ - Setting up project scaffolding before agent execution
214
+ - Providing configuration files
215
+ - Pre-loading test data or fixtures
216
+ - Creating initial source code files
217
+
218
+ ## Accessing the Modal SDK
219
+
220
+ For advanced features not exposed by `BaseSandbox`, you can access the underlying Modal SDK directly:
221
+
222
+ - `.client` - The `ModalClient` instance for accessing other Modal resources
223
+ - `.instance` - The `Sandbox` instance for direct sandbox operations
224
+
225
+ ```typescript
226
+ const modalSandbox = await ModalSandbox.create();
227
+
228
+ // Access the Modal client for other Modal resources
229
+ const client = modalSandbox.client;
230
+
231
+ // Access the raw Modal Sandbox for direct operations
232
+ const instance = modalSandbox.instance;
233
+
234
+ // Execute commands with specific options
235
+ const process = await instance.exec(["python", "-c", "print('Hello')"], {
236
+ stdout: "pipe",
237
+ stderr: "pipe",
238
+ });
239
+
240
+ // Open files for reading/writing
241
+ const writeHandle = await instance.open("/tmp/file.txt", "w");
242
+ await writeHandle.write(new TextEncoder().encode("Hello"));
243
+ await writeHandle.close();
244
+ ```
245
+
246
+ ## Reconnecting to Existing Sandboxes
247
+
248
+ Resume working with a sandbox that is still running:
249
+
250
+ ```typescript
251
+ // First session: create a sandbox
252
+ const sandbox = await ModalSandbox.create({
253
+ imageName: "python:3.12-slim",
254
+ timeout: 3600, // 1 hour
255
+ });
256
+ const sandboxId = sandbox.id;
257
+
258
+ // Later: reconnect to the same sandbox by ID
259
+ const reconnected = await ModalSandbox.fromId(sandboxId);
260
+ const result = await reconnected.execute("ls -la");
261
+
262
+ // Or reconnect by name (if sandbox has a name)
263
+ const sandbox2 = await ModalSandbox.create({
264
+ appName: "my-app",
265
+ sandboxName: "my-sandbox",
266
+ imageName: "python:3.12-slim",
267
+ });
268
+
269
+ const reconnected2 = await ModalSandbox.fromName("my-app", "my-sandbox");
270
+ ```
271
+
272
+ ## Error Handling
273
+
274
+ ```typescript
275
+ import { ModalSandboxError } from "@langchain/modal";
276
+
277
+ try {
278
+ await sandbox.execute("some command");
279
+ } catch (error) {
280
+ if (error instanceof ModalSandboxError) {
281
+ switch (error.code) {
282
+ case "NOT_INITIALIZED":
283
+ await sandbox.initialize();
284
+ break;
285
+ case "COMMAND_TIMEOUT":
286
+ console.error("Command took too long");
287
+ break;
288
+ case "AUTHENTICATION_FAILED":
289
+ console.error("Check your Modal token credentials");
290
+ break;
291
+ default:
292
+ throw error;
293
+ }
294
+ }
295
+ }
296
+ ```
297
+
298
+ ### Error Codes
299
+
300
+ | Code | Description |
301
+ | ------------------------- | ------------------------------------------- |
302
+ | `NOT_INITIALIZED` | Sandbox not initialized - call initialize() |
303
+ | `ALREADY_INITIALIZED` | Cannot initialize twice |
304
+ | `AUTHENTICATION_FAILED` | Invalid or missing Modal tokens |
305
+ | `SANDBOX_CREATION_FAILED` | Failed to create sandbox |
306
+ | `SANDBOX_NOT_FOUND` | Sandbox ID/name not found or expired |
307
+ | `COMMAND_TIMEOUT` | Command execution timed out |
308
+ | `COMMAND_FAILED` | Command execution failed |
309
+ | `FILE_OPERATION_FAILED` | File read/write failed |
310
+ | `RESOURCE_LIMIT_EXCEEDED` | CPU, memory, or storage limits exceeded |
311
+ | `VOLUME_ERROR` | Volume operation failed |
312
+
313
+ ## Inherited BaseSandbox Methods
314
+
315
+ `ModalSandbox` extends `BaseSandbox` and inherits these convenience methods:
316
+
317
+ | Method | Description |
318
+ | ------------ | ----------------------------- |
319
+ | `read()` | Read a file's contents |
320
+ | `write()` | Write content to a file |
321
+ | `edit()` | Replace text in a file |
322
+ | `lsInfo()` | List directory contents |
323
+ | `grepRaw()` | Search for patterns in files |
324
+ | `globInfo()` | Find files matching a pattern |
325
+
326
+ ## Limits and Constraints
327
+
328
+ | Constraint | Value |
329
+ | --------------- | --------------------------------------- |
330
+ | Max timeout | 86400 seconds (24 hours) |
331
+ | Default timeout | 300 seconds (5 minutes) |
332
+ | Network access | Full (by default, can be blocked) |
333
+ | File API | Alpha (up to 100 MiB read, 1 GiB write) |
334
+
335
+ ## Environment Variables
336
+
337
+ | Variable | Description |
338
+ | -------------------- | ---------------------- |
339
+ | `MODAL_TOKEN_ID` | Modal API token ID |
340
+ | `MODAL_TOKEN_SECRET` | Modal API token secret |
341
+
342
+ ## License
343
+
344
+ MIT