@cloudflare/sandbox 0.3.3 → 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 +12 -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 +199 -24
- 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-H4PW2LGW.js → chunk-SQLJNZ3K.js} +9 -2
- package/dist/chunk-SQLJNZ3K.js.map +1 -0
- package/dist/{client-COGWU6bz.d.ts → client-B3RUab0s.d.ts} +3 -1
- 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 +11 -2
- package/src/types.ts +69 -0
- package/dist/chunk-H4PW2LGW.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,17 @@
|
|
|
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
|
+
|
|
9
|
+
## 0.3.4
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- [#86](https://github.com/cloudflare/sandbox-sdk/pull/86) [`feafd32`](https://github.com/cloudflare/sandbox-sdk/commit/feafd32a51f50dfaf4994bddcbfb56d46cada622) Thanks [@ghostwriternr](https://github.com/ghostwriternr)! - Fix session reuse to reuse existing healthy session
|
|
14
|
+
|
|
3
15
|
## 0.3.3
|
|
4
16
|
|
|
5
17
|
### 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`);
|
|
@@ -121,13 +121,20 @@ export class Session {
|
|
|
121
121
|
timeout: NodeJS.Timeout;
|
|
122
122
|
}>();
|
|
123
123
|
private processes = new Map<string, ProcessRecord>(); // Session-specific processes
|
|
124
|
-
|
|
124
|
+
|
|
125
125
|
constructor(private options: SessionOptions) {
|
|
126
126
|
this.canIsolate = (options.isolation === true) && hasNamespaceSupport();
|
|
127
127
|
if (options.isolation === true && !this.canIsolate) {
|
|
128
128
|
console.log(`[Session] Isolation requested for '${options.id}' but not available`);
|
|
129
129
|
}
|
|
130
130
|
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Check if the session is ready for command execution
|
|
134
|
+
*/
|
|
135
|
+
isReady(): boolean {
|
|
136
|
+
return this.ready && this.control !== null && !this.control.killed;
|
|
137
|
+
}
|
|
131
138
|
|
|
132
139
|
async initialize(): Promise<void> {
|
|
133
140
|
// Use the proper TypeScript control process file
|
|
@@ -439,18 +446,182 @@ SANDBOX_EOF`;
|
|
|
439
446
|
};
|
|
440
447
|
}
|
|
441
448
|
|
|
442
|
-
async readFileOperation(path: string, encoding: string = 'utf-8'): Promise<{
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
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
|
+
|
|
446
506
|
return {
|
|
447
|
-
success:
|
|
448
|
-
exitCode:
|
|
449
|
-
content
|
|
450
|
-
path
|
|
507
|
+
success: true,
|
|
508
|
+
exitCode: 0,
|
|
509
|
+
content,
|
|
510
|
+
path,
|
|
511
|
+
encoding: actualEncoding,
|
|
512
|
+
isBinary,
|
|
513
|
+
mimeType,
|
|
514
|
+
size: fileSize
|
|
451
515
|
};
|
|
452
516
|
}
|
|
453
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`);
|
|
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
|
+
});
|
|
623
|
+
}
|
|
624
|
+
|
|
454
625
|
async mkdirOperation(path: string, recursive: boolean = false): Promise<{ success: boolean; exitCode: number; path: string; recursive: boolean }> {
|
|
455
626
|
const command = recursive ? `mkdir -p "${path}"` : `mkdir "${path}"`;
|
|
456
627
|
const result = await this.exec(command);
|
|
@@ -946,17 +1117,25 @@ export class SessionManager {
|
|
|
946
1117
|
throw new Error(`cwd must be an absolute path starting with '/', got: ${options.cwd}`);
|
|
947
1118
|
}
|
|
948
1119
|
}
|
|
949
|
-
|
|
950
|
-
//
|
|
1120
|
+
|
|
1121
|
+
// Check if session already exists
|
|
951
1122
|
const existing = this.sessions.get(options.id);
|
|
952
1123
|
if (existing) {
|
|
953
|
-
existing
|
|
1124
|
+
// If the existing session is healthy and ready, reuse it
|
|
1125
|
+
if (existing.isReady()) {
|
|
1126
|
+
console.log(`[SessionManager] Reusing existing session '${options.id}'`);
|
|
1127
|
+
return existing;
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
// If the session exists but is not ready, clean it up and create a new one
|
|
1131
|
+
console.log(`[SessionManager] Destroying unhealthy session '${options.id}' before recreating`);
|
|
1132
|
+
await existing.destroy();
|
|
954
1133
|
}
|
|
955
|
-
|
|
1134
|
+
|
|
956
1135
|
// Create new session
|
|
957
1136
|
const session = new Session(options);
|
|
958
1137
|
await session.initialize();
|
|
959
|
-
|
|
1138
|
+
|
|
960
1139
|
this.sessions.set(options.id, session);
|
|
961
1140
|
console.log(`[SessionManager] Created session '${options.id}'`);
|
|
962
1141
|
return session;
|
|
@@ -972,15 +1151,11 @@ export class SessionManager {
|
|
|
972
1151
|
|
|
973
1152
|
// Helper to get or create default session - reduces duplication
|
|
974
1153
|
async getOrCreateDefaultSession(): Promise<Session> {
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
isolation: true
|
|
981
|
-
});
|
|
982
|
-
}
|
|
983
|
-
return defaultSession;
|
|
1154
|
+
return await this.createSession({
|
|
1155
|
+
id: 'default',
|
|
1156
|
+
cwd: '/workspace', // Consistent default working directory
|
|
1157
|
+
isolation: true
|
|
1158
|
+
});
|
|
984
1159
|
}
|
|
985
1160
|
|
|
986
1161
|
async exec(command: string, options?: { cwd?: string }): Promise<ExecResult> {
|
|
@@ -999,7 +1174,7 @@ export class SessionManager {
|
|
|
999
1174
|
return defaultSession.writeFileOperation(path, content, encoding);
|
|
1000
1175
|
}
|
|
1001
1176
|
|
|
1002
|
-
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 }> {
|
|
1003
1178
|
const defaultSession = await this.getOrCreateDefaultSession();
|
|
1004
1179
|
return defaultSession.readFileOperation(path, encoding);
|
|
1005
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":[]}
|