@cloudflare/sandbox 0.3.4 → 0.3.5
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/CHANGELOG.md +6 -0
- package/Dockerfile +1 -0
- package/README.md +58 -5
- package/container_src/handler/file.ts +51 -0
- package/container_src/index.ts +8 -0
- package/container_src/isolation.ts +173 -9
- package/dist/{chunk-SMUEY5JR.js → chunk-32UDXUPC.js} +31 -1
- package/dist/chunk-32UDXUPC.js.map +1 -0
- package/dist/chunk-5DILEXGY.js +85 -0
- package/dist/chunk-5DILEXGY.js.map +1 -0
- package/dist/{chunk-Z6OZPC6U.js → chunk-D3U63BZP.js} +2 -2
- package/dist/{chunk-DEXT4CAF.js → chunk-SQLJNZ3K.js} +9 -2
- package/dist/chunk-SQLJNZ3K.js.map +1 -0
- package/dist/{client-DRhcuRza.d.ts → client-B3RUab0s.d.ts} +2 -0
- package/dist/client.d.ts +1 -1
- package/dist/client.js +1 -1
- package/dist/file-stream.d.ts +65 -0
- package/dist/file-stream.js +10 -0
- package/dist/file-stream.js.map +1 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.js +10 -4
- package/dist/interpreter-client.d.ts +1 -1
- package/dist/interpreter-client.js +2 -2
- package/dist/interpreter.d.ts +1 -1
- package/dist/request-handler.d.ts +1 -1
- package/dist/request-handler.js +3 -3
- package/dist/sandbox.d.ts +1 -1
- package/dist/sandbox.js +3 -3
- package/dist/types.d.ts +56 -1
- package/dist/types.js.map +1 -1
- package/package.json +1 -1
- package/src/client.ts +39 -0
- package/src/file-stream.ts +162 -0
- package/src/index.ts +6 -0
- package/src/sandbox.ts +10 -1
- package/src/types.ts +69 -0
- package/dist/chunk-DEXT4CAF.js.map +0 -1
- package/dist/chunk-SMUEY5JR.js.map +0 -1
- /package/dist/{chunk-Z6OZPC6U.js.map → chunk-D3U63BZP.js.map} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# @cloudflare/sandbox
|
|
2
2
|
|
|
3
|
+
## 0.3.5
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#88](https://github.com/cloudflare/sandbox-sdk/pull/88) [`46eb4e6`](https://github.com/cloudflare/sandbox-sdk/commit/46eb4e6b6c671b682fc74f83563ccf5f316011cb) Thanks [@ghostwriternr](https://github.com/ghostwriternr)! - Add binary file support with automatic MIME detection and streaming
|
|
8
|
+
|
|
3
9
|
## 0.3.4
|
|
4
10
|
|
|
5
11
|
### Patch Changes
|
package/Dockerfile
CHANGED
package/README.md
CHANGED
|
@@ -50,10 +50,11 @@ The Cloudflare Sandbox SDK enables you to run isolated code environments directl
|
|
|
50
50
|
- **🔒 Secure Isolation**: Each sandbox runs in its own container with full process isolation
|
|
51
51
|
- **⚡ Edge-Native**: Runs on Cloudflare's global network for low latency worldwide
|
|
52
52
|
- **📁 File System Access**: Read, write, and manage files within the sandbox
|
|
53
|
+
- **🖼️ Binary File Support**: Automatic MIME type detection and base64 encoding for images, PDFs, and other binary files
|
|
53
54
|
- **🔧 Command Execution**: Run any command or process inside the container
|
|
54
55
|
- **🌐 Preview URLs**: Expose services running in your sandbox via public URLs
|
|
55
56
|
- **🔄 Git Integration**: Clone repositories directly into sandboxes
|
|
56
|
-
- **🚀 Streaming Support**: Real-time output streaming for long-running commands
|
|
57
|
+
- **🚀 Streaming Support**: Real-time output streaming for long-running commands and file transfers
|
|
57
58
|
- **🎮 Session Management**: Maintain state across multiple operations
|
|
58
59
|
- **🧪 Code Interpreter**: Execute Python and JavaScript with rich outputs (charts, tables, formatted data)
|
|
59
60
|
- **📊 Multi-Language Support**: Persistent execution contexts for Python and JavaScript/TypeScript
|
|
@@ -72,7 +73,7 @@ npm install @cloudflare/sandbox
|
|
|
72
73
|
1. **Create a Dockerfile** (temporary requirement, will be removed in future releases):
|
|
73
74
|
|
|
74
75
|
```dockerfile
|
|
75
|
-
FROM docker.io/cloudflare/sandbox:0.3.
|
|
76
|
+
FROM docker.io/cloudflare/sandbox:0.3.5
|
|
76
77
|
|
|
77
78
|
# Expose the ports you want to expose
|
|
78
79
|
EXPOSE 3000
|
|
@@ -189,11 +190,57 @@ await sandbox.writeFile("/workspace/app.js", "console.log('Hello!');");
|
|
|
189
190
|
|
|
190
191
|
#### `readFile(path, options?)`
|
|
191
192
|
|
|
192
|
-
Read a file from the sandbox.
|
|
193
|
+
Read a file from the sandbox with automatic binary detection.
|
|
193
194
|
|
|
194
195
|
```typescript
|
|
196
|
+
// Read text files
|
|
195
197
|
const file = await sandbox.readFile("/package.json");
|
|
196
|
-
console.log(file.content);
|
|
198
|
+
console.log(file.content); // UTF-8 text content
|
|
199
|
+
|
|
200
|
+
// Read binary files - automatically detected and base64 encoded
|
|
201
|
+
const image = await sandbox.readFile("/workspace/chart.png");
|
|
202
|
+
console.log(image.mimeType); // "image/png"
|
|
203
|
+
console.log(image.isBinary); // true
|
|
204
|
+
console.log(image.encoding); // "base64"
|
|
205
|
+
console.log(image.size); // File size in bytes
|
|
206
|
+
|
|
207
|
+
// Use the base64 content directly in data URLs
|
|
208
|
+
const dataUrl = `data:${image.mimeType};base64,${image.content}`;
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
#### `readFileStream(path)`
|
|
212
|
+
|
|
213
|
+
Stream large files efficiently with automatic chunking and encoding.
|
|
214
|
+
|
|
215
|
+
```typescript
|
|
216
|
+
import { streamFile, collectFile } from '@cloudflare/sandbox';
|
|
217
|
+
|
|
218
|
+
// Stream a large file
|
|
219
|
+
const stream = await sandbox.readFileStream("/large-video.mp4");
|
|
220
|
+
|
|
221
|
+
// Option 1: Process chunks as they arrive
|
|
222
|
+
for await (const chunk of streamFile(stream)) {
|
|
223
|
+
if (chunk instanceof Uint8Array) {
|
|
224
|
+
// Binary chunk - already decoded from base64
|
|
225
|
+
console.log(`Received ${chunk.byteLength} bytes`);
|
|
226
|
+
// Process binary data...
|
|
227
|
+
} else {
|
|
228
|
+
// Text chunk
|
|
229
|
+
console.log('Text:', chunk);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Option 2: Collect entire file into memory
|
|
234
|
+
const { content, metadata } = await collectFile(stream);
|
|
235
|
+
console.log(`MIME: ${metadata.mimeType}, Size: ${metadata.size} bytes`);
|
|
236
|
+
|
|
237
|
+
if (content instanceof Uint8Array) {
|
|
238
|
+
// Binary file - ready to save or process
|
|
239
|
+
await writeToStorage(content);
|
|
240
|
+
} else {
|
|
241
|
+
// Text file
|
|
242
|
+
console.log('Content:', content);
|
|
243
|
+
}
|
|
197
244
|
```
|
|
198
245
|
|
|
199
246
|
#### `gitCheckout(repoUrl, options?)`
|
|
@@ -241,7 +288,8 @@ console.log(result.stdout); // "production"
|
|
|
241
288
|
#### File System Methods
|
|
242
289
|
|
|
243
290
|
- `writeFile(path, content, options?)` - Write content to a file
|
|
244
|
-
- `readFile(path, options?)` - Read a file
|
|
291
|
+
- `readFile(path, options?)` - Read a file with automatic binary detection and base64 encoding
|
|
292
|
+
- `readFileStream(path)` - Stream large files efficiently with chunking
|
|
245
293
|
- `mkdir(path, options?)` - Create a directory
|
|
246
294
|
- `deleteFile(path)` - Delete a file
|
|
247
295
|
- `renameFile(oldPath, newPath)` - Rename a file
|
|
@@ -665,10 +713,15 @@ for await (const event of parseSSEStream<ExecEvent>(stream)) {
|
|
|
665
713
|
|
|
666
714
|
The SDK exports utilities for working with Server-Sent Event streams:
|
|
667
715
|
|
|
716
|
+
**Command Execution:**
|
|
668
717
|
- **`parseSSEStream<T>(stream)`** - Convert ReadableStream to typed AsyncIterable
|
|
669
718
|
- **`responseToAsyncIterable<T>(response)`** - Convert SSE Response to AsyncIterable
|
|
670
719
|
- **`asyncIterableToSSEStream<T>(iterable)`** - Convert AsyncIterable back to SSE stream
|
|
671
720
|
|
|
721
|
+
**File Streaming:**
|
|
722
|
+
- **`streamFile(stream, signal?)`** - Convert file SSE stream to AsyncIterable with automatic base64 decoding
|
|
723
|
+
- **`collectFile(stream, signal?)`** - Collect entire file from stream into memory
|
|
724
|
+
|
|
672
725
|
#### Advanced Streaming Examples
|
|
673
726
|
|
|
674
727
|
**CI/CD Build System:**
|
|
@@ -204,6 +204,11 @@ export async function handleReadFileRequest(
|
|
|
204
204
|
path,
|
|
205
205
|
success: result.success,
|
|
206
206
|
timestamp: new Date().toISOString(),
|
|
207
|
+
// New metadata fields for binary file support
|
|
208
|
+
encoding: result.encoding,
|
|
209
|
+
isBinary: result.isBinary,
|
|
210
|
+
mimeType: result.mimeType,
|
|
211
|
+
size: result.size,
|
|
207
212
|
}),
|
|
208
213
|
{
|
|
209
214
|
headers: {
|
|
@@ -403,4 +408,50 @@ export async function handleListFilesRequest(
|
|
|
403
408
|
} catch (error) {
|
|
404
409
|
return createServerErrorResponse("handleListFilesRequest", error, corsHeaders);
|
|
405
410
|
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
export async function handleReadFileStreamRequest(
|
|
414
|
+
req: Request,
|
|
415
|
+
corsHeaders: Record<string, string>,
|
|
416
|
+
sessionManager: SessionManager
|
|
417
|
+
): Promise<Response> {
|
|
418
|
+
try {
|
|
419
|
+
const body = (await req.json()) as ReadFileRequest;
|
|
420
|
+
const { path, sessionId } = body;
|
|
421
|
+
|
|
422
|
+
// Validate path
|
|
423
|
+
const pathError = validatePath(path);
|
|
424
|
+
if (pathError) {
|
|
425
|
+
return createPathErrorResponse(pathError, corsHeaders);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
console.log(`[Server] Streaming file: ${path}${sessionId ? ` in session: ${sessionId}` : ''}`);
|
|
429
|
+
|
|
430
|
+
// Get the appropriate session
|
|
431
|
+
const session = sessionId
|
|
432
|
+
? sessionManager.getSession(sessionId)
|
|
433
|
+
: await sessionManager.getOrCreateDefaultSession();
|
|
434
|
+
|
|
435
|
+
if (!session) {
|
|
436
|
+
return createServerErrorResponse(
|
|
437
|
+
"handleReadFileStreamRequest",
|
|
438
|
+
new Error(`Session '${sessionId}' not found`),
|
|
439
|
+
corsHeaders
|
|
440
|
+
);
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// Create SSE stream
|
|
444
|
+
const stream = await session.readFileStreamOperation(path);
|
|
445
|
+
|
|
446
|
+
return new Response(stream, {
|
|
447
|
+
headers: {
|
|
448
|
+
"Content-Type": "text/event-stream",
|
|
449
|
+
"Cache-Control": "no-cache",
|
|
450
|
+
"Connection": "keep-alive",
|
|
451
|
+
...corsHeaders,
|
|
452
|
+
},
|
|
453
|
+
});
|
|
454
|
+
} catch (error) {
|
|
455
|
+
return createServerErrorResponse("handleReadFileStreamRequest", error, corsHeaders);
|
|
456
|
+
}
|
|
406
457
|
}
|
package/container_src/index.ts
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
handleMkdirRequest,
|
|
10
10
|
handleMoveFileRequest,
|
|
11
11
|
handleReadFileRequest,
|
|
12
|
+
handleReadFileStreamRequest,
|
|
12
13
|
handleRenameFileRequest,
|
|
13
14
|
handleWriteFileRequest,
|
|
14
15
|
} from "./handler/file";
|
|
@@ -190,6 +191,12 @@ const server = serve({
|
|
|
190
191
|
}
|
|
191
192
|
break;
|
|
192
193
|
|
|
194
|
+
case "/api/read/stream":
|
|
195
|
+
if (req.method === "POST") {
|
|
196
|
+
return handleReadFileStreamRequest(req, corsHeaders, sessionManager);
|
|
197
|
+
}
|
|
198
|
+
break;
|
|
199
|
+
|
|
193
200
|
case "/api/delete":
|
|
194
201
|
if (req.method === "POST") {
|
|
195
202
|
return handleDeleteFileRequest(req, corsHeaders, sessionManager);
|
|
@@ -569,6 +576,7 @@ console.log(` POST /api/git/checkout - Checkout a git repository`);
|
|
|
569
576
|
console.log(` POST /api/mkdir - Create a directory`);
|
|
570
577
|
console.log(` POST /api/write - Write a file`);
|
|
571
578
|
console.log(` POST /api/read - Read a file`);
|
|
579
|
+
console.log(` POST /api/read/stream - Stream a file (SSE)`);
|
|
572
580
|
console.log(` POST /api/delete - Delete a file`);
|
|
573
581
|
console.log(` POST /api/rename - Rename a file`);
|
|
574
582
|
console.log(` POST /api/move - Move a file`);
|
|
@@ -446,16 +446,180 @@ SANDBOX_EOF`;
|
|
|
446
446
|
};
|
|
447
447
|
}
|
|
448
448
|
|
|
449
|
-
async readFileOperation(path: string, encoding: string = 'utf-8'): Promise<{
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
449
|
+
async readFileOperation(path: string, encoding: string = 'utf-8'): Promise<{
|
|
450
|
+
success: boolean;
|
|
451
|
+
exitCode: number;
|
|
452
|
+
content: string;
|
|
453
|
+
path: string;
|
|
454
|
+
encoding?: 'utf-8' | 'base64';
|
|
455
|
+
isBinary?: boolean;
|
|
456
|
+
mimeType?: string;
|
|
457
|
+
size?: number;
|
|
458
|
+
}> {
|
|
459
|
+
// Step 1: Check if file exists and get metadata
|
|
460
|
+
const statCommand = `stat -c '%s' "${path}" 2>/dev/null || echo "FILE_NOT_FOUND"`;
|
|
461
|
+
const statResult = await this.exec(statCommand);
|
|
462
|
+
|
|
463
|
+
if (statResult.stdout.trim() === 'FILE_NOT_FOUND') {
|
|
464
|
+
// File doesn't exist - return error
|
|
465
|
+
return {
|
|
466
|
+
success: false,
|
|
467
|
+
exitCode: 1,
|
|
468
|
+
content: '',
|
|
469
|
+
path
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
const fileSize = parseInt(statResult.stdout.trim(), 10);
|
|
474
|
+
|
|
475
|
+
// Step 2: Detect MIME type using file command
|
|
476
|
+
const mimeCommand = `file --mime-type -b "${path}"`;
|
|
477
|
+
const mimeResult = await this.exec(mimeCommand);
|
|
478
|
+
const mimeType = mimeResult.stdout.trim();
|
|
479
|
+
|
|
480
|
+
// Step 3: Determine if file is binary based on MIME type
|
|
481
|
+
// Text MIME types: text/*, application/json, application/xml, application/javascript, etc.
|
|
482
|
+
const isBinary = !mimeType.startsWith('text/') &&
|
|
483
|
+
!mimeType.includes('json') &&
|
|
484
|
+
!mimeType.includes('xml') &&
|
|
485
|
+
!mimeType.includes('javascript') &&
|
|
486
|
+
!mimeType.includes('x-empty');
|
|
487
|
+
|
|
488
|
+
// Step 4: Read file with appropriate encoding
|
|
489
|
+
let content: string;
|
|
490
|
+
let actualEncoding: 'utf-8' | 'base64';
|
|
491
|
+
|
|
492
|
+
if (isBinary) {
|
|
493
|
+
// Use base64 for binary files
|
|
494
|
+
const base64Command = `base64 -w 0 "${path}"`;
|
|
495
|
+
const base64Result = await this.exec(base64Command);
|
|
496
|
+
content = base64Result.stdout;
|
|
497
|
+
actualEncoding = 'base64';
|
|
498
|
+
} else {
|
|
499
|
+
// Use cat for text files
|
|
500
|
+
const catCommand = `cat "${path}"`;
|
|
501
|
+
const catResult = await this.exec(catCommand);
|
|
502
|
+
content = catResult.stdout;
|
|
503
|
+
actualEncoding = 'utf-8';
|
|
504
|
+
}
|
|
505
|
+
|
|
453
506
|
return {
|
|
454
|
-
success:
|
|
455
|
-
exitCode:
|
|
456
|
-
content
|
|
457
|
-
path
|
|
507
|
+
success: true,
|
|
508
|
+
exitCode: 0,
|
|
509
|
+
content,
|
|
510
|
+
path,
|
|
511
|
+
encoding: actualEncoding,
|
|
512
|
+
isBinary,
|
|
513
|
+
mimeType,
|
|
514
|
+
size: fileSize
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
async readFileStreamOperation(path: string): Promise<ReadableStream<Uint8Array>> {
|
|
519
|
+
const encoder = new TextEncoder();
|
|
520
|
+
|
|
521
|
+
// Helper to send SSE event
|
|
522
|
+
const sseEvent = (event: any): Uint8Array => {
|
|
523
|
+
return encoder.encode(`data: ${JSON.stringify(event)}\n\n`);
|
|
458
524
|
};
|
|
525
|
+
|
|
526
|
+
// Create streaming response
|
|
527
|
+
return new ReadableStream({
|
|
528
|
+
start: async (controller) => {
|
|
529
|
+
try {
|
|
530
|
+
// Step 1: Get file metadata (same logic as readFileOperation)
|
|
531
|
+
const statCommand = `stat -c '%s' "${path}" 2>/dev/null || echo "FILE_NOT_FOUND"`;
|
|
532
|
+
const statResult = await this.exec(statCommand);
|
|
533
|
+
|
|
534
|
+
if (statResult.stdout.trim() === 'FILE_NOT_FOUND') {
|
|
535
|
+
// File doesn't exist - send error event
|
|
536
|
+
controller.enqueue(sseEvent({
|
|
537
|
+
type: 'error',
|
|
538
|
+
error: `File not found: ${path}`
|
|
539
|
+
}));
|
|
540
|
+
controller.close();
|
|
541
|
+
return;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
const fileSize = parseInt(statResult.stdout.trim(), 10);
|
|
545
|
+
|
|
546
|
+
// Step 2: Detect MIME type
|
|
547
|
+
const mimeCommand = `file --mime-type -b "${path}"`;
|
|
548
|
+
const mimeResult = await this.exec(mimeCommand);
|
|
549
|
+
const mimeType = mimeResult.stdout.trim();
|
|
550
|
+
|
|
551
|
+
// Step 3: Determine if binary
|
|
552
|
+
const isBinary = !mimeType.startsWith('text/') &&
|
|
553
|
+
!mimeType.includes('json') &&
|
|
554
|
+
!mimeType.includes('xml') &&
|
|
555
|
+
!mimeType.includes('javascript') &&
|
|
556
|
+
!mimeType.includes('x-empty');
|
|
557
|
+
|
|
558
|
+
const encoding: 'utf-8' | 'base64' = isBinary ? 'base64' : 'utf-8';
|
|
559
|
+
|
|
560
|
+
// Step 4: Send metadata event
|
|
561
|
+
controller.enqueue(sseEvent({
|
|
562
|
+
type: 'metadata',
|
|
563
|
+
mimeType,
|
|
564
|
+
size: fileSize,
|
|
565
|
+
isBinary,
|
|
566
|
+
encoding
|
|
567
|
+
}));
|
|
568
|
+
|
|
569
|
+
// Step 5: Stream file in chunks
|
|
570
|
+
// IMPORTANT: Chunk size MUST be divisible by 3 for base64 encoding!
|
|
571
|
+
// Base64 encodes 3 bytes at a time. If chunks aren't aligned,
|
|
572
|
+
// concatenating separately-encoded base64 strings corrupts the data.
|
|
573
|
+
const CHUNK_SIZE = 65535;
|
|
574
|
+
let bytesRead = 0;
|
|
575
|
+
|
|
576
|
+
while (bytesRead < fileSize) {
|
|
577
|
+
const remainingBytes = fileSize - bytesRead;
|
|
578
|
+
const chunkSize = Math.min(CHUNK_SIZE, remainingBytes);
|
|
579
|
+
|
|
580
|
+
// Use dd to read chunk at specific offset
|
|
581
|
+
// bs=1 means 1 byte block size, skip=offset, count=chunkSize
|
|
582
|
+
let chunkCommand: string;
|
|
583
|
+
if (isBinary) {
|
|
584
|
+
// For binary, read and encode as base64
|
|
585
|
+
chunkCommand = `dd if="${path}" bs=1 skip=${bytesRead} count=${chunkSize} 2>/dev/null | base64 -w 0`;
|
|
586
|
+
} else {
|
|
587
|
+
// For text, just read
|
|
588
|
+
chunkCommand = `dd if="${path}" bs=1 skip=${bytesRead} count=${chunkSize} 2>/dev/null`;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
const chunkResult = await this.exec(chunkCommand);
|
|
592
|
+
|
|
593
|
+
// Send chunk event
|
|
594
|
+
controller.enqueue(sseEvent({
|
|
595
|
+
type: 'chunk',
|
|
596
|
+
data: chunkResult.stdout
|
|
597
|
+
}));
|
|
598
|
+
|
|
599
|
+
bytesRead += chunkSize;
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
// Step 6: Send complete event
|
|
603
|
+
controller.enqueue(sseEvent({
|
|
604
|
+
type: 'complete',
|
|
605
|
+
bytesRead
|
|
606
|
+
}));
|
|
607
|
+
|
|
608
|
+
controller.close();
|
|
609
|
+
} catch (error) {
|
|
610
|
+
// Send error event
|
|
611
|
+
controller.enqueue(sseEvent({
|
|
612
|
+
type: 'error',
|
|
613
|
+
error: error instanceof Error ? error.message : String(error)
|
|
614
|
+
}));
|
|
615
|
+
controller.close();
|
|
616
|
+
}
|
|
617
|
+
},
|
|
618
|
+
|
|
619
|
+
cancel() {
|
|
620
|
+
console.log(`[Session] File stream cancelled for: ${path}`);
|
|
621
|
+
}
|
|
622
|
+
});
|
|
459
623
|
}
|
|
460
624
|
|
|
461
625
|
async mkdirOperation(path: string, recursive: boolean = false): Promise<{ success: boolean; exitCode: number; path: string; recursive: boolean }> {
|
|
@@ -1010,7 +1174,7 @@ export class SessionManager {
|
|
|
1010
1174
|
return defaultSession.writeFileOperation(path, content, encoding);
|
|
1011
1175
|
}
|
|
1012
1176
|
|
|
1013
|
-
async readFile(path: string, encoding?: string): Promise<{ success: boolean; exitCode: number; content: string; path: string }> {
|
|
1177
|
+
async readFile(path: string, encoding?: string): Promise<{ success: boolean; exitCode: number; content: string; path: string; encoding?: 'utf-8' | 'base64'; isBinary?: boolean; mimeType?: string; size?: number }> {
|
|
1014
1178
|
const defaultSession = await this.getOrCreateDefaultSession();
|
|
1015
1179
|
return defaultSession.readFileOperation(path, encoding);
|
|
1016
1180
|
}
|
|
@@ -249,6 +249,36 @@ var HttpClient = class {
|
|
|
249
249
|
throw error;
|
|
250
250
|
}
|
|
251
251
|
}
|
|
252
|
+
async readFileStream(path, sessionId) {
|
|
253
|
+
try {
|
|
254
|
+
const response = await this.doFetch(`/api/read/stream`, {
|
|
255
|
+
method: "POST",
|
|
256
|
+
headers: {
|
|
257
|
+
"Content-Type": "application/json"
|
|
258
|
+
},
|
|
259
|
+
body: JSON.stringify({
|
|
260
|
+
path,
|
|
261
|
+
sessionId
|
|
262
|
+
})
|
|
263
|
+
});
|
|
264
|
+
if (!response.ok) {
|
|
265
|
+
const errorData = await response.json().catch(() => ({}));
|
|
266
|
+
throw new Error(
|
|
267
|
+
errorData.error || `HTTP error! status: ${response.status}`
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
if (!response.body) {
|
|
271
|
+
throw new Error("No response body for file streaming");
|
|
272
|
+
}
|
|
273
|
+
console.log(
|
|
274
|
+
`[HTTP Client] Started streaming file: ${path}${sessionId ? ` in session: ${sessionId}` : ""}`
|
|
275
|
+
);
|
|
276
|
+
return response.body;
|
|
277
|
+
} catch (error) {
|
|
278
|
+
console.error("[HTTP Client] Error streaming file:", error);
|
|
279
|
+
throw error;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
252
282
|
async deleteFile(path, sessionId) {
|
|
253
283
|
try {
|
|
254
284
|
const response = await this.doFetch(`/api/delete`, {
|
|
@@ -638,4 +668,4 @@ var HttpClient = class {
|
|
|
638
668
|
export {
|
|
639
669
|
HttpClient
|
|
640
670
|
};
|
|
641
|
-
//# sourceMappingURL=chunk-
|
|
671
|
+
//# sourceMappingURL=chunk-32UDXUPC.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/client.ts"],"sourcesContent":["import type { ExecuteRequest } from \"../container_src/types\";\nimport type { Sandbox } from \"./index\";\nimport type {\n BaseExecOptions,\n DeleteFileResponse,\n ExecuteResponse,\n GetProcessLogsResponse,\n GetProcessResponse,\n GitCheckoutResponse,\n ListFilesResponse,\n ListProcessesResponse,\n MkdirResponse,\n MoveFileResponse,\n ReadFileResponse,\n RenameFileResponse,\n StartProcessRequest,\n StartProcessResponse,\n WriteFileResponse,\n} from \"./types\";\n\n\ninterface CommandsResponse {\n availableCommands: string[];\n timestamp: string;\n}\n\ninterface GitCheckoutRequest {\n repoUrl: string;\n branch?: string;\n targetDir?: string;\n sessionId: string;\n}\n\n\ninterface MkdirRequest {\n path: string;\n recursive?: boolean;\n sessionId: string;\n}\n\n\ninterface WriteFileRequest {\n path: string;\n content: string;\n encoding?: string;\n sessionId: string;\n}\n\n\ninterface ReadFileRequest {\n path: string;\n encoding?: string;\n sessionId: string;\n}\n\n\ninterface DeleteFileRequest {\n path: string;\n sessionId: string;\n}\n\n\ninterface RenameFileRequest {\n oldPath: string;\n newPath: string;\n sessionId: string;\n}\n\n\ninterface MoveFileRequest {\n sourcePath: string;\n destinationPath: string;\n sessionId: string;\n}\n\n\ninterface ListFilesRequest {\n path: string;\n options?: {\n recursive?: boolean;\n includeHidden?: boolean;\n };\n sessionId: string;\n}\n\n\ninterface PreviewInfo {\n url: string;\n port: number;\n name?: string;\n}\n\ninterface ExposedPort extends PreviewInfo {\n exposedAt: string;\n timestamp: string;\n}\n\ninterface ExposePortResponse {\n success: boolean;\n port: number;\n name?: string;\n exposedAt: string;\n timestamp: string;\n}\n\ninterface UnexposePortResponse {\n success: boolean;\n port: number;\n timestamp: string;\n}\n\ninterface GetExposedPortsResponse {\n ports: ExposedPort[];\n count: number;\n timestamp: string;\n}\n\ninterface PingResponse {\n message: string;\n timestamp: string;\n}\n\ninterface HttpClientOptions {\n stub?: Sandbox;\n baseUrl?: string;\n port?: number;\n onCommandStart?: (command: string) => void;\n onOutput?: (\n stream: \"stdout\" | \"stderr\",\n data: string,\n command: string\n ) => void;\n onCommandComplete?: (\n success: boolean,\n exitCode: number,\n stdout: string,\n stderr: string,\n command: string\n ) => void;\n onError?: (error: string, command?: string) => void;\n}\n\nexport class HttpClient {\n private baseUrl: string;\n private options: HttpClientOptions;\n\n constructor(options: HttpClientOptions = {}) {\n this.options = {\n ...options,\n };\n this.baseUrl = this.options.baseUrl!;\n }\n\n protected async doFetch(\n path: string,\n options?: RequestInit\n ): Promise<Response> {\n const url = this.options.stub\n ? `http://localhost:${this.options.port}${path}`\n : `${this.baseUrl}${path}`;\n const method = options?.method || \"GET\";\n\n console.log(`[HTTP Client] Making ${method} request to ${url}`);\n\n try {\n let response: Response;\n\n if (this.options.stub) {\n response = await this.options.stub.containerFetch(\n url,\n options,\n this.options.port\n );\n } else {\n response = await fetch(url, options);\n }\n\n console.log(\n `[HTTP Client] Response: ${response.status} ${response.statusText}`\n );\n\n if (!response.ok) {\n console.error(\n `[HTTP Client] Request failed: ${method} ${url} - ${response.status} ${response.statusText}`\n );\n }\n\n return response;\n } catch (error) {\n console.error(`[HTTP Client] Request error: ${method} ${url}`, error);\n throw error;\n }\n }\n\n async createSession(options: {\n id: string;\n env?: Record<string, string>;\n cwd?: string;\n isolation?: boolean;\n }): Promise<{ success: boolean; id: string; message: string }> {\n try {\n const response = await this.doFetch(`/api/session/create`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(options),\n });\n\n if (!response.ok) {\n const errorData = (await response.json().catch(() => ({}))) as {\n error?: string;\n };\n throw new Error(\n errorData.error || `Failed to create session: ${response.status}`\n );\n }\n\n const data = await response.json() as { success: boolean; id: string; message: string };\n console.log(`[HTTP Client] Session created: ${options.id}`);\n return data;\n } catch (error) {\n console.error(\"[HTTP Client] Error creating session:\", error);\n throw error;\n }\n }\n\n async exec(\n sessionId: string,\n command: string,\n options?: Pick<BaseExecOptions, \"cwd\" | \"env\">\n ): Promise<ExecuteResponse> {\n try {\n // Always use session-specific endpoint\n const response = await this.doFetch(`/api/execute`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({ id: sessionId, command }),\n });\n\n if (!response.ok) {\n const errorData = (await response.json().catch(() => ({}))) as {\n error?: string;\n };\n throw new Error(\n errorData.error || `Failed to execute in session: ${response.status}`\n );\n }\n\n const data = await response.json() as { stdout: string; stderr: string; exitCode: number; success: boolean };\n console.log(\n `[HTTP Client] Command executed in session ${sessionId}: ${command}`\n );\n \n // Convert to ExecuteResponse format for consistency\n const executeResponse: ExecuteResponse = {\n ...data,\n command,\n timestamp: new Date().toISOString()\n };\n\n // Call the callback if provided\n this.options.onCommandComplete?.(\n executeResponse.success,\n executeResponse.exitCode,\n executeResponse.stdout,\n executeResponse.stderr,\n executeResponse.command\n );\n\n return executeResponse;\n } catch (error) {\n console.error(\"[HTTP Client] Error executing in session:\", error);\n this.options.onError?.(\n error instanceof Error ? error.message : \"Unknown error\",\n command\n );\n throw error;\n }\n }\n\n async execStream(\n sessionId: string,\n command: string\n ): Promise<ReadableStream<Uint8Array>> {\n try {\n // Always use session-specific streaming endpoint\n const response = await this.doFetch(`/api/execute/stream`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({ \n id: sessionId,\n command\n }),\n });\n\n if (!response.ok) {\n const errorData = (await response.json().catch(() => ({}))) as {\n error?: string;\n };\n throw new Error(\n errorData.error || `Failed to stream execute in session: ${response.status}`\n );\n }\n\n if (!response.body) {\n throw new Error(\"No response body for streaming execution\");\n }\n\n console.log(\n `[HTTP Client] Started streaming command in session ${sessionId}: ${command}`\n );\n return response.body;\n } catch (error) {\n console.error(\"[HTTP Client] Error streaming execute in session:\", error);\n throw error;\n }\n }\n\n async gitCheckout(\n repoUrl: string,\n sessionId: string,\n branch: string = \"main\",\n targetDir?: string\n ): Promise<GitCheckoutResponse> {\n try {\n const response = await this.doFetch(`/api/git/checkout`, {\n body: JSON.stringify({\n branch,\n repoUrl,\n targetDir,\n sessionId,\n } as GitCheckoutRequest),\n headers: {\n \"Content-Type\": \"application/json\",\n },\n method: \"POST\",\n });\n\n if (!response.ok) {\n const errorData = (await response.json().catch(() => ({}))) as {\n error?: string;\n };\n throw new Error(\n errorData.error || `HTTP error! status: ${response.status}`\n );\n }\n\n const data: GitCheckoutResponse = await response.json();\n console.log(\n `[HTTP Client] Git checkout completed: ${repoUrl}, Success: ${data.success}, Target: ${data.targetDir}`\n );\n\n return data;\n } catch (error) {\n console.error(\"[HTTP Client] Error in git checkout:\", error);\n throw error;\n }\n }\n\n async mkdir(\n path: string,\n recursive: boolean = false,\n sessionId: string\n ): Promise<MkdirResponse> {\n try {\n const response = await this.doFetch(`/api/mkdir`, {\n body: JSON.stringify({\n path,\n recursive,\n sessionId,\n } as MkdirRequest),\n headers: {\n \"Content-Type\": \"application/json\",\n },\n method: \"POST\",\n });\n\n if (!response.ok) {\n const errorData = (await response.json().catch(() => ({}))) as {\n error?: string;\n };\n throw new Error(\n errorData.error || `HTTP error! status: ${response.status}`\n );\n }\n\n const data: MkdirResponse = await response.json();\n console.log(\n `[HTTP Client] Directory created: ${path}, Success: ${data.success}, Recursive: ${data.recursive}${sessionId ? ` in session: ${sessionId}` : ''}`\n );\n\n return data;\n } catch (error) {\n console.error(\"[HTTP Client] Error creating directory:\", error);\n throw error;\n }\n }\n\n async writeFile(\n path: string,\n content: string,\n encoding: string = \"utf-8\",\n sessionId: string\n ): Promise<WriteFileResponse> {\n try {\n const response = await this.doFetch(`/api/write`, {\n body: JSON.stringify({\n content,\n encoding,\n path,\n sessionId,\n } as WriteFileRequest),\n headers: {\n \"Content-Type\": \"application/json\",\n },\n method: \"POST\",\n });\n\n if (!response.ok) {\n const errorData = (await response.json().catch(() => ({}))) as {\n error?: string;\n };\n throw new Error(\n errorData.error || `HTTP error! status: ${response.status}`\n );\n }\n\n const data: WriteFileResponse = await response.json();\n console.log(\n `[HTTP Client] File written: ${path}, Success: ${data.success}${sessionId ? ` in session: ${sessionId}` : ''}`\n );\n\n return data;\n } catch (error) {\n console.error(\"[HTTP Client] Error writing file:\", error);\n throw error;\n }\n }\n\n async readFile(\n path: string,\n encoding: string = \"utf-8\",\n sessionId: string\n ): Promise<ReadFileResponse> {\n try {\n const response = await this.doFetch(`/api/read`, {\n body: JSON.stringify({\n encoding,\n path,\n sessionId,\n } as ReadFileRequest),\n headers: {\n \"Content-Type\": \"application/json\",\n },\n method: \"POST\",\n });\n\n if (!response.ok) {\n const errorData = (await response.json().catch(() => ({}))) as {\n error?: string;\n };\n throw new Error(\n errorData.error || `HTTP error! status: ${response.status}`\n );\n }\n\n const data: ReadFileResponse = await response.json();\n console.log(\n `[HTTP Client] File read: ${path}, Success: ${data.success}, Content length: ${data.content.length}${sessionId ? ` in session: ${sessionId}` : ''}`\n );\n\n return data;\n } catch (error) {\n console.error(\"[HTTP Client] Error reading file:\", error);\n throw error;\n }\n }\n\n async readFileStream(\n path: string,\n sessionId: string\n ): Promise<ReadableStream<Uint8Array>> {\n try {\n const response = await this.doFetch(`/api/read/stream`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({\n path,\n sessionId,\n } as ReadFileRequest),\n });\n\n if (!response.ok) {\n const errorData = (await response.json().catch(() => ({}))) as {\n error?: string;\n };\n throw new Error(\n errorData.error || `HTTP error! status: ${response.status}`\n );\n }\n\n if (!response.body) {\n throw new Error(\"No response body for file streaming\");\n }\n\n console.log(\n `[HTTP Client] Started streaming file: ${path}${sessionId ? ` in session: ${sessionId}` : ''}`\n );\n return response.body;\n } catch (error) {\n console.error(\"[HTTP Client] Error streaming file:\", error);\n throw error;\n }\n }\n\n async deleteFile(\n path: string,\n sessionId: string\n ): Promise<DeleteFileResponse> {\n try {\n const response = await this.doFetch(`/api/delete`, {\n body: JSON.stringify({\n path,\n sessionId,\n } as DeleteFileRequest),\n headers: {\n \"Content-Type\": \"application/json\",\n },\n method: \"POST\",\n });\n\n if (!response.ok) {\n const errorData = (await response.json().catch(() => ({}))) as {\n error?: string;\n };\n throw new Error(\n errorData.error || `HTTP error! status: ${response.status}`\n );\n }\n\n const data: DeleteFileResponse = await response.json();\n console.log(\n `[HTTP Client] File deleted: ${path}, Success: ${data.success}${sessionId ? ` in session: ${sessionId}` : ''}`\n );\n\n return data;\n } catch (error) {\n console.error(\"[HTTP Client] Error deleting file:\", error);\n throw error;\n }\n }\n\n async renameFile(\n oldPath: string,\n newPath: string,\n sessionId: string\n ): Promise<RenameFileResponse> {\n try {\n const response = await this.doFetch(`/api/rename`, {\n body: JSON.stringify({\n newPath,\n oldPath,\n sessionId,\n } as RenameFileRequest),\n headers: {\n \"Content-Type\": \"application/json\",\n },\n method: \"POST\",\n });\n\n if (!response.ok) {\n const errorData = (await response.json().catch(() => ({}))) as {\n error?: string;\n };\n throw new Error(\n errorData.error || `HTTP error! status: ${response.status}`\n );\n }\n\n const data: RenameFileResponse = await response.json();\n console.log(\n `[HTTP Client] File renamed: ${oldPath} -> ${newPath}, Success: ${data.success}${sessionId ? ` in session: ${sessionId}` : ''}`\n );\n\n return data;\n } catch (error) {\n console.error(\"[HTTP Client] Error renaming file:\", error);\n throw error;\n }\n }\n\n async moveFile(\n sourcePath: string,\n destinationPath: string,\n sessionId: string\n ): Promise<MoveFileResponse> {\n try {\n const response = await this.doFetch(`/api/move`, {\n body: JSON.stringify({\n destinationPath,\n sourcePath,\n sessionId,\n } as MoveFileRequest),\n headers: {\n \"Content-Type\": \"application/json\",\n },\n method: \"POST\",\n });\n\n if (!response.ok) {\n const errorData = (await response.json().catch(() => ({}))) as {\n error?: string;\n };\n throw new Error(\n errorData.error || `HTTP error! status: ${response.status}`\n );\n }\n\n const data: MoveFileResponse = await response.json();\n console.log(\n `[HTTP Client] File moved: ${sourcePath} -> ${destinationPath}, Success: ${data.success}${sessionId ? ` in session: ${sessionId}` : ''}`\n );\n\n return data;\n } catch (error) {\n console.error(\"[HTTP Client] Error moving file:\", error);\n throw error;\n }\n }\n\n async listFiles(\n path: string,\n sessionId: string,\n options?: {\n recursive?: boolean;\n includeHidden?: boolean;\n }\n ): Promise<ListFilesResponse> {\n try {\n const response = await this.doFetch(`/api/list-files`, {\n body: JSON.stringify({\n path,\n options,\n sessionId,\n } as ListFilesRequest),\n headers: {\n \"Content-Type\": \"application/json\",\n },\n method: \"POST\",\n });\n\n if (!response.ok) {\n const errorData = (await response.json().catch(() => ({}))) as {\n error?: string;\n };\n throw new Error(\n errorData.error || `HTTP error! status: ${response.status}`\n );\n }\n\n const data: ListFilesResponse = await response.json();\n console.log(\n `[HTTP Client] Listed ${data.files.length} files in: ${path}, Success: ${data.success}${sessionId ? ` in session: ${sessionId}` : ''}`\n );\n\n return data;\n } catch (error) {\n console.error(\"[HTTP Client] Error listing files:\", error);\n throw error;\n }\n }\n\n async exposePort(port: number, name?: string): Promise<ExposePortResponse> {\n try {\n const response = await this.doFetch(`/api/expose-port`, {\n body: JSON.stringify({\n port,\n name,\n }),\n headers: {\n \"Content-Type\": \"application/json\",\n },\n method: \"POST\",\n });\n\n if (!response.ok) {\n const errorData = (await response.json().catch(() => ({}))) as {\n error?: string;\n };\n console.log(errorData);\n throw new Error(\n errorData.error || `HTTP error! status: ${response.status}`\n );\n }\n\n const data: ExposePortResponse = await response.json();\n console.log(\n `[HTTP Client] Port exposed: ${port}${\n name ? ` (${name})` : \"\"\n }, Success: ${data.success}`\n );\n\n return data;\n } catch (error) {\n console.error(\"[HTTP Client] Error exposing port:\", error);\n throw error;\n }\n }\n\n async unexposePort(port: number): Promise<UnexposePortResponse> {\n try {\n const response = await this.doFetch(`/api/unexpose-port`, {\n body: JSON.stringify({\n port,\n }),\n headers: {\n \"Content-Type\": \"application/json\",\n },\n method: \"DELETE\",\n });\n\n if (!response.ok) {\n const errorData = (await response.json().catch(() => ({}))) as {\n error?: string;\n };\n throw new Error(\n errorData.error || `HTTP error! status: ${response.status}`\n );\n }\n\n const data: UnexposePortResponse = await response.json();\n console.log(\n `[HTTP Client] Port unexposed: ${port}, Success: ${data.success}`\n );\n\n return data;\n } catch (error) {\n console.error(\"[HTTP Client] Error unexposing port:\", error);\n throw error;\n }\n }\n\n async getExposedPorts(): Promise<GetExposedPortsResponse> {\n try {\n const response = await this.doFetch(`/api/exposed-ports`, {\n headers: {\n \"Content-Type\": \"application/json\",\n },\n method: \"GET\",\n });\n\n if (!response.ok) {\n const errorData = (await response.json().catch(() => ({}))) as {\n error?: string;\n };\n throw new Error(\n errorData.error || `HTTP error! status: ${response.status}`\n );\n }\n\n const data: GetExposedPortsResponse = await response.json();\n console.log(`[HTTP Client] Got ${data.count} exposed ports`);\n\n return data;\n } catch (error) {\n console.error(\"[HTTP Client] Error getting exposed ports:\", error);\n throw error;\n }\n }\n\n async ping(): Promise<string> {\n try {\n const response = await this.doFetch(`/api/ping`, {\n headers: {\n \"Content-Type\": \"application/json\",\n },\n method: \"GET\",\n });\n\n if (!response.ok) {\n throw new Error(`HTTP error! status: ${response.status}`);\n }\n\n const data: PingResponse = await response.json();\n console.log(`[HTTP Client] Ping response: ${data.message}`);\n return data.timestamp;\n } catch (error) {\n console.error(\"[HTTP Client] Error pinging server:\", error);\n throw error;\n }\n }\n\n\n // Process management methods\n async startProcess(\n command: string,\n sessionId: string,\n options?: {\n processId?: string;\n timeout?: number;\n env?: Record<string, string>;\n cwd?: string;\n encoding?: string;\n autoCleanup?: boolean;\n }\n ): Promise<StartProcessResponse> {\n try {\n const response = await this.doFetch(\"/api/process/start\", {\n body: JSON.stringify({\n command,\n sessionId,\n options,\n } as StartProcessRequest),\n headers: {\n \"Content-Type\": \"application/json\",\n },\n method: \"POST\",\n });\n\n if (!response.ok) {\n const errorData = (await response.json().catch(() => ({}))) as {\n error?: string;\n };\n throw new Error(\n errorData.error || `HTTP error! status: ${response.status}`\n );\n }\n\n const data: StartProcessResponse = await response.json();\n console.log(\n `[HTTP Client] Process started: ${command}, ID: ${data.process.id}`\n );\n\n return data;\n } catch (error) {\n console.error(\"[HTTP Client] Error starting process:\", error);\n throw error;\n }\n }\n\n async listProcesses(sessionId?: string): Promise<ListProcessesResponse> {\n try {\n const url = sessionId \n ? `/api/process/list?session=${encodeURIComponent(sessionId)}`\n : \"/api/process/list\";\n const response = await this.doFetch(url, {\n headers: {\n \"Content-Type\": \"application/json\",\n },\n method: \"GET\",\n });\n\n if (!response.ok) {\n const errorData = (await response.json().catch(() => ({}))) as {\n error?: string;\n };\n throw new Error(\n errorData.error || `HTTP error! status: ${response.status}`\n );\n }\n\n const data: ListProcessesResponse = await response.json();\n console.log(`[HTTP Client] Listed ${data.processes.length} processes`);\n\n return data;\n } catch (error) {\n console.error(\"[HTTP Client] Error listing processes:\", error);\n throw error;\n }\n }\n\n async getProcess(processId: string): Promise<GetProcessResponse> {\n try {\n const response = await this.doFetch(`/api/process/${processId}`, {\n headers: {\n \"Content-Type\": \"application/json\",\n },\n method: \"GET\",\n });\n\n if (!response.ok) {\n const errorData = (await response.json().catch(() => ({}))) as {\n error?: string;\n };\n throw new Error(\n errorData.error || `HTTP error! status: ${response.status}`\n );\n }\n\n const data: GetProcessResponse = await response.json();\n console.log(\n `[HTTP Client] Got process ${processId}: ${\n data.process?.status || \"not found\"\n }`\n );\n\n return data;\n } catch (error) {\n console.error(\"[HTTP Client] Error getting process:\", error);\n throw error;\n }\n }\n\n async killProcess(\n processId: string\n ): Promise<{ success: boolean; message: string }> {\n try {\n const response = await this.doFetch(`/api/process/${processId}`, {\n headers: {\n \"Content-Type\": \"application/json\",\n },\n method: \"DELETE\",\n });\n\n if (!response.ok) {\n const errorData = (await response.json().catch(() => ({}))) as {\n error?: string;\n };\n throw new Error(\n errorData.error || `HTTP error! status: ${response.status}`\n );\n }\n\n const data = (await response.json()) as {\n success: boolean;\n message: string;\n };\n console.log(`[HTTP Client] Killed process ${processId}`);\n\n return data;\n } catch (error) {\n console.error(\"[HTTP Client] Error killing process:\", error);\n throw error;\n }\n }\n\n async killAllProcesses(sessionId?: string): Promise<{\n success: boolean;\n killedCount: number;\n message: string;\n }> {\n try {\n const url = sessionId \n ? `/api/process/kill-all?session=${encodeURIComponent(sessionId)}`\n : \"/api/process/kill-all\";\n const response = await this.doFetch(url, {\n headers: {\n \"Content-Type\": \"application/json\",\n },\n method: \"DELETE\",\n });\n\n if (!response.ok) {\n const errorData = (await response.json().catch(() => ({}))) as {\n error?: string;\n };\n throw new Error(\n errorData.error || `HTTP error! status: ${response.status}`\n );\n }\n\n const data = (await response.json()) as {\n success: boolean;\n killedCount: number;\n message: string;\n };\n console.log(`[HTTP Client] Killed ${data.killedCount} processes`);\n\n return data;\n } catch (error) {\n console.error(\"[HTTP Client] Error killing all processes:\", error);\n throw error;\n }\n }\n\n async getProcessLogs(processId: string): Promise<GetProcessLogsResponse> {\n try {\n const response = await this.doFetch(`/api/process/${processId}/logs`, {\n headers: {\n \"Content-Type\": \"application/json\",\n },\n method: \"GET\",\n });\n\n if (!response.ok) {\n const errorData = (await response.json().catch(() => ({}))) as {\n error?: string;\n };\n throw new Error(\n errorData.error || `HTTP error! status: ${response.status}`\n );\n }\n\n const data: GetProcessLogsResponse = await response.json();\n console.log(`[HTTP Client] Got logs for process ${processId}`);\n\n return data;\n } catch (error) {\n console.error(\"[HTTP Client] Error getting process logs:\", error);\n throw error;\n }\n }\n\n async streamProcessLogs(\n processId: string,\n options?: { signal?: AbortSignal }\n ): Promise<ReadableStream<Uint8Array>> {\n try {\n const response = await this.doFetch(`/api/process/${processId}/stream`, {\n headers: {\n Accept: \"text/event-stream\",\n \"Cache-Control\": \"no-cache\",\n },\n method: \"GET\",\n signal: options?.signal,\n });\n\n if (!response.ok) {\n const errorData = (await response.json().catch(() => ({}))) as {\n error?: string;\n };\n throw new Error(\n errorData.error || `HTTP error! status: ${response.status}`\n );\n }\n\n if (!response.body) {\n throw new Error(\"No response body for streaming request\");\n }\n\n console.log(\n `[HTTP Client] Started streaming logs for process ${processId}`\n );\n\n return response.body;\n } catch (error) {\n console.error(\"[HTTP Client] Error streaming process logs:\", error);\n throw error;\n }\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import {
|
|
2
|
+
parseSSEStream
|
|
3
|
+
} from "./chunk-NNGBXDMY.js";
|
|
4
|
+
|
|
5
|
+
// src/file-stream.ts
|
|
6
|
+
async function* streamFile(stream, signal) {
|
|
7
|
+
let metadata;
|
|
8
|
+
try {
|
|
9
|
+
for await (const event of parseSSEStream(stream, signal)) {
|
|
10
|
+
switch (event.type) {
|
|
11
|
+
case "metadata":
|
|
12
|
+
metadata = {
|
|
13
|
+
mimeType: event.mimeType,
|
|
14
|
+
size: event.size,
|
|
15
|
+
isBinary: event.isBinary,
|
|
16
|
+
encoding: event.encoding
|
|
17
|
+
};
|
|
18
|
+
streamFile.metadata = metadata;
|
|
19
|
+
break;
|
|
20
|
+
case "chunk":
|
|
21
|
+
if (metadata?.isBinary && metadata?.encoding === "base64") {
|
|
22
|
+
const binaryString = atob(event.data);
|
|
23
|
+
const bytes = new Uint8Array(binaryString.length);
|
|
24
|
+
for (let i = 0; i < binaryString.length; i++) {
|
|
25
|
+
bytes[i] = binaryString.charCodeAt(i);
|
|
26
|
+
}
|
|
27
|
+
yield bytes;
|
|
28
|
+
} else {
|
|
29
|
+
yield event.data;
|
|
30
|
+
}
|
|
31
|
+
break;
|
|
32
|
+
case "complete":
|
|
33
|
+
console.log(`[streamFile] File streaming complete: ${event.bytesRead} bytes read`);
|
|
34
|
+
return;
|
|
35
|
+
case "error":
|
|
36
|
+
throw new Error(`File streaming error: ${event.error}`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
} catch (error) {
|
|
40
|
+
console.error("[streamFile] Error streaming file:", error);
|
|
41
|
+
throw error;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
async function collectFile(stream, signal) {
|
|
45
|
+
let metadata;
|
|
46
|
+
const chunks = [];
|
|
47
|
+
for await (const chunk of streamFile(stream, signal)) {
|
|
48
|
+
chunks.push(chunk);
|
|
49
|
+
if (!metadata && streamFile.metadata) {
|
|
50
|
+
metadata = streamFile.metadata;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
if (!metadata) {
|
|
54
|
+
throw new Error("No metadata received from file stream");
|
|
55
|
+
}
|
|
56
|
+
if (chunks.length === 0) {
|
|
57
|
+
return {
|
|
58
|
+
content: metadata.isBinary ? new Uint8Array(0) : "",
|
|
59
|
+
metadata
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
if (chunks[0] instanceof Uint8Array) {
|
|
63
|
+
const totalLength = chunks.reduce((sum, chunk) => {
|
|
64
|
+
return sum + chunk.byteLength;
|
|
65
|
+
}, 0);
|
|
66
|
+
const result = new Uint8Array(totalLength);
|
|
67
|
+
let offset = 0;
|
|
68
|
+
for (const chunk of chunks) {
|
|
69
|
+
result.set(chunk, offset);
|
|
70
|
+
offset += chunk.byteLength;
|
|
71
|
+
}
|
|
72
|
+
return { content: result, metadata };
|
|
73
|
+
} else {
|
|
74
|
+
return {
|
|
75
|
+
content: chunks.join(""),
|
|
76
|
+
metadata
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export {
|
|
82
|
+
streamFile,
|
|
83
|
+
collectFile
|
|
84
|
+
};
|
|
85
|
+
//# sourceMappingURL=chunk-5DILEXGY.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/file-stream.ts"],"sourcesContent":["/**\n * File streaming utilities for reading binary and text files\n * Provides simple AsyncIterable API over SSE stream with automatic base64 decoding\n */\n\nimport { parseSSEStream } from './sse-parser';\nimport type { FileChunk, FileMetadata, FileStreamEvent } from './types';\n\n/**\n * Convert ReadableStream of SSE file events to AsyncIterable of file chunks\n * Automatically decodes base64 for binary files and provides metadata\n *\n * @param stream - The SSE ReadableStream from readFileStream()\n * @param signal - Optional AbortSignal for cancellation\n * @returns AsyncIterable that yields file chunks (string for text, Uint8Array for binary)\n *\n * @example\n * ```typescript\n * const stream = await sandbox.readFileStream('/path/to/file.png');\n *\n * for await (const chunk of streamFile(stream)) {\n * if (chunk instanceof Uint8Array) {\n * // Binary chunk - already decoded from base64\n * console.log('Binary chunk:', chunk.byteLength, 'bytes');\n * } else {\n * // Text chunk\n * console.log('Text chunk:', chunk);\n * }\n * }\n *\n * // Access metadata\n * const iter = streamFile(stream);\n * for await (const chunk of iter) {\n * console.log('MIME type:', iter.metadata?.mimeType);\n * // process chunk...\n * }\n * ```\n */\nexport async function* streamFile(\n stream: ReadableStream<Uint8Array>,\n signal?: AbortSignal\n): AsyncGenerator<FileChunk, void, undefined> {\n let metadata: FileMetadata | undefined;\n\n try {\n for await (const event of parseSSEStream<FileStreamEvent>(stream, signal)) {\n switch (event.type) {\n case 'metadata':\n // Store metadata for access via iterator\n metadata = {\n mimeType: event.mimeType,\n size: event.size,\n isBinary: event.isBinary,\n encoding: event.encoding,\n };\n // Store on generator function for external access\n (streamFile as any).metadata = metadata;\n break;\n\n case 'chunk':\n // Auto-decode base64 for binary files\n if (metadata?.isBinary && metadata?.encoding === 'base64') {\n // Decode base64 to Uint8Array\n const binaryString = atob(event.data);\n const bytes = new Uint8Array(binaryString.length);\n for (let i = 0; i < binaryString.length; i++) {\n bytes[i] = binaryString.charCodeAt(i);\n }\n yield bytes;\n } else {\n // Text file - yield as-is\n yield event.data;\n }\n break;\n\n case 'complete':\n // Stream completed successfully\n console.log(`[streamFile] File streaming complete: ${event.bytesRead} bytes read`);\n return;\n\n case 'error':\n // Stream error\n throw new Error(`File streaming error: ${event.error}`);\n }\n }\n } catch (error) {\n console.error('[streamFile] Error streaming file:', error);\n throw error;\n }\n}\n\n/**\n * Helper to collect entire file from stream into memory\n * Useful for smaller files where you want the complete content at once\n *\n * @param stream - The SSE ReadableStream from readFileStream()\n * @param signal - Optional AbortSignal for cancellation\n * @returns Object with content (string or Uint8Array) and metadata\n *\n * @example\n * ```typescript\n * const stream = await sandbox.readFileStream('/path/to/image.png');\n * const { content, metadata } = await collectFile(stream);\n *\n * if (content instanceof Uint8Array) {\n * console.log('Binary file:', metadata.mimeType, content.byteLength, 'bytes');\n * } else {\n * console.log('Text file:', metadata.mimeType, content.length, 'chars');\n * }\n * ```\n */\nexport async function collectFile(\n stream: ReadableStream<Uint8Array>,\n signal?: AbortSignal\n): Promise<{ content: string | Uint8Array; metadata: FileMetadata }> {\n let metadata: FileMetadata | undefined;\n const chunks: FileChunk[] = [];\n\n for await (const chunk of streamFile(stream, signal)) {\n chunks.push(chunk);\n // Capture metadata from first iteration\n if (!metadata && (streamFile as any).metadata) {\n metadata = (streamFile as any).metadata;\n }\n }\n\n if (!metadata) {\n throw new Error('No metadata received from file stream');\n }\n\n // Combine chunks based on type\n if (chunks.length === 0) {\n // Empty file\n return {\n content: metadata.isBinary ? new Uint8Array(0) : '',\n metadata,\n };\n }\n\n // Check if binary or text based on first chunk\n if (chunks[0] instanceof Uint8Array) {\n // Binary file - concatenate Uint8Arrays\n const totalLength = chunks.reduce((sum, chunk) => {\n return sum + (chunk as Uint8Array).byteLength;\n }, 0);\n\n const result = new Uint8Array(totalLength);\n let offset = 0;\n for (const chunk of chunks) {\n result.set(chunk as Uint8Array, offset);\n offset += (chunk as Uint8Array).byteLength;\n }\n\n return { content: result, metadata };\n } else {\n // Text file - concatenate strings\n return {\n content: chunks.join(''),\n metadata,\n };\n }\n}\n"],"mappings":";;;;;AAsCA,gBAAuB,WACrB,QACA,QAC4C;AAC5C,MAAI;AAEJ,MAAI;AACF,qBAAiB,SAAS,eAAgC,QAAQ,MAAM,GAAG;AACzE,cAAQ,MAAM,MAAM;AAAA,QAClB,KAAK;AAEH,qBAAW;AAAA,YACT,UAAU,MAAM;AAAA,YAChB,MAAM,MAAM;AAAA,YACZ,UAAU,MAAM;AAAA,YAChB,UAAU,MAAM;AAAA,UAClB;AAEA,UAAC,WAAmB,WAAW;AAC/B;AAAA,QAEF,KAAK;AAEH,cAAI,UAAU,YAAY,UAAU,aAAa,UAAU;AAEzD,kBAAM,eAAe,KAAK,MAAM,IAAI;AACpC,kBAAM,QAAQ,IAAI,WAAW,aAAa,MAAM;AAChD,qBAAS,IAAI,GAAG,IAAI,aAAa,QAAQ,KAAK;AAC5C,oBAAM,CAAC,IAAI,aAAa,WAAW,CAAC;AAAA,YACtC;AACA,kBAAM;AAAA,UACR,OAAO;AAEL,kBAAM,MAAM;AAAA,UACd;AACA;AAAA,QAEF,KAAK;AAEH,kBAAQ,IAAI,yCAAyC,MAAM,SAAS,aAAa;AACjF;AAAA,QAEF,KAAK;AAEH,gBAAM,IAAI,MAAM,yBAAyB,MAAM,KAAK,EAAE;AAAA,MAC1D;AAAA,IACF;AAAA,EACF,SAAS,OAAO;AACd,YAAQ,MAAM,sCAAsC,KAAK;AACzD,UAAM;AAAA,EACR;AACF;AAsBA,eAAsB,YACpB,QACA,QACmE;AACnE,MAAI;AACJ,QAAM,SAAsB,CAAC;AAE7B,mBAAiB,SAAS,WAAW,QAAQ,MAAM,GAAG;AACpD,WAAO,KAAK,KAAK;AAEjB,QAAI,CAAC,YAAa,WAAmB,UAAU;AAC7C,iBAAY,WAAmB;AAAA,IACjC;AAAA,EACF;AAEA,MAAI,CAAC,UAAU;AACb,UAAM,IAAI,MAAM,uCAAuC;AAAA,EACzD;AAGA,MAAI,OAAO,WAAW,GAAG;AAEvB,WAAO;AAAA,MACL,SAAS,SAAS,WAAW,IAAI,WAAW,CAAC,IAAI;AAAA,MACjD;AAAA,IACF;AAAA,EACF;AAGA,MAAI,OAAO,CAAC,aAAa,YAAY;AAEnC,UAAM,cAAc,OAAO,OAAO,CAAC,KAAK,UAAU;AAChD,aAAO,MAAO,MAAqB;AAAA,IACrC,GAAG,CAAC;AAEJ,UAAM,SAAS,IAAI,WAAW,WAAW;AACzC,QAAI,SAAS;AACb,eAAW,SAAS,QAAQ;AAC1B,aAAO,IAAI,OAAqB,MAAM;AACtC,gBAAW,MAAqB;AAAA,IAClC;AAEA,WAAO,EAAE,SAAS,QAAQ,SAAS;AAAA,EACrC,OAAO;AAEL,WAAO;AAAA,MACL,SAAS,OAAO,KAAK,EAAE;AAAA,MACvB;AAAA,IACF;AAAA,EACF;AACF;","names":[]}
|