@agentuity/core 3.0.0-alpha.7 → 3.0.0-beta.0
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/dist/services/coder/client.d.ts +13 -1
- package/dist/services/coder/client.d.ts.map +1 -1
- package/dist/services/coder/client.js +22 -1
- package/dist/services/coder/client.js.map +1 -1
- package/dist/services/coder/protocol.d.ts +121 -5
- package/dist/services/coder/protocol.d.ts.map +1 -1
- package/dist/services/coder/protocol.js +117 -0
- package/dist/services/coder/protocol.js.map +1 -1
- package/dist/services/coder/types.d.ts +73 -0
- package/dist/services/coder/types.d.ts.map +1 -1
- package/dist/services/coder/types.js +83 -1
- package/dist/services/coder/types.js.map +1 -1
- package/dist/services/coder/workspaces.d.ts +11 -1
- package/dist/services/coder/workspaces.d.ts.map +1 -1
- package/dist/services/coder/workspaces.js +34 -1
- package/dist/services/coder/workspaces.js.map +1 -1
- package/dist/services/keyvalue/service.d.ts +9 -3
- package/dist/services/keyvalue/service.d.ts.map +1 -1
- package/dist/services/keyvalue/service.js +6 -3
- package/dist/services/keyvalue/service.js.map +1 -1
- package/dist/services/sandbox/api-reference.js +8 -8
- package/dist/services/sandbox/api-reference.js.map +1 -1
- package/dist/services/sandbox/client.d.ts +3 -2
- package/dist/services/sandbox/client.d.ts.map +1 -1
- package/dist/services/sandbox/client.js.map +1 -1
- package/dist/services/sandbox/create.d.ts +5 -0
- package/dist/services/sandbox/create.d.ts.map +1 -1
- package/dist/services/sandbox/create.js +8 -0
- package/dist/services/sandbox/create.js.map +1 -1
- package/dist/services/sandbox/get.d.ts +8 -4
- package/dist/services/sandbox/get.d.ts.map +1 -1
- package/dist/services/sandbox/get.js +28 -3
- package/dist/services/sandbox/get.js.map +1 -1
- package/dist/services/sandbox/getStatus.d.ts +3 -0
- package/dist/services/sandbox/getStatus.d.ts.map +1 -1
- package/dist/services/sandbox/getStatus.js +19 -2
- package/dist/services/sandbox/getStatus.js.map +1 -1
- package/dist/services/sandbox/index.d.ts +1 -1
- package/dist/services/sandbox/index.d.ts.map +1 -1
- package/dist/services/sandbox/list.d.ts +3 -0
- package/dist/services/sandbox/list.d.ts.map +1 -1
- package/dist/services/sandbox/list.js +5 -0
- package/dist/services/sandbox/list.js.map +1 -1
- package/dist/services/sandbox/pause.d.ts +17 -1
- package/dist/services/sandbox/pause.d.ts.map +1 -1
- package/dist/services/sandbox/pause.js +21 -3
- package/dist/services/sandbox/pause.js.map +1 -1
- package/dist/services/sandbox/run.d.ts +3 -2
- package/dist/services/sandbox/run.d.ts.map +1 -1
- package/dist/services/sandbox/run.js +229 -82
- package/dist/services/sandbox/run.js.map +1 -1
- package/dist/services/sandbox/types.d.ts +11 -4
- package/dist/services/sandbox/types.d.ts.map +1 -1
- package/dist/services/sandbox/types.js +12 -0
- package/dist/services/sandbox/types.js.map +1 -1
- package/dist/services/stream/namespaces.d.ts +2 -2
- package/dist/services/stream/namespaces.js +2 -2
- package/dist/services/stream/namespaces.js.map +1 -1
- package/dist/services/vector/service.d.ts +11 -11
- package/dist/services/vector/service.d.ts.map +1 -1
- package/dist/services/vector/service.js.map +1 -1
- package/package.json +3 -3
- package/src/services/coder/client.ts +34 -0
- package/src/services/coder/protocol.ts +121 -0
- package/src/services/coder/types.ts +94 -1
- package/src/services/coder/workspaces.ts +74 -0
- package/src/services/keyvalue/service.ts +16 -7
- package/src/services/sandbox/api-reference.ts +8 -8
- package/src/services/sandbox/client.ts +4 -4
- package/src/services/sandbox/create.ts +10 -0
- package/src/services/sandbox/get.ts +32 -3
- package/src/services/sandbox/getStatus.ts +23 -2
- package/src/services/sandbox/index.ts +1 -1
- package/src/services/sandbox/list.ts +5 -0
- package/src/services/sandbox/pause.ts +38 -4
- package/src/services/sandbox/run.ts +339 -103
- package/src/services/sandbox/types.ts +17 -2
- package/src/services/stream/namespaces.ts +2 -2
- package/src/services/vector/service.ts +11 -21
|
@@ -1,16 +1,21 @@
|
|
|
1
1
|
import type { Logger } from '../../logger.ts';
|
|
2
2
|
import type { Readable, Writable } from 'node:stream';
|
|
3
3
|
import { PassThrough } from 'node:stream';
|
|
4
|
+
import { finished } from 'node:stream/promises';
|
|
4
5
|
import { z } from 'zod';
|
|
5
6
|
import { APIClient, PaymentRequiredError } from '../api.ts';
|
|
6
7
|
import { sandboxCreate } from './create.ts';
|
|
7
8
|
import { sandboxDestroy } from './destroy.ts';
|
|
9
|
+
import { executionGet } from './execution.ts';
|
|
8
10
|
import { sandboxGetStatus } from './getStatus.ts';
|
|
9
11
|
import { ExecutionCancelledError, writeAndDrain } from './util.ts';
|
|
10
12
|
import { SandboxRunOptionsSchema, type SandboxRunResult } from './types.ts';
|
|
11
13
|
import { getServiceUrls } from '../config.ts';
|
|
12
14
|
|
|
13
15
|
const timingLogsEnabled = false;
|
|
16
|
+
const EXECUTION_WAIT_DURATION = '5m';
|
|
17
|
+
const EXIT_CODE_FAST_WAIT_DURATION = '250ms';
|
|
18
|
+
const TERMINAL_EXECUTION_STATUSES = new Set(['completed', 'failed', 'timeout', 'cancelled']);
|
|
14
19
|
|
|
15
20
|
/**
|
|
16
21
|
* Creates a Writable stream that captures all chunks to a buffer array
|
|
@@ -187,52 +192,46 @@ export async function sandboxRun(
|
|
|
187
192
|
}
|
|
188
193
|
}
|
|
189
194
|
|
|
190
|
-
// Wait for
|
|
191
|
-
//
|
|
192
|
-
|
|
195
|
+
// Wait for execution completion in parallel with stream consumption. The old
|
|
196
|
+
// flow waited for stream EOF first and only then started polling for the
|
|
197
|
+
// final exit code, which adds avoidable tail latency now that create returns
|
|
198
|
+
// an execution ID immediately for oneshot sandboxes.
|
|
199
|
+
let finalExecution:
|
|
200
|
+
| {
|
|
201
|
+
exitCode?: number;
|
|
202
|
+
status: string;
|
|
203
|
+
}
|
|
204
|
+
| undefined;
|
|
205
|
+
if (createResponse.executionId) {
|
|
206
|
+
logger?.debug(
|
|
207
|
+
'waiting for execution %s and %d stream(s) in parallel',
|
|
208
|
+
createResponse.executionId,
|
|
209
|
+
streamPromises.length
|
|
210
|
+
);
|
|
211
|
+
const completionPromise = waitForRunCompletion(
|
|
212
|
+
client,
|
|
213
|
+
sandboxId,
|
|
214
|
+
createResponse.executionId,
|
|
215
|
+
orgId,
|
|
216
|
+
signal,
|
|
217
|
+
logger,
|
|
218
|
+
started
|
|
219
|
+
);
|
|
193
220
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
let onAbort: (() => void) | undefined;
|
|
199
|
-
try {
|
|
200
|
-
await Promise.race([
|
|
201
|
-
Promise.allSettled(streamPromises),
|
|
202
|
-
new Promise<never>((_, reject) => {
|
|
203
|
-
onAbort = () => {
|
|
204
|
-
abortController.abort();
|
|
205
|
-
reject(
|
|
206
|
-
new ExecutionCancelledError({
|
|
207
|
-
message: 'Sandbox execution cancelled',
|
|
208
|
-
sandboxId,
|
|
209
|
-
})
|
|
210
|
-
);
|
|
211
|
-
};
|
|
212
|
-
if (signal.aborted) {
|
|
213
|
-
onAbort();
|
|
214
|
-
} else {
|
|
215
|
-
signal.addEventListener('abort', onAbort, { once: true });
|
|
216
|
-
}
|
|
217
|
-
}),
|
|
218
|
-
]);
|
|
219
|
-
} finally {
|
|
220
|
-
if (onAbort && signal) {
|
|
221
|
-
signal.removeEventListener('abort', onAbort);
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
} else {
|
|
225
|
-
await Promise.allSettled(streamPromises);
|
|
226
|
-
}
|
|
221
|
+
finalExecution = signal
|
|
222
|
+
? await raceWithAbort(completionPromise, signal, abortController, sandboxId)
|
|
223
|
+
: await completionPromise;
|
|
224
|
+
await waitForStreamsToDrain(streamPromises, signal, abortController, sandboxId);
|
|
227
225
|
} else {
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
226
|
+
logger?.debug(
|
|
227
|
+
'missing executionId on create response, falling back to stream-first completion'
|
|
228
|
+
);
|
|
229
|
+
await waitForStreamsToDrain(streamPromises, signal, abortController, sandboxId);
|
|
231
230
|
}
|
|
232
231
|
|
|
233
232
|
if (timingLogsEnabled)
|
|
234
|
-
console.error(`[TIMING] +${Date.now() - started}ms:
|
|
235
|
-
logger?.debug('
|
|
233
|
+
console.error(`[TIMING] +${Date.now() - started}ms: completion wait finished`);
|
|
234
|
+
logger?.debug('completion wait finished, resolving final exit code');
|
|
236
235
|
|
|
237
236
|
// Stream EOF means the sandbox is done — hadron only closes streams after the
|
|
238
237
|
// container exits. Poll for the exit code with retries because the lifecycle
|
|
@@ -245,94 +244,108 @@ export async function sandboxRun(
|
|
|
245
244
|
// linear 1s polling interval (not exponential backoff) so we don't overshoot
|
|
246
245
|
// the window — 15 attempts × 1s = 15s total, which comfortably covers the
|
|
247
246
|
// drain + lifecycle propagation delay.
|
|
248
|
-
|
|
249
|
-
const abortAwareSleep = (ms: number): Promise<void> =>
|
|
250
|
-
new Promise((resolve, reject) => {
|
|
251
|
-
if (signal?.aborted) {
|
|
252
|
-
reject(new DOMException('Aborted', 'AbortError'));
|
|
253
|
-
return;
|
|
254
|
-
}
|
|
255
|
-
const timer = setTimeout(resolve, ms);
|
|
256
|
-
signal?.addEventListener(
|
|
257
|
-
'abort',
|
|
258
|
-
() => {
|
|
259
|
-
clearTimeout(timer);
|
|
260
|
-
reject(new DOMException('Aborted', 'AbortError'));
|
|
261
|
-
},
|
|
262
|
-
{ once: true }
|
|
263
|
-
);
|
|
264
|
-
});
|
|
265
|
-
|
|
266
|
-
let exitCode = 0;
|
|
267
|
-
const maxStatusRetries = 15;
|
|
268
|
-
const statusPollInterval = 1000;
|
|
247
|
+
let exitCode = finalExecution?.exitCode ?? 0;
|
|
269
248
|
const statusPollStart = Date.now();
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
249
|
+
let shouldWaitForSandboxStatus = finalExecution?.exitCode == null;
|
|
250
|
+
let sandboxStatusReconciled = false;
|
|
251
|
+
if (finalExecution?.exitCode == null) {
|
|
252
|
+
if (createResponse.executionId && finalExecution?.status === 'completed') {
|
|
253
|
+
try {
|
|
254
|
+
const execution = await executionGet(client, {
|
|
255
|
+
executionId: createResponse.executionId,
|
|
256
|
+
orgId,
|
|
257
|
+
wait: EXIT_CODE_FAST_WAIT_DURATION,
|
|
258
|
+
signal,
|
|
259
|
+
});
|
|
260
|
+
if (execution.exitCode != null) {
|
|
261
|
+
exitCode = execution.exitCode;
|
|
262
|
+
finalExecution.exitCode = execution.exitCode;
|
|
263
|
+
shouldWaitForSandboxStatus = false;
|
|
264
|
+
logger?.debug(
|
|
265
|
+
'[run] exit code %d found from fast execution retry (+%dms)',
|
|
266
|
+
exitCode,
|
|
267
|
+
Date.now() - statusPollStart
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
} catch (err) {
|
|
271
|
+
if (!(err instanceof DOMException && err.name === 'AbortError')) {
|
|
272
|
+
logger?.debug(
|
|
273
|
+
'[run] fast execution exit code retry failed (+%dms): %s',
|
|
274
|
+
Date.now() - statusPollStart,
|
|
275
|
+
err
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
273
279
|
}
|
|
280
|
+
}
|
|
281
|
+
if (shouldWaitForSandboxStatus) {
|
|
274
282
|
try {
|
|
275
|
-
const sandboxStatus = await sandboxGetStatus(client, {
|
|
283
|
+
const sandboxStatus = await sandboxGetStatus(client, {
|
|
284
|
+
sandboxId,
|
|
285
|
+
orgId,
|
|
286
|
+
waitForStatus: ['terminated', 'failed'],
|
|
287
|
+
waitMs: 15000,
|
|
288
|
+
});
|
|
276
289
|
if (sandboxStatus.exitCode != null) {
|
|
277
290
|
exitCode = sandboxStatus.exitCode;
|
|
291
|
+
sandboxStatusReconciled = true;
|
|
278
292
|
logger?.debug(
|
|
279
|
-
'[run] exit code %d found
|
|
293
|
+
'[run] exit code %d found after server-side wait (+%dms)',
|
|
280
294
|
exitCode,
|
|
281
|
-
attempt + 1,
|
|
282
|
-
maxStatusRetries,
|
|
283
295
|
Date.now() - statusPollStart
|
|
284
296
|
);
|
|
285
|
-
break;
|
|
286
297
|
} else if (sandboxStatus.status === 'failed') {
|
|
287
298
|
exitCode = 1;
|
|
299
|
+
sandboxStatusReconciled = true;
|
|
288
300
|
logger?.debug(
|
|
289
|
-
'[run] sandbox failed
|
|
290
|
-
attempt + 1,
|
|
291
|
-
maxStatusRetries,
|
|
301
|
+
'[run] sandbox failed after server-side wait (+%dms)',
|
|
292
302
|
Date.now() - statusPollStart
|
|
293
303
|
);
|
|
294
|
-
break;
|
|
295
304
|
} else if (sandboxStatus.status === 'terminated') {
|
|
296
|
-
|
|
297
|
-
// terminated event may have overwritten it. Stop polling —
|
|
298
|
-
// no further updates will come.
|
|
305
|
+
sandboxStatusReconciled = true;
|
|
299
306
|
logger?.debug(
|
|
300
|
-
'[run] sandbox terminated without exit code
|
|
301
|
-
|
|
302
|
-
|
|
307
|
+
'[run] sandbox terminated without exit code after server-side wait (+%dms)',
|
|
308
|
+
Date.now() - statusPollStart
|
|
309
|
+
);
|
|
310
|
+
} else {
|
|
311
|
+
logger?.debug(
|
|
312
|
+
'[run] sandbox status wait expired with status=%s (+%dms)',
|
|
313
|
+
sandboxStatus.status,
|
|
303
314
|
Date.now() - statusPollStart
|
|
304
315
|
);
|
|
305
|
-
break;
|
|
306
|
-
}
|
|
307
|
-
// Exit code not yet propagated — wait before next poll.
|
|
308
|
-
if (attempt < maxStatusRetries - 1) {
|
|
309
|
-
await abortAwareSleep(statusPollInterval);
|
|
310
316
|
}
|
|
311
317
|
} catch (err) {
|
|
312
|
-
if (err instanceof DOMException && err.name === 'AbortError') {
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
'[run] sandboxGetStatus attempt %d/%d failed (+%dms): %s',
|
|
319
|
-
attempt + 1,
|
|
320
|
-
maxStatusRetries,
|
|
321
|
-
Date.now() - statusPollStart,
|
|
322
|
-
err
|
|
323
|
-
);
|
|
324
|
-
if (attempt < maxStatusRetries - 1) {
|
|
325
|
-
await abortAwareSleep(statusPollInterval);
|
|
318
|
+
if (!(err instanceof DOMException && err.name === 'AbortError')) {
|
|
319
|
+
logger?.debug(
|
|
320
|
+
'[run] sandboxGetStatus server-side wait failed (+%dms): %s',
|
|
321
|
+
Date.now() - statusPollStart,
|
|
322
|
+
err
|
|
323
|
+
);
|
|
326
324
|
}
|
|
327
325
|
}
|
|
328
326
|
}
|
|
329
|
-
if (
|
|
327
|
+
if (
|
|
328
|
+
finalExecution &&
|
|
329
|
+
finalExecution?.exitCode == null &&
|
|
330
|
+
finalExecution?.status !== 'completed' &&
|
|
331
|
+
!sandboxStatusReconciled
|
|
332
|
+
) {
|
|
333
|
+
exitCode = 1;
|
|
330
334
|
logger?.debug(
|
|
331
|
-
'[run] exit code
|
|
332
|
-
|
|
333
|
-
Date.now() - statusPollStart
|
|
335
|
+
'[run] using fallback exit code 1 for terminal status=%s after sandbox status reconciliation failed',
|
|
336
|
+
finalExecution?.status
|
|
334
337
|
);
|
|
335
338
|
}
|
|
339
|
+
if (exitCode === 0) {
|
|
340
|
+
if (finalExecution?.exitCode != null) {
|
|
341
|
+
logger?.debug('[run] using execution exit code 0 from long-poll result');
|
|
342
|
+
} else {
|
|
343
|
+
logger?.debug(
|
|
344
|
+
'[run] exit code wait finished with default 0 (+%dms)',
|
|
345
|
+
Date.now() - statusPollStart
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
336
349
|
|
|
337
350
|
if (timingLogsEnabled)
|
|
338
351
|
console.error(
|
|
@@ -363,6 +376,223 @@ export async function sandboxRun(
|
|
|
363
376
|
}
|
|
364
377
|
}
|
|
365
378
|
|
|
379
|
+
async function waitForRunCompletion(
|
|
380
|
+
client: APIClient,
|
|
381
|
+
sandboxId: string,
|
|
382
|
+
executionId: string,
|
|
383
|
+
orgId: string | undefined,
|
|
384
|
+
signal: AbortSignal | undefined,
|
|
385
|
+
logger: Logger | undefined,
|
|
386
|
+
started: number
|
|
387
|
+
): Promise<{ exitCode?: number; status: string }> {
|
|
388
|
+
const completionAbortController = new AbortController();
|
|
389
|
+
let onAbort: (() => void) | undefined;
|
|
390
|
+
if (signal) {
|
|
391
|
+
onAbort = () => completionAbortController.abort(signal.reason);
|
|
392
|
+
if (signal.aborted) {
|
|
393
|
+
onAbort();
|
|
394
|
+
} else {
|
|
395
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
try {
|
|
400
|
+
const completionSignal = completionAbortController.signal;
|
|
401
|
+
const executionPromise = waitForExecutionCompletion(
|
|
402
|
+
client,
|
|
403
|
+
executionId,
|
|
404
|
+
orgId,
|
|
405
|
+
completionSignal,
|
|
406
|
+
logger,
|
|
407
|
+
started
|
|
408
|
+
);
|
|
409
|
+
const statusPromise = waitForSandboxStatusCompletion(
|
|
410
|
+
client,
|
|
411
|
+
sandboxId,
|
|
412
|
+
orgId,
|
|
413
|
+
completionSignal,
|
|
414
|
+
logger,
|
|
415
|
+
started
|
|
416
|
+
).catch((err) => {
|
|
417
|
+
if (completionSignal.aborted) {
|
|
418
|
+
throw err;
|
|
419
|
+
}
|
|
420
|
+
logger?.debug('[run] sandbox status completion wait failed: %s', err);
|
|
421
|
+
return new Promise<never>(() => {});
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
const result = await Promise.race([executionPromise, statusPromise]);
|
|
425
|
+
return result;
|
|
426
|
+
} finally {
|
|
427
|
+
if (onAbort && signal) {
|
|
428
|
+
signal.removeEventListener('abort', onAbort);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
async function waitForExecutionCompletion(
|
|
434
|
+
client: APIClient,
|
|
435
|
+
executionId: string,
|
|
436
|
+
orgId: string | undefined,
|
|
437
|
+
signal: AbortSignal | undefined,
|
|
438
|
+
logger: Logger | undefined,
|
|
439
|
+
started: number
|
|
440
|
+
): Promise<{ exitCode?: number; status: string }> {
|
|
441
|
+
while (true) {
|
|
442
|
+
if (signal?.aborted) {
|
|
443
|
+
throw new DOMException('Aborted', 'AbortError');
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
const result = await executionGet(client, {
|
|
447
|
+
executionId,
|
|
448
|
+
orgId,
|
|
449
|
+
wait: EXECUTION_WAIT_DURATION,
|
|
450
|
+
signal,
|
|
451
|
+
});
|
|
452
|
+
logger?.debug(
|
|
453
|
+
'[run] execution wait: id=%s status=%s exit=%s +%dms',
|
|
454
|
+
executionId,
|
|
455
|
+
result.status,
|
|
456
|
+
result.exitCode ?? 'undefined',
|
|
457
|
+
Date.now() - started
|
|
458
|
+
);
|
|
459
|
+
|
|
460
|
+
if (TERMINAL_EXECUTION_STATUSES.has(result.status)) {
|
|
461
|
+
return {
|
|
462
|
+
exitCode: result.exitCode,
|
|
463
|
+
status: result.status,
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
async function waitForSandboxStatusCompletion(
|
|
470
|
+
client: APIClient,
|
|
471
|
+
sandboxId: string,
|
|
472
|
+
orgId: string | undefined,
|
|
473
|
+
signal: AbortSignal | undefined,
|
|
474
|
+
logger: Logger | undefined,
|
|
475
|
+
started: number
|
|
476
|
+
): Promise<{ exitCode?: number; status: string }> {
|
|
477
|
+
while (true) {
|
|
478
|
+
if (signal?.aborted) {
|
|
479
|
+
throw new DOMException('Aborted', 'AbortError');
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
const result = await sandboxGetStatus(client, {
|
|
483
|
+
sandboxId,
|
|
484
|
+
orgId,
|
|
485
|
+
waitForStatus: ['idle', 'terminated', 'failed'],
|
|
486
|
+
waitMs: 300000,
|
|
487
|
+
signal,
|
|
488
|
+
});
|
|
489
|
+
logger?.debug(
|
|
490
|
+
'[run] sandbox status wait: sandbox=%s status=%s exit=%s +%dms',
|
|
491
|
+
sandboxId,
|
|
492
|
+
result.status,
|
|
493
|
+
result.exitCode ?? 'undefined',
|
|
494
|
+
Date.now() - started
|
|
495
|
+
);
|
|
496
|
+
|
|
497
|
+
if (result.exitCode != null) {
|
|
498
|
+
return {
|
|
499
|
+
exitCode: result.exitCode,
|
|
500
|
+
status: 'completed',
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
if (result.status === 'failed') {
|
|
504
|
+
return {
|
|
505
|
+
exitCode: 1,
|
|
506
|
+
status: 'failed',
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
if (result.status === 'terminated') {
|
|
510
|
+
return {
|
|
511
|
+
status: 'completed',
|
|
512
|
+
};
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
await new Promise((resolve) => setTimeout(resolve, 25));
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
async function waitForStreamsToDrain(
|
|
520
|
+
streamPromises: Promise<void>[],
|
|
521
|
+
signal: AbortSignal | undefined,
|
|
522
|
+
abortController: AbortController,
|
|
523
|
+
sandboxId: string
|
|
524
|
+
): Promise<void> {
|
|
525
|
+
if (streamPromises.length === 0) {
|
|
526
|
+
return;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
if (signal) {
|
|
530
|
+
let onAbort: (() => void) | undefined;
|
|
531
|
+
try {
|
|
532
|
+
await Promise.race([
|
|
533
|
+
Promise.allSettled(streamPromises).then(() => undefined),
|
|
534
|
+
new Promise<never>((_, reject) => {
|
|
535
|
+
onAbort = () => {
|
|
536
|
+
abortController.abort();
|
|
537
|
+
reject(
|
|
538
|
+
new ExecutionCancelledError({
|
|
539
|
+
message: 'Sandbox execution cancelled',
|
|
540
|
+
sandboxId,
|
|
541
|
+
})
|
|
542
|
+
);
|
|
543
|
+
};
|
|
544
|
+
if (signal.aborted) {
|
|
545
|
+
onAbort();
|
|
546
|
+
} else {
|
|
547
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
548
|
+
}
|
|
549
|
+
}),
|
|
550
|
+
]);
|
|
551
|
+
} finally {
|
|
552
|
+
if (onAbort) {
|
|
553
|
+
signal.removeEventListener('abort', onAbort);
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
return;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
await Promise.allSettled(streamPromises);
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
async function raceWithAbort<T>(
|
|
563
|
+
promise: Promise<T>,
|
|
564
|
+
signal: AbortSignal,
|
|
565
|
+
abortController: AbortController,
|
|
566
|
+
sandboxId: string
|
|
567
|
+
): Promise<T> {
|
|
568
|
+
let onAbort: (() => void) | undefined;
|
|
569
|
+
try {
|
|
570
|
+
return await Promise.race([
|
|
571
|
+
promise,
|
|
572
|
+
new Promise<never>((_, reject) => {
|
|
573
|
+
onAbort = () => {
|
|
574
|
+
abortController.abort();
|
|
575
|
+
reject(
|
|
576
|
+
new ExecutionCancelledError({
|
|
577
|
+
message: 'Sandbox execution cancelled',
|
|
578
|
+
sandboxId,
|
|
579
|
+
})
|
|
580
|
+
);
|
|
581
|
+
};
|
|
582
|
+
if (signal.aborted) {
|
|
583
|
+
onAbort();
|
|
584
|
+
} else {
|
|
585
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
586
|
+
}
|
|
587
|
+
}),
|
|
588
|
+
]);
|
|
589
|
+
} finally {
|
|
590
|
+
if (onAbort) {
|
|
591
|
+
signal.removeEventListener('abort', onAbort);
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
|
|
366
596
|
async function createStdinStream(
|
|
367
597
|
region: string,
|
|
368
598
|
apiKey: string,
|
|
@@ -537,6 +767,12 @@ async function streamUrlToWritable(
|
|
|
537
767
|
// Signal end-of-stream to the tee/pipe chain so downstream
|
|
538
768
|
// consumers (e.g. process.stdout pipe) know no more data is coming.
|
|
539
769
|
writable.end();
|
|
770
|
+
if ('once' in writable) {
|
|
771
|
+
await finished(writable as NodeJS.WritableStream).catch(() => {
|
|
772
|
+
// Ignore finish errors here; the main read/write path already
|
|
773
|
+
// reported meaningful stream errors.
|
|
774
|
+
});
|
|
775
|
+
}
|
|
540
776
|
} catch (err) {
|
|
541
777
|
if (err instanceof Error && err.name === 'AbortError') {
|
|
542
778
|
logger?.debug('[stream] aborted after %dms', Date.now() - streamStart);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { StructuredError } from '../../error.ts';
|
|
3
3
|
import { SortDirectionSchema } from '../pagination.ts';
|
|
4
|
+
import type { SandboxPauseResult } from './pause.ts';
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Resource limits for a sandbox using Kubernetes-style units
|
|
@@ -274,6 +275,13 @@ export const SandboxTimeoutConfigSchema = z.object({
|
|
|
274
275
|
.string()
|
|
275
276
|
.optional()
|
|
276
277
|
.describe('Maximum execution time per command (e.g., "5m", "1h")'),
|
|
278
|
+
/** Maximum duration a sandbox can remain paused before termination (e.g., "24h", "0" for infinite) */
|
|
279
|
+
paused: z
|
|
280
|
+
.string()
|
|
281
|
+
.optional()
|
|
282
|
+
.describe(
|
|
283
|
+
'Maximum duration a sandbox can remain paused before termination (e.g., "24h", "0" for infinite)'
|
|
284
|
+
),
|
|
277
285
|
});
|
|
278
286
|
export type SandboxTimeoutConfig = z.infer<typeof SandboxTimeoutConfigSchema>;
|
|
279
287
|
|
|
@@ -399,7 +407,7 @@ export const SandboxSchema = z.object({
|
|
|
399
407
|
.describe('Set environment variables on the sandbox. Pass null to delete a variable.'),
|
|
400
408
|
/** Pause the sandbox, creating a checkpoint of its current state. */
|
|
401
409
|
pause: z
|
|
402
|
-
.custom<() => Promise<
|
|
410
|
+
.custom<() => Promise<SandboxPauseResult>>()
|
|
403
411
|
.describe('Pause the sandbox, creating a checkpoint of its current state.'),
|
|
404
412
|
/** Resume the sandbox from a paused or evacuated state. */
|
|
405
413
|
resume: z
|
|
@@ -592,6 +600,11 @@ export const SandboxInfoSchema = z.object({
|
|
|
592
600
|
idle: z.string().optional().describe('Idle timeout duration (e.g., "10m0s")'),
|
|
593
601
|
/** Execution timeout duration (e.g., "5m0s") */
|
|
594
602
|
execution: z.string().optional().describe('Execution timeout duration (e.g., "5m0s")'),
|
|
603
|
+
/** Paused timeout duration (e.g., "24h0s", "0s" for infinite) */
|
|
604
|
+
paused: z
|
|
605
|
+
.string()
|
|
606
|
+
.optional()
|
|
607
|
+
.describe('Paused timeout duration (e.g., "24h0s", "0s" for infinite)'),
|
|
595
608
|
})
|
|
596
609
|
.optional()
|
|
597
610
|
.describe('Timeout configuration for this sandbox'),
|
|
@@ -830,6 +843,8 @@ export const SnapshotCreateOptionsSchema = z.object({
|
|
|
830
843
|
tag: z.string().optional().describe('Tag for the snapshot (defaults to "latest")'),
|
|
831
844
|
/** Make the snapshot publicly accessible */
|
|
832
845
|
public: z.boolean().optional().describe('Make the snapshot publicly accessible'),
|
|
846
|
+
/** Organization ID to use for CLI-authenticated requests */
|
|
847
|
+
orgId: z.string().optional().describe('Organization ID for CLI-authenticated requests'),
|
|
833
848
|
});
|
|
834
849
|
export type SnapshotCreateOptions = z.infer<typeof SnapshotCreateOptionsSchema>;
|
|
835
850
|
|
|
@@ -905,7 +920,7 @@ export interface SandboxService {
|
|
|
905
920
|
list(params?: ListSandboxesParams): Promise<ListSandboxesResponse>;
|
|
906
921
|
destroy(sandboxId: string): Promise<void>;
|
|
907
922
|
/** Pause a running sandbox, creating a checkpoint of its current state. */
|
|
908
|
-
pause(sandboxId: string): Promise<
|
|
923
|
+
pause(sandboxId: string): Promise<SandboxPauseResult>;
|
|
909
924
|
/** Resume a paused or evacuated sandbox from its checkpoint. */
|
|
910
925
|
resume(sandboxId: string): Promise<void>;
|
|
911
926
|
snapshot: SnapshotService;
|
|
@@ -40,11 +40,11 @@ export const StreamNamespaceEntrySchema = z.object({
|
|
|
40
40
|
chunks: z.number().describe('number of chunks'),
|
|
41
41
|
completed: z.boolean().describe('whether the stream upload is completed'),
|
|
42
42
|
size_bytes: z.number().describe('size in bytes'),
|
|
43
|
-
started_at: z.string().nullable().describe('ISO 8601 stream start timestamp'),
|
|
43
|
+
started_at: z.string().nullable().optional().describe('ISO 8601 stream start timestamp'),
|
|
44
44
|
ended_at: z.string().nullable().describe('ISO 8601 stream end timestamp'),
|
|
45
45
|
headers: z.record(z.string(), z.string()).nullable().optional().describe('stream headers'),
|
|
46
46
|
metadata: z.record(z.string(), z.string()).nullable().optional().describe('stream metadata'),
|
|
47
|
-
expires_at: z.string().nullable().describe('ISO 8601 expiration timestamp or null'),
|
|
47
|
+
expires_at: z.string().nullable().optional().describe('ISO 8601 expiration timestamp or null'),
|
|
48
48
|
url: z.string().describe('public URL to access the stream'),
|
|
49
49
|
});
|
|
50
50
|
|
|
@@ -162,7 +162,7 @@ export const VectorSearchParamsSchema = <T extends z.ZodTypeAny>(metadataSchema:
|
|
|
162
162
|
),
|
|
163
163
|
});
|
|
164
164
|
|
|
165
|
-
export type VectorSearchParams<T
|
|
165
|
+
export type VectorSearchParams<T = Record<string, unknown>> = {
|
|
166
166
|
query: string;
|
|
167
167
|
limit?: number;
|
|
168
168
|
similarity?: number;
|
|
@@ -210,7 +210,7 @@ export const VectorSearchResultSchema = <T extends z.ZodTypeAny>(metadataSchema:
|
|
|
210
210
|
.describe('the expiration time of the vector as an ISO 8601 timestamp.'),
|
|
211
211
|
});
|
|
212
212
|
|
|
213
|
-
export type VectorSearchResult<T
|
|
213
|
+
export type VectorSearchResult<T = Record<string, unknown>> = {
|
|
214
214
|
id: string;
|
|
215
215
|
key: string;
|
|
216
216
|
metadata?: T;
|
|
@@ -237,9 +237,7 @@ export const VectorSearchResultWithDocumentSchema = <T extends z.ZodTypeAny>(met
|
|
|
237
237
|
embeddings: z.array(z.number()).optional().describe('the embeddings of the vector object'),
|
|
238
238
|
});
|
|
239
239
|
|
|
240
|
-
export type VectorSearchResultWithDocument<
|
|
241
|
-
T extends Record<string, unknown> = Record<string, unknown>,
|
|
242
|
-
> = VectorSearchResult<T> & {
|
|
240
|
+
export type VectorSearchResultWithDocument<T = Record<string, unknown>> = VectorSearchResult<T> & {
|
|
243
241
|
document?: string;
|
|
244
242
|
embeddings?: Array<number>;
|
|
245
243
|
};
|
|
@@ -277,7 +275,7 @@ export const VectorResultFoundSchema = <T extends z.ZodTypeAny>(metadataSchema:
|
|
|
277
275
|
exists: z.literal(true).describe('the vector was found'),
|
|
278
276
|
});
|
|
279
277
|
|
|
280
|
-
export type VectorResultFound<T
|
|
278
|
+
export type VectorResultFound<T = Record<string, unknown>> = {
|
|
281
279
|
data: VectorSearchResultWithDocument<T>;
|
|
282
280
|
exists: true;
|
|
283
281
|
};
|
|
@@ -302,9 +300,7 @@ export type VectorResultNotFound = z.infer<typeof VectorResultNotFoundSchema>;
|
|
|
302
300
|
/**
|
|
303
301
|
* Result of a get operation
|
|
304
302
|
*/
|
|
305
|
-
export type VectorResult<T
|
|
306
|
-
| VectorResultFound<T>
|
|
307
|
-
| VectorResultNotFound;
|
|
303
|
+
export type VectorResult<T = Record<string, unknown>> = VectorResultFound<T> | VectorResultNotFound;
|
|
308
304
|
|
|
309
305
|
export const VectorResultSchema = <T extends z.ZodTypeAny>(metadataSchema: T) =>
|
|
310
306
|
z.discriminatedUnion('exists', [
|
|
@@ -525,10 +521,7 @@ export interface VectorStorage {
|
|
|
525
521
|
* }
|
|
526
522
|
* ```
|
|
527
523
|
*/
|
|
528
|
-
get<T
|
|
529
|
-
name: string,
|
|
530
|
-
key: string
|
|
531
|
-
): Promise<VectorResult<T>>;
|
|
524
|
+
get<T = Record<string, unknown>>(name: string, key: string): Promise<VectorResult<T>>;
|
|
532
525
|
|
|
533
526
|
/**
|
|
534
527
|
* Get multiple vectors by their keys in a single request
|
|
@@ -545,7 +538,7 @@ export interface VectorStorage {
|
|
|
545
538
|
* }
|
|
546
539
|
* ```
|
|
547
540
|
*/
|
|
548
|
-
getMany<T
|
|
541
|
+
getMany<T = Record<string, unknown>>(
|
|
549
542
|
name: string,
|
|
550
543
|
...keys: string[]
|
|
551
544
|
): Promise<Map<string, VectorSearchResultWithDocument<T>>>;
|
|
@@ -579,7 +572,7 @@ export interface VectorStorage {
|
|
|
579
572
|
* }
|
|
580
573
|
* ```
|
|
581
574
|
*/
|
|
582
|
-
search<T
|
|
575
|
+
search<T = Record<string, unknown>>(
|
|
583
576
|
name: string,
|
|
584
577
|
params: VectorSearchParams<T>
|
|
585
578
|
): Promise<VectorSearchResult<T>[]>;
|
|
@@ -897,10 +890,7 @@ export class VectorStorageService implements VectorStorage {
|
|
|
897
890
|
throw await toServiceException('PUT', url, res.response);
|
|
898
891
|
}
|
|
899
892
|
|
|
900
|
-
async get<T
|
|
901
|
-
name: string,
|
|
902
|
-
key: string
|
|
903
|
-
): Promise<VectorResult<T>> {
|
|
893
|
+
async get<T = Record<string, unknown>>(name: string, key: string): Promise<VectorResult<T>> {
|
|
904
894
|
if (!name || typeof name !== 'string' || name.trim().length === 0) {
|
|
905
895
|
throw new VectorStorageNameRequiredError();
|
|
906
896
|
}
|
|
@@ -947,7 +937,7 @@ export class VectorStorageService implements VectorStorage {
|
|
|
947
937
|
throw await toServiceException('GET', url, res.response);
|
|
948
938
|
}
|
|
949
939
|
|
|
950
|
-
async getMany<T
|
|
940
|
+
async getMany<T = Record<string, unknown>>(
|
|
951
941
|
name: string,
|
|
952
942
|
...keys: string[]
|
|
953
943
|
): Promise<Map<string, VectorSearchResultWithDocument<T>>> {
|
|
@@ -978,7 +968,7 @@ export class VectorStorageService implements VectorStorage {
|
|
|
978
968
|
return resultMap;
|
|
979
969
|
}
|
|
980
970
|
|
|
981
|
-
async search<T
|
|
971
|
+
async search<T = Record<string, unknown>>(
|
|
982
972
|
name: string,
|
|
983
973
|
params: VectorSearchParams<T>
|
|
984
974
|
): Promise<VectorSearchResult<T>[]> {
|