@cloudflare/sandbox 0.0.0-46eb4e6 → 0.0.0-485cf61
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 +0 -6
- package/Dockerfile +82 -18
- package/README.md +89 -824
- package/dist/chunk-3NEP4CNV.js +99 -0
- package/dist/chunk-3NEP4CNV.js.map +1 -0
- package/dist/chunk-6IYG2RIN.js +117 -0
- package/dist/chunk-6IYG2RIN.js.map +1 -0
- package/dist/chunk-HB44YO2A.js +2331 -0
- package/dist/chunk-HB44YO2A.js.map +1 -0
- package/dist/chunk-KPVMMMIP.js +105 -0
- package/dist/chunk-KPVMMMIP.js.map +1 -0
- package/dist/chunk-NNGBXDMY.js +89 -0
- package/dist/chunk-NNGBXDMY.js.map +1 -0
- package/dist/file-stream.d.ts +43 -0
- package/dist/file-stream.js +9 -0
- package/dist/file-stream.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +55 -0
- package/dist/index.js.map +1 -0
- package/dist/interpreter.d.ts +33 -0
- package/dist/interpreter.js +8 -0
- package/dist/interpreter.js.map +1 -0
- package/dist/request-handler.d.ts +18 -0
- package/dist/request-handler.js +12 -0
- package/dist/request-handler.js.map +1 -0
- package/dist/sandbox-CtlKjZwf.d.ts +583 -0
- package/dist/sandbox.d.ts +4 -0
- package/dist/sandbox.js +12 -0
- package/dist/sandbox.js.map +1 -0
- package/dist/security.d.ts +35 -0
- package/dist/security.js +15 -0
- package/dist/security.js.map +1 -0
- package/dist/sse-parser.d.ts +28 -0
- package/dist/sse-parser.js +11 -0
- package/dist/sse-parser.js.map +1 -0
- package/package.json +11 -5
- package/src/clients/base-client.ts +297 -0
- package/src/clients/command-client.ts +118 -0
- package/src/clients/file-client.ts +272 -0
- package/src/clients/git-client.ts +95 -0
- package/src/clients/index.ts +63 -0
- package/src/{interpreter-client.ts → clients/interpreter-client.ts} +151 -171
- package/src/clients/port-client.ts +108 -0
- package/src/clients/process-client.ts +180 -0
- package/src/clients/sandbox-client.ts +41 -0
- package/src/clients/types.ts +81 -0
- package/src/clients/utility-client.ts +97 -0
- package/src/errors/adapter.ts +180 -0
- package/src/errors/classes.ts +469 -0
- package/src/errors/index.ts +105 -0
- package/src/file-stream.ts +119 -117
- package/src/index.ts +81 -69
- package/src/interpreter.ts +17 -8
- package/src/request-handler.ts +61 -7
- package/src/sandbox.ts +698 -495
- package/src/security.ts +20 -0
- package/startup.sh +7 -0
- package/tests/base-client.test.ts +328 -0
- package/tests/command-client.test.ts +407 -0
- package/tests/file-client.test.ts +643 -0
- package/tests/file-stream.test.ts +306 -0
- package/tests/git-client.test.ts +328 -0
- package/tests/port-client.test.ts +301 -0
- package/tests/process-client.test.ts +658 -0
- package/tests/sandbox.test.ts +465 -0
- package/tests/sse-parser.test.ts +291 -0
- package/tests/utility-client.test.ts +266 -0
- package/tests/wrangler.jsonc +35 -0
- package/tsconfig.json +9 -1
- package/vitest.config.ts +31 -0
- package/container_src/bun.lock +0 -76
- package/container_src/circuit-breaker.ts +0 -121
- package/container_src/control-process.ts +0 -784
- package/container_src/handler/exec.ts +0 -185
- package/container_src/handler/file.ts +0 -457
- package/container_src/handler/git.ts +0 -130
- package/container_src/handler/ports.ts +0 -314
- package/container_src/handler/process.ts +0 -568
- package/container_src/handler/session.ts +0 -92
- package/container_src/index.ts +0 -600
- package/container_src/interpreter-service.ts +0 -276
- package/container_src/isolation.ts +0 -1213
- package/container_src/mime-processor.ts +0 -255
- package/container_src/package.json +0 -18
- package/container_src/runtime/executors/javascript/node_executor.ts +0 -123
- package/container_src/runtime/executors/python/ipython_executor.py +0 -338
- package/container_src/runtime/executors/typescript/ts_executor.ts +0 -138
- package/container_src/runtime/process-pool.ts +0 -464
- package/container_src/shell-escape.ts +0 -42
- package/container_src/startup.sh +0 -11
- package/container_src/types.ts +0 -131
- package/src/client.ts +0 -1048
- package/src/errors.ts +0 -219
- package/src/interpreter-types.ts +0 -390
- package/src/types.ts +0 -571
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
package/package.json
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cloudflare/sandbox",
|
|
3
|
-
"version": "0.0.0-
|
|
3
|
+
"version": "0.0.0-485cf61",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "https://github.com/cloudflare/sandbox-sdk"
|
|
7
7
|
},
|
|
8
8
|
"description": "A sandboxed environment for running commands",
|
|
9
9
|
"dependencies": {
|
|
10
|
-
"@cloudflare/containers": "^0.0.28"
|
|
10
|
+
"@cloudflare/containers": "^0.0.28",
|
|
11
|
+
"@repo/shared": "^0.0.0"
|
|
11
12
|
},
|
|
12
13
|
"tags": [
|
|
13
14
|
"sandbox",
|
|
@@ -18,9 +19,14 @@
|
|
|
18
19
|
],
|
|
19
20
|
"scripts": {
|
|
20
21
|
"build": "rm -rf dist && tsup src/*.ts --outDir dist --dts --sourcemap --format esm",
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
"
|
|
22
|
+
"check": "biome check && npm run typecheck",
|
|
23
|
+
"fix": "biome check --fix && npm run typecheck",
|
|
24
|
+
"typecheck": "tsc --noEmit",
|
|
25
|
+
"docker:local": "cd ../.. && docker build -f packages/sandbox/Dockerfile -t cloudflare/sandbox-test:$npm_package_version .",
|
|
26
|
+
"docker:publish": "cd ../.. && docker buildx build --platform linux/amd64,linux/arm64 -f packages/sandbox/Dockerfile -t cloudflare/sandbox:$npm_package_version --push .",
|
|
27
|
+
"docker:publish:beta": "cd ../.. && docker buildx build --platform linux/amd64,linux/arm64 -f packages/sandbox/Dockerfile -t cloudflare/sandbox:$npm_package_version-beta --push .",
|
|
28
|
+
"test": "vitest run --config vitest.config.ts",
|
|
29
|
+
"test:e2e": "cd ../.. && vitest run --config vitest.e2e.config.ts \"$@\""
|
|
24
30
|
},
|
|
25
31
|
"exports": {
|
|
26
32
|
".": {
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
import type { ErrorResponse as NewErrorResponse } from '../errors';
|
|
2
|
+
import { createErrorFromResponse, ErrorCode } from '../errors';
|
|
3
|
+
import type {
|
|
4
|
+
HttpClientOptions,
|
|
5
|
+
ResponseHandler
|
|
6
|
+
} from './types';
|
|
7
|
+
|
|
8
|
+
// Container provisioning retry configuration
|
|
9
|
+
const TIMEOUT_MS = 60_000; // 60 seconds total timeout budget
|
|
10
|
+
const MIN_TIME_FOR_RETRY_MS = 10_000; // Need at least 10s remaining to retry (8s Container + 2s delay)
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Abstract base class providing common HTTP functionality for all domain clients
|
|
14
|
+
*/
|
|
15
|
+
export abstract class BaseHttpClient {
|
|
16
|
+
protected baseUrl: string;
|
|
17
|
+
protected options: HttpClientOptions;
|
|
18
|
+
|
|
19
|
+
constructor(options: HttpClientOptions = {}) {
|
|
20
|
+
this.options = {
|
|
21
|
+
...options,
|
|
22
|
+
};
|
|
23
|
+
this.baseUrl = this.options.baseUrl!;
|
|
24
|
+
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Core HTTP request method with automatic retry for container provisioning delays
|
|
29
|
+
*/
|
|
30
|
+
protected async doFetch(
|
|
31
|
+
path: string,
|
|
32
|
+
options?: RequestInit
|
|
33
|
+
): Promise<Response> {
|
|
34
|
+
const startTime = Date.now();
|
|
35
|
+
let attempt = 0;
|
|
36
|
+
|
|
37
|
+
console.log(`[DEBUG] doFetch called for ${options?.method || 'GET'} ${path}`);
|
|
38
|
+
|
|
39
|
+
while (true) {
|
|
40
|
+
const response = await this.executeFetch(path, options);
|
|
41
|
+
|
|
42
|
+
console.log(`[DEBUG] Response status: ${response.status}`);
|
|
43
|
+
|
|
44
|
+
// Only retry container provisioning 503s, not user app 503s
|
|
45
|
+
if (response.status === 503) {
|
|
46
|
+
console.log('[DEBUG] Got 503 response, checking if container provisioning error...');
|
|
47
|
+
|
|
48
|
+
const isContainerProvisioning = await this.isContainerProvisioningError(response);
|
|
49
|
+
|
|
50
|
+
console.log('[DEBUG] isContainerProvisioning result:', isContainerProvisioning);
|
|
51
|
+
|
|
52
|
+
if (isContainerProvisioning) {
|
|
53
|
+
const elapsed = Date.now() - startTime;
|
|
54
|
+
const remaining = TIMEOUT_MS - elapsed;
|
|
55
|
+
|
|
56
|
+
console.log(`[DEBUG] Elapsed: ${elapsed}ms, Remaining: ${remaining}ms`);
|
|
57
|
+
|
|
58
|
+
// Check if we have enough time for another attempt
|
|
59
|
+
// (Need at least 10s: 8s for Container timeout + 2s delay)
|
|
60
|
+
if (remaining > MIN_TIME_FOR_RETRY_MS) {
|
|
61
|
+
// Exponential backoff: 2s, 4s, 8s, 16s (capped at 16s)
|
|
62
|
+
const delay = Math.min(2000 * 2 ** attempt, 16000);
|
|
63
|
+
|
|
64
|
+
console.log(
|
|
65
|
+
`[Sandbox SDK] Container provisioning in progress (attempt ${attempt + 1}), ` +
|
|
66
|
+
`retrying in ${delay}ms (${Math.floor(remaining / 1000)}s remaining)`
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
await new Promise(resolve => setTimeout(resolve, delay));
|
|
70
|
+
attempt++;
|
|
71
|
+
continue;
|
|
72
|
+
} else {
|
|
73
|
+
// Exhausted retries - log error and return response
|
|
74
|
+
// Let existing error handling convert to proper error
|
|
75
|
+
console.error(
|
|
76
|
+
`[Sandbox SDK] Container failed to provision after ${attempt + 1} attempts over 60s.`
|
|
77
|
+
);
|
|
78
|
+
return response;
|
|
79
|
+
}
|
|
80
|
+
} else {
|
|
81
|
+
console.log('[DEBUG] Not a container provisioning error, returning 503 immediately');
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Return response (success, user app error, or non-retryable error)
|
|
86
|
+
if (response.status !== 200) {
|
|
87
|
+
console.log(`[DEBUG] Returning response with status ${response.status}`);
|
|
88
|
+
}
|
|
89
|
+
return response;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Make a POST request with JSON body
|
|
95
|
+
*/
|
|
96
|
+
protected async post<T>(
|
|
97
|
+
endpoint: string,
|
|
98
|
+
data: Record<string, any>,
|
|
99
|
+
responseHandler?: ResponseHandler<T>
|
|
100
|
+
): Promise<T> {
|
|
101
|
+
const response = await this.doFetch(endpoint, {
|
|
102
|
+
method: 'POST',
|
|
103
|
+
headers: {
|
|
104
|
+
'Content-Type': 'application/json',
|
|
105
|
+
},
|
|
106
|
+
body: JSON.stringify(data),
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
return this.handleResponse(response, responseHandler);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Make a GET request
|
|
114
|
+
*/
|
|
115
|
+
protected async get<T>(
|
|
116
|
+
endpoint: string,
|
|
117
|
+
responseHandler?: ResponseHandler<T>
|
|
118
|
+
): Promise<T> {
|
|
119
|
+
const response = await this.doFetch(endpoint, {
|
|
120
|
+
method: 'GET',
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
return this.handleResponse(response, responseHandler);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Make a DELETE request
|
|
128
|
+
*/
|
|
129
|
+
protected async delete<T>(
|
|
130
|
+
endpoint: string,
|
|
131
|
+
responseHandler?: ResponseHandler<T>
|
|
132
|
+
): Promise<T> {
|
|
133
|
+
const response = await this.doFetch(endpoint, {
|
|
134
|
+
method: 'DELETE',
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
return this.handleResponse(response, responseHandler);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Handle HTTP response with error checking and parsing
|
|
143
|
+
*/
|
|
144
|
+
protected async handleResponse<T>(
|
|
145
|
+
response: Response,
|
|
146
|
+
customHandler?: ResponseHandler<T>
|
|
147
|
+
): Promise<T> {
|
|
148
|
+
if (!response.ok) {
|
|
149
|
+
await this.handleErrorResponse(response);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (customHandler) {
|
|
153
|
+
return customHandler(response);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
try {
|
|
157
|
+
return await response.json();
|
|
158
|
+
} catch (error) {
|
|
159
|
+
// Handle malformed JSON responses gracefully
|
|
160
|
+
const errorResponse: NewErrorResponse = {
|
|
161
|
+
code: ErrorCode.INVALID_JSON_RESPONSE,
|
|
162
|
+
message: `Invalid JSON response: ${error instanceof Error ? error.message : 'Unknown parsing error'}`,
|
|
163
|
+
context: {},
|
|
164
|
+
httpStatus: response.status,
|
|
165
|
+
timestamp: new Date().toISOString()
|
|
166
|
+
};
|
|
167
|
+
throw createErrorFromResponse(errorResponse);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Handle error responses with consistent error throwing
|
|
173
|
+
*/
|
|
174
|
+
protected async handleErrorResponse(response: Response): Promise<never> {
|
|
175
|
+
let errorData: NewErrorResponse;
|
|
176
|
+
|
|
177
|
+
try {
|
|
178
|
+
errorData = await response.json();
|
|
179
|
+
} catch {
|
|
180
|
+
// Fallback if response isn't JSON or parsing fails
|
|
181
|
+
errorData = {
|
|
182
|
+
code: ErrorCode.INTERNAL_ERROR,
|
|
183
|
+
message: `HTTP error! status: ${response.status}`,
|
|
184
|
+
context: { statusText: response.statusText },
|
|
185
|
+
httpStatus: response.status,
|
|
186
|
+
timestamp: new Date().toISOString()
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Convert ErrorResponse to appropriate Error class
|
|
191
|
+
const error = createErrorFromResponse(errorData);
|
|
192
|
+
|
|
193
|
+
// Call error callback if provided
|
|
194
|
+
this.options.onError?.(errorData.message, undefined);
|
|
195
|
+
|
|
196
|
+
throw error;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Create a streaming response handler for Server-Sent Events
|
|
203
|
+
*/
|
|
204
|
+
protected async handleStreamResponse(
|
|
205
|
+
response: Response
|
|
206
|
+
): Promise<ReadableStream<Uint8Array>> {
|
|
207
|
+
if (!response.ok) {
|
|
208
|
+
await this.handleErrorResponse(response);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (!response.body) {
|
|
212
|
+
throw new Error('No response body for streaming');
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return response.body;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Utility method to log successful operations
|
|
220
|
+
*/
|
|
221
|
+
protected logSuccess(operation: string, details?: string): void {
|
|
222
|
+
const message = details
|
|
223
|
+
? `[HTTP Client] ${operation}: ${details}`
|
|
224
|
+
: `[HTTP Client] ${operation} completed successfully`;
|
|
225
|
+
console.log(message);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Utility method to log errors
|
|
230
|
+
*/
|
|
231
|
+
protected logError(operation: string, error: unknown): void {
|
|
232
|
+
console.error(`[HTTP Client] Error in ${operation}:`, error);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Check if 503 response is from container provisioning (retryable)
|
|
237
|
+
* vs user application (not retryable)
|
|
238
|
+
*/
|
|
239
|
+
private async isContainerProvisioningError(response: Response): Promise<boolean> {
|
|
240
|
+
try {
|
|
241
|
+
// Clone response so we don't consume the original body
|
|
242
|
+
const cloned = response.clone();
|
|
243
|
+
const text = await cloned.text();
|
|
244
|
+
|
|
245
|
+
console.log('[DEBUG] 503 response body:', text.substring(0, 200));
|
|
246
|
+
|
|
247
|
+
// Container package returns specific message for provisioning errors
|
|
248
|
+
const isProvisioning = text.includes('There is no Container instance available');
|
|
249
|
+
|
|
250
|
+
console.log('[DEBUG] Is container provisioning error?', isProvisioning);
|
|
251
|
+
|
|
252
|
+
return isProvisioning;
|
|
253
|
+
} catch (error) {
|
|
254
|
+
console.error('[DEBUG] Error checking response body:', error);
|
|
255
|
+
// If we can't read the body, don't retry to be safe
|
|
256
|
+
return false;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
private async executeFetch(path: string, options?: RequestInit): Promise<Response> {
|
|
261
|
+
const url = this.options.stub
|
|
262
|
+
? `http://localhost:${this.options.port}${path}`
|
|
263
|
+
: `${this.baseUrl}${path}`;
|
|
264
|
+
const method = options?.method || "GET";
|
|
265
|
+
|
|
266
|
+
console.log(`[HTTP Client] Making ${method} request to ${url}`);
|
|
267
|
+
|
|
268
|
+
try {
|
|
269
|
+
let response: Response;
|
|
270
|
+
|
|
271
|
+
if (this.options.stub) {
|
|
272
|
+
response = await this.options.stub.containerFetch(
|
|
273
|
+
url,
|
|
274
|
+
options || {},
|
|
275
|
+
this.options.port
|
|
276
|
+
);
|
|
277
|
+
} else {
|
|
278
|
+
response = await fetch(url, options);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
console.log(
|
|
282
|
+
`[HTTP Client] Response: ${response.status} ${response.statusText}`
|
|
283
|
+
);
|
|
284
|
+
|
|
285
|
+
if (!response.ok) {
|
|
286
|
+
console.error(
|
|
287
|
+
`[HTTP Client] Request failed: ${method} ${url} - ${response.status} ${response.statusText}`
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
return response;
|
|
292
|
+
} catch (error) {
|
|
293
|
+
console.error(`[HTTP Client] Request error: ${method} ${url}`, error);
|
|
294
|
+
throw error;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { BaseHttpClient } from './base-client';
|
|
2
|
+
import type { BaseApiResponse, HttpClientOptions, SessionRequest } from './types';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Request interface for command execution
|
|
6
|
+
*/
|
|
7
|
+
export interface ExecuteRequest extends SessionRequest {
|
|
8
|
+
command: string;
|
|
9
|
+
timeoutMs?: number;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Response interface for command execution
|
|
14
|
+
*/
|
|
15
|
+
export interface ExecuteResponse extends BaseApiResponse {
|
|
16
|
+
stdout: string;
|
|
17
|
+
stderr: string;
|
|
18
|
+
exitCode: number;
|
|
19
|
+
command: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Client for command execution operations
|
|
24
|
+
*/
|
|
25
|
+
export class CommandClient extends BaseHttpClient {
|
|
26
|
+
constructor(options: HttpClientOptions = {}) {
|
|
27
|
+
super(options);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Execute a command and return the complete result
|
|
32
|
+
* @param command - The command to execute
|
|
33
|
+
* @param sessionId - The session ID for this command execution
|
|
34
|
+
* @param timeoutMs - Optional timeout in milliseconds (unlimited by default)
|
|
35
|
+
*/
|
|
36
|
+
async execute(
|
|
37
|
+
command: string,
|
|
38
|
+
sessionId: string,
|
|
39
|
+
timeoutMs?: number
|
|
40
|
+
): Promise<ExecuteResponse> {
|
|
41
|
+
try {
|
|
42
|
+
const data: ExecuteRequest = {
|
|
43
|
+
command,
|
|
44
|
+
sessionId,
|
|
45
|
+
...(timeoutMs !== undefined && { timeoutMs })
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const response = await this.post<ExecuteResponse>(
|
|
49
|
+
'/api/execute',
|
|
50
|
+
data
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
this.logSuccess(
|
|
54
|
+
'Command executed',
|
|
55
|
+
`${command}, Success: ${response.success}`
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
// Call the callback if provided
|
|
59
|
+
this.options.onCommandComplete?.(
|
|
60
|
+
response.success,
|
|
61
|
+
response.exitCode,
|
|
62
|
+
response.stdout,
|
|
63
|
+
response.stderr,
|
|
64
|
+
response.command
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
return response;
|
|
68
|
+
} catch (error) {
|
|
69
|
+
this.logError('execute', error);
|
|
70
|
+
|
|
71
|
+
// Call error callback if provided
|
|
72
|
+
this.options.onError?.(
|
|
73
|
+
error instanceof Error ? error.message : String(error),
|
|
74
|
+
command
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
throw error;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Execute a command and return a stream of events
|
|
83
|
+
* @param command - The command to execute
|
|
84
|
+
* @param sessionId - The session ID for this command execution
|
|
85
|
+
*/
|
|
86
|
+
async executeStream(
|
|
87
|
+
command: string,
|
|
88
|
+
sessionId: string
|
|
89
|
+
): Promise<ReadableStream<Uint8Array>> {
|
|
90
|
+
try {
|
|
91
|
+
const data = { command, sessionId };
|
|
92
|
+
|
|
93
|
+
const response = await this.doFetch('/api/execute/stream', {
|
|
94
|
+
method: 'POST',
|
|
95
|
+
headers: {
|
|
96
|
+
'Content-Type': 'application/json',
|
|
97
|
+
},
|
|
98
|
+
body: JSON.stringify(data),
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
const stream = await this.handleStreamResponse(response);
|
|
102
|
+
|
|
103
|
+
this.logSuccess('Command stream started', command);
|
|
104
|
+
|
|
105
|
+
return stream;
|
|
106
|
+
} catch (error) {
|
|
107
|
+
this.logError('executeStream', error);
|
|
108
|
+
|
|
109
|
+
// Call error callback if provided
|
|
110
|
+
this.options.onError?.(
|
|
111
|
+
error instanceof Error ? error.message : String(error),
|
|
112
|
+
command
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
throw error;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|