@cloudflare/sandbox 0.0.0-6d1b288 → 0.0.0-70dcdae
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 +75 -0
- package/Dockerfile +73 -9
- package/container_src/handler/exec.ts +337 -0
- package/container_src/handler/file.ts +844 -0
- package/container_src/handler/git.ts +182 -0
- package/container_src/handler/ports.ts +314 -0
- package/container_src/handler/process.ts +640 -0
- package/container_src/index.ts +115 -2374
- package/container_src/types.ts +103 -0
- package/package.json +16 -8
- package/src/client.ts +376 -1062
- package/src/index.ts +20 -79
- package/src/request-handler.ts +144 -0
- package/src/sandbox.ts +645 -0
- package/src/security.ts +113 -0
- package/src/sse-parser.ts +147 -0
- package/src/types.ts +386 -0
- package/tsconfig.json +1 -1
- package/README.md +0 -62
- package/tests/client.example.ts +0 -308
- package/tests/connection-test.ts +0 -81
- package/tests/simple-test.ts +0 -81
- package/tests/test1.ts +0 -281
- package/tests/test2.ts +0 -710
package/src/client.ts
CHANGED
|
@@ -1,9 +1,15 @@
|
|
|
1
|
-
import type { DurableObject } from "cloudflare:workers";
|
|
2
1
|
import type { Sandbox } from "./index";
|
|
2
|
+
import type {
|
|
3
|
+
GetProcessLogsResponse,
|
|
4
|
+
GetProcessResponse,
|
|
5
|
+
ListProcessesResponse,
|
|
6
|
+
StartProcessRequest,
|
|
7
|
+
StartProcessResponse
|
|
8
|
+
} from "./types";
|
|
3
9
|
|
|
4
10
|
interface ExecuteRequest {
|
|
5
11
|
command: string;
|
|
6
|
-
|
|
12
|
+
sessionId?: string;
|
|
7
13
|
}
|
|
8
14
|
|
|
9
15
|
export interface ExecuteResponse {
|
|
@@ -12,7 +18,6 @@ export interface ExecuteResponse {
|
|
|
12
18
|
stderr: string;
|
|
13
19
|
exitCode: number;
|
|
14
20
|
command: string;
|
|
15
|
-
args: string[];
|
|
16
21
|
timestamp: string;
|
|
17
22
|
}
|
|
18
23
|
|
|
@@ -85,6 +90,20 @@ export interface WriteFileResponse {
|
|
|
85
90
|
timestamp: string;
|
|
86
91
|
}
|
|
87
92
|
|
|
93
|
+
interface ReadFileRequest {
|
|
94
|
+
path: string;
|
|
95
|
+
encoding?: string;
|
|
96
|
+
sessionId?: string;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export interface ReadFileResponse {
|
|
100
|
+
success: boolean;
|
|
101
|
+
exitCode: number;
|
|
102
|
+
path: string;
|
|
103
|
+
content: string;
|
|
104
|
+
timestamp: string;
|
|
105
|
+
}
|
|
106
|
+
|
|
88
107
|
interface DeleteFileRequest {
|
|
89
108
|
path: string;
|
|
90
109
|
sessionId?: string;
|
|
@@ -125,36 +144,47 @@ export interface MoveFileResponse {
|
|
|
125
144
|
timestamp: string;
|
|
126
145
|
}
|
|
127
146
|
|
|
128
|
-
interface
|
|
129
|
-
|
|
147
|
+
interface PreviewInfo {
|
|
148
|
+
url: string;
|
|
149
|
+
port: number;
|
|
150
|
+
name?: string;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
interface ExposedPort extends PreviewInfo {
|
|
154
|
+
exposedAt: string;
|
|
155
|
+
timestamp: string;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
interface ExposePortResponse {
|
|
159
|
+
success: boolean;
|
|
160
|
+
port: number;
|
|
161
|
+
name?: string;
|
|
162
|
+
exposedAt: string;
|
|
163
|
+
timestamp: string;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
interface UnexposePortResponse {
|
|
167
|
+
success: boolean;
|
|
168
|
+
port: number;
|
|
169
|
+
timestamp: string;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
interface GetExposedPortsResponse {
|
|
173
|
+
ports: ExposedPort[];
|
|
174
|
+
count: number;
|
|
130
175
|
timestamp: string;
|
|
131
176
|
}
|
|
132
177
|
|
|
133
|
-
interface
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
args?: string[];
|
|
137
|
-
stream?: "stdout" | "stderr";
|
|
138
|
-
data?: string;
|
|
139
|
-
message?: string;
|
|
140
|
-
path?: string;
|
|
141
|
-
oldPath?: string;
|
|
142
|
-
newPath?: string;
|
|
143
|
-
sourcePath?: string;
|
|
144
|
-
destinationPath?: string;
|
|
145
|
-
success?: boolean;
|
|
146
|
-
exitCode?: number;
|
|
147
|
-
stdout?: string;
|
|
148
|
-
stderr?: string;
|
|
149
|
-
error?: string;
|
|
150
|
-
timestamp?: string;
|
|
178
|
+
interface PingResponse {
|
|
179
|
+
message: string;
|
|
180
|
+
timestamp: string;
|
|
151
181
|
}
|
|
152
182
|
|
|
153
183
|
interface HttpClientOptions {
|
|
154
184
|
stub?: Sandbox;
|
|
155
185
|
baseUrl?: string;
|
|
156
186
|
port?: number;
|
|
157
|
-
onCommandStart?: (command: string
|
|
187
|
+
onCommandStart?: (command: string) => void;
|
|
158
188
|
onOutput?: (
|
|
159
189
|
stream: "stdout" | "stderr",
|
|
160
190
|
data: string,
|
|
@@ -165,11 +195,9 @@ interface HttpClientOptions {
|
|
|
165
195
|
exitCode: number,
|
|
166
196
|
stdout: string,
|
|
167
197
|
stderr: string,
|
|
168
|
-
command: string
|
|
169
|
-
args: string[]
|
|
198
|
+
command: string
|
|
170
199
|
) => void;
|
|
171
|
-
onError?: (error: string, command?: string
|
|
172
|
-
onStreamEvent?: (event: StreamEvent) => void;
|
|
200
|
+
onError?: (error: string, command?: string) => void;
|
|
173
201
|
}
|
|
174
202
|
|
|
175
203
|
export class HttpClient {
|
|
@@ -188,111 +216,45 @@ export class HttpClient {
|
|
|
188
216
|
path: string,
|
|
189
217
|
options?: RequestInit
|
|
190
218
|
): Promise<Response> {
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
}
|
|
196
|
-
// Public methods to set event handlers
|
|
197
|
-
setOnOutput(
|
|
198
|
-
handler: (
|
|
199
|
-
stream: "stdout" | "stderr",
|
|
200
|
-
data: string,
|
|
201
|
-
command: string
|
|
202
|
-
) => void
|
|
203
|
-
): void {
|
|
204
|
-
this.options.onOutput = handler;
|
|
205
|
-
}
|
|
219
|
+
const url = this.options.stub
|
|
220
|
+
? `http://localhost:${this.options.port}${path}`
|
|
221
|
+
: `${this.baseUrl}${path}`;
|
|
222
|
+
const method = options?.method || "GET";
|
|
206
223
|
|
|
207
|
-
|
|
208
|
-
handler: (
|
|
209
|
-
success: boolean,
|
|
210
|
-
exitCode: number,
|
|
211
|
-
stdout: string,
|
|
212
|
-
stderr: string,
|
|
213
|
-
command: string,
|
|
214
|
-
args: string[]
|
|
215
|
-
) => void
|
|
216
|
-
): void {
|
|
217
|
-
this.options.onCommandComplete = handler;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
setOnStreamEvent(handler: (event: StreamEvent) => void): void {
|
|
221
|
-
this.options.onStreamEvent = handler;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
// Public getter methods
|
|
225
|
-
getOnOutput():
|
|
226
|
-
| ((stream: "stdout" | "stderr", data: string, command: string) => void)
|
|
227
|
-
| undefined {
|
|
228
|
-
return this.options.onOutput;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
getOnCommandComplete():
|
|
232
|
-
| ((
|
|
233
|
-
success: boolean,
|
|
234
|
-
exitCode: number,
|
|
235
|
-
stdout: string,
|
|
236
|
-
stderr: string,
|
|
237
|
-
command: string,
|
|
238
|
-
args: string[]
|
|
239
|
-
) => void)
|
|
240
|
-
| undefined {
|
|
241
|
-
return this.options.onCommandComplete;
|
|
242
|
-
}
|
|
224
|
+
console.log(`[HTTP Client] Making ${method} request to ${url}`);
|
|
243
225
|
|
|
244
|
-
getOnStreamEvent(): ((event: StreamEvent) => void) | undefined {
|
|
245
|
-
return this.options.onStreamEvent;
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
async createSession(): Promise<string> {
|
|
249
226
|
try {
|
|
250
|
-
|
|
251
|
-
headers: {
|
|
252
|
-
"Content-Type": "application/json",
|
|
253
|
-
},
|
|
254
|
-
method: "POST",
|
|
255
|
-
});
|
|
227
|
+
let response: Response;
|
|
256
228
|
|
|
257
|
-
if (
|
|
258
|
-
|
|
229
|
+
if (this.options.stub) {
|
|
230
|
+
response = await this.options.stub.containerFetch(
|
|
231
|
+
url,
|
|
232
|
+
options,
|
|
233
|
+
this.options.port
|
|
234
|
+
);
|
|
235
|
+
} else {
|
|
236
|
+
response = await fetch(url, options);
|
|
259
237
|
}
|
|
260
238
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
return this.sessionId;
|
|
265
|
-
} catch (error) {
|
|
266
|
-
console.error("[HTTP Client] Error creating session:", error);
|
|
267
|
-
throw error;
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
async listSessions(): Promise<SessionListResponse> {
|
|
272
|
-
try {
|
|
273
|
-
const response = await this.doFetch(`/api/session/list`, {
|
|
274
|
-
headers: {
|
|
275
|
-
"Content-Type": "application/json",
|
|
276
|
-
},
|
|
277
|
-
method: "GET",
|
|
278
|
-
});
|
|
239
|
+
console.log(
|
|
240
|
+
`[HTTP Client] Response: ${response.status} ${response.statusText}`
|
|
241
|
+
);
|
|
279
242
|
|
|
280
243
|
if (!response.ok) {
|
|
281
|
-
|
|
244
|
+
console.error(
|
|
245
|
+
`[HTTP Client] Request failed: ${method} ${url} - ${response.status} ${response.statusText}`
|
|
246
|
+
);
|
|
282
247
|
}
|
|
283
248
|
|
|
284
|
-
|
|
285
|
-
console.log(`[HTTP Client] Listed ${data.count} sessions`);
|
|
286
|
-
return data;
|
|
249
|
+
return response;
|
|
287
250
|
} catch (error) {
|
|
288
|
-
console.error(
|
|
251
|
+
console.error(`[HTTP Client] Request error: ${method} ${url}`, error);
|
|
289
252
|
throw error;
|
|
290
253
|
}
|
|
291
254
|
}
|
|
292
255
|
|
|
293
256
|
async execute(
|
|
294
257
|
command: string,
|
|
295
|
-
args: string[] = [],
|
|
296
258
|
sessionId?: string
|
|
297
259
|
): Promise<ExecuteResponse> {
|
|
298
260
|
try {
|
|
@@ -300,10 +262,9 @@ export class HttpClient {
|
|
|
300
262
|
|
|
301
263
|
const response = await this.doFetch(`/api/execute`, {
|
|
302
264
|
body: JSON.stringify({
|
|
303
|
-
args,
|
|
304
265
|
command,
|
|
305
266
|
sessionId: targetSessionId,
|
|
306
|
-
}),
|
|
267
|
+
} as ExecuteRequest),
|
|
307
268
|
headers: {
|
|
308
269
|
"Content-Type": "application/json",
|
|
309
270
|
},
|
|
@@ -330,8 +291,7 @@ export class HttpClient {
|
|
|
330
291
|
data.exitCode,
|
|
331
292
|
data.stdout,
|
|
332
293
|
data.stderr,
|
|
333
|
-
data.command
|
|
334
|
-
data.args
|
|
294
|
+
data.command
|
|
335
295
|
);
|
|
336
296
|
|
|
337
297
|
return data;
|
|
@@ -339,29 +299,28 @@ export class HttpClient {
|
|
|
339
299
|
console.error("[HTTP Client] Error executing command:", error);
|
|
340
300
|
this.options.onError?.(
|
|
341
301
|
error instanceof Error ? error.message : "Unknown error",
|
|
342
|
-
command
|
|
343
|
-
args
|
|
302
|
+
command
|
|
344
303
|
);
|
|
345
304
|
throw error;
|
|
346
305
|
}
|
|
347
306
|
}
|
|
348
307
|
|
|
349
|
-
|
|
308
|
+
|
|
309
|
+
async executeCommandStream(
|
|
350
310
|
command: string,
|
|
351
|
-
args: string[] = [],
|
|
352
311
|
sessionId?: string
|
|
353
|
-
): Promise<
|
|
312
|
+
): Promise<ReadableStream<Uint8Array>> {
|
|
354
313
|
try {
|
|
355
314
|
const targetSessionId = sessionId || this.sessionId;
|
|
356
315
|
|
|
357
316
|
const response = await this.doFetch(`/api/execute/stream`, {
|
|
358
317
|
body: JSON.stringify({
|
|
359
|
-
args,
|
|
360
318
|
command,
|
|
361
319
|
sessionId: targetSessionId,
|
|
362
320
|
}),
|
|
363
321
|
headers: {
|
|
364
322
|
"Content-Type": "application/json",
|
|
323
|
+
"Accept": "text/event-stream",
|
|
365
324
|
},
|
|
366
325
|
method: "POST",
|
|
367
326
|
});
|
|
@@ -379,95 +338,13 @@ export class HttpClient {
|
|
|
379
338
|
throw new Error("No response body for streaming request");
|
|
380
339
|
}
|
|
381
340
|
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
try {
|
|
386
|
-
while (true) {
|
|
387
|
-
const { done, value } = await reader.read();
|
|
388
|
-
|
|
389
|
-
if (done) {
|
|
390
|
-
break;
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
const chunk = decoder.decode(value, { stream: true });
|
|
394
|
-
const lines = chunk.split("\n");
|
|
395
|
-
|
|
396
|
-
for (const line of lines) {
|
|
397
|
-
if (line.startsWith("data: ")) {
|
|
398
|
-
try {
|
|
399
|
-
const eventData = line.slice(6); // Remove 'data: ' prefix
|
|
400
|
-
const event: StreamEvent = JSON.parse(eventData);
|
|
401
|
-
|
|
402
|
-
console.log(`[HTTP Client] Stream event: ${event.type}`);
|
|
403
|
-
this.options.onStreamEvent?.(event);
|
|
404
|
-
|
|
405
|
-
switch (event.type) {
|
|
406
|
-
case "command_start":
|
|
407
|
-
console.log(
|
|
408
|
-
`[HTTP Client] Command started: ${
|
|
409
|
-
event.command
|
|
410
|
-
} ${event.args?.join(" ")}`
|
|
411
|
-
);
|
|
412
|
-
this.options.onCommandStart?.(
|
|
413
|
-
event.command!,
|
|
414
|
-
event.args || []
|
|
415
|
-
);
|
|
416
|
-
break;
|
|
417
|
-
|
|
418
|
-
case "output":
|
|
419
|
-
console.log(`[${event.stream}] ${event.data}`);
|
|
420
|
-
this.options.onOutput?.(
|
|
421
|
-
event.stream!,
|
|
422
|
-
event.data!,
|
|
423
|
-
event.command!
|
|
424
|
-
);
|
|
425
|
-
break;
|
|
426
|
-
|
|
427
|
-
case "command_complete":
|
|
428
|
-
console.log(
|
|
429
|
-
`[HTTP Client] Command completed: ${event.command}, Success: ${event.success}, Exit code: ${event.exitCode}`
|
|
430
|
-
);
|
|
431
|
-
this.options.onCommandComplete?.(
|
|
432
|
-
event.success!,
|
|
433
|
-
event.exitCode!,
|
|
434
|
-
event.stdout!,
|
|
435
|
-
event.stderr!,
|
|
436
|
-
event.command!,
|
|
437
|
-
event.args || []
|
|
438
|
-
);
|
|
439
|
-
break;
|
|
440
|
-
|
|
441
|
-
case "error":
|
|
442
|
-
console.error(
|
|
443
|
-
`[HTTP Client] Command error: ${event.error}`
|
|
444
|
-
);
|
|
445
|
-
this.options.onError?.(
|
|
446
|
-
event.error!,
|
|
447
|
-
event.command,
|
|
448
|
-
event.args
|
|
449
|
-
);
|
|
450
|
-
break;
|
|
451
|
-
}
|
|
452
|
-
} catch (parseError) {
|
|
453
|
-
console.warn(
|
|
454
|
-
"[HTTP Client] Failed to parse stream event:",
|
|
455
|
-
parseError
|
|
456
|
-
);
|
|
457
|
-
}
|
|
458
|
-
}
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
} finally {
|
|
462
|
-
reader.releaseLock();
|
|
463
|
-
}
|
|
464
|
-
} catch (error) {
|
|
465
|
-
console.error("[HTTP Client] Error in streaming execution:", error);
|
|
466
|
-
this.options.onError?.(
|
|
467
|
-
error instanceof Error ? error.message : "Unknown error",
|
|
468
|
-
command,
|
|
469
|
-
args
|
|
341
|
+
console.log(
|
|
342
|
+
`[HTTP Client] Started command stream: ${command}`
|
|
470
343
|
);
|
|
344
|
+
|
|
345
|
+
return response.body;
|
|
346
|
+
} catch (error) {
|
|
347
|
+
console.error("[HTTP Client] Error in command stream:", error);
|
|
471
348
|
throw error;
|
|
472
349
|
}
|
|
473
350
|
}
|
|
@@ -487,7 +364,7 @@ export class HttpClient {
|
|
|
487
364
|
repoUrl,
|
|
488
365
|
sessionId: targetSessionId,
|
|
489
366
|
targetDir,
|
|
490
|
-
}),
|
|
367
|
+
} as GitCheckoutRequest),
|
|
491
368
|
headers: {
|
|
492
369
|
"Content-Type": "application/json",
|
|
493
370
|
},
|
|
@@ -515,135 +392,6 @@ export class HttpClient {
|
|
|
515
392
|
}
|
|
516
393
|
}
|
|
517
394
|
|
|
518
|
-
async gitCheckoutStream(
|
|
519
|
-
repoUrl: string,
|
|
520
|
-
branch: string = "main",
|
|
521
|
-
targetDir?: string,
|
|
522
|
-
sessionId?: string
|
|
523
|
-
): Promise<void> {
|
|
524
|
-
try {
|
|
525
|
-
const targetSessionId = sessionId || this.sessionId;
|
|
526
|
-
|
|
527
|
-
const response = await this.doFetch(`/api/git/checkout/stream`, {
|
|
528
|
-
body: JSON.stringify({
|
|
529
|
-
branch,
|
|
530
|
-
repoUrl,
|
|
531
|
-
sessionId: targetSessionId,
|
|
532
|
-
targetDir,
|
|
533
|
-
}),
|
|
534
|
-
headers: {
|
|
535
|
-
"Content-Type": "application/json",
|
|
536
|
-
},
|
|
537
|
-
method: "POST",
|
|
538
|
-
});
|
|
539
|
-
|
|
540
|
-
if (!response.ok) {
|
|
541
|
-
const errorData = (await response.json().catch(() => ({}))) as {
|
|
542
|
-
error?: string;
|
|
543
|
-
};
|
|
544
|
-
throw new Error(
|
|
545
|
-
errorData.error || `HTTP error! status: ${response.status}`
|
|
546
|
-
);
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
if (!response.body) {
|
|
550
|
-
throw new Error("No response body for streaming request");
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
const reader = response.body.getReader();
|
|
554
|
-
const decoder = new TextDecoder();
|
|
555
|
-
|
|
556
|
-
try {
|
|
557
|
-
while (true) {
|
|
558
|
-
const { done, value } = await reader.read();
|
|
559
|
-
|
|
560
|
-
if (done) {
|
|
561
|
-
break;
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
const chunk = decoder.decode(value, { stream: true });
|
|
565
|
-
const lines = chunk.split("\n");
|
|
566
|
-
|
|
567
|
-
for (const line of lines) {
|
|
568
|
-
if (line.startsWith("data: ")) {
|
|
569
|
-
try {
|
|
570
|
-
const eventData = line.slice(6); // Remove 'data: ' prefix
|
|
571
|
-
const event: StreamEvent = JSON.parse(eventData);
|
|
572
|
-
|
|
573
|
-
console.log(
|
|
574
|
-
`[HTTP Client] Git checkout stream event: ${event.type}`
|
|
575
|
-
);
|
|
576
|
-
this.options.onStreamEvent?.(event);
|
|
577
|
-
|
|
578
|
-
switch (event.type) {
|
|
579
|
-
case "command_start":
|
|
580
|
-
console.log(
|
|
581
|
-
`[HTTP Client] Git checkout started: ${
|
|
582
|
-
event.command
|
|
583
|
-
} ${event.args?.join(" ")}`
|
|
584
|
-
);
|
|
585
|
-
this.options.onCommandStart?.(
|
|
586
|
-
event.command!,
|
|
587
|
-
event.args || []
|
|
588
|
-
);
|
|
589
|
-
break;
|
|
590
|
-
|
|
591
|
-
case "output":
|
|
592
|
-
console.log(`[${event.stream}] ${event.data}`);
|
|
593
|
-
this.options.onOutput?.(
|
|
594
|
-
event.stream!,
|
|
595
|
-
event.data!,
|
|
596
|
-
event.command!
|
|
597
|
-
);
|
|
598
|
-
break;
|
|
599
|
-
|
|
600
|
-
case "command_complete":
|
|
601
|
-
console.log(
|
|
602
|
-
`[HTTP Client] Git checkout completed: ${event.command}, Success: ${event.success}, Exit code: ${event.exitCode}`
|
|
603
|
-
);
|
|
604
|
-
this.options.onCommandComplete?.(
|
|
605
|
-
event.success!,
|
|
606
|
-
event.exitCode!,
|
|
607
|
-
event.stdout!,
|
|
608
|
-
event.stderr!,
|
|
609
|
-
event.command!,
|
|
610
|
-
event.args || []
|
|
611
|
-
);
|
|
612
|
-
break;
|
|
613
|
-
|
|
614
|
-
case "error":
|
|
615
|
-
console.error(
|
|
616
|
-
`[HTTP Client] Git checkout error: ${event.error}`
|
|
617
|
-
);
|
|
618
|
-
this.options.onError?.(
|
|
619
|
-
event.error!,
|
|
620
|
-
event.command,
|
|
621
|
-
event.args
|
|
622
|
-
);
|
|
623
|
-
break;
|
|
624
|
-
}
|
|
625
|
-
} catch (parseError) {
|
|
626
|
-
console.warn(
|
|
627
|
-
"[HTTP Client] Failed to parse git checkout stream event:",
|
|
628
|
-
parseError
|
|
629
|
-
);
|
|
630
|
-
}
|
|
631
|
-
}
|
|
632
|
-
}
|
|
633
|
-
}
|
|
634
|
-
} finally {
|
|
635
|
-
reader.releaseLock();
|
|
636
|
-
}
|
|
637
|
-
} catch (error) {
|
|
638
|
-
console.error("[HTTP Client] Error in streaming git checkout:", error);
|
|
639
|
-
this.options.onError?.(
|
|
640
|
-
error instanceof Error ? error.message : "Unknown error",
|
|
641
|
-
"git clone",
|
|
642
|
-
[branch, repoUrl, targetDir || ""]
|
|
643
|
-
);
|
|
644
|
-
throw error;
|
|
645
|
-
}
|
|
646
|
-
}
|
|
647
395
|
|
|
648
396
|
async mkdir(
|
|
649
397
|
path: string,
|
|
@@ -658,7 +406,7 @@ export class HttpClient {
|
|
|
658
406
|
path,
|
|
659
407
|
recursive,
|
|
660
408
|
sessionId: targetSessionId,
|
|
661
|
-
}),
|
|
409
|
+
} as MkdirRequest),
|
|
662
410
|
headers: {
|
|
663
411
|
"Content-Type": "application/json",
|
|
664
412
|
},
|
|
@@ -686,129 +434,6 @@ export class HttpClient {
|
|
|
686
434
|
}
|
|
687
435
|
}
|
|
688
436
|
|
|
689
|
-
async mkdirStream(
|
|
690
|
-
path: string,
|
|
691
|
-
recursive: boolean = false,
|
|
692
|
-
sessionId?: string
|
|
693
|
-
): Promise<void> {
|
|
694
|
-
try {
|
|
695
|
-
const targetSessionId = sessionId || this.sessionId;
|
|
696
|
-
|
|
697
|
-
const response = await this.doFetch(`/api/mkdir/stream`, {
|
|
698
|
-
body: JSON.stringify({
|
|
699
|
-
path,
|
|
700
|
-
recursive,
|
|
701
|
-
sessionId: targetSessionId,
|
|
702
|
-
}),
|
|
703
|
-
headers: {
|
|
704
|
-
"Content-Type": "application/json",
|
|
705
|
-
},
|
|
706
|
-
method: "POST",
|
|
707
|
-
});
|
|
708
|
-
|
|
709
|
-
if (!response.ok) {
|
|
710
|
-
const errorData = (await response.json().catch(() => ({}))) as {
|
|
711
|
-
error?: string;
|
|
712
|
-
};
|
|
713
|
-
throw new Error(
|
|
714
|
-
errorData.error || `HTTP error! status: ${response.status}`
|
|
715
|
-
);
|
|
716
|
-
}
|
|
717
|
-
|
|
718
|
-
if (!response.body) {
|
|
719
|
-
throw new Error("No response body for streaming request");
|
|
720
|
-
}
|
|
721
|
-
|
|
722
|
-
const reader = response.body.getReader();
|
|
723
|
-
const decoder = new TextDecoder();
|
|
724
|
-
|
|
725
|
-
try {
|
|
726
|
-
while (true) {
|
|
727
|
-
const { done, value } = await reader.read();
|
|
728
|
-
|
|
729
|
-
if (done) {
|
|
730
|
-
break;
|
|
731
|
-
}
|
|
732
|
-
|
|
733
|
-
const chunk = decoder.decode(value, { stream: true });
|
|
734
|
-
const lines = chunk.split("\n");
|
|
735
|
-
|
|
736
|
-
for (const line of lines) {
|
|
737
|
-
if (line.startsWith("data: ")) {
|
|
738
|
-
try {
|
|
739
|
-
const eventData = line.slice(6); // Remove 'data: ' prefix
|
|
740
|
-
const event: StreamEvent = JSON.parse(eventData);
|
|
741
|
-
|
|
742
|
-
console.log(`[HTTP Client] Mkdir stream event: ${event.type}`);
|
|
743
|
-
this.options.onStreamEvent?.(event);
|
|
744
|
-
|
|
745
|
-
switch (event.type) {
|
|
746
|
-
case "command_start":
|
|
747
|
-
console.log(
|
|
748
|
-
`[HTTP Client] Mkdir started: ${
|
|
749
|
-
event.command
|
|
750
|
-
} ${event.args?.join(" ")}`
|
|
751
|
-
);
|
|
752
|
-
this.options.onCommandStart?.(
|
|
753
|
-
event.command!,
|
|
754
|
-
event.args || []
|
|
755
|
-
);
|
|
756
|
-
break;
|
|
757
|
-
|
|
758
|
-
case "output":
|
|
759
|
-
console.log(`[${event.stream}] ${event.data}`);
|
|
760
|
-
this.options.onOutput?.(
|
|
761
|
-
event.stream!,
|
|
762
|
-
event.data!,
|
|
763
|
-
event.command!
|
|
764
|
-
);
|
|
765
|
-
break;
|
|
766
|
-
|
|
767
|
-
case "command_complete":
|
|
768
|
-
console.log(
|
|
769
|
-
`[HTTP Client] Mkdir completed: ${event.command}, Success: ${event.success}, Exit code: ${event.exitCode}`
|
|
770
|
-
);
|
|
771
|
-
this.options.onCommandComplete?.(
|
|
772
|
-
event.success!,
|
|
773
|
-
event.exitCode!,
|
|
774
|
-
event.stdout!,
|
|
775
|
-
event.stderr!,
|
|
776
|
-
event.command!,
|
|
777
|
-
event.args || []
|
|
778
|
-
);
|
|
779
|
-
break;
|
|
780
|
-
|
|
781
|
-
case "error":
|
|
782
|
-
console.error(`[HTTP Client] Mkdir error: ${event.error}`);
|
|
783
|
-
this.options.onError?.(
|
|
784
|
-
event.error!,
|
|
785
|
-
event.command,
|
|
786
|
-
event.args
|
|
787
|
-
);
|
|
788
|
-
break;
|
|
789
|
-
}
|
|
790
|
-
} catch (parseError) {
|
|
791
|
-
console.warn(
|
|
792
|
-
"[HTTP Client] Failed to parse mkdir stream event:",
|
|
793
|
-
parseError
|
|
794
|
-
);
|
|
795
|
-
}
|
|
796
|
-
}
|
|
797
|
-
}
|
|
798
|
-
}
|
|
799
|
-
} finally {
|
|
800
|
-
reader.releaseLock();
|
|
801
|
-
}
|
|
802
|
-
} catch (error) {
|
|
803
|
-
console.error("[HTTP Client] Error in streaming mkdir:", error);
|
|
804
|
-
this.options.onError?.(
|
|
805
|
-
error instanceof Error ? error.message : "Unknown error",
|
|
806
|
-
"mkdir",
|
|
807
|
-
recursive ? ["-p", path] : [path]
|
|
808
|
-
);
|
|
809
|
-
throw error;
|
|
810
|
-
}
|
|
811
|
-
}
|
|
812
437
|
|
|
813
438
|
async writeFile(
|
|
814
439
|
path: string,
|
|
@@ -825,7 +450,7 @@ export class HttpClient {
|
|
|
825
450
|
encoding,
|
|
826
451
|
path,
|
|
827
452
|
sessionId: targetSessionId,
|
|
828
|
-
}),
|
|
453
|
+
} as WriteFileRequest),
|
|
829
454
|
headers: {
|
|
830
455
|
"Content-Type": "application/json",
|
|
831
456
|
},
|
|
@@ -853,22 +478,21 @@ export class HttpClient {
|
|
|
853
478
|
}
|
|
854
479
|
}
|
|
855
480
|
|
|
856
|
-
|
|
481
|
+
|
|
482
|
+
async readFile(
|
|
857
483
|
path: string,
|
|
858
|
-
content: string,
|
|
859
484
|
encoding: string = "utf-8",
|
|
860
485
|
sessionId?: string
|
|
861
|
-
): Promise<
|
|
486
|
+
): Promise<ReadFileResponse> {
|
|
862
487
|
try {
|
|
863
488
|
const targetSessionId = sessionId || this.sessionId;
|
|
864
489
|
|
|
865
|
-
const response = await this.doFetch(`/api/
|
|
490
|
+
const response = await this.doFetch(`/api/read`, {
|
|
866
491
|
body: JSON.stringify({
|
|
867
|
-
content,
|
|
868
492
|
encoding,
|
|
869
493
|
path,
|
|
870
494
|
sessionId: targetSessionId,
|
|
871
|
-
}),
|
|
495
|
+
} as ReadFileRequest),
|
|
872
496
|
headers: {
|
|
873
497
|
"Content-Type": "application/json",
|
|
874
498
|
},
|
|
@@ -884,100 +508,19 @@ export class HttpClient {
|
|
|
884
508
|
);
|
|
885
509
|
}
|
|
886
510
|
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
511
|
+
const data: ReadFileResponse = await response.json();
|
|
512
|
+
console.log(
|
|
513
|
+
`[HTTP Client] File read: ${path}, Success: ${data.success}, Content length: ${data.content.length}`
|
|
514
|
+
);
|
|
890
515
|
|
|
891
|
-
|
|
892
|
-
const decoder = new TextDecoder();
|
|
893
|
-
|
|
894
|
-
try {
|
|
895
|
-
while (true) {
|
|
896
|
-
const { done, value } = await reader.read();
|
|
897
|
-
|
|
898
|
-
if (done) {
|
|
899
|
-
break;
|
|
900
|
-
}
|
|
901
|
-
|
|
902
|
-
const chunk = decoder.decode(value, { stream: true });
|
|
903
|
-
const lines = chunk.split("\n");
|
|
904
|
-
|
|
905
|
-
for (const line of lines) {
|
|
906
|
-
if (line.startsWith("data: ")) {
|
|
907
|
-
try {
|
|
908
|
-
const eventData = line.slice(6); // Remove 'data: ' prefix
|
|
909
|
-
const event: StreamEvent = JSON.parse(eventData);
|
|
910
|
-
|
|
911
|
-
console.log(
|
|
912
|
-
`[HTTP Client] Write file stream event: ${event.type}`
|
|
913
|
-
);
|
|
914
|
-
this.options.onStreamEvent?.(event);
|
|
915
|
-
|
|
916
|
-
switch (event.type) {
|
|
917
|
-
case "command_start":
|
|
918
|
-
console.log(
|
|
919
|
-
`[HTTP Client] Write file started: ${event.path}`
|
|
920
|
-
);
|
|
921
|
-
this.options.onCommandStart?.("write", [
|
|
922
|
-
path,
|
|
923
|
-
content,
|
|
924
|
-
encoding,
|
|
925
|
-
]);
|
|
926
|
-
break;
|
|
927
|
-
|
|
928
|
-
case "output":
|
|
929
|
-
console.log(`[output] ${event.message}`);
|
|
930
|
-
this.options.onOutput?.("stdout", event.message!, "write");
|
|
931
|
-
break;
|
|
932
|
-
|
|
933
|
-
case "command_complete":
|
|
934
|
-
console.log(
|
|
935
|
-
`[HTTP Client] Write file completed: ${event.path}, Success: ${event.success}`
|
|
936
|
-
);
|
|
937
|
-
this.options.onCommandComplete?.(
|
|
938
|
-
event.success!,
|
|
939
|
-
0,
|
|
940
|
-
"",
|
|
941
|
-
"",
|
|
942
|
-
"write",
|
|
943
|
-
[path, content, encoding]
|
|
944
|
-
);
|
|
945
|
-
break;
|
|
946
|
-
|
|
947
|
-
case "error":
|
|
948
|
-
console.error(
|
|
949
|
-
`[HTTP Client] Write file error: ${event.error}`
|
|
950
|
-
);
|
|
951
|
-
this.options.onError?.(event.error!, "write", [
|
|
952
|
-
path,
|
|
953
|
-
content,
|
|
954
|
-
encoding,
|
|
955
|
-
]);
|
|
956
|
-
break;
|
|
957
|
-
}
|
|
958
|
-
} catch (parseError) {
|
|
959
|
-
console.warn(
|
|
960
|
-
"[HTTP Client] Failed to parse write file stream event:",
|
|
961
|
-
parseError
|
|
962
|
-
);
|
|
963
|
-
}
|
|
964
|
-
}
|
|
965
|
-
}
|
|
966
|
-
}
|
|
967
|
-
} finally {
|
|
968
|
-
reader.releaseLock();
|
|
969
|
-
}
|
|
516
|
+
return data;
|
|
970
517
|
} catch (error) {
|
|
971
|
-
console.error("[HTTP Client] Error
|
|
972
|
-
this.options.onError?.(
|
|
973
|
-
error instanceof Error ? error.message : "Unknown error",
|
|
974
|
-
"write",
|
|
975
|
-
[path, content, encoding]
|
|
976
|
-
);
|
|
518
|
+
console.error("[HTTP Client] Error reading file:", error);
|
|
977
519
|
throw error;
|
|
978
520
|
}
|
|
979
521
|
}
|
|
980
522
|
|
|
523
|
+
|
|
981
524
|
async deleteFile(
|
|
982
525
|
path: string,
|
|
983
526
|
sessionId?: string
|
|
@@ -989,7 +532,7 @@ export class HttpClient {
|
|
|
989
532
|
body: JSON.stringify({
|
|
990
533
|
path,
|
|
991
534
|
sessionId: targetSessionId,
|
|
992
|
-
}),
|
|
535
|
+
} as DeleteFileRequest),
|
|
993
536
|
headers: {
|
|
994
537
|
"Content-Type": "application/json",
|
|
995
538
|
},
|
|
@@ -1017,15 +560,21 @@ export class HttpClient {
|
|
|
1017
560
|
}
|
|
1018
561
|
}
|
|
1019
562
|
|
|
1020
|
-
|
|
563
|
+
|
|
564
|
+
async renameFile(
|
|
565
|
+
oldPath: string,
|
|
566
|
+
newPath: string,
|
|
567
|
+
sessionId?: string
|
|
568
|
+
): Promise<RenameFileResponse> {
|
|
1021
569
|
try {
|
|
1022
570
|
const targetSessionId = sessionId || this.sessionId;
|
|
1023
571
|
|
|
1024
|
-
const response = await this.doFetch(`/api/
|
|
572
|
+
const response = await this.doFetch(`/api/rename`, {
|
|
1025
573
|
body: JSON.stringify({
|
|
1026
|
-
|
|
574
|
+
newPath,
|
|
575
|
+
oldPath,
|
|
1027
576
|
sessionId: targetSessionId,
|
|
1028
|
-
}),
|
|
577
|
+
} as RenameFileRequest),
|
|
1029
578
|
headers: {
|
|
1030
579
|
"Content-Type": "application/json",
|
|
1031
580
|
},
|
|
@@ -1041,101 +590,33 @@ export class HttpClient {
|
|
|
1041
590
|
);
|
|
1042
591
|
}
|
|
1043
592
|
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
593
|
+
const data: RenameFileResponse = await response.json();
|
|
594
|
+
console.log(
|
|
595
|
+
`[HTTP Client] File renamed: ${oldPath} -> ${newPath}, Success: ${data.success}`
|
|
596
|
+
);
|
|
1047
597
|
|
|
1048
|
-
|
|
1049
|
-
const decoder = new TextDecoder();
|
|
1050
|
-
|
|
1051
|
-
try {
|
|
1052
|
-
while (true) {
|
|
1053
|
-
const { done, value } = await reader.read();
|
|
1054
|
-
|
|
1055
|
-
if (done) {
|
|
1056
|
-
break;
|
|
1057
|
-
}
|
|
1058
|
-
|
|
1059
|
-
const chunk = decoder.decode(value, { stream: true });
|
|
1060
|
-
const lines = chunk.split("\n");
|
|
1061
|
-
|
|
1062
|
-
for (const line of lines) {
|
|
1063
|
-
if (line.startsWith("data: ")) {
|
|
1064
|
-
try {
|
|
1065
|
-
const eventData = line.slice(6); // Remove 'data: ' prefix
|
|
1066
|
-
const event: StreamEvent = JSON.parse(eventData);
|
|
1067
|
-
|
|
1068
|
-
console.log(
|
|
1069
|
-
`[HTTP Client] Delete file stream event: ${event.type}`
|
|
1070
|
-
);
|
|
1071
|
-
this.options.onStreamEvent?.(event);
|
|
1072
|
-
|
|
1073
|
-
switch (event.type) {
|
|
1074
|
-
case "command_start":
|
|
1075
|
-
console.log(
|
|
1076
|
-
`[HTTP Client] Delete file started: ${event.path}`
|
|
1077
|
-
);
|
|
1078
|
-
this.options.onCommandStart?.("delete", [path]);
|
|
1079
|
-
break;
|
|
1080
|
-
|
|
1081
|
-
case "command_complete":
|
|
1082
|
-
console.log(
|
|
1083
|
-
`[HTTP Client] Delete file completed: ${event.path}, Success: ${event.success}`
|
|
1084
|
-
);
|
|
1085
|
-
this.options.onCommandComplete?.(
|
|
1086
|
-
event.success!,
|
|
1087
|
-
0,
|
|
1088
|
-
"",
|
|
1089
|
-
"",
|
|
1090
|
-
"delete",
|
|
1091
|
-
[path]
|
|
1092
|
-
);
|
|
1093
|
-
break;
|
|
1094
|
-
|
|
1095
|
-
case "error":
|
|
1096
|
-
console.error(
|
|
1097
|
-
`[HTTP Client] Delete file error: ${event.error}`
|
|
1098
|
-
);
|
|
1099
|
-
this.options.onError?.(event.error!, "delete", [path]);
|
|
1100
|
-
break;
|
|
1101
|
-
}
|
|
1102
|
-
} catch (parseError) {
|
|
1103
|
-
console.warn(
|
|
1104
|
-
"[HTTP Client] Failed to parse delete file stream event:",
|
|
1105
|
-
parseError
|
|
1106
|
-
);
|
|
1107
|
-
}
|
|
1108
|
-
}
|
|
1109
|
-
}
|
|
1110
|
-
}
|
|
1111
|
-
} finally {
|
|
1112
|
-
reader.releaseLock();
|
|
1113
|
-
}
|
|
598
|
+
return data;
|
|
1114
599
|
} catch (error) {
|
|
1115
|
-
console.error("[HTTP Client] Error
|
|
1116
|
-
this.options.onError?.(
|
|
1117
|
-
error instanceof Error ? error.message : "Unknown error",
|
|
1118
|
-
"delete",
|
|
1119
|
-
[path]
|
|
1120
|
-
);
|
|
600
|
+
console.error("[HTTP Client] Error renaming file:", error);
|
|
1121
601
|
throw error;
|
|
1122
602
|
}
|
|
1123
603
|
}
|
|
1124
604
|
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
605
|
+
|
|
606
|
+
async moveFile(
|
|
607
|
+
sourcePath: string,
|
|
608
|
+
destinationPath: string,
|
|
1128
609
|
sessionId?: string
|
|
1129
|
-
): Promise<
|
|
610
|
+
): Promise<MoveFileResponse> {
|
|
1130
611
|
try {
|
|
1131
612
|
const targetSessionId = sessionId || this.sessionId;
|
|
1132
613
|
|
|
1133
|
-
const response = await this.doFetch(`/api/
|
|
614
|
+
const response = await this.doFetch(`/api/move`, {
|
|
1134
615
|
body: JSON.stringify({
|
|
1135
|
-
|
|
1136
|
-
oldPath,
|
|
616
|
+
destinationPath,
|
|
1137
617
|
sessionId: targetSessionId,
|
|
1138
|
-
|
|
618
|
+
sourcePath,
|
|
619
|
+
} as MoveFileRequest),
|
|
1139
620
|
headers: {
|
|
1140
621
|
"Content-Type": "application/json",
|
|
1141
622
|
},
|
|
@@ -1151,31 +632,25 @@ export class HttpClient {
|
|
|
1151
632
|
);
|
|
1152
633
|
}
|
|
1153
634
|
|
|
1154
|
-
const data:
|
|
635
|
+
const data: MoveFileResponse = await response.json();
|
|
1155
636
|
console.log(
|
|
1156
|
-
`[HTTP Client] File
|
|
637
|
+
`[HTTP Client] File moved: ${sourcePath} -> ${destinationPath}, Success: ${data.success}`
|
|
1157
638
|
);
|
|
1158
639
|
|
|
1159
640
|
return data;
|
|
1160
641
|
} catch (error) {
|
|
1161
|
-
console.error("[HTTP Client] Error
|
|
642
|
+
console.error("[HTTP Client] Error moving file:", error);
|
|
1162
643
|
throw error;
|
|
1163
644
|
}
|
|
1164
645
|
}
|
|
1165
646
|
|
|
1166
|
-
async renameFileStream(
|
|
1167
|
-
oldPath: string,
|
|
1168
|
-
newPath: string,
|
|
1169
|
-
sessionId?: string
|
|
1170
|
-
): Promise<void> {
|
|
1171
|
-
try {
|
|
1172
|
-
const targetSessionId = sessionId || this.sessionId;
|
|
1173
647
|
|
|
1174
|
-
|
|
648
|
+
async exposePort(port: number, name?: string): Promise<ExposePortResponse> {
|
|
649
|
+
try {
|
|
650
|
+
const response = await this.doFetch(`/api/expose-port`, {
|
|
1175
651
|
body: JSON.stringify({
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
sessionId: targetSessionId,
|
|
652
|
+
port,
|
|
653
|
+
name,
|
|
1179
654
|
}),
|
|
1180
655
|
headers: {
|
|
1181
656
|
"Content-Type": "application/json",
|
|
@@ -1187,113 +662,34 @@ export class HttpClient {
|
|
|
1187
662
|
const errorData = (await response.json().catch(() => ({}))) as {
|
|
1188
663
|
error?: string;
|
|
1189
664
|
};
|
|
665
|
+
console.log(errorData);
|
|
1190
666
|
throw new Error(
|
|
1191
667
|
errorData.error || `HTTP error! status: ${response.status}`
|
|
1192
668
|
);
|
|
1193
669
|
}
|
|
1194
670
|
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
671
|
+
const data: ExposePortResponse = await response.json();
|
|
672
|
+
console.log(
|
|
673
|
+
`[HTTP Client] Port exposed: ${port}${name ? ` (${name})` : ""}, Success: ${data.success}`
|
|
674
|
+
);
|
|
1198
675
|
|
|
1199
|
-
|
|
1200
|
-
const decoder = new TextDecoder();
|
|
1201
|
-
|
|
1202
|
-
try {
|
|
1203
|
-
while (true) {
|
|
1204
|
-
const { done, value } = await reader.read();
|
|
1205
|
-
|
|
1206
|
-
if (done) {
|
|
1207
|
-
break;
|
|
1208
|
-
}
|
|
1209
|
-
|
|
1210
|
-
const chunk = decoder.decode(value, { stream: true });
|
|
1211
|
-
const lines = chunk.split("\n");
|
|
1212
|
-
|
|
1213
|
-
for (const line of lines) {
|
|
1214
|
-
if (line.startsWith("data: ")) {
|
|
1215
|
-
try {
|
|
1216
|
-
const eventData = line.slice(6); // Remove 'data: ' prefix
|
|
1217
|
-
const event: StreamEvent = JSON.parse(eventData);
|
|
1218
|
-
|
|
1219
|
-
console.log(
|
|
1220
|
-
`[HTTP Client] Rename file stream event: ${event.type}`
|
|
1221
|
-
);
|
|
1222
|
-
this.options.onStreamEvent?.(event);
|
|
1223
|
-
|
|
1224
|
-
switch (event.type) {
|
|
1225
|
-
case "command_start":
|
|
1226
|
-
console.log(
|
|
1227
|
-
`[HTTP Client] Rename file started: ${event.oldPath} -> ${event.newPath}`
|
|
1228
|
-
);
|
|
1229
|
-
this.options.onCommandStart?.("rename", [oldPath, newPath]);
|
|
1230
|
-
break;
|
|
1231
|
-
|
|
1232
|
-
case "command_complete":
|
|
1233
|
-
console.log(
|
|
1234
|
-
`[HTTP Client] Rename file completed: ${event.oldPath} -> ${event.newPath}, Success: ${event.success}`
|
|
1235
|
-
);
|
|
1236
|
-
this.options.onCommandComplete?.(
|
|
1237
|
-
event.success!,
|
|
1238
|
-
0,
|
|
1239
|
-
"",
|
|
1240
|
-
"",
|
|
1241
|
-
"rename",
|
|
1242
|
-
[oldPath, newPath]
|
|
1243
|
-
);
|
|
1244
|
-
break;
|
|
1245
|
-
|
|
1246
|
-
case "error":
|
|
1247
|
-
console.error(
|
|
1248
|
-
`[HTTP Client] Rename file error: ${event.error}`
|
|
1249
|
-
);
|
|
1250
|
-
this.options.onError?.(event.error!, "rename", [
|
|
1251
|
-
oldPath,
|
|
1252
|
-
newPath,
|
|
1253
|
-
]);
|
|
1254
|
-
break;
|
|
1255
|
-
}
|
|
1256
|
-
} catch (parseError) {
|
|
1257
|
-
console.warn(
|
|
1258
|
-
"[HTTP Client] Failed to parse rename file stream event:",
|
|
1259
|
-
parseError
|
|
1260
|
-
);
|
|
1261
|
-
}
|
|
1262
|
-
}
|
|
1263
|
-
}
|
|
1264
|
-
}
|
|
1265
|
-
} finally {
|
|
1266
|
-
reader.releaseLock();
|
|
1267
|
-
}
|
|
676
|
+
return data;
|
|
1268
677
|
} catch (error) {
|
|
1269
|
-
console.error("[HTTP Client] Error
|
|
1270
|
-
this.options.onError?.(
|
|
1271
|
-
error instanceof Error ? error.message : "Unknown error",
|
|
1272
|
-
"rename",
|
|
1273
|
-
[oldPath, newPath]
|
|
1274
|
-
);
|
|
678
|
+
console.error("[HTTP Client] Error exposing port:", error);
|
|
1275
679
|
throw error;
|
|
1276
680
|
}
|
|
1277
681
|
}
|
|
1278
682
|
|
|
1279
|
-
async
|
|
1280
|
-
sourcePath: string,
|
|
1281
|
-
destinationPath: string,
|
|
1282
|
-
sessionId?: string
|
|
1283
|
-
): Promise<MoveFileResponse> {
|
|
683
|
+
async unexposePort(port: number): Promise<UnexposePortResponse> {
|
|
1284
684
|
try {
|
|
1285
|
-
const
|
|
1286
|
-
|
|
1287
|
-
const response = await this.doFetch(`/api/move`, {
|
|
685
|
+
const response = await this.doFetch(`/api/unexpose-port`, {
|
|
1288
686
|
body: JSON.stringify({
|
|
1289
|
-
|
|
1290
|
-
sessionId: targetSessionId,
|
|
1291
|
-
sourcePath,
|
|
687
|
+
port,
|
|
1292
688
|
}),
|
|
1293
689
|
headers: {
|
|
1294
690
|
"Content-Type": "application/json",
|
|
1295
691
|
},
|
|
1296
|
-
method: "
|
|
692
|
+
method: "DELETE",
|
|
1297
693
|
});
|
|
1298
694
|
|
|
1299
695
|
if (!response.ok) {
|
|
@@ -1305,36 +701,25 @@ export class HttpClient {
|
|
|
1305
701
|
);
|
|
1306
702
|
}
|
|
1307
703
|
|
|
1308
|
-
const data:
|
|
704
|
+
const data: UnexposePortResponse = await response.json();
|
|
1309
705
|
console.log(
|
|
1310
|
-
`[HTTP Client]
|
|
706
|
+
`[HTTP Client] Port unexposed: ${port}, Success: ${data.success}`
|
|
1311
707
|
);
|
|
1312
708
|
|
|
1313
709
|
return data;
|
|
1314
710
|
} catch (error) {
|
|
1315
|
-
console.error("[HTTP Client] Error
|
|
711
|
+
console.error("[HTTP Client] Error unexposing port:", error);
|
|
1316
712
|
throw error;
|
|
1317
713
|
}
|
|
1318
714
|
}
|
|
1319
715
|
|
|
1320
|
-
async
|
|
1321
|
-
sourcePath: string,
|
|
1322
|
-
destinationPath: string,
|
|
1323
|
-
sessionId?: string
|
|
1324
|
-
): Promise<void> {
|
|
716
|
+
async getExposedPorts(): Promise<GetExposedPortsResponse> {
|
|
1325
717
|
try {
|
|
1326
|
-
const
|
|
1327
|
-
|
|
1328
|
-
const response = await this.doFetch(`/api/move/stream`, {
|
|
1329
|
-
body: JSON.stringify({
|
|
1330
|
-
destinationPath,
|
|
1331
|
-
sessionId: targetSessionId,
|
|
1332
|
-
sourcePath,
|
|
1333
|
-
}),
|
|
718
|
+
const response = await this.doFetch(`/api/exposed-ports`, {
|
|
1334
719
|
headers: {
|
|
1335
720
|
"Content-Type": "application/json",
|
|
1336
721
|
},
|
|
1337
|
-
method: "
|
|
722
|
+
method: "GET",
|
|
1338
723
|
});
|
|
1339
724
|
|
|
1340
725
|
if (!response.ok) {
|
|
@@ -1346,89 +731,14 @@ export class HttpClient {
|
|
|
1346
731
|
);
|
|
1347
732
|
}
|
|
1348
733
|
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
734
|
+
const data: GetExposedPortsResponse = await response.json();
|
|
735
|
+
console.log(
|
|
736
|
+
`[HTTP Client] Got ${data.count} exposed ports`
|
|
737
|
+
);
|
|
1352
738
|
|
|
1353
|
-
|
|
1354
|
-
const decoder = new TextDecoder();
|
|
1355
|
-
|
|
1356
|
-
try {
|
|
1357
|
-
while (true) {
|
|
1358
|
-
const { done, value } = await reader.read();
|
|
1359
|
-
|
|
1360
|
-
if (done) {
|
|
1361
|
-
break;
|
|
1362
|
-
}
|
|
1363
|
-
|
|
1364
|
-
const chunk = decoder.decode(value, { stream: true });
|
|
1365
|
-
const lines = chunk.split("\n");
|
|
1366
|
-
|
|
1367
|
-
for (const line of lines) {
|
|
1368
|
-
if (line.startsWith("data: ")) {
|
|
1369
|
-
try {
|
|
1370
|
-
const eventData = line.slice(6); // Remove 'data: ' prefix
|
|
1371
|
-
const event: StreamEvent = JSON.parse(eventData);
|
|
1372
|
-
|
|
1373
|
-
console.log(
|
|
1374
|
-
`[HTTP Client] Move file stream event: ${event.type}`
|
|
1375
|
-
);
|
|
1376
|
-
this.options.onStreamEvent?.(event);
|
|
1377
|
-
|
|
1378
|
-
switch (event.type) {
|
|
1379
|
-
case "command_start":
|
|
1380
|
-
console.log(
|
|
1381
|
-
`[HTTP Client] Move file started: ${event.sourcePath} -> ${event.destinationPath}`
|
|
1382
|
-
);
|
|
1383
|
-
this.options.onCommandStart?.("move", [
|
|
1384
|
-
sourcePath,
|
|
1385
|
-
destinationPath,
|
|
1386
|
-
]);
|
|
1387
|
-
break;
|
|
1388
|
-
|
|
1389
|
-
case "command_complete":
|
|
1390
|
-
console.log(
|
|
1391
|
-
`[HTTP Client] Move file completed: ${event.sourcePath} -> ${event.destinationPath}, Success: ${event.success}`
|
|
1392
|
-
);
|
|
1393
|
-
this.options.onCommandComplete?.(
|
|
1394
|
-
event.success!,
|
|
1395
|
-
0,
|
|
1396
|
-
"",
|
|
1397
|
-
"",
|
|
1398
|
-
"move",
|
|
1399
|
-
[sourcePath, destinationPath]
|
|
1400
|
-
);
|
|
1401
|
-
break;
|
|
1402
|
-
|
|
1403
|
-
case "error":
|
|
1404
|
-
console.error(
|
|
1405
|
-
`[HTTP Client] Move file error: ${event.error}`
|
|
1406
|
-
);
|
|
1407
|
-
this.options.onError?.(event.error!, "move", [
|
|
1408
|
-
sourcePath,
|
|
1409
|
-
destinationPath,
|
|
1410
|
-
]);
|
|
1411
|
-
break;
|
|
1412
|
-
}
|
|
1413
|
-
} catch (parseError) {
|
|
1414
|
-
console.warn(
|
|
1415
|
-
"[HTTP Client] Failed to parse move file stream event:",
|
|
1416
|
-
parseError
|
|
1417
|
-
);
|
|
1418
|
-
}
|
|
1419
|
-
}
|
|
1420
|
-
}
|
|
1421
|
-
}
|
|
1422
|
-
} finally {
|
|
1423
|
-
reader.releaseLock();
|
|
1424
|
-
}
|
|
739
|
+
return data;
|
|
1425
740
|
} catch (error) {
|
|
1426
|
-
console.error("[HTTP Client] Error
|
|
1427
|
-
this.options.onError?.(
|
|
1428
|
-
error instanceof Error ? error.message : "Unknown error",
|
|
1429
|
-
"move",
|
|
1430
|
-
[sourcePath, destinationPath]
|
|
1431
|
-
);
|
|
741
|
+
console.error("[HTTP Client] Error getting exposed ports:", error);
|
|
1432
742
|
throw error;
|
|
1433
743
|
}
|
|
1434
744
|
}
|
|
@@ -1490,235 +800,239 @@ export class HttpClient {
|
|
|
1490
800
|
clearSession(): void {
|
|
1491
801
|
this.sessionId = null;
|
|
1492
802
|
}
|
|
1493
|
-
}
|
|
1494
803
|
|
|
1495
|
-
//
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
804
|
+
// Process management methods
|
|
805
|
+
async startProcess(
|
|
806
|
+
command: string,
|
|
807
|
+
options?: {
|
|
808
|
+
processId?: string;
|
|
809
|
+
sessionId?: string;
|
|
810
|
+
timeout?: number;
|
|
811
|
+
env?: Record<string, string>;
|
|
812
|
+
cwd?: string;
|
|
813
|
+
encoding?: string;
|
|
814
|
+
autoCleanup?: boolean;
|
|
815
|
+
}
|
|
816
|
+
): Promise<StartProcessResponse> {
|
|
817
|
+
try {
|
|
818
|
+
const targetSessionId = options?.sessionId || this.sessionId;
|
|
1499
819
|
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
}
|
|
1514
|
-
}
|
|
820
|
+
const response = await this.doFetch("/api/process/start", {
|
|
821
|
+
body: JSON.stringify({
|
|
822
|
+
command,
|
|
823
|
+
options: {
|
|
824
|
+
...options,
|
|
825
|
+
sessionId: targetSessionId,
|
|
826
|
+
},
|
|
827
|
+
} as StartProcessRequest),
|
|
828
|
+
headers: {
|
|
829
|
+
"Content-Type": "application/json",
|
|
830
|
+
},
|
|
831
|
+
method: "POST",
|
|
832
|
+
});
|
|
1515
833
|
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
try {
|
|
1526
|
-
await client.executeStream(command, args);
|
|
1527
|
-
} finally {
|
|
1528
|
-
client.clearSession();
|
|
1529
|
-
}
|
|
1530
|
-
}
|
|
834
|
+
if (!response.ok) {
|
|
835
|
+
const errorData = (await response.json().catch(() => ({}))) as {
|
|
836
|
+
error?: string;
|
|
837
|
+
};
|
|
838
|
+
throw new Error(
|
|
839
|
+
errorData.error || `HTTP error! status: ${response.status}`
|
|
840
|
+
);
|
|
841
|
+
}
|
|
1531
842
|
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
targetDir?: string,
|
|
1537
|
-
options?: HttpClientOptions
|
|
1538
|
-
): Promise<GitCheckoutResponse> {
|
|
1539
|
-
const client = createClient(options);
|
|
1540
|
-
await client.createSession();
|
|
1541
|
-
|
|
1542
|
-
try {
|
|
1543
|
-
return await client.gitCheckout(repoUrl, branch, targetDir);
|
|
1544
|
-
} finally {
|
|
1545
|
-
client.clearSession();
|
|
1546
|
-
}
|
|
1547
|
-
}
|
|
843
|
+
const data: StartProcessResponse = await response.json();
|
|
844
|
+
console.log(
|
|
845
|
+
`[HTTP Client] Process started: ${command}, ID: ${data.process.id}`
|
|
846
|
+
);
|
|
1548
847
|
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
): Promise<MkdirResponse> {
|
|
1555
|
-
const client = createClient(options);
|
|
1556
|
-
await client.createSession();
|
|
1557
|
-
|
|
1558
|
-
try {
|
|
1559
|
-
return await client.mkdir(path, recursive);
|
|
1560
|
-
} finally {
|
|
1561
|
-
client.clearSession();
|
|
848
|
+
return data;
|
|
849
|
+
} catch (error) {
|
|
850
|
+
console.error("[HTTP Client] Error starting process:", error);
|
|
851
|
+
throw error;
|
|
852
|
+
}
|
|
1562
853
|
}
|
|
1563
|
-
}
|
|
1564
854
|
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
await client.createSession();
|
|
1574
|
-
|
|
1575
|
-
try {
|
|
1576
|
-
await client.gitCheckoutStream(repoUrl, branch, targetDir);
|
|
1577
|
-
} finally {
|
|
1578
|
-
client.clearSession();
|
|
1579
|
-
}
|
|
1580
|
-
}
|
|
855
|
+
async listProcesses(): Promise<ListProcessesResponse> {
|
|
856
|
+
try {
|
|
857
|
+
const response = await this.doFetch("/api/process/list", {
|
|
858
|
+
headers: {
|
|
859
|
+
"Content-Type": "application/json",
|
|
860
|
+
},
|
|
861
|
+
method: "GET",
|
|
862
|
+
});
|
|
1581
863
|
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
try {
|
|
1592
|
-
await client.mkdirStream(path, recursive);
|
|
1593
|
-
} finally {
|
|
1594
|
-
client.clearSession();
|
|
1595
|
-
}
|
|
1596
|
-
}
|
|
864
|
+
if (!response.ok) {
|
|
865
|
+
const errorData = (await response.json().catch(() => ({}))) as {
|
|
866
|
+
error?: string;
|
|
867
|
+
};
|
|
868
|
+
throw new Error(
|
|
869
|
+
errorData.error || `HTTP error! status: ${response.status}`
|
|
870
|
+
);
|
|
871
|
+
}
|
|
1597
872
|
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
encoding: string = "utf-8",
|
|
1603
|
-
options?: HttpClientOptions
|
|
1604
|
-
): Promise<WriteFileResponse> {
|
|
1605
|
-
const client = createClient(options);
|
|
1606
|
-
await client.createSession();
|
|
1607
|
-
|
|
1608
|
-
try {
|
|
1609
|
-
return await client.writeFile(path, content, encoding);
|
|
1610
|
-
} finally {
|
|
1611
|
-
client.clearSession();
|
|
1612
|
-
}
|
|
1613
|
-
}
|
|
873
|
+
const data: ListProcessesResponse = await response.json();
|
|
874
|
+
console.log(
|
|
875
|
+
`[HTTP Client] Listed ${data.processes.length} processes`
|
|
876
|
+
);
|
|
1614
877
|
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
options?: HttpClientOptions
|
|
1621
|
-
): Promise<void> {
|
|
1622
|
-
const client = createClient(options);
|
|
1623
|
-
await client.createSession();
|
|
1624
|
-
|
|
1625
|
-
try {
|
|
1626
|
-
await client.writeFileStream(path, content, encoding);
|
|
1627
|
-
} finally {
|
|
1628
|
-
client.clearSession();
|
|
878
|
+
return data;
|
|
879
|
+
} catch (error) {
|
|
880
|
+
console.error("[HTTP Client] Error listing processes:", error);
|
|
881
|
+
throw error;
|
|
882
|
+
}
|
|
1629
883
|
}
|
|
1630
|
-
}
|
|
1631
884
|
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
try {
|
|
1641
|
-
return await client.deleteFile(path);
|
|
1642
|
-
} finally {
|
|
1643
|
-
client.clearSession();
|
|
1644
|
-
}
|
|
1645
|
-
}
|
|
885
|
+
async getProcess(processId: string): Promise<GetProcessResponse> {
|
|
886
|
+
try {
|
|
887
|
+
const response = await this.doFetch(`/api/process/${processId}`, {
|
|
888
|
+
headers: {
|
|
889
|
+
"Content-Type": "application/json",
|
|
890
|
+
},
|
|
891
|
+
method: "GET",
|
|
892
|
+
});
|
|
1646
893
|
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
894
|
+
if (!response.ok) {
|
|
895
|
+
const errorData = (await response.json().catch(() => ({}))) as {
|
|
896
|
+
error?: string;
|
|
897
|
+
};
|
|
898
|
+
throw new Error(
|
|
899
|
+
errorData.error || `HTTP error! status: ${response.status}`
|
|
900
|
+
);
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
const data: GetProcessResponse = await response.json();
|
|
904
|
+
console.log(
|
|
905
|
+
`[HTTP Client] Got process ${processId}: ${data.process?.status || 'not found'}`
|
|
906
|
+
);
|
|
907
|
+
|
|
908
|
+
return data;
|
|
909
|
+
} catch (error) {
|
|
910
|
+
console.error("[HTTP Client] Error getting process:", error);
|
|
911
|
+
throw error;
|
|
912
|
+
}
|
|
1659
913
|
}
|
|
1660
|
-
}
|
|
1661
914
|
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
915
|
+
async killProcess(processId: string): Promise<{ success: boolean; message: string }> {
|
|
916
|
+
try {
|
|
917
|
+
const response = await this.doFetch(`/api/process/${processId}`, {
|
|
918
|
+
headers: {
|
|
919
|
+
"Content-Type": "application/json",
|
|
920
|
+
},
|
|
921
|
+
method: "DELETE",
|
|
922
|
+
});
|
|
923
|
+
|
|
924
|
+
if (!response.ok) {
|
|
925
|
+
const errorData = (await response.json().catch(() => ({}))) as {
|
|
926
|
+
error?: string;
|
|
927
|
+
};
|
|
928
|
+
throw new Error(
|
|
929
|
+
errorData.error || `HTTP error! status: ${response.status}`
|
|
930
|
+
);
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
const data = await response.json() as { success: boolean; message: string };
|
|
934
|
+
console.log(
|
|
935
|
+
`[HTTP Client] Killed process ${processId}`
|
|
936
|
+
);
|
|
937
|
+
|
|
938
|
+
return data;
|
|
939
|
+
} catch (error) {
|
|
940
|
+
console.error("[HTTP Client] Error killing process:", error);
|
|
941
|
+
throw error;
|
|
942
|
+
}
|
|
1675
943
|
}
|
|
1676
|
-
}
|
|
1677
944
|
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
945
|
+
async killAllProcesses(): Promise<{ success: boolean; killedCount: number; message: string }> {
|
|
946
|
+
try {
|
|
947
|
+
const response = await this.doFetch("/api/process/kill-all", {
|
|
948
|
+
headers: {
|
|
949
|
+
"Content-Type": "application/json",
|
|
950
|
+
},
|
|
951
|
+
method: "DELETE",
|
|
952
|
+
});
|
|
953
|
+
|
|
954
|
+
if (!response.ok) {
|
|
955
|
+
const errorData = (await response.json().catch(() => ({}))) as {
|
|
956
|
+
error?: string;
|
|
957
|
+
};
|
|
958
|
+
throw new Error(
|
|
959
|
+
errorData.error || `HTTP error! status: ${response.status}`
|
|
960
|
+
);
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
const data = await response.json() as { success: boolean; killedCount: number; message: string };
|
|
964
|
+
console.log(
|
|
965
|
+
`[HTTP Client] Killed ${data.killedCount} processes`
|
|
966
|
+
);
|
|
967
|
+
|
|
968
|
+
return data;
|
|
969
|
+
} catch (error) {
|
|
970
|
+
console.error("[HTTP Client] Error killing all processes:", error);
|
|
971
|
+
throw error;
|
|
972
|
+
}
|
|
1691
973
|
}
|
|
1692
|
-
}
|
|
1693
974
|
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
975
|
+
async getProcessLogs(processId: string): Promise<GetProcessLogsResponse> {
|
|
976
|
+
try {
|
|
977
|
+
const response = await this.doFetch(`/api/process/${processId}/logs`, {
|
|
978
|
+
headers: {
|
|
979
|
+
"Content-Type": "application/json",
|
|
980
|
+
},
|
|
981
|
+
method: "GET",
|
|
982
|
+
});
|
|
983
|
+
|
|
984
|
+
if (!response.ok) {
|
|
985
|
+
const errorData = (await response.json().catch(() => ({}))) as {
|
|
986
|
+
error?: string;
|
|
987
|
+
};
|
|
988
|
+
throw new Error(
|
|
989
|
+
errorData.error || `HTTP error! status: ${response.status}`
|
|
990
|
+
);
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
const data: GetProcessLogsResponse = await response.json();
|
|
994
|
+
console.log(
|
|
995
|
+
`[HTTP Client] Got logs for process ${processId}`
|
|
996
|
+
);
|
|
997
|
+
|
|
998
|
+
return data;
|
|
999
|
+
} catch (error) {
|
|
1000
|
+
console.error("[HTTP Client] Error getting process logs:", error);
|
|
1001
|
+
throw error;
|
|
1002
|
+
}
|
|
1707
1003
|
}
|
|
1708
|
-
}
|
|
1709
1004
|
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1005
|
+
async streamProcessLogs(processId: string): Promise<ReadableStream<Uint8Array>> {
|
|
1006
|
+
try {
|
|
1007
|
+
const response = await this.doFetch(`/api/process/${processId}/stream`, {
|
|
1008
|
+
headers: {
|
|
1009
|
+
"Accept": "text/event-stream",
|
|
1010
|
+
"Cache-Control": "no-cache",
|
|
1011
|
+
},
|
|
1012
|
+
method: "GET",
|
|
1013
|
+
});
|
|
1014
|
+
|
|
1015
|
+
if (!response.ok) {
|
|
1016
|
+
const errorData = (await response.json().catch(() => ({}))) as {
|
|
1017
|
+
error?: string;
|
|
1018
|
+
};
|
|
1019
|
+
throw new Error(
|
|
1020
|
+
errorData.error || `HTTP error! status: ${response.status}`
|
|
1021
|
+
);
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
if (!response.body) {
|
|
1025
|
+
throw new Error("No response body for streaming request");
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
console.log(
|
|
1029
|
+
`[HTTP Client] Started streaming logs for process ${processId}`
|
|
1030
|
+
);
|
|
1031
|
+
|
|
1032
|
+
return response.body;
|
|
1033
|
+
} catch (error) {
|
|
1034
|
+
console.error("[HTTP Client] Error streaming process logs:", error);
|
|
1035
|
+
throw error;
|
|
1036
|
+
}
|
|
1723
1037
|
}
|
|
1724
1038
|
}
|