@opengeni/runtime 0.14.1 → 0.14.15

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.
Files changed (79) hide show
  1. package/dist/{chunk-4YJ5Z52Z.js → chunk-7E2ZSCZK.js} +6 -6
  2. package/dist/{chunk-4YJ5Z52Z.js.map → chunk-7E2ZSCZK.js.map} +1 -1
  3. package/dist/{chunk-HHWIZHQM.js → chunk-GNEGANUF.js} +942 -300
  4. package/dist/chunk-GNEGANUF.js.map +1 -0
  5. package/dist/codex-tool-search.d.ts +100 -0
  6. package/dist/context-compaction.d.ts +236 -0
  7. package/dist/history-sanitizer.d.ts +320 -0
  8. package/dist/index.d.ts +186 -718
  9. package/dist/index.js +612 -126
  10. package/dist/index.js.map +1 -1
  11. package/dist/{localSources-T3HZ6KY4.js → localSources-BXHBKGF4.js} +2 -2
  12. package/dist/mcp-network.d.ts +32 -35
  13. package/dist/metrics.d.ts +33 -0
  14. package/dist/sandbox/capabilities.d.ts +11 -0
  15. package/dist/sandbox/channel-a.d.ts +226 -0
  16. package/dist/sandbox/display-stack.d.ts +99 -0
  17. package/dist/sandbox/errors.d.ts +16 -0
  18. package/dist/sandbox/exec-banner.d.ts +19 -0
  19. package/dist/sandbox/index.d.ts +190 -6
  20. package/dist/sandbox/index.js +20 -4
  21. package/dist/sandbox/op-correlation.d.ts +24 -0
  22. package/dist/sandbox/provider-errors.d.ts +32 -0
  23. package/dist/sandbox/provider-operation-gate.d.ts +12 -0
  24. package/dist/sandbox/providers/blaxel.d.ts +2 -0
  25. package/dist/sandbox/providers/cloudflare.d.ts +2 -0
  26. package/dist/sandbox/providers/daytona.d.ts +2 -0
  27. package/dist/sandbox/providers/docker.d.ts +2 -0
  28. package/dist/sandbox/providers/e2b.d.ts +2 -0
  29. package/dist/sandbox/providers/index.d.ts +11 -0
  30. package/dist/sandbox/providers/local.d.ts +2 -0
  31. package/dist/sandbox/providers/modal.d.ts +132 -0
  32. package/dist/sandbox/providers/none.d.ts +2 -0
  33. package/dist/sandbox/providers/runloop.d.ts +2 -0
  34. package/dist/sandbox/providers/selfhosted.d.ts +2 -0
  35. package/dist/sandbox/providers/types.d.ts +30 -0
  36. package/dist/sandbox/providers/vercel.d.ts +2 -0
  37. package/dist/sandbox/recording.d.ts +80 -0
  38. package/dist/sandbox/routing/backend-resolver.d.ts +173 -0
  39. package/dist/sandbox/routing/routing-session.d.ts +401 -0
  40. package/dist/sandbox/run-credentials.d.ts +65 -0
  41. package/dist/sandbox/select.d.ts +120 -0
  42. package/dist/sandbox/selfhosted/capabilities.d.ts +91 -0
  43. package/dist/sandbox/selfhosted/control-rpc.d.ts +175 -0
  44. package/dist/sandbox/selfhosted/fault-rendering.d.ts +40 -0
  45. package/dist/sandbox/selfhosted/op-observer.d.ts +40 -0
  46. package/dist/sandbox/selfhosted/op-stream.d.ts +104 -0
  47. package/dist/sandbox/selfhosted/op-testing.d.ts +108 -0
  48. package/dist/sandbox/selfhosted/op-transport.d.ts +60 -0
  49. package/dist/sandbox/selfhosted/retry-policy.d.ts +106 -0
  50. package/dist/sandbox/selfhosted/session.d.ts +552 -0
  51. package/dist/sandbox/selfhosted/testing.d.ts +48 -0
  52. package/dist/sandbox/stream-port.d.ts +80 -0
  53. package/dist/sandbox/stream-token.d.ts +44 -0
  54. package/dist/sandbox/terminal-server.d.ts +67 -0
  55. package/dist/sandbox/toolspace-token.d.ts +23 -0
  56. package/dist/sandbox/turn-tool-cancellation.d.ts +72 -0
  57. package/dist/sandbox/workspace-archive.d.ts +39 -0
  58. package/dist/sandbox-computer.d.ts +227 -0
  59. package/dist/screenshot-error-card.d.ts +1 -0
  60. package/dist/skill-library.d.ts +10 -12
  61. package/dist/usage-telemetry.d.ts +45 -0
  62. package/dist/workspace-skills.d.ts +31 -0
  63. package/package.json +7 -7
  64. package/src/context-compaction.ts +600 -6
  65. package/src/history-sanitizer.ts +10 -92
  66. package/src/index.ts +301 -62
  67. package/src/sandbox/channel-a.ts +267 -81
  68. package/src/sandbox/index.ts +56 -94
  69. package/src/sandbox/provider-operation-gate.ts +110 -0
  70. package/src/sandbox/providers/docker.ts +13 -1
  71. package/src/sandbox/providers/modal.ts +433 -3
  72. package/src/sandbox/routing/routing-session.ts +84 -8
  73. package/src/sandbox/selfhosted/session.ts +7 -6
  74. package/src/sandbox/turn-tool-cancellation.ts +47 -7
  75. package/src/sandbox/workspace-archive.ts +176 -73
  76. package/src/sandbox-computer.ts +25 -75
  77. package/dist/chunk-HHWIZHQM.js.map +0 -1
  78. package/dist/index-BraazrAO.d.ts +0 -2931
  79. /package/dist/{localSources-T3HZ6KY4.js.map → localSources-BXHBKGF4.js.map} +0 -0
@@ -34,6 +34,7 @@ import { PROVIDER_REGISTRY } from "./providers";
34
34
  import { SandboxConfigError, SandboxResumeStateUnavailableError } from "./errors";
35
35
  import { isProviderSandboxNotFoundError } from "./provider-errors";
36
36
  import {
37
+ decodeNativeSnapshotRef,
37
38
  readVerifiedWorkspaceArchive,
38
39
  verifyRestoredWorkspace,
39
40
  WorkspaceArchiveIntegrityError,
@@ -81,10 +82,17 @@ export {
81
82
  WORKSPACE_ARCHIVE_DESCRIPTOR_VERSION,
82
83
  WorkspaceArchiveIntegrityError,
83
84
  captureVerifiedWorkspaceArchive,
85
+ decodeNativeSnapshotRef,
86
+ describeLegacyNativeSnapshotArchive,
87
+ describeNativeSnapshotArchive,
84
88
  fingerprintSandboxWorkspace,
85
89
  parseWorkspaceArchiveDescriptor,
86
90
  readVerifiedWorkspaceArchive,
87
91
  verifyRestoredWorkspace,
92
+ type NativeSnapshotDescriptor,
93
+ type NativeSnapshotProvider,
94
+ type NativeSnapshotRef,
95
+ type TarWorkspaceArchiveDescriptor,
88
96
  type VerifiedWorkspaceArchive,
89
97
  type WorkspaceArchiveDescriptor,
90
98
  type WorkspaceArchiveIntegrityCode,
@@ -92,13 +100,20 @@ export {
92
100
  } from "./workspace-archive";
93
101
  export {
94
102
  ensureModalRegistryImage,
103
+ deleteModalCheckpointSnapshot,
104
+ inspectModalSandboxLifecycle,
105
+ modalSessionMatchesCheckpointProviderBinding,
95
106
  modalSandboxAttributionEnvironment,
96
107
  modalSandboxAttributionTags,
108
+ resolveModalCheckpointProviderBinding,
109
+ resolveModalCheckpointProviderBindingForLiveSandbox,
110
+ resolveModalCheckpointProviderBindingForSession,
97
111
  sweepModalOrphanSandboxes,
98
112
  tagModalSandbox,
99
113
  terminateModalSandboxById,
100
114
  type LiveModalSandboxLeaseAttribution,
101
115
  type ModalModuleLoader,
116
+ type ModalCheckpointProviderBinding,
102
117
  type ModalOrphanSweepResult,
103
118
  type ModalOrphanSweepTermination,
104
119
  type ModalSandboxAttribution,
@@ -506,7 +521,9 @@ function withDockerNetwork(client: SandboxClient, network: string | undefined):
506
521
  }
507
522
  : {}),
508
523
  ...(client.delete
509
- ? { delete: async (state: SandboxSessionState) => await client.delete!(state) }
524
+ ? {
525
+ delete: async (state: SandboxSessionState) => await client.delete!(state),
526
+ }
510
527
  : {}),
511
528
  ...(client.serializeSessionState
512
529
  ? {
@@ -628,84 +645,14 @@ export function readWorkspaceArchiveFromEnvelopeSessionState(
628
645
  }
629
646
  }
630
647
 
631
- // The native snapshot-ref prefixes the @openai/agents-extensions modal client
632
- // encodes (snapshots.mjs `NATIVE_SNAPSHOT_PREFIXES`). The ref is
633
- // `<PREFIX>\n{"snapshot_id":"...",...}`. We re-implement the decode here because
634
- // `@openai/agents-extensions/sandbox/shared` is NOT an exported subpath (the
635
- // package `exports` map only exposes `./sandbox/<provider>`), so decodeNativeSnapshotRef
636
- // is unreachable — same reasoning as isProviderSandboxNotFoundError below.
637
- const MODAL_SNAPSHOT_REF_PREFIXES = [
638
- "MODAL_SANDBOX_FS_SNAPSHOT_V1\n",
639
- "MODAL_SANDBOX_DIR_SNAPSHOT_V1\n",
640
- ];
641
-
642
648
  /** Decode the Modal snapshot id out of a persisted base64 archive ref, or
643
- * undefined when the archive is a tar payload (no provider snapshot to GC) or
644
- * is unparseable. Used only for keep-latest-per-lease snapshot GC. */
649
+ * undefined when the archive is a tar payload or is unparseable. */
645
650
  export function decodeModalSnapshotId(archive: Uint8Array): string | undefined {
646
- let text: string;
647
- try {
648
- text = new TextDecoder().decode(archive);
649
- } catch {
650
- return undefined;
651
- }
652
- for (const prefix of MODAL_SNAPSHOT_REF_PREFIXES) {
653
- if (!text.startsWith(prefix)) {
654
- continue;
655
- }
656
- try {
657
- const payload = JSON.parse(text.slice(prefix.length)) as { snapshot_id?: unknown };
658
- return typeof payload.snapshot_id === "string" && payload.snapshot_id.length > 0
659
- ? payload.snapshot_id
660
- : undefined;
661
- } catch {
662
- return undefined;
663
- }
664
- }
665
- return undefined;
666
- }
667
-
668
- /**
669
- * Best-effort GC of a SUPERSEDED Modal filesystem/directory snapshot
670
- * (sandbox-file-persistence). restoreSnapshotFilesystem terminates the previous
671
- * SANDBOX but never deletes the prior SNAPSHOT image, so snapshots accumulate
672
- * unbounded across warm/cold cycles. The reaper keeps only the latest per lease:
673
- * when it writes a NEW archive it passes the PRIOR archive here to delete its
674
- * image via the live session's Modal client (`session.modal.images.delete(id)` —
675
- * the same API the SDK uses for directory images). Never throws (GC is a
676
- * best-effort backstop; a leaked snapshot is a cost issue, not a correctness one).
677
- * A tar archive (no snapshot id) is a no-op. Returns the deleted snapshot id (or
678
- * undefined when nothing was deleted) for observability.
679
- */
680
- export async function deletePriorPersistedSnapshot(
681
- session: unknown,
682
- priorArchiveBase64: string | null | undefined,
683
- ): Promise<string | undefined> {
684
- if (!priorArchiveBase64) {
685
- return undefined;
686
- }
687
- let bytes: Uint8Array;
688
- try {
689
- bytes = Uint8Array.from(Buffer.from(priorArchiveBase64, "base64"));
690
- } catch {
691
- return undefined;
692
- }
693
- const snapshotId = decodeModalSnapshotId(bytes);
694
- if (!snapshotId) {
695
- return undefined;
696
- }
697
- const modal = (session as { modal?: { images?: { delete?: (id: string) => Promise<unknown> } } })
698
- .modal;
699
- const del = modal?.images?.delete;
700
- if (typeof del !== "function") {
701
- return undefined;
702
- }
703
- try {
704
- await del.call(modal!.images, snapshotId);
705
- return snapshotId;
706
- } catch {
707
- return undefined;
708
- }
651
+ const ref = decodeNativeSnapshotRef(archive);
652
+ return ref?.provider === "modal_snapshot_filesystem" ||
653
+ ref?.provider === "modal_snapshot_directory"
654
+ ? ref.snapshotId
655
+ : undefined;
709
656
  }
710
657
 
711
658
  export async function deserializeSandboxSessionStateEnvelope(
@@ -932,7 +879,9 @@ async function terminateCreatedSandbox(
932
879
  session: unknown,
933
880
  sessionState: unknown,
934
881
  ): Promise<void> {
935
- const clientWithDelete = client as { delete?: (state: unknown) => Promise<unknown> };
882
+ const clientWithDelete = client as {
883
+ delete?: (state: unknown) => Promise<unknown>;
884
+ };
936
885
  if (typeof clientWithDelete.delete === "function" && sessionState !== undefined) {
937
886
  try {
938
887
  await clientWithDelete.delete(sessionState);
@@ -980,6 +929,13 @@ export async function establishSandboxSessionFromEnvelope(
980
929
  * is never observable as ready while verification is in flight. */
981
930
  onWorkspaceRestoreVerifying?: (descriptor: WorkspaceArchiveDescriptor) => Promise<void>;
982
931
  metrics?: RuntimeMetricsHooks;
932
+ /** Isolated conformance-test/embedding seam. Ordinary runtime callers use
933
+ * the validated provider registry. */
934
+ clientFactory?: (
935
+ backend: SandboxBackend,
936
+ settings: Settings,
937
+ environment: Record<string, string>,
938
+ ) => unknown;
983
939
  },
984
940
  ): Promise<EstablishedSandboxSession> {
985
941
  const envelopeBackend =
@@ -987,9 +943,11 @@ export async function establishSandboxSessionFromEnvelope(
987
943
  const backend =
988
944
  opts.backendOverride ?? envelopeBackend ?? (settings.sandboxBackend as SandboxBackend);
989
945
  const environment = opts.environment ?? collectSandboxEnvironment(settings);
990
- const client = createSandboxClientForBackend(backend, settings, environment) as
991
- | ResumeCapableClient
992
- | undefined;
946
+ const client = (opts.clientFactory ?? createSandboxClientForBackend)(
947
+ backend,
948
+ settings,
949
+ environment,
950
+ ) as ResumeCapableClient | undefined;
993
951
  if (!client) {
994
952
  throw new SandboxConfigError(
995
953
  backend,
@@ -1137,22 +1095,26 @@ export async function establishSandboxSessionFromEnvelope(
1137
1095
  throw error;
1138
1096
  }
1139
1097
  }
1140
- try {
1141
- // Verification happens on the hydrated replacement before any caller
1142
- // may publish it warm or route an operation to it.
1143
- await verifyRestoredWorkspace(restored, workspaceArchive.descriptor);
1144
- } catch (error) {
1145
- await terminateCreatedSandbox(client, restored, (restored as { state?: unknown }).state);
1146
- if (error instanceof WorkspaceArchiveIntegrityError) throw error;
1147
- throw new WorkspaceArchiveIntegrityError(
1148
- "workspace_fingerprint_unavailable",
1149
- `failed to verify selected workspace archive revision ${workspaceArchive.descriptor.revision}`,
1150
- { retryable: true },
1151
- );
1098
+ // Native provider snapshots are restored by their exact opaque receipt.
1099
+ // OpenGeni verifies receipt identity/hash before hydration, but it must not
1100
+ // impose a tar/inode equivalence contract on the provider's filesystem
1101
+ // image. Tar archives remain content-verified after hydration.
1102
+ if (workspaceArchive.kind === "tar") {
1103
+ try {
1104
+ await verifyRestoredWorkspace(restored, workspaceArchive.descriptor);
1105
+ } catch (error) {
1106
+ await terminateCreatedSandbox(client, restored, (restored as { state?: unknown }).state);
1107
+ if (error instanceof WorkspaceArchiveIntegrityError) throw error;
1108
+ throw new WorkspaceArchiveIntegrityError(
1109
+ "workspace_fingerprint_unavailable",
1110
+ `failed to verify selected workspace archive revision ${workspaceArchive.descriptor.revision}`,
1111
+ { retryable: true },
1112
+ );
1113
+ }
1152
1114
  }
1153
1115
  hydrationApplied = true;
1154
1116
  console.info(
1155
- `[sandbox] cold-restore verified workspace archive revision ${workspaceArchive.descriptor.revision}`,
1117
+ `[sandbox] cold-restore applied ${workspaceArchive.kind === "provider_snapshot" ? "native snapshot receipt" : "verified tar archive"} revision ${workspaceArchive.descriptor.revision}`,
1156
1118
  );
1157
1119
  }
1158
1120
  restoredState = (restored as { state?: unknown }).state;
@@ -0,0 +1,110 @@
1
+ type ProviderOperationGate = {
2
+ activeOperations: number;
3
+ captureActive: boolean;
4
+ operationWaiters: Array<() => void>;
5
+ captureWaiters: Array<() => void>;
6
+ };
7
+
8
+ const providerOperationGates = new WeakMap<object, ProviderOperationGate>();
9
+
10
+ function sessionIdentity(session: unknown): object {
11
+ const identityType = typeof session;
12
+ if (!["object", "function"].includes(identityType) || session === null) {
13
+ throw new Error("Sandbox provider operation has no session identity");
14
+ }
15
+ return session as object;
16
+ }
17
+
18
+ function gateFor(session: object): ProviderOperationGate {
19
+ const existing = providerOperationGates.get(session);
20
+ if (existing) return existing;
21
+ const created: ProviderOperationGate = {
22
+ activeOperations: 0,
23
+ captureActive: false,
24
+ operationWaiters: [],
25
+ captureWaiters: [],
26
+ };
27
+ providerOperationGates.set(session, created);
28
+ return created;
29
+ }
30
+
31
+ async function enterOperation(gate: ProviderOperationGate): Promise<void> {
32
+ if (!gate.captureActive && gate.captureWaiters.length === 0) {
33
+ gate.activeOperations += 1;
34
+ return;
35
+ }
36
+ await new Promise<void>((resolve) => gate.operationWaiters.push(resolve));
37
+ }
38
+
39
+ function leaveOperation(gate: ProviderOperationGate): void {
40
+ gate.activeOperations -= 1;
41
+ if (gate.activeOperations < 0) {
42
+ throw new Error("Sandbox provider operation gate underflow");
43
+ }
44
+ if (gate.activeOperations === 0) {
45
+ const nextCapture = gate.captureWaiters.shift();
46
+ if (nextCapture) {
47
+ gate.captureActive = true;
48
+ nextCapture();
49
+ }
50
+ }
51
+ }
52
+
53
+ async function enterCapture(gate: ProviderOperationGate): Promise<void> {
54
+ if (!gate.captureActive && gate.activeOperations === 0) {
55
+ gate.captureActive = true;
56
+ return;
57
+ }
58
+ await new Promise<void>((resolve) => gate.captureWaiters.push(resolve));
59
+ }
60
+
61
+ function leaveCapture(gate: ProviderOperationGate): void {
62
+ if (!gate.captureActive) {
63
+ throw new Error("Sandbox provider capture gate was not held");
64
+ }
65
+ const nextCapture = gate.captureWaiters.shift();
66
+ if (nextCapture) {
67
+ nextCapture();
68
+ return;
69
+ }
70
+ gate.captureActive = false;
71
+ const operations = gate.operationWaiters.splice(0);
72
+ gate.activeOperations += operations.length;
73
+ for (const operation of operations) operation();
74
+ }
75
+
76
+ /**
77
+ * Coordinate every command/filesystem/control call against one in-process
78
+ * provider session. Ordinary operations may overlap; once a capture queues,
79
+ * later operations wait until its exclusive provider pause completes.
80
+ */
81
+ export async function withSandboxProviderOperation<T>(
82
+ session: unknown,
83
+ operation: () => Promise<T>,
84
+ ): Promise<T> {
85
+ const gate = gateFor(sessionIdentity(session));
86
+ await enterOperation(gate);
87
+ try {
88
+ return await operation();
89
+ } finally {
90
+ leaveOperation(gate);
91
+ }
92
+ }
93
+
94
+ /**
95
+ * Hold the provider session exclusively for the complete verified archive
96
+ * capture. The durable lease claim handles other workers; this local gate
97
+ * closes the same-holder race between native reads and the heartbeat capture.
98
+ */
99
+ export async function withSandboxProviderCapture<T>(
100
+ session: unknown,
101
+ capture: () => Promise<T>,
102
+ ): Promise<T> {
103
+ const gate = gateFor(sessionIdentity(session));
104
+ await enterCapture(gate);
105
+ try {
106
+ return await capture();
107
+ } finally {
108
+ leaveCapture(gate);
109
+ }
110
+ }
@@ -1,5 +1,7 @@
1
1
  import { DockerSandboxClient } from "@openai/agents/sandbox/local";
2
+ import { isAbsolute } from "node:path";
2
3
  import { CAPABILITY_DESCRIPTORS } from "../capabilities";
4
+ import { SandboxConfigError } from "../errors";
3
5
  import type { ProviderRegistration } from "./types";
4
6
 
5
7
  export const dockerProvider: ProviderRegistration = {
@@ -7,11 +9,21 @@ export const dockerProvider: ProviderRegistration = {
7
9
  descriptor: CAPABILITY_DESCRIPTORS.docker,
8
10
  // Local dev container — no credentials. (The dockerNetwork decoration is
9
11
  // applied by the factory, not here: it wraps the constructed client.)
10
- validateCredentials() {},
12
+ validateCredentials(settings) {
13
+ const workspaceBaseDir = settings.dockerWorkspaceBaseDir?.trim();
14
+ if (workspaceBaseDir && !isAbsolute(workspaceBaseDir)) {
15
+ throw new SandboxConfigError(
16
+ "docker",
17
+ "OPENGENI_DOCKER_WORKSPACE_BASE_DIR must be an absolute path",
18
+ );
19
+ }
20
+ },
11
21
  build({ settings, exposedPorts }) {
22
+ const workspaceBaseDir = settings.dockerWorkspaceBaseDir?.trim();
12
23
  return new DockerSandboxClient({
13
24
  image: settings.dockerImage,
14
25
  exposedPorts,
26
+ ...(workspaceBaseDir ? { workspaceBaseDir } : {}),
15
27
  });
16
28
  },
17
29
  };