@opengeni/runtime 0.2.3 → 0.3.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/{chunk-KNW7AMQB.js → chunk-D5KU3QUC.js} +231 -21
- package/dist/chunk-D5KU3QUC.js.map +1 -0
- package/dist/index.d.ts +18 -2
- package/dist/index.js +82 -6
- package/dist/index.js.map +1 -1
- package/dist/sandbox/index.d.ts +48 -2
- package/dist/sandbox/index.js +11 -1
- package/package.json +3 -3
- package/src/index.ts +66 -4
- package/src/sandbox/display-stack.ts +47 -12
- package/src/sandbox/index.ts +72 -12
- package/src/sandbox/providers/modal.ts +225 -0
- package/src/sandbox/routing/routing-session.ts +2 -2
- package/src/sandbox/selfhosted/session.ts +21 -5
- package/src/sandbox-computer.ts +52 -17
- package/dist/chunk-KNW7AMQB.js.map +0 -1
|
@@ -202,6 +202,23 @@ var localProvider = {
|
|
|
202
202
|
// src/sandbox/providers/modal.ts
|
|
203
203
|
import { ModalImageSelector, ModalSandboxClient } from "@openai/agents-extensions/sandbox/modal";
|
|
204
204
|
import { effectiveModalIdleTimeoutSeconds } from "@opengeni/config";
|
|
205
|
+
var MODAL_ORPHAN_SWEEP_LIMIT = 50;
|
|
206
|
+
var MODAL_UNATTRIBUTED_ORPHAN_GRACE_MS = 30 * 6e4;
|
|
207
|
+
function modalSandboxAttributionEnvironment(input) {
|
|
208
|
+
return {
|
|
209
|
+
OPENGENI_SANDBOX_LEASE_ID: input.leaseId,
|
|
210
|
+
OPENGENI_SANDBOX_GROUP_ID: input.sandboxGroupId,
|
|
211
|
+
OPENGENI_WORKSPACE_ID: input.workspaceId
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
function modalSandboxAttributionTags(input) {
|
|
215
|
+
return {
|
|
216
|
+
opengeni: "true",
|
|
217
|
+
opengeni_lease_id: input.leaseId,
|
|
218
|
+
opengeni_workspace_id: input.workspaceId,
|
|
219
|
+
opengeni_sandbox_group_id: input.sandboxGroupId
|
|
220
|
+
};
|
|
221
|
+
}
|
|
205
222
|
var modalProvider = {
|
|
206
223
|
backend: "modal",
|
|
207
224
|
descriptor: CAPABILITY_DESCRIPTORS.modal,
|
|
@@ -220,8 +237,13 @@ var modalProvider = {
|
|
|
220
237
|
const options = {
|
|
221
238
|
appName: settings.modalAppName,
|
|
222
239
|
timeoutMs: settings.modalTimeoutSeconds * 1e3,
|
|
240
|
+
sandboxCreateTimeoutS: Math.ceil(settings.sandboxWarmingTimeoutMs / 1e3),
|
|
223
241
|
exposedPorts,
|
|
224
|
-
env: environment
|
|
242
|
+
env: environment,
|
|
243
|
+
// The Modal JS SDK's sandbox default command already sleeps until timeout
|
|
244
|
+
// or explicit termination. Do not let the Agents extension stamp a separate
|
|
245
|
+
// hardcoded sleep command; OPENGENI_MODAL_TIMEOUT_SECONDS owns lifetime.
|
|
246
|
+
useSleepCmd: false
|
|
225
247
|
};
|
|
226
248
|
options.idleTimeoutMs = effectiveModalIdleTimeoutSeconds(settings) * 1e3;
|
|
227
249
|
if (settings.modalWorkspacePersistence) {
|
|
@@ -242,6 +264,137 @@ var modalProvider = {
|
|
|
242
264
|
return new ModalSandboxClient(options);
|
|
243
265
|
}
|
|
244
266
|
};
|
|
267
|
+
function modalClientOptions(settings) {
|
|
268
|
+
return {
|
|
269
|
+
...settings.modalTokenId ? { tokenId: settings.modalTokenId } : {},
|
|
270
|
+
...settings.modalTokenSecret ? { tokenSecret: settings.modalTokenSecret } : {},
|
|
271
|
+
...settings.modalEnvironment ? { environment: settings.modalEnvironment } : {},
|
|
272
|
+
...settings.modalTimeoutSeconds ? { timeoutMs: settings.modalTimeoutSeconds * 1e3 } : {}
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
async function createModalClient(settings) {
|
|
276
|
+
const modal = await import("modal");
|
|
277
|
+
return new modal.ModalClient(modalClientOptions(settings));
|
|
278
|
+
}
|
|
279
|
+
async function tagModalSandbox(settings, sandboxId, attribution) {
|
|
280
|
+
if (!sandboxId) {
|
|
281
|
+
return false;
|
|
282
|
+
}
|
|
283
|
+
const modal = await createModalClient(settings);
|
|
284
|
+
try {
|
|
285
|
+
const sandbox = await modal.sandboxes.fromId(sandboxId);
|
|
286
|
+
await sandbox.setTags(modalSandboxAttributionTags(attribution));
|
|
287
|
+
return true;
|
|
288
|
+
} finally {
|
|
289
|
+
modal.close();
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
async function terminateModalSandboxById(settings, sandboxId) {
|
|
293
|
+
if (!sandboxId) {
|
|
294
|
+
return true;
|
|
295
|
+
}
|
|
296
|
+
const modal = await createModalClient(settings);
|
|
297
|
+
try {
|
|
298
|
+
const sandbox = await modal.sandboxes.fromId(sandboxId);
|
|
299
|
+
await sandbox.terminate();
|
|
300
|
+
return true;
|
|
301
|
+
} finally {
|
|
302
|
+
modal.close();
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
function tagsFromInfo(info) {
|
|
306
|
+
const tags = {};
|
|
307
|
+
for (const tag of info.tags ?? []) {
|
|
308
|
+
if (typeof tag.tagName === "string" && typeof tag.tagValue === "string") {
|
|
309
|
+
tags[tag.tagName] = tag.tagValue;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
return tags;
|
|
313
|
+
}
|
|
314
|
+
function sandboxCreatedAtMs(info) {
|
|
315
|
+
if (typeof info.createdAt !== "number" || !Number.isFinite(info.createdAt) || info.createdAt <= 0) {
|
|
316
|
+
return null;
|
|
317
|
+
}
|
|
318
|
+
return info.createdAt < 1e10 ? Math.floor(info.createdAt * 1e3) : Math.floor(info.createdAt);
|
|
319
|
+
}
|
|
320
|
+
function attributionKey(input) {
|
|
321
|
+
return `${input.workspaceId}:${input.sandboxGroupId}:${input.leaseId}`;
|
|
322
|
+
}
|
|
323
|
+
async function sweepModalOrphanSandboxes(settings, liveLeases, options = {}) {
|
|
324
|
+
const nowMs = options.now?.getTime() ?? Date.now();
|
|
325
|
+
const maxTerminations = options.maxTerminations ?? MODAL_ORPHAN_SWEEP_LIMIT;
|
|
326
|
+
const unattributedGraceMs = options.unattributedGraceMs ?? MODAL_UNATTRIBUTED_ORPHAN_GRACE_MS;
|
|
327
|
+
const liveByAttribution = new Map(liveLeases.map((lease) => [attributionKey(lease), lease]));
|
|
328
|
+
const ownedClient = options.client ? null : await createModalClient(settings);
|
|
329
|
+
const modal = options.client ?? ownedClient;
|
|
330
|
+
try {
|
|
331
|
+
const app = await modal.apps.fromName(settings.modalAppName, {
|
|
332
|
+
createIfMissing: false,
|
|
333
|
+
...settings.modalEnvironment ? { environment: settings.modalEnvironment } : {}
|
|
334
|
+
});
|
|
335
|
+
const appId = app.appId;
|
|
336
|
+
if (!appId) {
|
|
337
|
+
return { examined: 0, terminated: [], skipped: 0 };
|
|
338
|
+
}
|
|
339
|
+
let examined = 0;
|
|
340
|
+
let skipped = 0;
|
|
341
|
+
const terminated = [];
|
|
342
|
+
let beforeTimestamp;
|
|
343
|
+
while (terminated.length < maxTerminations) {
|
|
344
|
+
const response = await modal.cpClient.sandboxList({
|
|
345
|
+
appId,
|
|
346
|
+
...beforeTimestamp !== void 0 ? { beforeTimestamp } : {},
|
|
347
|
+
includeFinished: false,
|
|
348
|
+
...settings.modalEnvironment ? { environmentName: settings.modalEnvironment } : {},
|
|
349
|
+
tags: []
|
|
350
|
+
});
|
|
351
|
+
const sandboxes = response.sandboxes ?? [];
|
|
352
|
+
if (sandboxes.length === 0) {
|
|
353
|
+
break;
|
|
354
|
+
}
|
|
355
|
+
for (const info of sandboxes) {
|
|
356
|
+
examined += 1;
|
|
357
|
+
const tags = tagsFromInfo(info);
|
|
358
|
+
const leaseId = tags.opengeni_lease_id;
|
|
359
|
+
const workspaceId = tags.opengeni_workspace_id;
|
|
360
|
+
const sandboxGroupId = tags.opengeni_sandbox_group_id;
|
|
361
|
+
let reason = null;
|
|
362
|
+
if (leaseId && workspaceId && sandboxGroupId) {
|
|
363
|
+
const live = liveByAttribution.get(attributionKey({ leaseId, workspaceId, sandboxGroupId }));
|
|
364
|
+
if (!live || live.instanceId && live.instanceId !== info.id) {
|
|
365
|
+
reason = "stale_attribution";
|
|
366
|
+
}
|
|
367
|
+
} else {
|
|
368
|
+
const createdAtMs = sandboxCreatedAtMs(info);
|
|
369
|
+
if (createdAtMs !== null && nowMs - createdAtMs >= unattributedGraceMs) {
|
|
370
|
+
reason = "unattributed";
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
if (!reason) {
|
|
374
|
+
skipped += 1;
|
|
375
|
+
continue;
|
|
376
|
+
}
|
|
377
|
+
try {
|
|
378
|
+
const sandbox = await modal.sandboxes.fromId(info.id);
|
|
379
|
+
await sandbox.terminate();
|
|
380
|
+
terminated.push({ sandboxId: info.id, reason, tags });
|
|
381
|
+
} catch {
|
|
382
|
+
skipped += 1;
|
|
383
|
+
}
|
|
384
|
+
if (terminated.length >= maxTerminations) {
|
|
385
|
+
break;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
beforeTimestamp = sandboxes[sandboxes.length - 1]?.createdAt;
|
|
389
|
+
if (beforeTimestamp === void 0) {
|
|
390
|
+
break;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
return { examined, terminated, skipped };
|
|
394
|
+
} finally {
|
|
395
|
+
ownedClient?.close();
|
|
396
|
+
}
|
|
397
|
+
}
|
|
245
398
|
|
|
246
399
|
// src/sandbox/providers/none.ts
|
|
247
400
|
var noneProvider = {
|
|
@@ -757,16 +910,24 @@ var SelfhostedSession = class {
|
|
|
757
910
|
/** Computer-use VIEW op: capture a single PNG screenshot of the machine's desktop
|
|
758
911
|
* plus its geometry (via ScreenCaptureKit / x11). NOT consent-gated (a view op —
|
|
759
912
|
* the view/control decoupling), so it works with a display but no screen-control
|
|
760
|
-
* consent. Returns the raw encoded bytes + width/height
|
|
913
|
+
* consent. Returns the raw encoded bytes + the ENCODED width/height, plus the
|
|
914
|
+
* NATIVE (pre-downscale) geometry: when the agent had to downscale the PNG to fit
|
|
915
|
+
* the transport's max payload, `nativeWidth`/`nativeHeight` carry the original
|
|
916
|
+
* capture size so the computer-use layer can scale model clicks (in encoded-pixel
|
|
917
|
+
* space) back to native pixels. An older agent leaves them 0 → read as "same as
|
|
918
|
+
* width/height" (no downscale). */
|
|
761
919
|
async screenshot() {
|
|
762
920
|
const result = await this.call({ $case: "desktopScreenshot", desktopScreenshot: {} });
|
|
763
921
|
if (result.$case !== "desktopScreenshot") {
|
|
764
922
|
throw new Error(`selfhosted screenshot: unexpected result ${result.$case}`);
|
|
765
923
|
}
|
|
924
|
+
const s = result.desktopScreenshot;
|
|
766
925
|
return {
|
|
767
|
-
png:
|
|
768
|
-
width:
|
|
769
|
-
height:
|
|
926
|
+
png: s.png,
|
|
927
|
+
width: s.width,
|
|
928
|
+
height: s.height,
|
|
929
|
+
nativeWidth: s.nativeWidth || s.width,
|
|
930
|
+
nativeHeight: s.nativeHeight || s.height
|
|
770
931
|
};
|
|
771
932
|
}
|
|
772
933
|
/** A cheap liveness probe — request a Ping on the subject; returns true iff a
|
|
@@ -1346,6 +1507,7 @@ var STREAM_PORT = DESKTOP_STREAM_PORT4;
|
|
|
1346
1507
|
var DISPLAY_STACK_TIMEOUT_MS = 9e4;
|
|
1347
1508
|
var PAINT_PROBE_ATTEMPTS = 150;
|
|
1348
1509
|
var PAINT_PROBE_INTERVAL_S = 0.2;
|
|
1510
|
+
var PAINT_MIN_BYTES = 6e4;
|
|
1349
1511
|
var DEFAULT_DESKTOP_GEOMETRY = { width: 1280, height: 800, dpi: 96 };
|
|
1350
1512
|
var DisplayStackError = class extends Error {
|
|
1351
1513
|
exitCode;
|
|
@@ -1370,7 +1532,7 @@ function buildDisplayStackScript(options = {}) {
|
|
|
1370
1532
|
const port = options.port ?? DESKTOP_STREAM_PORT4;
|
|
1371
1533
|
const env = `DESKTOP_W=${geometry.width} DESKTOP_H=${geometry.height} DESKTOP_DPI=${geometry.dpi} STREAM_PORT=${port}`;
|
|
1372
1534
|
const bringUp = `if nc -z 127.0.0.1 ${port} >/dev/null 2>&1 && nc -z 127.0.0.1 5900 >/dev/null 2>&1; then echo "OPENGENI_DESKTOP_UP port=${port} geometry=${geometry.width}x${geometry.height} dpi=${geometry.dpi} (precheck)"; else mkdir -p /tmp/opengeni-desktop && flock -w 45 /tmp/opengeni-desktop/up.outer.lock env ${env} opengeni-desktop-up; fi`;
|
|
1373
|
-
const paintProbe = `p=/tmp/opengeni-desktop/paint-probe.png; for i in $(seq 1 ${PAINT_PROBE_ATTEMPTS}); do if DISPLAY=:0 scrot -o "$p" >/dev/null 2>&1
|
|
1535
|
+
const paintProbe = `p=/tmp/opengeni-desktop/paint-probe.png; for i in $(seq 1 ${PAINT_PROBE_ATTEMPTS}); do if DISPLAY=:0 scrot -o "$p" >/dev/null 2>&1; then sz=$(wc -c < "$p" 2>/dev/null || echo 0); else sz=0; fi; rm -f "$p"; if [ "$sz" -ge ${PAINT_MIN_BYTES} ]; then break; fi; if [ "$i" = "${PAINT_PROBE_ATTEMPTS}" ]; then echo "OPENGENI_DESKTOP_NOT_PAINTING scrot below ${PAINT_MIN_BYTES}B after warmup (last=$sz)"; exit 14; fi; sleep ${PAINT_PROBE_INTERVAL_S}; done`;
|
|
1374
1536
|
return `mkdir -p /tmp/opengeni-desktop; { ${bringUp} ; } && { ${paintProbe} ; }`;
|
|
1375
1537
|
}
|
|
1376
1538
|
function execResultOutput(result) {
|
|
@@ -3296,6 +3458,21 @@ function readInstanceId(session) {
|
|
|
3296
3458
|
const candidate = state.sandboxId ?? state.instanceId ?? state.id ?? state.hostId ?? state.containerId;
|
|
3297
3459
|
return typeof candidate === "string" && candidate.length > 0 ? candidate : "";
|
|
3298
3460
|
}
|
|
3461
|
+
async function terminateCreatedSandbox(client, session, sessionState) {
|
|
3462
|
+
const clientWithDelete = client;
|
|
3463
|
+
if (typeof clientWithDelete.delete === "function" && sessionState !== void 0) {
|
|
3464
|
+
try {
|
|
3465
|
+
await clientWithDelete.delete(sessionState);
|
|
3466
|
+
} catch {
|
|
3467
|
+
}
|
|
3468
|
+
return;
|
|
3469
|
+
}
|
|
3470
|
+
const sess = session;
|
|
3471
|
+
try {
|
|
3472
|
+
await (sess.terminate ?? sess.kill ?? sess.close)?.();
|
|
3473
|
+
} catch {
|
|
3474
|
+
}
|
|
3475
|
+
}
|
|
3299
3476
|
async function establishSandboxSessionFromEnvelope(settings, envelope, opts) {
|
|
3300
3477
|
const envelopeBackend = typeof envelope?.backendId === "string" ? envelope.backendId : void 0;
|
|
3301
3478
|
const backend = opts.backendOverride ?? envelopeBackend ?? settings.sandboxBackend;
|
|
@@ -3312,32 +3489,60 @@ async function establishSandboxSessionFromEnvelope(settings, envelope, opts) {
|
|
|
3312
3489
|
const workspaceArchive = readWorkspaceArchiveFromEnvelopeSessionState(envelopeSessionState);
|
|
3313
3490
|
const coldRestore = async (resumeFallbackState) => {
|
|
3314
3491
|
const restored = await client.create({ manifest: createManifest });
|
|
3492
|
+
let restoredState = restored.state;
|
|
3493
|
+
let established = {
|
|
3494
|
+
client,
|
|
3495
|
+
session: restored,
|
|
3496
|
+
sessionState: restoredState ?? resumeFallbackState,
|
|
3497
|
+
instanceId: readInstanceId(restored),
|
|
3498
|
+
backendId: client.backendId
|
|
3499
|
+
};
|
|
3500
|
+
if (opts.onSandboxCreated) {
|
|
3501
|
+
try {
|
|
3502
|
+
await opts.onSandboxCreated(established);
|
|
3503
|
+
} catch (createCallbackError) {
|
|
3504
|
+
await terminateCreatedSandbox(client, restored, restoredState);
|
|
3505
|
+
throw createCallbackError;
|
|
3506
|
+
}
|
|
3507
|
+
}
|
|
3315
3508
|
if (workspaceArchive) {
|
|
3316
3509
|
const hydrate = restored.hydrateWorkspace;
|
|
3317
3510
|
if (typeof hydrate === "function") {
|
|
3318
3511
|
try {
|
|
3319
3512
|
await hydrate.call(restored, workspaceArchive);
|
|
3320
3513
|
} catch (hydrateError) {
|
|
3321
|
-
|
|
3322
|
-
|
|
3323
|
-
|
|
3324
|
-
|
|
3325
|
-
|
|
3326
|
-
|
|
3327
|
-
|
|
3328
|
-
|
|
3329
|
-
|
|
3514
|
+
await terminateCreatedSandbox(client, restored, restoredState);
|
|
3515
|
+
throw hydrateError;
|
|
3516
|
+
}
|
|
3517
|
+
const hydratedState = restored.state;
|
|
3518
|
+
const hydratedInstanceId = readInstanceId(restored);
|
|
3519
|
+
if (hydratedInstanceId && hydratedInstanceId !== established.instanceId) {
|
|
3520
|
+
established = {
|
|
3521
|
+
client,
|
|
3522
|
+
session: restored,
|
|
3523
|
+
sessionState: hydratedState ?? resumeFallbackState,
|
|
3524
|
+
instanceId: hydratedInstanceId,
|
|
3525
|
+
backendId: client.backendId
|
|
3526
|
+
};
|
|
3527
|
+
if (opts.onSandboxCreated) {
|
|
3330
3528
|
try {
|
|
3331
|
-
await
|
|
3332
|
-
} catch {
|
|
3529
|
+
await opts.onSandboxCreated(established);
|
|
3530
|
+
} catch (createCallbackError) {
|
|
3531
|
+
await terminateCreatedSandbox(client, restored, hydratedState);
|
|
3532
|
+
throw createCallbackError;
|
|
3333
3533
|
}
|
|
3334
3534
|
}
|
|
3335
|
-
throw hydrateError;
|
|
3336
3535
|
}
|
|
3337
3536
|
}
|
|
3338
3537
|
}
|
|
3339
|
-
|
|
3340
|
-
return {
|
|
3538
|
+
restoredState = restored.state;
|
|
3539
|
+
return {
|
|
3540
|
+
client,
|
|
3541
|
+
session: restored,
|
|
3542
|
+
sessionState: restoredState ?? resumeFallbackState,
|
|
3543
|
+
instanceId: readInstanceId(restored),
|
|
3544
|
+
backendId: client.backendId
|
|
3545
|
+
};
|
|
3341
3546
|
};
|
|
3342
3547
|
const envelopeProviderState = envelopeSessionState && typeof envelopeSessionState === "object" ? envelopeSessionState.providerState : void 0;
|
|
3343
3548
|
const hasResumableInstance = Boolean(
|
|
@@ -3395,6 +3600,11 @@ export {
|
|
|
3395
3600
|
assertDescriptorRegistryInvariants,
|
|
3396
3601
|
SandboxConfigError,
|
|
3397
3602
|
SandboxProviderUnavailableError,
|
|
3603
|
+
modalSandboxAttributionEnvironment,
|
|
3604
|
+
modalSandboxAttributionTags,
|
|
3605
|
+
tagModalSandbox,
|
|
3606
|
+
terminateModalSandboxById,
|
|
3607
|
+
sweepModalOrphanSandboxes,
|
|
3398
3608
|
subjectFor,
|
|
3399
3609
|
SelfhostedControlError,
|
|
3400
3610
|
agentErrorToControlError,
|
|
@@ -3482,4 +3692,4 @@ export {
|
|
|
3482
3692
|
collectSandboxEnvironment2 as collectSandboxEnvironment,
|
|
3483
3693
|
parseExposedPorts2 as parseExposedPorts
|
|
3484
3694
|
};
|
|
3485
|
-
//# sourceMappingURL=chunk-
|
|
3695
|
+
//# sourceMappingURL=chunk-D5KU3QUC.js.map
|