@opengeni/runtime 0.6.1 → 0.7.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.
Files changed (45) hide show
  1. package/dist/{chunk-DYBF7ZEB.js → chunk-RYDCVTMW.js} +1958 -207
  2. package/dist/chunk-RYDCVTMW.js.map +1 -0
  3. package/dist/{index-BTo_bZvN.d.ts → index-DIiiwQY2.d.ts} +689 -52
  4. package/dist/index.d.ts +293 -123
  5. package/dist/index.js +1819 -790
  6. package/dist/index.js.map +1 -1
  7. package/dist/sandbox/index.d.ts +1 -1
  8. package/dist/sandbox/index.js +79 -3
  9. package/package.json +21 -20
  10. package/src/codex-tool-search.ts +185 -31
  11. package/src/context-compaction.ts +78 -243
  12. package/src/history-sanitizer.ts +172 -20
  13. package/src/index.ts +2573 -898
  14. package/src/metrics.ts +28 -2
  15. package/src/sandbox/capabilities.ts +12 -3
  16. package/src/sandbox/channel-a.ts +420 -72
  17. package/src/sandbox/display-stack.ts +18 -4
  18. package/src/sandbox/errors.ts +13 -0
  19. package/src/sandbox/index.ts +398 -112
  20. package/src/sandbox/op-correlation.ts +70 -0
  21. package/src/sandbox/providers/index.ts +9 -3
  22. package/src/sandbox/providers/modal.ts +84 -14
  23. package/src/sandbox/providers/selfhosted.ts +1 -4
  24. package/src/sandbox/recording.ts +146 -90
  25. package/src/sandbox/routing/backend-resolver.ts +144 -10
  26. package/src/sandbox/routing/routing-session.ts +129 -25
  27. package/src/sandbox/select.ts +60 -8
  28. package/src/sandbox/selfhosted/capabilities.ts +59 -25
  29. package/src/sandbox/selfhosted/control-rpc.ts +160 -28
  30. package/src/sandbox/selfhosted/fault-rendering.ts +226 -0
  31. package/src/sandbox/selfhosted/op-observer.ts +54 -0
  32. package/src/sandbox/selfhosted/op-stream.ts +801 -0
  33. package/src/sandbox/selfhosted/op-testing.ts +468 -0
  34. package/src/sandbox/selfhosted/op-transport.ts +151 -0
  35. package/src/sandbox/selfhosted/retry-policy.ts +177 -0
  36. package/src/sandbox/selfhosted/session.ts +498 -55
  37. package/src/sandbox/selfhosted/testing.ts +60 -19
  38. package/src/sandbox/stream-port.ts +17 -5
  39. package/src/sandbox/stream-token.ts +8 -2
  40. package/src/sandbox/terminal-server.ts +18 -4
  41. package/src/sandbox-computer.ts +332 -70
  42. package/src/usage-telemetry.ts +73 -0
  43. package/dist/chunk-DYBF7ZEB.js.map +0 -1
  44. package/src/image-history.ts +0 -149
  45. package/src/screenshot-omitted-card.ts +0 -15
package/dist/index.js CHANGED
@@ -10,21 +10,43 @@ import {
10
10
  DISPLAY_STACK_TIMEOUT_MS,
11
11
  DisplayStackError,
12
12
  DisplayStackUnsupportedError,
13
+ FAULT_FIELD_WHAT_HAPPENED,
14
+ FAULT_FIELD_WHAT_PRESERVED,
15
+ FAULT_FIELD_WHAT_TO_TRY,
16
+ FAULT_FIELD_WHICH_LAYER,
13
17
  MockAgentResponder,
18
+ NEVER_SENT_DETAIL_KEY,
14
19
  NatsControlRpc,
20
+ NatsOpStreamTransport,
21
+ OP_STREAM_ACK_INTERVAL_MS,
22
+ OP_STREAM_DEFAULT_WINDOW_BYTES,
23
+ OP_STREAM_RECONNECT_HOLD_MS,
24
+ OP_STREAM_SILENCE_TIMEOUT_MS,
25
+ OpStreamUnavailableError,
15
26
  PROVIDER_REGISTRY,
27
+ REPOSITORY_DISCOVERY_LIMIT,
16
28
  RecordingError,
17
29
  RecordingUnavailableError,
18
30
  RoutingSandboxSession,
19
31
  RoutingUnsupportedError,
20
32
  SELFHOSTED_DEFAULT_TIMEOUT_MS,
33
+ SELFHOSTED_DRAINING_MAX_RETRIES,
34
+ SELFHOSTED_EXEC_DRAINING_MAX_RETRIES,
35
+ SELFHOSTED_IDEMPOTENT_READONLY_OPS,
36
+ SELFHOSTED_INFRASTRUCTURE_FAULT_CLASSES,
37
+ SELFHOSTED_NEVER_SENT_MAX_RETRIES,
21
38
  SELFHOSTED_RECONNECT_WINDOW_MS,
22
39
  SELFHOSTED_RELAY_STREAM_PATH,
40
+ SELFHOSTED_RETRY_BACKOFF_BASE_MS,
41
+ SELFHOSTED_RETRY_BACKOFF_CAP_MS,
42
+ SELFHOSTED_RETRY_BACKOFF_FACTOR,
43
+ SELFHOSTED_TIMEOUT_MAX_RETRIES,
23
44
  STREAM_PORT,
24
45
  STREAM_TOKEN_DEFAULT_TTL_SECONDS,
25
46
  SandboxChannelAService,
26
47
  SandboxConfigError,
27
48
  SandboxProviderUnavailableError,
49
+ SandboxResumeStateUnavailableError,
28
50
  SelfhostedControlError,
29
51
  SelfhostedSandboxClient,
30
52
  SelfhostedSession,
@@ -47,17 +69,23 @@ import {
47
69
  contentTypeForCodec,
48
70
  createSandboxClient,
49
71
  createSandboxClientForBackend,
72
+ decideSelfhostedRetry,
50
73
  decodeModalSnapshotId,
74
+ defaultSelfhostedRetryClock,
51
75
  deletePriorPersistedSnapshot,
52
76
  deleteRecordingArtifacts,
53
77
  deserializeSandboxSessionStateEnvelope,
54
78
  desktopCapableBackend,
79
+ drainingExhaustedError,
80
+ drainingMessage,
55
81
  ensureDisplayStack,
56
82
  ensureModalRegistryImage,
57
83
  ensureTerminalServer,
58
84
  establishSandboxSessionFromEnvelope,
85
+ execDeadlineHint,
59
86
  exposeStreamPort,
60
87
  extForCodec,
88
+ inspectRecordingArtifact,
61
89
  isExecSessionLostBanner,
62
90
  isProviderSandboxNotFoundError,
63
91
  isSelfhostedProviderNotFoundError,
@@ -68,26 +96,35 @@ import {
68
96
  modalSandboxAttributionTags,
69
97
  negotiateCapabilities,
70
98
  negotiateSelfhostedCapabilities,
99
+ nextDurableOpId,
71
100
  offlineAgentError,
72
101
  offlineControlResponse,
102
+ opAckSubject,
103
+ opFrameSubject,
73
104
  parseExecBannerSessionId,
74
105
  parseExposedPorts,
75
106
  parseNumstatZ,
76
107
  parsePorcelainV2,
77
108
  parseUnifiedPatch,
78
- readRecordingBytes,
109
+ payloadTooLargeMessage,
79
110
  readWorkspaceArchiveFromEnvelopeSessionState,
80
111
  recordingStorageKey,
112
+ renderSelfhostedFault,
81
113
  restoredSandboxSessionStateFromEntry,
114
+ runWithToolCallCorrelation,
82
115
  sandboxStateEntryFromRunState,
116
+ sanitizeOpIdToken,
83
117
  selectBackend,
118
+ selfhostedFaultClass,
84
119
  selfhostedLiveness,
120
+ selfhostedRetryBackoffMs,
85
121
  serializeEstablishedSandboxEnvelope,
86
122
  setSelfhostedApplyDiff,
87
123
  startRecording,
88
124
  stopRecording,
89
125
  stripExecBanner,
90
126
  subjectFor,
127
+ swapTargetEstablishability,
91
128
  sweepModalOrphanSandboxes,
92
129
  tagModalSandbox,
93
130
  tearDownDisplayStack,
@@ -95,12 +132,24 @@ import {
95
132
  terminateModalSandboxById,
96
133
  timeoutAgentError,
97
134
  timeoutControlResponse,
135
+ uploadRecordingArtifact,
98
136
  verifyStreamToken
99
- } from "./chunk-DYBF7ZEB.js";
137
+ } from "./chunk-RYDCVTMW.js";
100
138
 
101
139
  // src/index.ts
102
- import { AGENT_INSTRUCTIONS_CORE_PLACEHOLDER, collectSandboxEnvironment as collectSandboxEnvironment2, contextInputBudgetTokens, contextServerCompactThreshold, firstPartyMcpBaseUrl, resolveContextCompactionMode, resolveModelProvider, sandboxLifecycleHookIds } from "@opengeni/config";
103
- import { CAPABILITY_DESCRIPTORS as CAPABILITY_DESCRIPTORS2, isClearedRunStateBlob, prefixedMcpToolName as sharedPrefixedMcpToolName, signDelegatedAccessToken } from "@opengeni/contracts";
140
+ import {
141
+ AGENT_INSTRUCTIONS_CORE_PLACEHOLDER,
142
+ collectSandboxEnvironment as collectSandboxEnvironment2,
143
+ firstPartyMcpBaseUrl,
144
+ resolveModelProvider,
145
+ sandboxLifecycleHookIds
146
+ } from "@opengeni/config";
147
+ import {
148
+ CAPABILITY_DESCRIPTORS as CAPABILITY_DESCRIPTORS2,
149
+ isClearedRunStateBlob,
150
+ prefixedMcpToolName as sharedPrefixedMcpToolName,
151
+ signDelegatedAccessToken
152
+ } from "@opengeni/contracts";
104
153
  import {
105
154
  Agent,
106
155
  AgentsError,
@@ -121,15 +170,11 @@ import {
121
170
  webSearchTool,
122
171
  applyDiff
123
172
  } from "@openai/agents";
124
- import {
125
- localDirLazySkillSource
126
- } from "@openai/agents/sandbox/local";
173
+ import { localDirLazySkillSource } from "@openai/agents/sandbox/local";
127
174
  import {
128
175
  Manifest,
129
176
  SandboxAgent,
130
- StaticCompactionPolicy,
131
177
  azureBlobMount,
132
- compaction,
133
178
  dir,
134
179
  file,
135
180
  filesystem,
@@ -142,7 +187,14 @@ import {
142
187
  } from "@openai/agents/sandbox";
143
188
  import { ModalCloudBucketMountStrategy } from "@openai/agents-extensions/sandbox/modal";
144
189
  import OpenAI from "openai";
145
- import { CODEX_APPS_MCP_SERVER_ID, CODEX_MODEL_ID_PREFIX, CODEX_ORIGINATOR, codexAppsSanitizingFetch, codexRequestStorage, codexSubscriptionFetch } from "@opengeni/codex";
190
+ import {
191
+ CODEX_APPS_MCP_SERVER_ID,
192
+ CODEX_MODEL_ID_PREFIX,
193
+ CODEX_ORIGINATOR,
194
+ codexAppsSanitizingFetch,
195
+ codexRequestStorage,
196
+ codexSubscriptionFetch
197
+ } from "@opengeni/codex";
146
198
  import { cpSync, existsSync, mkdirSync, readdirSync, renameSync, rmSync } from "fs";
147
199
  import { dirname, isAbsolute, join, posix as posixPath, relative } from "path";
148
200
  import { fileURLToPath } from "url";
@@ -195,6 +247,68 @@ function callIdOf(item) {
195
247
  }
196
248
  return void 0;
197
249
  }
250
+ function viewImagePathOf(item) {
251
+ if (!item || typeof item !== "object") {
252
+ return void 0;
253
+ }
254
+ const record = item;
255
+ if (record.type !== "function_call" || record.name !== "view_image" || typeof record.arguments !== "string") {
256
+ return void 0;
257
+ }
258
+ try {
259
+ const parsed = JSON.parse(record.arguments);
260
+ return typeof parsed.path === "string" && parsed.path.length > 0 ? parsed.path : void 0;
261
+ } catch {
262
+ return void 0;
263
+ }
264
+ }
265
+ function elideSupersededViewImagePairs(items) {
266
+ if (items.length === 0) {
267
+ return [];
268
+ }
269
+ const callIndexById = /* @__PURE__ */ new Map();
270
+ const resultIndexById = /* @__PURE__ */ new Map();
271
+ items.forEach((item, index) => {
272
+ const callId = callIdOf(item);
273
+ if (!callId) {
274
+ return;
275
+ }
276
+ if (itemType(item) === "function_call") {
277
+ callIndexById.set(callId, index);
278
+ } else if (itemType(item) === "function_call_result" || itemType(item) === "function_call_output") {
279
+ resultIndexById.set(callId, index);
280
+ }
281
+ });
282
+ const pairedByPath = /* @__PURE__ */ new Map();
283
+ for (const [callId, callIndex] of callIndexById) {
284
+ const path = viewImagePathOf(items[callIndex]);
285
+ const resultIndex = resultIndexById.get(callId);
286
+ if (!path || resultIndex === void 0 || resultIndex <= callIndex) {
287
+ continue;
288
+ }
289
+ const pairs = pairedByPath.get(path) ?? [];
290
+ pairs.push({ callIndex, resultIndex });
291
+ pairedByPath.set(path, pairs);
292
+ }
293
+ const dropped = /* @__PURE__ */ new Set();
294
+ for (const pairs of pairedByPath.values()) {
295
+ if (pairs.length < 2) {
296
+ continue;
297
+ }
298
+ pairs.sort((left, right) => left.callIndex - right.callIndex);
299
+ for (const pair of pairs.slice(0, -1)) {
300
+ dropped.add(pair.callIndex);
301
+ dropped.add(pair.resultIndex);
302
+ for (let index = pair.callIndex - 1; index >= 0; index -= 1) {
303
+ if (itemType(items[index]) !== "reasoning") {
304
+ break;
305
+ }
306
+ dropped.add(index);
307
+ }
308
+ }
309
+ }
310
+ return dropped.size === 0 ? items.slice() : items.filter((_item, index) => !dropped.has(index));
311
+ }
198
312
  function sanitizeHistoryItemsForModel(items) {
199
313
  if (items.length === 0) {
200
314
  return [];
@@ -265,9 +379,24 @@ function sanitizeHistoryItemsForModel(items) {
265
379
  }
266
380
  }
267
381
  if (dropped.size === 0) {
268
- return items.slice();
382
+ return items.map(stripInternalResumeMarker);
383
+ }
384
+ return items.filter((_item, index) => !dropped.has(index)).map(stripInternalResumeMarker);
385
+ }
386
+ var INTERNAL_RESUME_MESSAGE_MARKER = "opengeni_internal_resume";
387
+ function stripInternalResumeMarker(item) {
388
+ const providerData = item.providerData;
389
+ if (!providerData || typeof providerData !== "object" || !(INTERNAL_RESUME_MESSAGE_MARKER in providerData)) {
390
+ return item;
391
+ }
392
+ const { [INTERNAL_RESUME_MESSAGE_MARKER]: _dropped, ...rest } = providerData;
393
+ const next = { ...item };
394
+ if (Object.keys(rest).length > 0) {
395
+ next.providerData = rest;
396
+ } else {
397
+ delete next.providerData;
269
398
  }
270
- return items.filter((_item, index) => !dropped.has(index));
399
+ return next;
271
400
  }
272
401
  function stripReasoningEncryptedContent(item) {
273
402
  const type = itemType(item);
@@ -519,109 +648,6 @@ function computerCallNormalizingFetch(base) {
519
648
  };
520
649
  }
521
650
 
522
- // src/screenshot-omitted-card.ts
523
- var SCREENSHOT_OMITTED_IMAGE_DATA_URL = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAgwAAAC0CAYAAAD4gHQ+AAAG2klEQVR42u3dMXLbMBCGUR/ChQuPT5KD+c5q1Ki2b+CMRlji38UrXpWYoUkA/AoM8vb17/sHAOAvbx4CACAYAADBAAAIBgBAMAAAggEAEAwAgGAAABAMAIBgAAAEAwAgGAAAwQAACAYAQDAAAIIBAEAwAACCAQAQDACAYAAABAMAIBgAAMEAAAiGco/77SVXXz/9fqvv/9nrvzoedr/ftPvfPX7S14fV7+fZn582f0+7vggQDIJBMAgGwSAYXF8wCAbBIBgEg2AQDK4vGASDYBAMgkEwCAbXFwznBUP1C0q/fvUHK+35rF7QT3+/u38+fX149YOyejxb/9yfYBAMgkEwCAbBYP1zf4JBMAgGwSAYBINgEAyiQDAIBsEgGASDYBAMgkEwCIbc51P954JBMCSPP+ufYBAMJoxgEAyCQTBY/9yfYBAMgkEwCAbBIBgEA4JBMAgGwSAYBINgEAyzDm5K39SXdtDU7utXL8jdD86yIAqGk4Oh28FcCAbBIBgEg2AQDIJBMAgGwSAYBINgEAyCQTAIBsEgGASDYBAMgkEwzA+GaZsGLShrP9DT3p9gsOlUMNj0KBgEgwVFMAgGwWB+uz/BIBgEg2DwwRIM5rdgQDAIBsHggyUYBINgEAyCIeWDaEHpfbCUD5ZgEAyCAcEgGCwoPliCQTAIBsEgGASDCSsYBINgEAyCQTAIBhNWMAgGwWB+C4a5//nU6oM7qv+9qxe06vs/7fmkLTjV47/7gtjt/VbPr9MPbtq9PiAYBINgEAyCQTAIBhEgGASDYBAMgkEwCAbBIBgEg2AQDIJBMAgGwWDTIwAgGAAAwQAACAYAQDAAAAgGAEAwAACCAQAQDACAYAAABAMAIBgAAMEAACAYAADBAAAIBgBAMAAAggEAEAwAgGAAAASDhwAACAYAQDAAAIIBAEgIho/3z59Kj/ttqdX/XvX9P3v9bs8//frV47v786/+farH/+7xOW38GP/Wt78IBsEgGASDYBAMgkEwCAbBIBgEg2AQDIJBMAgGwSAYBINgEAyCQTBMDIa061dPqN0Tdvfzm35/xn/tB331398dkNPGT/f5nRZU6e9PMAgGwWD8CwbBIBgEg2AQDIJBMAgGwSAYBINgEAyCQTAIBsEgGASDYBAMgkEwnDz+q/9cMAgGwSAYBINgEAyCQTAIBsEgGASDYBAMgkEwCAbBIBgsmIJBMBj/gkEwCIazg8HBGrMnbLeDs04f/+kHB63+c+NHMCSNH8EgGASDYBAMgkEwCAbBIBhMWMEgGASDYBAMgkEwmLCCQTAIBsEgGASDTY9pm6pseuy9aXT687Gp2KZHwWDTo2AQDIJBMAgGwSAYBINgEAyCwYIvGASD9UcwCAbBIBgEg2AQDIJBMAiGvgPGpqO995e+aa3b8xEMxo9gEAyCQTAIBsEgGASDYBAMgkEwCAbBIBgEg/VHMAgGwSAYBINgEAyCQTA4uGndwS/dFrS0g6fSru8/n/JBT5pf09bP064//WBAwWBBEwyCQTAIBuuDYBAMgsGCIBgEg2CwPggGwSAYTFjBIBgEg2AQDIIBAJhIMAAAggEAEAwAgGAAAAQDACAYAADBAAAIBgAAwQAACAYAQDAAAIIBABAMAIBgAAAEAwAgGAAABAMAIBgAAMEAAAgGAEAwAACCAQAQDACAYFjvcb8ttfrfS3sh3Z7Pq8/zfz9/9fjxfme936uvb/wjGASDD4pg8H4Fg/GPYBAMgkEweL+CQTCAYBAMPiiCQTAIBh89em96rJ5w1T+/+/dPW/DTrt99fKd/0E+f36evXwgGweCDIhi8X8Fg/QLBIBgEg/crGKxfIBgEg2AQDIJBMIBgEAyCQTCkPP/d4yNtfl89ngUDgkEwCAbBIBgEg2BAMAgGwSAYBINgEAwIBsHggyIYBINgEAwIBsGQdLDP7oOz0g+uEQyCYef4SX//IBgEg2AQDIJBMIBgEAyCQTAIBsEgGBAMgkEwCAbBIBgEA4JBMHTaZGXTo2BYGag2Pdr0iGAQDIJBMAgGwSAYEAyCQTAIBsEgGAQDgkEwCAbBIBgEg2AAwSAYrno+1R8gwTArCP3nU1m/X/omYgSDYBAMgkEwCAbBgGAQDIJBMAgGwSAYEAwmnGAQDIJBMAgGBEO3BbX7wT7VBzd1XxCnHeS0+6Au4yf74DLBgGAQDIJBMAgGwSAYEAyCQTAIBsEgGAQDgkEwCAbBIBiMH8GAYAAAEAwAgGAAAAQDACAYAADBAAAIBgBAMAAAggEAQDAAAIIBABAMAIBgAAAEAwAgGAAAwQAACAYAAMEAAAgGAEAwAACCAQAQDACAYAAABAMAcLxffBoP915amCIAAAAASUVORK5CYII=";
524
-
525
- // src/image-history.ts
526
- var SCREENSHOT_OMITTED_PLACEHOLDER = "[screenshot omitted: an older desktop frame \u2014 the full image remains in the session event log]";
527
- var DATA_IMAGE_BASE64_PATTERN = /data:image\/[a-z0-9.+-]+;base64,[a-z0-9+/=_-]+/i;
528
- function elideStaleScreenshotImages(items, options = {}) {
529
- const keepLast = Math.max(0, Math.floor(options.keepLast ?? 3));
530
- const placeholder = options.placeholder ?? SCREENSHOT_OMITTED_PLACEHOLDER;
531
- const occurrences = [];
532
- for (let i = 0; i < items.length; i += 1) {
533
- collectItemImageOccurrences(items[i], [i], placeholder, occurrences);
534
- }
535
- const elidedCount = Math.max(0, occurrences.length - keepLast);
536
- if (elidedCount === 0) {
537
- return { items: items.slice(), imageCount: occurrences.length, elidedCount: 0 };
538
- }
539
- const cloned = structuredClone(items);
540
- for (const occurrence of occurrences.slice(0, elidedCount)) {
541
- setPath(cloned, occurrence.path, occurrence.replacement);
542
- }
543
- return { items: cloned, imageCount: occurrences.length, elidedCount };
544
- }
545
- function collectItemImageOccurrences(item, path, placeholder, out) {
546
- if (!isRecord(item)) {
547
- return;
548
- }
549
- if (item.type === "message" && (item.role === "user" || item.role === "system")) {
550
- return;
551
- }
552
- if (item.type === "computer_call_result" || item.type === "computer_call_output") {
553
- collectComputerOutputImages(item, path, out);
554
- return;
555
- }
556
- if (item.type === "function_call_result" || item.type === "function_call_output") {
557
- collectToolResultImages(item.output, [...path, "output"], placeholder, out);
558
- }
559
- }
560
- function collectComputerOutputImages(item, path, out) {
561
- const output = item.output;
562
- if (!isRecord(output) || output.type !== "computer_screenshot") {
563
- return;
564
- }
565
- for (const key of ["data", "image_url", "imageUrl"]) {
566
- if (isImageDataUrl(output[key])) {
567
- out.push({ path: [...path, "output", key], replacement: SCREENSHOT_OMITTED_IMAGE_DATA_URL });
568
- return;
569
- }
570
- }
571
- }
572
- function collectToolResultImages(value, path, placeholder, out) {
573
- if (typeof value === "string") {
574
- if (isImageDataUrl(value)) {
575
- out.push({ path, replacement: placeholder });
576
- }
577
- return;
578
- }
579
- if (Array.isArray(value)) {
580
- for (let i = 0; i < value.length; i += 1) {
581
- collectToolResultImages(value[i], [...path, i], placeholder, out);
582
- }
583
- return;
584
- }
585
- if (!isRecord(value)) {
586
- return;
587
- }
588
- if (value.type === "input_image") {
589
- for (const key of ["image", "imageUrl", "image_url"]) {
590
- if (isImageDataUrl(value[key])) {
591
- out.push({ path, replacement: { type: "input_text", text: placeholder } });
592
- return;
593
- }
594
- }
595
- }
596
- for (const key of ["content", "text", "output"]) {
597
- if (key in value) {
598
- collectToolResultImages(value[key], [...path, key], placeholder, out);
599
- }
600
- }
601
- }
602
- function isImageDataUrl(value) {
603
- return typeof value === "string" && DATA_IMAGE_BASE64_PATTERN.test(value);
604
- }
605
- function isRecord(value) {
606
- return Boolean(value) && typeof value === "object" && !Array.isArray(value);
607
- }
608
- function setPath(root, path, value) {
609
- if (path.length === 0) {
610
- return;
611
- }
612
- let cursor = root;
613
- for (let i = 0; i < path.length - 1; i += 1) {
614
- const segment = path[i];
615
- cursor = Array.isArray(cursor) ? cursor[segment] : cursor[segment];
616
- }
617
- const last = path[path.length - 1];
618
- if (Array.isArray(cursor)) {
619
- cursor[last] = value;
620
- } else {
621
- cursor[last] = value;
622
- }
623
- }
624
-
625
651
  // src/codex-tool-search.ts
626
652
  import { toolSearchTool } from "@openai/agents";
627
653
  var CODEX_APPS_TOOL_PREFIX = "codex_apps__";
@@ -746,7 +772,10 @@ ${sources.map((s) => `- ${s}`).join("\n")}`;
746
772
  var SEARCH_TOOL_PARAMETERS = {
747
773
  type: "object",
748
774
  properties: {
749
- query: { type: "string", description: "Plain-language description of the capability you need." },
775
+ query: {
776
+ type: "string",
777
+ description: "Plain-language description of the capability you need."
778
+ },
750
779
  limit: { type: "number", description: "Maximum number of tools to return (default 8)." }
751
780
  },
752
781
  required: ["query"],
@@ -759,10 +788,28 @@ function codexToolSearchExecutor(args) {
759
788
  return bm25RankTools(deferred, query, limit);
760
789
  }
761
790
  var NO_NAMESPACES = /* @__PURE__ */ new Set();
762
- function buildCodexToolSearchTool(connectorNamespaces = NO_NAMESPACES) {
791
+ function hasConnectorSources(connectorNamespaces) {
792
+ for (const namespace of connectorNamespaces) {
793
+ if (typeof namespace === "string" && namespace.length > 0) {
794
+ return true;
795
+ }
796
+ }
797
+ return false;
798
+ }
799
+ function resolveSearchToolDescription(connectorNamespaces, freeze) {
800
+ if (freeze?.value != null) {
801
+ return freeze.value;
802
+ }
803
+ const rendered = renderSearchToolDescription(connectorNamespaces);
804
+ if (freeze && hasConnectorSources(connectorNamespaces)) {
805
+ freeze.value = rendered;
806
+ }
807
+ return rendered;
808
+ }
809
+ function buildCodexToolSearchToolFromDescription(description) {
763
810
  return toolSearchTool({
764
811
  execution: "client",
765
- description: renderSearchToolDescription(connectorNamespaces),
812
+ description,
766
813
  parameters: SEARCH_TOOL_PARAMETERS,
767
814
  execute: codexToolSearchExecutor
768
815
  });
@@ -771,7 +818,7 @@ function isToolSearchTool(tool2) {
771
818
  const t = tool2;
772
819
  return !!t && (t.name === "tool_search" || t.providerData?.type === "tool_search");
773
820
  }
774
- function applyCodexToolSearch(tools, connectorNamespaces = NO_NAMESPACES) {
821
+ function applyCodexToolSearch(tools, connectorNamespaces = NO_NAMESPACES, descriptionFreeze) {
775
822
  for (const tool2 of tools) {
776
823
  if (isCodexAppsFunctionTool(tool2) && tool2.deferLoading !== true) {
777
824
  tool2.deferLoading = true;
@@ -780,16 +827,24 @@ function applyCodexToolSearch(tools, connectorNamespaces = NO_NAMESPACES) {
780
827
  if (tools.some(isToolSearchTool)) {
781
828
  return tools;
782
829
  }
783
- return [...tools, buildCodexToolSearchTool(connectorNamespaces)];
830
+ const description = resolveSearchToolDescription(connectorNamespaces, descriptionFreeze);
831
+ return [...tools, buildCodexToolSearchToolFromDescription(description)];
784
832
  }
785
833
  function installCodexToolSearch(agent, connectorNamespaces = NO_NAMESPACES) {
834
+ installCodexToolSearchWithFreeze(agent, connectorNamespaces, { value: null });
835
+ }
836
+ function installCodexToolSearchWithFreeze(agent, connectorNamespaces, descriptionFreeze) {
786
837
  const originalGetAllTools = agent.getAllTools.bind(agent);
787
- agent.getAllTools = (async (runContext) => applyCodexToolSearch(await originalGetAllTools(runContext), connectorNamespaces));
838
+ agent.getAllTools = async (runContext) => applyCodexToolSearch(
839
+ await originalGetAllTools(runContext),
840
+ connectorNamespaces,
841
+ descriptionFreeze
842
+ );
788
843
  const originalClone = agent.clone?.bind(agent);
789
844
  if (originalClone) {
790
845
  const cloneWithToolSearch = (config) => {
791
846
  const cloned = originalClone(config);
792
- installCodexToolSearch(cloned, connectorNamespaces);
847
+ installCodexToolSearchWithFreeze(cloned, connectorNamespaces, descriptionFreeze);
793
848
  return cloned;
794
849
  };
795
850
  agent.clone = cloneWithToolSearch;
@@ -800,10 +855,9 @@ function installCodexToolSearch(agent, connectorNamespaces = NO_NAMESPACES) {
800
855
  var COMPACTION_SUMMARY_MARKER = "opengeni_context_summary";
801
856
  var SUMMARY_BUFFER_TOKENS = 2e4;
802
857
  var COMPACT_USER_MESSAGE_MAX_TOKENS = 2e4;
803
- var DEFAULT_COMPACTION_THRESHOLD_RATIO = 0.6;
858
+ var DEFAULT_COMPACTION_THRESHOLD_RATIO = 0.9;
804
859
  var MIN_COMPACTION_THRESHOLD_RATIO = 0.3;
805
860
  var MAX_COMPACTION_THRESHOLD_RATIO = 0.9;
806
- var COMPACTION_FALLBACK_TARGET_RATIO = 0.5;
807
861
  var COMPACTION_PROMPT = [
808
862
  "You are performing a CONTEXT CHECKPOINT COMPACTION. Create a handoff summary for another LLM that will resume the task.",
809
863
  "",
@@ -839,6 +893,9 @@ function isUserMessage(item) {
839
893
  function isCompactionSummary(item) {
840
894
  return isUserMessage(item) && item[COMPACTION_SUMMARY_MARKER] === true;
841
895
  }
896
+ function isEphemeralInternalContext(item) {
897
+ return itemType2(item) === "message" && itemRole(item) === "system";
898
+ }
842
899
  function estimateItemTokens(item) {
843
900
  let text;
844
901
  try {
@@ -857,13 +914,21 @@ function estimateTokens(items) {
857
914
  }
858
915
  function clampCompactionThresholdRatio(value) {
859
916
  const numeric = typeof value === "number" && Number.isFinite(value) ? value : DEFAULT_COMPACTION_THRESHOLD_RATIO;
860
- return Math.min(MAX_COMPACTION_THRESHOLD_RATIO, Math.max(MIN_COMPACTION_THRESHOLD_RATIO, numeric));
917
+ return Math.min(
918
+ MAX_COMPACTION_THRESHOLD_RATIO,
919
+ Math.max(MIN_COMPACTION_THRESHOLD_RATIO, numeric)
920
+ );
861
921
  }
862
- function clientCompactionThresholdTokens(input) {
863
- return Math.floor(Math.max(0, input.contextWindowTokens) * clampCompactionThresholdRatio(input.contextCompactionThresholdRatio));
922
+ function compactionThresholdTokens(input) {
923
+ const window = Math.max(0, input.contextWindowTokens);
924
+ const codexMaximum = Math.floor(window * MAX_COMPACTION_THRESHOLD_RATIO);
925
+ if (typeof input.contextAutoCompactThresholdTokens === "number" && Number.isFinite(input.contextAutoCompactThresholdTokens) && input.contextAutoCompactThresholdTokens > 0) {
926
+ return Math.min(Math.floor(input.contextAutoCompactThresholdTokens), codexMaximum);
927
+ }
928
+ return Math.floor(window * clampCompactionThresholdRatio(input.contextCompactionThresholdRatio));
864
929
  }
865
- function decideClientCompaction(input) {
866
- const thresholdTokens = clientCompactionThresholdTokens(input);
930
+ function decideCompaction(input) {
931
+ const thresholdTokens = compactionThresholdTokens(input);
867
932
  const recorded = typeof input.lastInputTokens === "number" && input.lastInputTokens > 0 ? input.lastInputTokens : 0;
868
933
  const signalTokens = recorded > 0 ? recorded : estimateTokens(input.items);
869
934
  if (input.items.length === 0) {
@@ -872,7 +937,7 @@ function decideClientCompaction(input) {
872
937
  if (input.force) {
873
938
  return { shouldCompact: true, reason: "force", signalTokens, thresholdTokens };
874
939
  }
875
- if (signalTokens > thresholdTokens) {
940
+ if (signalTokens >= thresholdTokens) {
876
941
  return { shouldCompact: true, reason: "above_threshold", signalTokens, thresholdTokens };
877
942
  }
878
943
  return { shouldCompact: false, reason: "below_threshold", signalTokens, thresholdTokens };
@@ -881,14 +946,17 @@ var CompactionNeededError = class extends Error {
881
946
  signalTokens;
882
947
  thresholdTokens;
883
948
  signalSource;
949
+ trigger;
884
950
  constructor(input) {
951
+ const trigger = input.trigger ?? "threshold";
885
952
  super(
886
- `Context compaction needed: signal ${input.signalTokens} tokens exceeded threshold ${input.thresholdTokens}`
953
+ trigger === "operator" ? "Context compaction requested by the operator" : `Context compaction needed: signal ${input.signalTokens} tokens exceeded threshold ${input.thresholdTokens}`
887
954
  );
888
955
  this.name = "CompactionNeededError";
889
956
  this.signalTokens = input.signalTokens;
890
957
  this.thresholdTokens = input.thresholdTokens;
891
958
  this.signalSource = input.signalSource;
959
+ this.trigger = trigger;
892
960
  }
893
961
  };
894
962
  function findCompactionNeededError(error, seen = /* @__PURE__ */ new WeakSet()) {
@@ -905,42 +973,6 @@ function findCompactionNeededError(error, seen = /* @__PURE__ */ new WeakSet())
905
973
  const record = error;
906
974
  return findCompactionNeededError(record.cause, seen) ?? findCompactionNeededError(record.error, seen);
907
975
  }
908
- function findKeepBoundary(items, keepRecentTokens) {
909
- const boundaries = [];
910
- for (let i = 0; i < items.length; i += 1) {
911
- if (isUserMessage(items[i])) {
912
- boundaries.push(i);
913
- }
914
- }
915
- if (boundaries.length === 0) {
916
- return 0;
917
- }
918
- for (let b = 0; b < boundaries.length; b += 1) {
919
- const boundary = boundaries[b];
920
- if (estimateTokens(items.slice(boundary)) <= keepRecentTokens) {
921
- return boundary;
922
- }
923
- }
924
- return boundaries[boundaries.length - 1];
925
- }
926
- function enforceInputBudget(items, maxTokens, trailingTokens = 0) {
927
- const total = estimateTokens(items) + Math.max(0, trailingTokens);
928
- if (items.length === 0 || total <= maxTokens) {
929
- return { items: items.slice(), trimmed: false, droppedCount: 0, estimatedTokens: total };
930
- }
931
- const historyBudget = Math.max(0, maxTokens - Math.max(0, trailingTokens));
932
- const boundary = findKeepBoundary(items, historyBudget);
933
- if (boundary <= 0) {
934
- return { items: items.slice(), trimmed: false, droppedCount: 0, estimatedTokens: total };
935
- }
936
- const kept = items.slice(boundary);
937
- return {
938
- items: kept,
939
- trimmed: true,
940
- droppedCount: boundary,
941
- estimatedTokens: estimateTokens(kept) + Math.max(0, trailingTokens)
942
- };
943
- }
944
976
  function buildCompactionPromptInput(items) {
945
977
  return [
946
978
  ...items,
@@ -952,70 +984,27 @@ function buildCompactionPromptInput(items) {
952
984
  ];
953
985
  }
954
986
  function buildCompactionReplacementHistory(items, summaryBody) {
955
- const history = [];
956
- for (const item of items) {
987
+ const retainedReversed = [];
988
+ let remaining = COMPACT_USER_MESSAGE_MAX_TOKENS;
989
+ for (let index = items.length - 1; index >= 0 && remaining > 0; index -= 1) {
990
+ const item = items[index];
957
991
  if (!isUserMessage(item) || isCompactionSummary(item)) {
958
992
  continue;
959
993
  }
960
- history.push(compactUserMessage(item));
961
- }
962
- history.push(buildSummaryItem(summaryBody));
963
- return history;
964
- }
965
- function buildDeterministicFallbackCompactionHistory(input) {
966
- const sourceTokens = Math.max(1, estimateTokens(input.items));
967
- const requestedTarget = typeof input.targetTokens === "number" && input.targetTokens > 0 ? Math.floor(input.targetTokens) : Math.floor(sourceTokens * COMPACTION_FALLBACK_TARGET_RATIO);
968
- const targetTokens = Math.max(1, Math.min(requestedTarget, Math.max(1, sourceTokens - 1)));
969
- const summaryItem = buildSummaryItem([
970
- "Non-LLM context compaction fallback.",
971
- input.cause ? `Summarizer failure: ${input.cause}` : "Summarizer failure: unavailable.",
972
- "Older assistant, tool, reasoning, and image history was dropped deterministically.",
973
- "Retained context above is limited to initial instructions and the most recent user messages that fit the fallback budget; oversized retained messages may be middle-truncated."
974
- ].join("\n"));
975
- const summaryTokens = estimateItemTokens(summaryItem);
976
- let remaining = Math.max(0, targetTokens - summaryTokens);
977
- const retainedInstructions = [];
978
- for (const item of input.items) {
979
- if (!isInitialInstructionMessage(item)) {
980
- if (isUserMessage(item)) {
981
- break;
982
- }
983
- continue;
984
- }
985
- if (remaining <= 0) {
994
+ const textTokens = estimatedTextTokens(messageText(item));
995
+ retainedReversed.push(compactMessageToTokenBudget(item, remaining));
996
+ if (textTokens > remaining) {
997
+ remaining = 0;
986
998
  break;
987
999
  }
988
- const capped = compactMessageToEstimatedItemBudget(item, Math.min(2e3, remaining));
989
- if (!capped) {
990
- continue;
991
- }
992
- const tokens = estimateItemTokens(capped);
993
- if (tokens <= remaining) {
994
- retainedInstructions.push(capped);
995
- remaining -= tokens;
996
- }
997
- }
998
- const retainedUsers = [];
999
- for (let index = input.items.length - 1; index >= 0 && remaining > 0; index -= 1) {
1000
- const item = input.items[index];
1001
- if (!isUserMessage(item) || isCompactionSummary(item)) {
1002
- continue;
1003
- }
1004
- const capped = compactMessageToEstimatedItemBudget(item, Math.min(COMPACT_USER_MESSAGE_MAX_TOKENS, remaining));
1005
- if (!capped) {
1006
- continue;
1007
- }
1008
- const tokens = estimateItemTokens(capped);
1009
- if (tokens <= remaining) {
1010
- retainedUsers.push(capped);
1011
- remaining -= tokens;
1012
- }
1000
+ remaining -= textTokens;
1013
1001
  }
1014
- retainedUsers.reverse();
1015
- return [...retainedInstructions, ...retainedUsers, summaryItem];
1002
+ const history = retainedReversed.reverse();
1003
+ history.push(buildSummaryItem(summaryBody));
1004
+ return history;
1016
1005
  }
1017
1006
  function buildSummaryItem(summaryBody) {
1018
- const trimmed = summaryBody.trim();
1007
+ const trimmed = summaryBody.trim() || "(no summary available)";
1019
1008
  return {
1020
1009
  type: "message",
1021
1010
  role: "user",
@@ -1024,9 +1013,6 @@ ${trimmed}`,
1024
1013
  [COMPACTION_SUMMARY_MARKER]: true
1025
1014
  };
1026
1015
  }
1027
- function compactUserMessage(item) {
1028
- return compactMessageToTokenBudget(item, COMPACT_USER_MESSAGE_MAX_TOKENS);
1029
- }
1030
1016
  function compactMessageToTokenBudget(item, maxTokens) {
1031
1017
  const text = messageText(item);
1032
1018
  const next = { ...item };
@@ -1037,26 +1023,6 @@ function compactMessageToTokenBudget(item, maxTokens) {
1037
1023
  next.content = contentWithoutImages(item);
1038
1024
  return next;
1039
1025
  }
1040
- function compactMessageToEstimatedItemBudget(item, maxItemTokens) {
1041
- if (maxItemTokens <= 0) {
1042
- return null;
1043
- }
1044
- const overheadTokens = estimateItemTokens({ ...item, content: "" });
1045
- let contentBudget = Math.max(0, maxItemTokens - overheadTokens);
1046
- for (let attempt = 0; attempt < 6; attempt += 1) {
1047
- const capped = compactMessageToTokenBudget(item, contentBudget);
1048
- const estimated = estimateItemTokens(capped);
1049
- if (estimated <= maxItemTokens) {
1050
- return capped;
1051
- }
1052
- const overage = estimated - maxItemTokens;
1053
- if (contentBudget <= 0) {
1054
- break;
1055
- }
1056
- contentBudget = Math.max(0, contentBudget - Math.max(1, overage));
1057
- }
1058
- return null;
1059
- }
1060
1026
  function estimatedTextTokens(text) {
1061
1027
  return Math.ceil(text.length / 4);
1062
1028
  }
@@ -1107,46 +1073,25 @@ function messageText(item) {
1107
1073
  }
1108
1074
  return "";
1109
1075
  }
1110
- function isInitialInstructionMessage(item) {
1111
- const type = itemType2(item);
1112
- const role = itemRole(item);
1113
- return type === "message" && (role === "system" || role === "developer");
1114
- }
1115
- function renderCompactionPromptInputForChat(input, options = {}) {
1116
- const rendered = input.map(renderItem);
1117
- if (typeof options.maxEstimatedTokens !== "number" || options.maxEstimatedTokens <= 0) {
1118
- return rendered.join("\n");
1119
- }
1120
- const maxChars = Math.max(1, Math.floor(options.maxEstimatedTokens * 4));
1121
- const last = rendered.at(-1);
1122
- const prefix = rendered.slice(0, -1);
1123
- let kept = prefix.slice();
1124
- while (kept.length > 0 && transcriptLength([...kept, last].filter((line) => line !== void 0)) > maxChars) {
1125
- kept.shift();
1126
- }
1127
- const lines = [...kept, last].filter((line) => line !== void 0);
1128
- const joined = lines.join("\n");
1129
- if (joined.length <= maxChars) {
1130
- return joined;
1131
- }
1132
- return truncateMiddleByChars(joined, maxChars);
1076
+ function renderCompactionPromptInputForChat(input) {
1077
+ return input.map(renderItem).join("\n");
1133
1078
  }
1134
1079
  function renderItem(item) {
1135
1080
  const type = itemType2(item) ?? "unknown";
1136
1081
  if (type === "message") {
1137
1082
  const role = itemRole(item) ?? "assistant";
1138
- return `[${role}] ${truncateForTranscript(messageText(item), 4e3)}`;
1083
+ return `[${role}] ${messageText(item)}`;
1139
1084
  }
1140
1085
  if (type === "reasoning") {
1141
1086
  return "[reasoning] (omitted)";
1142
1087
  }
1143
1088
  if (RESULT_TYPES2.has(type)) {
1144
- return `[tool_result] ${truncateForTranscript(resultText(item), 2e3)}`;
1089
+ return `[tool_result] ${resultText(item)}`;
1145
1090
  }
1146
1091
  if (RESULT_TYPE_BY_CALL_TYPE2[type]) {
1147
- return `[tool_call ${type}] ${truncateForTranscript(callText(item), 1e3)}`;
1092
+ return `[tool_call ${type}] ${callText(item)}`;
1148
1093
  }
1149
- return `[${type}] ${truncateForTranscript(safeStringify(item), 1e3)}`;
1094
+ return `[${type}] ${safeStringify(item)}`;
1150
1095
  }
1151
1096
  function resultText(item) {
1152
1097
  const output = item.output;
@@ -1169,35 +1114,15 @@ function safeStringify(value) {
1169
1114
  return String(value);
1170
1115
  }
1171
1116
  }
1172
- function truncateForTranscript(text, max) {
1173
- if (text.length <= max) {
1174
- return text;
1175
- }
1176
- return `${text.slice(0, max)}... (${text.length - max} more chars)`;
1177
- }
1178
- function transcriptLength(lines) {
1179
- if (lines.length === 0) {
1180
- return 0;
1181
- }
1182
- return lines.reduce((sum, line) => sum + line.length, 0) + Math.max(0, lines.length - 1);
1183
- }
1184
- function truncateMiddleByChars(text, maxChars) {
1185
- if (text.length <= maxChars) {
1186
- return text;
1187
- }
1188
- if (maxChars <= USER_MESSAGE_TRUNCATION_MARKER.length) {
1189
- return USER_MESSAGE_TRUNCATION_MARKER.slice(0, maxChars);
1190
- }
1191
- const keepChars = maxChars - USER_MESSAGE_TRUNCATION_MARKER.length;
1192
- const headChars = Math.ceil(keepChars / 2);
1193
- const tailChars = Math.floor(keepChars / 2);
1194
- return `${text.slice(0, headChars)}${USER_MESSAGE_TRUNCATION_MARKER}${text.slice(text.length - tailChars)}`;
1195
- }
1196
1117
 
1197
1118
  // src/sandbox-computer.ts
1198
1119
  import { computerTool, tool } from "@openai/agents";
1199
1120
  import { Capability } from "@openai/agents/sandbox";
1200
- import { KeyAction, PointerAction, PointerButton } from "@opengeni/agent-proto";
1121
+ import {
1122
+ KeyAction,
1123
+ PointerAction,
1124
+ PointerButton
1125
+ } from "@opengeni/agent-proto";
1201
1126
  function requireBoundSession(capabilityType, session) {
1202
1127
  if (!session) {
1203
1128
  throw new ComputerUnavailableError(`capability "${capabilityType}" used before bind(session)`);
@@ -1245,7 +1170,13 @@ function toKeysym(k) {
1245
1170
  if (/^f([1-9]|1[0-2])$/.test(low)) return low.toUpperCase();
1246
1171
  return low.length === 1 ? low : k;
1247
1172
  }
1248
- var BUTTON_NUM = { left: 1, wheel: 2, right: 3, back: 8, forward: 9 };
1173
+ var BUTTON_NUM = {
1174
+ left: 1,
1175
+ wheel: 2,
1176
+ right: 3,
1177
+ back: 8,
1178
+ forward: 9
1179
+ };
1249
1180
  var ComputerUnavailableError = class extends Error {
1250
1181
  constructor(message) {
1251
1182
  super(message);
@@ -1273,9 +1204,6 @@ ${stderr}` : ""}`);
1273
1204
  this.stderr = stderr;
1274
1205
  this.name = "ComputerActionError";
1275
1206
  }
1276
- cmd;
1277
- exitCode;
1278
- stderr;
1279
1207
  };
1280
1208
  var SandboxComputer = class {
1281
1209
  environment = "ubuntu";
@@ -1288,6 +1216,9 @@ var SandboxComputer = class {
1288
1216
  tmp;
1289
1217
  screenshotWarmupBudgetMs;
1290
1218
  screenshotRetryDelayMs;
1219
+ prepare;
1220
+ prepared = false;
1221
+ preparation = null;
1291
1222
  constructor(session, opts = {}) {
1292
1223
  this.session = session;
1293
1224
  this.display = opts.display ?? DEFAULT_DISPLAY;
@@ -1300,15 +1231,34 @@ var SandboxComputer = class {
1300
1231
  this.tmp = opts.screenshotTmpDir ?? "/tmp";
1301
1232
  this.screenshotWarmupBudgetMs = opts.screenshotWarmupBudgetMs ?? SCREENSHOT_WARMUP_BUDGET_MS;
1302
1233
  this.screenshotRetryDelayMs = opts.screenshotRetryDelayMs ?? SCREENSHOT_RETRY_DELAY_MS;
1234
+ this.prepare = opts.prepare;
1303
1235
  }
1304
1236
  /** Rebind to a freshly resumed-by-id session after a box rollover / re-establish. */
1305
1237
  rebind(session) {
1306
1238
  this.session = session;
1239
+ this.prepared = false;
1240
+ this.preparation = null;
1241
+ }
1242
+ async prepareOnce() {
1243
+ if (this.prepared) return;
1244
+ if (!this.prepare) {
1245
+ this.prepared = true;
1246
+ return;
1247
+ }
1248
+ this.preparation ??= this.prepare(this.session);
1249
+ try {
1250
+ await this.preparation;
1251
+ this.prepared = true;
1252
+ } catch (error) {
1253
+ this.preparation = null;
1254
+ throw error;
1255
+ }
1307
1256
  }
1308
1257
  // The single command primitive. Dual-paths exec/execCommand (F1), then uses the
1309
1258
  // established string-aware parsers (F2/F3): exitCode from the preamble, and
1310
1259
  // "still running" → a retriable failure. Returns the command OUTPUT body.
1311
1260
  async x(cmd) {
1261
+ await this.prepareOnce();
1312
1262
  const args = {
1313
1263
  cmd: `DISPLAY=${this.display} ${cmd}`,
1314
1264
  ...this.runAs ? { runAs: this.runAs } : {},
@@ -1356,7 +1306,9 @@ var SandboxComputer = class {
1356
1306
  await this.x(`scrot --pointer --overwrite ${f}`);
1357
1307
  const bytes = await this.readScreenshotBytes(f);
1358
1308
  if (bytes.length === 0) {
1359
- throw new ComputerUnavailableError("scrot produced an empty screenshot (display not up?)");
1309
+ throw new ComputerUnavailableError(
1310
+ "scrot produced an empty screenshot (display not up?)"
1311
+ );
1360
1312
  }
1361
1313
  return Buffer.from(bytes).toString("base64");
1362
1314
  } catch (error) {
@@ -1365,7 +1317,9 @@ var SandboxComputer = class {
1365
1317
  throw error;
1366
1318
  }
1367
1319
  } finally {
1368
- await this.x(`rm -f ${f}`).catch(() => void 0);
1320
+ if (this.prepared) {
1321
+ await this.x(`rm -f ${f}`).catch(() => void 0);
1322
+ }
1369
1323
  }
1370
1324
  if (Date.now() + this.screenshotRetryDelayMs >= deadline) {
1371
1325
  break;
@@ -1408,7 +1362,9 @@ var SandboxComputer = class {
1408
1362
  if (typeof this.session.execCommand === "function") {
1409
1363
  return stripExecBanner(await this.session.execCommand(args));
1410
1364
  }
1411
- throw new ComputerUnavailableError("session cannot run commands (no exec/execCommand) \u2014 screenshots unavailable");
1365
+ throw new ComputerUnavailableError(
1366
+ "session cannot run commands (no exec/execCommand) \u2014 screenshots unavailable"
1367
+ );
1412
1368
  }
1413
1369
  // Read the screenshot PNG bytes off the box. A SINGLE `base64 <file>` over Modal's
1414
1370
  // exec silently returns "" once the base64 exceeds the exec-stdout buffer cap (a full
@@ -1545,7 +1501,10 @@ var NativeDesktopComputer = class {
1545
1501
  }
1546
1502
  async pointer(x, y, action, button) {
1547
1503
  const n = this.toNative(x, y);
1548
- await this.session.desktopInput({ $case: "pointer", pointer: { x: n.x, y: n.y, action, button } });
1504
+ await this.session.desktopInput({
1505
+ $case: "pointer",
1506
+ pointer: { x: n.x, y: n.y, action, button }
1507
+ });
1549
1508
  }
1550
1509
  async screenshot() {
1551
1510
  let lastError;
@@ -1559,7 +1518,9 @@ var NativeDesktopComputer = class {
1559
1518
  try {
1560
1519
  const { png, width, height, nativeWidth, nativeHeight } = await this.session.screenshot();
1561
1520
  if (png.length === 0) {
1562
- throw new ComputerUnavailableError("native desktop screenshot returned an empty frame (display not up?)");
1521
+ throw new ComputerUnavailableError(
1522
+ "native desktop screenshot returned an empty frame (display not up?)"
1523
+ );
1563
1524
  }
1564
1525
  this.lastEncoded = [width, height];
1565
1526
  this.lastNative = [nativeWidth || width, nativeHeight || height];
@@ -1573,44 +1534,82 @@ var NativeDesktopComputer = class {
1573
1534
  }
1574
1535
  }
1575
1536
  const reason = lastError instanceof Error ? lastError.message : String(lastError);
1576
- console.warn(`[NativeDesktopComputer] screenshot failed after ${attempt} attempt(s): ${reason}`);
1537
+ console.warn(
1538
+ `[NativeDesktopComputer] screenshot failed after ${attempt} attempt(s): ${reason}`
1539
+ );
1577
1540
  if (lastError instanceof Error) {
1578
1541
  throw lastError;
1579
1542
  }
1580
- throw new ComputerUnavailableError("native desktop screenshot returned an empty frame (display not up?)");
1543
+ throw new ComputerUnavailableError(
1544
+ "native desktop screenshot returned an empty frame (display not up?)"
1545
+ );
1581
1546
  }
1582
1547
  async click(x, y, button) {
1583
1548
  this.guardWrite();
1584
- await this.pointer(x, y, PointerAction.POINTER_ACTION_CLICK, POINTER_BUTTON[button] ?? PointerButton.POINTER_BUTTON_LEFT);
1549
+ await this.pointer(
1550
+ x,
1551
+ y,
1552
+ PointerAction.POINTER_ACTION_CLICK,
1553
+ POINTER_BUTTON[button] ?? PointerButton.POINTER_BUTTON_LEFT
1554
+ );
1585
1555
  }
1586
1556
  async doubleClick(x, y) {
1587
1557
  this.guardWrite();
1588
- await this.pointer(x, y, PointerAction.POINTER_ACTION_DOUBLE_CLICK, PointerButton.POINTER_BUTTON_LEFT);
1558
+ await this.pointer(
1559
+ x,
1560
+ y,
1561
+ PointerAction.POINTER_ACTION_DOUBLE_CLICK,
1562
+ PointerButton.POINTER_BUTTON_LEFT
1563
+ );
1589
1564
  }
1590
1565
  async move(x, y) {
1591
1566
  this.guardWrite();
1592
- await this.pointer(x, y, PointerAction.POINTER_ACTION_MOVE, PointerButton.POINTER_BUTTON_UNSPECIFIED);
1567
+ await this.pointer(
1568
+ x,
1569
+ y,
1570
+ PointerAction.POINTER_ACTION_MOVE,
1571
+ PointerButton.POINTER_BUTTON_UNSPECIFIED
1572
+ );
1593
1573
  }
1594
1574
  async scroll(x, y, sx, sy) {
1595
1575
  this.guardWrite();
1596
1576
  const n = this.toNative(x, y);
1597
- await this.session.desktopInput({ $case: "scroll", scroll: { x: n.x, y: n.y, deltaX: sx, deltaY: sy } });
1577
+ await this.session.desktopInput({
1578
+ $case: "scroll",
1579
+ scroll: { x: n.x, y: n.y, deltaX: sx, deltaY: sy }
1580
+ });
1598
1581
  }
1599
1582
  async type(text) {
1600
1583
  this.guardWrite();
1601
- await this.session.desktopInput({ $case: "key", key: { key: text, isText: true, action: KeyAction.KEY_ACTION_PRESS } });
1584
+ await this.session.desktopInput({
1585
+ $case: "key",
1586
+ key: { key: text, isText: true, action: KeyAction.KEY_ACTION_PRESS }
1587
+ });
1602
1588
  }
1603
1589
  async keypress(keys) {
1604
1590
  this.guardWrite();
1605
- await this.session.desktopInput({ $case: "key", key: { key: keys.join("+"), isText: false, action: KeyAction.KEY_ACTION_PRESS } });
1591
+ await this.session.desktopInput({
1592
+ $case: "key",
1593
+ key: { key: keys.join("+"), isText: false, action: KeyAction.KEY_ACTION_PRESS }
1594
+ });
1606
1595
  }
1607
1596
  async drag(path) {
1608
1597
  this.guardWrite();
1609
1598
  if (path.length === 0) return;
1610
1599
  const [sx, sy] = path[0];
1611
- await this.pointer(sx, sy, PointerAction.POINTER_ACTION_DOWN, PointerButton.POINTER_BUTTON_LEFT);
1600
+ await this.pointer(
1601
+ sx,
1602
+ sy,
1603
+ PointerAction.POINTER_ACTION_DOWN,
1604
+ PointerButton.POINTER_BUTTON_LEFT
1605
+ );
1612
1606
  for (const [px, py] of path.slice(1)) {
1613
- await this.pointer(px, py, PointerAction.POINTER_ACTION_MOVE, PointerButton.POINTER_BUTTON_LEFT);
1607
+ await this.pointer(
1608
+ px,
1609
+ py,
1610
+ PointerAction.POINTER_ACTION_MOVE,
1611
+ PointerButton.POINTER_BUTTON_LEFT
1612
+ );
1614
1613
  }
1615
1614
  const [ex, ey] = path[path.length - 1];
1616
1615
  await this.pointer(ex, ey, PointerAction.POINTER_ACTION_UP, PointerButton.POINTER_BUTTON_LEFT);
@@ -1624,14 +1623,20 @@ function isNativeDesktopSession(session) {
1624
1623
  return typeof s.desktopInput === "function" && typeof s.screenshot === "function";
1625
1624
  }
1626
1625
  function sniffScreenshotMediaType(bytes) {
1627
- if (bytes[0] === 137 && bytes[1] === 80 && bytes[2] === 78 && bytes[3] === 71) return "image/png";
1626
+ if (bytes[0] === 137 && bytes[1] === 80 && bytes[2] === 78 && bytes[3] === 71)
1627
+ return "image/png";
1628
1628
  if (bytes[0] === 255 && bytes[1] === 216 && bytes[2] === 255) return "image/jpeg";
1629
- if (bytes[0] === 71 && bytes[1] === 73 && bytes[2] === 70 && bytes[3] === 56) return "image/gif";
1630
- if (bytes[0] === 82 && bytes[1] === 73 && bytes[2] === 70 && bytes[3] === 70 && bytes[8] === 87 && bytes[9] === 69 && bytes[10] === 66 && bytes[11] === 80) return "image/webp";
1629
+ if (bytes[0] === 71 && bytes[1] === 73 && bytes[2] === 70 && bytes[3] === 56)
1630
+ return "image/gif";
1631
+ if (bytes[0] === 82 && bytes[1] === 73 && bytes[2] === 70 && bytes[3] === 70 && bytes[8] === 87 && bytes[9] === 69 && bytes[10] === 66 && bytes[11] === 80)
1632
+ return "image/webp";
1631
1633
  return "image/png";
1632
1634
  }
1633
1635
  function imageOutputFromScreenshotBytes(bytes) {
1634
- return { type: "image", image: { data: Uint8Array.from(bytes), mediaType: sniffScreenshotMediaType(bytes) } };
1636
+ return {
1637
+ type: "image",
1638
+ image: { data: Uint8Array.from(bytes), mediaType: sniffScreenshotMediaType(bytes) }
1639
+ };
1635
1640
  }
1636
1641
  function renderImageForTextTransport(output) {
1637
1642
  if (typeof output === "string") return output;
@@ -1646,8 +1651,14 @@ function supportsStructuredToolOutputTransport(modelInstance) {
1646
1651
  }
1647
1652
  var COMPUTER_READ_ONLY_MESSAGE = "computer-use is read-only for this session \u2014 click, double_click, move, scroll, type, keypress, and drag are disabled. Call computer_screenshot to observe the desktop.";
1648
1653
  var COORD_PROPS = {
1649
- x: { type: "integer", description: "X coordinate in the pixels of the most recent computer_screenshot" },
1650
- y: { type: "integer", description: "Y coordinate in the pixels of the most recent computer_screenshot" }
1654
+ x: {
1655
+ type: "integer",
1656
+ description: "X coordinate in the pixels of the most recent computer_screenshot"
1657
+ },
1658
+ y: {
1659
+ type: "integer",
1660
+ description: "Y coordinate in the pixels of the most recent computer_screenshot"
1661
+ }
1651
1662
  };
1652
1663
  function objectSchema(properties, required) {
1653
1664
  return { type: "object", properties, required, additionalProperties: false };
@@ -1681,14 +1692,24 @@ function computerFunctionTools(computer, readOnly, needsApproval, imageFunctionR
1681
1692
  name: "computer_click",
1682
1693
  description: "Click the mouse at (x, y). `button` is one of left|right|wheel|back|forward (default left). Take a computer_screenshot first to find coordinates.",
1683
1694
  parameters: objectSchema(
1684
- { ...COORD_PROPS, button: { type: "string", enum: ["left", "right", "wheel", "back", "forward"], description: "Mouse button; defaults to left" } },
1695
+ {
1696
+ ...COORD_PROPS,
1697
+ button: {
1698
+ type: "string",
1699
+ enum: ["left", "right", "wheel", "back", "forward"],
1700
+ description: "Mouse button; defaults to left"
1701
+ }
1702
+ },
1685
1703
  ["x", "y"]
1686
1704
  ),
1687
1705
  strict: false,
1688
1706
  ...approval,
1689
1707
  execute: async (input) => {
1690
1708
  const { x, y, button } = input;
1691
- return write(`clicked ${button ?? "left"} at (${x}, ${y})`, () => computer.click(x, y, button ?? "left"));
1709
+ return write(
1710
+ `clicked ${button ?? "left"} at (${x}, ${y})`,
1711
+ () => computer.click(x, y, button ?? "left")
1712
+ );
1692
1713
  }
1693
1714
  }),
1694
1715
  tool({
@@ -1719,8 +1740,14 @@ function computerFunctionTools(computer, readOnly, needsApproval, imageFunctionR
1719
1740
  parameters: objectSchema(
1720
1741
  {
1721
1742
  ...COORD_PROPS,
1722
- scroll_x: { type: "integer", description: "Horizontal scroll amount in pixels (positive = right)" },
1723
- scroll_y: { type: "integer", description: "Vertical scroll amount in pixels (positive = down)" }
1743
+ scroll_x: {
1744
+ type: "integer",
1745
+ description: "Horizontal scroll amount in pixels (positive = right)"
1746
+ },
1747
+ scroll_y: {
1748
+ type: "integer",
1749
+ description: "Vertical scroll amount in pixels (positive = down)"
1750
+ }
1724
1751
  },
1725
1752
  ["x", "y", "scroll_x", "scroll_y"]
1726
1753
  ),
@@ -1728,13 +1755,19 @@ function computerFunctionTools(computer, readOnly, needsApproval, imageFunctionR
1728
1755
  ...approval,
1729
1756
  execute: async (input) => {
1730
1757
  const { x, y, scroll_x, scroll_y } = input;
1731
- return write(`scrolled (${scroll_x}, ${scroll_y}) at (${x}, ${y})`, () => computer.scroll(x, y, scroll_x, scroll_y));
1758
+ return write(
1759
+ `scrolled (${scroll_x}, ${scroll_y}) at (${x}, ${y})`,
1760
+ () => computer.scroll(x, y, scroll_x, scroll_y)
1761
+ );
1732
1762
  }
1733
1763
  }),
1734
1764
  tool({
1735
1765
  name: "computer_type",
1736
1766
  description: "Type a literal text string at the current keyboard focus. Click the target field first.",
1737
- parameters: objectSchema({ text: { type: "string", description: "The literal text to type" } }, ["text"]),
1767
+ parameters: objectSchema(
1768
+ { text: { type: "string", description: "The literal text to type" } },
1769
+ ["text"]
1770
+ ),
1738
1771
  strict: false,
1739
1772
  ...approval,
1740
1773
  execute: async (input) => {
@@ -1746,7 +1779,13 @@ function computerFunctionTools(computer, readOnly, needsApproval, imageFunctionR
1746
1779
  name: "computer_keypress",
1747
1780
  description: 'Press a key or chord. `keys` is an ordered list pressed together, e.g. ["ctrl","c"] or ["Enter"]. Use key names (ctrl, alt, shift, cmd, enter, tab, esc, arrows\u2026), not characters.',
1748
1781
  parameters: objectSchema(
1749
- { keys: { type: "array", items: { type: "string" }, description: "Keys pressed together as a chord" } },
1782
+ {
1783
+ keys: {
1784
+ type: "array",
1785
+ items: { type: "string" },
1786
+ description: "Keys pressed together as a chord"
1787
+ }
1788
+ },
1750
1789
  ["keys"]
1751
1790
  ),
1752
1791
  strict: false,
@@ -1792,7 +1831,6 @@ var ComputerUseCapability = class extends Capability {
1792
1831
  super();
1793
1832
  this.args = args;
1794
1833
  }
1795
- args;
1796
1834
  type = "computer-use";
1797
1835
  tools() {
1798
1836
  const session = requireBoundSession("computer-use", this._session);
@@ -1804,22 +1842,45 @@ var ComputerUseCapability = class extends Capability {
1804
1842
  ...this.args.readOnly !== void 0 ? { readOnly: this.args.readOnly } : {},
1805
1843
  ...this.args.display ? { display: this.args.display } : {},
1806
1844
  // The SDK base exposes the bound runAs as a protected field.
1807
- ...typeof this._runAs === "string" ? { runAs: this._runAs } : {}
1845
+ ...typeof this._runAs === "string" ? { runAs: this._runAs } : {},
1846
+ // Crucially lazy: tool registration does no provider work. The first
1847
+ // click/type/screenshot boots the display, then lets the worker start
1848
+ // any computer-use-only recording. One SandboxComputer instance caches
1849
+ // this for the rest of the turn.
1850
+ prepare: async (preparedSession) => {
1851
+ await ensureDisplayStack(preparedSession);
1852
+ await this.args.onReady?.(preparedSession);
1853
+ }
1808
1854
  });
1809
1855
  switch (this.args.toolMode) {
1810
1856
  case "hosted":
1811
1857
  return [this.hostedComputerTool(computer)];
1812
1858
  case "function-image":
1813
- return computerFunctionTools(computer, this.args.readOnly ?? false, this.args.needsApproval, true);
1859
+ return computerFunctionTools(
1860
+ computer,
1861
+ this.args.readOnly ?? false,
1862
+ this.args.needsApproval,
1863
+ true
1864
+ );
1814
1865
  case "function-text":
1815
- return computerFunctionTools(computer, this.args.readOnly ?? false, this.args.needsApproval, false);
1866
+ return computerFunctionTools(
1867
+ computer,
1868
+ this.args.readOnly ?? false,
1869
+ this.args.needsApproval,
1870
+ false
1871
+ );
1816
1872
  case void 0:
1817
1873
  break;
1818
1874
  }
1819
1875
  if (supportsStructuredToolOutputTransport(this._modelInstance)) {
1820
1876
  return [this.hostedComputerTool(computer)];
1821
1877
  }
1822
- return computerFunctionTools(computer, this.args.readOnly ?? false, this.args.needsApproval, this.args.imageFunctionResults ?? false);
1878
+ return computerFunctionTools(
1879
+ computer,
1880
+ this.args.readOnly ?? false,
1881
+ this.args.needsApproval,
1882
+ this.args.imageFunctionResults ?? false
1883
+ );
1823
1884
  }
1824
1885
  /** The single HOSTED `computer_use_preview` tool bound to `computer` — identical
1825
1886
  * construction for the explicit "hosted" mode and the legacy structured-sniff path. */
@@ -1833,8 +1894,52 @@ var ComputerUseCapability = class extends Capability {
1833
1894
 
1834
1895
  // src/index.ts
1835
1896
  import { OpenAIChatCompletionsModel as OpenAIChatCompletionsModel2, OpenAIResponsesModel as OpenAIResponsesModel2 } from "@openai/agents";
1897
+
1898
+ // src/usage-telemetry.ts
1899
+ function modelCallUsageTelemetry(usage) {
1900
+ return {
1901
+ inputTokens: finiteNumberOrNull(usage?.inputTokens),
1902
+ outputTokens: finiteNumberOrNull(usage?.outputTokens),
1903
+ cachedTokens: usage ? firstPositiveDetailNumber(usage.inputTokensDetails, [
1904
+ "cached_tokens",
1905
+ "cachedInputTokens",
1906
+ "cached_input_tokens"
1907
+ ]) : null,
1908
+ reasoningTokens: usage ? firstPositiveDetailNumber(usage.outputTokensDetails, [
1909
+ "reasoning_tokens",
1910
+ "reasoningTokens",
1911
+ "reasoning_output_tokens"
1912
+ ]) : null
1913
+ };
1914
+ }
1915
+ function firstPositiveDetailNumber(details, keys) {
1916
+ if (!details) {
1917
+ return null;
1918
+ }
1919
+ const entries = Array.isArray(details) ? details : [details];
1920
+ let sawZero = false;
1921
+ for (const entry of entries) {
1922
+ for (const key of keys) {
1923
+ const value = finiteNumberOrNull(entry[key]);
1924
+ if (value !== null && value > 0) {
1925
+ return value;
1926
+ }
1927
+ if (value === 0) {
1928
+ sawZero = true;
1929
+ }
1930
+ }
1931
+ }
1932
+ return sawZero ? 0 : null;
1933
+ }
1934
+ function finiteNumberOrNull(value) {
1935
+ return typeof value === "number" && Number.isFinite(value) ? value : null;
1936
+ }
1937
+
1938
+ // src/index.ts
1836
1939
  import { MaxTurnsExceededError as MaxTurnsExceededError2 } from "@openai/agents";
1837
- setSelfhostedApplyDiff(applyDiff);
1940
+ setSelfhostedApplyDiff(
1941
+ applyDiff
1942
+ );
1838
1943
  ensureReadableStreamFrom();
1839
1944
  var SANDBOX_LIFECYCLE_COMMAND_TIMEOUT_MS = 12e4;
1840
1945
  function ensureReadableStreamFrom() {
@@ -1902,7 +2007,7 @@ function buildOpenAIClientFromSettings(settings, providerId = settings.openaiPro
1902
2007
  defaultQuery: azureOpenAIDefaultQuery(settings, baseURL),
1903
2008
  defaultHeaders: settings.azureOpenaiAdToken && !settings.azureOpenaiApiKey ? { Authorization: `Bearer ${settings.azureOpenaiAdToken}` } : void 0,
1904
2009
  // Rewrite every outbound /responses computer_call to the ACTIONS-ONLY shape
1905
- // the GA Azure computer tool (gpt-5.5) accepts. This is the lowest reachable
2010
+ // the GA Azure computer tool accepts. This is the lowest reachable
1906
2011
  // seam — below the SDK responses converter, which always re-synthesizes BOTH
1907
2012
  // `action` and `actions` (rejected 400 "exactly one of action or actions").
1908
2013
  // See computerCallNormalizingFetch / rewriteComputerCallsToActionsOnly.
@@ -1922,19 +2027,35 @@ function buildProviderClient(provider, settings) {
1922
2027
  if (cached) {
1923
2028
  return cached;
1924
2029
  }
1925
- const client = provider.builtin ? buildOpenAIClientFromSettings(settings, provider.id) : provider.kind === "codex-subscription" ? new OpenAI({
1926
- apiKey: provider.apiKey ?? "codex-subscription",
1927
- ...provider.baseUrl ? { baseURL: provider.baseUrl } : {},
1928
- maxRetries: settings.openaiMaxRetries,
1929
- fetch: codexSubscriptionFetch(instrumentedModelFetch(provider.id, globalThis.fetch))
1930
- }) : new OpenAI({
1931
- ...provider.apiKey ? { apiKey: provider.apiKey } : {},
1932
- ...provider.baseUrl ? { baseURL: provider.baseUrl } : {},
1933
- maxRetries: settings.openaiMaxRetries,
1934
- ...provider.defaultQuery ? { defaultQuery: provider.defaultQuery } : {},
1935
- ...provider.defaultHeaders ? { defaultHeaders: provider.defaultHeaders } : {},
1936
- fetch: instrumentedModelFetch(provider.id, globalThis.fetch)
1937
- });
2030
+ const client = provider.builtin ? buildOpenAIClientFromSettings(settings, provider.id) : provider.kind === "codex-subscription" ? (
2031
+ // Codex subscription: the static apiKey is a placeholder — the real per-request
2032
+ // bearer + ChatGPT-Account-ID, the /responses->/codex/responses rewrite, and the
2033
+ // body normalization are all injected by codexSubscriptionFetch, which reads the
2034
+ // per-workspace token from codexRequestStorage (AsyncLocalStorage) at call time.
2035
+ // The provider id is constant ("codex-subscription"), so one cached client serves
2036
+ // every workspace without baking a token into it.
2037
+ new OpenAI({
2038
+ apiKey: provider.apiKey ?? "codex-subscription",
2039
+ ...provider.baseUrl ? { baseURL: provider.baseUrl } : {},
2040
+ // Codex transport owns exactly one explicit 401 refresh/retry. Blind
2041
+ // SDK retries on network/5xx/partial streams can replay provider work
2042
+ // or external tool side effects without a durable checkpoint.
2043
+ maxRetries: 0,
2044
+ fetch: codexSubscriptionFetch(instrumentedModelFetch(provider.id, globalThis.fetch))
2045
+ })
2046
+ ) : (
2047
+ // ResolvedModelProvider.apiKey is already the resolved key (configuredProviders
2048
+ // ran resolveProviderApiKey at config time, collapsing apiKey/apiKeyEnv), so it
2049
+ // is passed straight through here rather than re-resolved.
2050
+ new OpenAI({
2051
+ ...provider.apiKey ? { apiKey: provider.apiKey } : {},
2052
+ ...provider.baseUrl ? { baseURL: provider.baseUrl } : {},
2053
+ maxRetries: settings.openaiMaxRetries,
2054
+ ...provider.defaultQuery ? { defaultQuery: provider.defaultQuery } : {},
2055
+ ...provider.defaultHeaders ? { defaultHeaders: provider.defaultHeaders } : {},
2056
+ fetch: instrumentedModelFetch(provider.id, globalThis.fetch)
2057
+ })
2058
+ );
1938
2059
  providerClientCache.set(provider.id, client);
1939
2060
  return client;
1940
2061
  }
@@ -1958,11 +2079,13 @@ var MultiProviderModelProvider = class {
1958
2079
  constructor(settings) {
1959
2080
  this.settings = settings;
1960
2081
  }
1961
- settings;
1962
2082
  fallback;
1963
2083
  async getModel(modelName) {
1964
2084
  if (modelName) {
1965
- const resolved = resolveTurnModel(settingsForRunScopedModelResolution(this.settings, modelName), modelName);
2085
+ const resolved = resolveTurnModel(
2086
+ settingsForRunScopedModelResolution(this.settings, modelName),
2087
+ modelName
2088
+ );
1966
2089
  if (resolved) {
1967
2090
  if (modelName.startsWith(CODEX_MODEL_ID_PREFIX) && resolved.provider.kind !== "codex-subscription") {
1968
2091
  throw new CodexSubscriptionUnavailableError(modelName);
@@ -1999,6 +2122,14 @@ var CodexSubscriptionUnavailableError = class extends Error {
1999
2122
  this.name = "CodexSubscriptionUnavailableError";
2000
2123
  }
2001
2124
  };
2125
+ var WorkspaceModelPolicyBlockedError = class extends Error {
2126
+ constructor(modelName, providerId, reason) {
2127
+ super(
2128
+ reason === "provider" ? `Model "${modelName}" is not available in this workspace: its provider ("${providerId}") is not in the workspace's allowed providers. Pick an allowed model, or ask a workspace admin to change the workspace model policy.` : `Model "${modelName}" is not in this workspace's allowed models. Pick an allowed model, or ask a workspace admin to change the workspace model policy.`
2129
+ );
2130
+ this.name = "WorkspaceModelPolicyBlockedError";
2131
+ }
2132
+ };
2002
2133
  function configureOpenAI(settings) {
2003
2134
  setOpenAIResponsesTransport(settings.openaiResponsesTransport);
2004
2135
  setTracingDisabled(settings.disableOpenaiTracing || !settings.observabilityOtlpEndpoint);
@@ -2017,7 +2148,7 @@ function configureOpenAI(settings) {
2017
2148
  setDefaultModelProvider(router);
2018
2149
  }
2019
2150
  function instrumentedModelFetch(provider, inner) {
2020
- return (async (input, init) => {
2151
+ return async (input, init) => {
2021
2152
  if (!isModelCallFetch(input)) {
2022
2153
  return await inner(input, init);
2023
2154
  }
@@ -2030,7 +2161,7 @@ function instrumentedModelFetch(provider, inner) {
2030
2161
  recordModelCallMetric(provider, "failed", started);
2031
2162
  throw error;
2032
2163
  }
2033
- });
2164
+ };
2034
2165
  }
2035
2166
  function isModelCallFetch(input) {
2036
2167
  const rawUrl = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
@@ -2056,37 +2187,43 @@ async function summarizeForCompaction(settings, input, options = {}) {
2056
2187
  const api = options.api ?? "responses";
2057
2188
  const model = options.model ?? settings.openaiModel;
2058
2189
  const maxTokens = options.maxOutputTokens ?? SUMMARY_BUFFER_TOKENS;
2059
- const transcript = renderCompactionPromptInputForChat(
2060
- input,
2061
- typeof options.maxTranscriptTokens === "number" && options.maxTranscriptTokens > 0 ? { maxEstimatedTokens: options.maxTranscriptTokens } : {}
2062
- );
2063
- try {
2064
- if (api === "chat") {
2065
- const completion = await client.chat.completions.create({
2066
- model,
2067
- max_tokens: maxTokens,
2068
- messages: [{ role: "user", content: transcript }]
2069
- });
2070
- const text2 = completion.choices?.[0]?.message?.content;
2071
- const trimmed2 = typeof text2 === "string" ? text2.trim() : "";
2072
- return trimmed2.length > 0 ? trimmed2 : null;
2073
- }
2074
- const response = await client.responses.create({
2190
+ if (api === "chat") {
2191
+ const transcript = renderCompactionPromptInputForChat(input);
2192
+ const completion = await client.chat.completions.create({
2075
2193
  model,
2076
- // store:false is the OpenAI-platform-only storeless precondition; Azure
2077
- // rejects it. The summarizer's resolved client is OpenAI/Azure on the
2078
- // built-in path (api "responses"), so gate it on the built-in provider.
2079
- ...settings.openaiProvider === "azure" ? {} : { store: false },
2080
- max_output_tokens: maxTokens,
2081
- input: transcript
2194
+ max_tokens: maxTokens,
2195
+ messages: [{ role: "user", content: transcript }],
2196
+ ...options.promptCacheKey ? { prompt_cache_key: options.promptCacheKey } : {}
2082
2197
  });
2083
- const text = extractResponseOutputText(response);
2084
- const trimmed = text.trim();
2085
- return trimmed.length > 0 ? trimmed : null;
2086
- } catch (error) {
2087
- console.error("context compaction summarize failed", error);
2088
- return null;
2198
+ const usage2 = modelResponseUsageFromResponse(completion);
2199
+ if (usage2) {
2200
+ await options.onUsage?.(usage2);
2201
+ }
2202
+ const text = completion.choices?.[0]?.message?.content;
2203
+ return typeof text === "string" ? text.trim() : "";
2204
+ }
2205
+ const request = {
2206
+ systemInstructions: options.systemInstructions ?? "",
2207
+ input,
2208
+ modelSettings: {
2209
+ maxTokens,
2210
+ // Azure rejects store:false; the Codex subscription transport enforces
2211
+ // it independently. The OpenAI platform path remains explicitly storeless.
2212
+ ...settings.openaiProvider === "azure" ? {} : { store: false },
2213
+ ...options.promptCacheKey ? { providerData: { prompt_cache_key: options.promptCacheKey } } : {}
2214
+ },
2215
+ tools: [],
2216
+ toolsExplicitlyProvided: true,
2217
+ outputType: "text",
2218
+ handoffs: [],
2219
+ tracing: false
2220
+ };
2221
+ const response = await new CompactionResponsesModel(client, model).fetchResponse(request);
2222
+ const usage = modelResponseUsageFromResponse(response);
2223
+ if (usage) {
2224
+ await options.onUsage?.(usage);
2089
2225
  }
2226
+ return extractResponseOutputText(response).trim();
2090
2227
  }
2091
2228
  function extractResponseOutputText(response) {
2092
2229
  if (!response || typeof response !== "object") {
@@ -2123,6 +2260,19 @@ function extractResponseOutputText(response) {
2123
2260
  }
2124
2261
  return parts.join("");
2125
2262
  }
2263
+ var CompactionResponsesModel = class extends OpenAIResponsesModel {
2264
+ fetchResponse(request) {
2265
+ return this._fetchResponse(request, false);
2266
+ }
2267
+ };
2268
+ function rigInstructions(rig) {
2269
+ return [
2270
+ `This session runs on rig "${rig.name}" (active version v${rig.version}) \u2014 a shared, versioned sandbox machine definition for your workspace.`,
2271
+ "Your sandbox is an EPHEMERAL FORK of that rig: you have root and may install anything freely, but everything you change here is junk that dies with the box and never reaches the rig or other sessions.",
2272
+ "For a DURABLE, team-wide change (tooling every future session on this rig should have), propose it with rig_propose_change, passing the EXACT command that already worked in this box \u2014 never assume an unverified change propagates.",
2273
+ "If tooling you expect is missing, consult rig_get to see the rig's current setup and checks before reinstalling."
2274
+ ];
2275
+ }
2126
2276
  function workspaceEnvironmentInstructions(environment) {
2127
2277
  const lines = [
2128
2278
  `A workspace environment named "${environment.name}" is attached to this session; its variables are exported in the sandbox shell environment.`
@@ -2137,14 +2287,17 @@ function workspaceEnvironmentInstructions(environment) {
2137
2287
  }
2138
2288
  return lines;
2139
2289
  }
2140
- function coreInstructions(workspaceEnvironment) {
2290
+ function coreInstructions(workspaceEnvironment, rig) {
2141
2291
  return [
2142
2292
  "If the session has a goal, you own it: keep working until you call opengeni__goal_complete with concrete evidence or opengeni__goal_pause with a rationale; revise it with opengeni__goal_update; create one with opengeni__goal_set when given a long-running objective.",
2143
- ...workspaceEnvironment ? workspaceEnvironmentInstructions(workspaceEnvironment) : []
2293
+ ...workspaceEnvironment ? workspaceEnvironmentInstructions(workspaceEnvironment) : [],
2294
+ // Rig doctrine (M3): data-conditional, inside the non-bypassable CORE so a
2295
+ // white-label persona template can never drop it. Absent for rig-less sessions.
2296
+ ...rig ? rigInstructions(rig) : []
2144
2297
  ];
2145
2298
  }
2146
- function composeAgentInstructions(template, workspaceEnvironment) {
2147
- const core = coreInstructions(workspaceEnvironment).join(" ");
2299
+ function composeAgentInstructions(template, workspaceEnvironment, rig) {
2300
+ const core = coreInstructions(workspaceEnvironment, rig).join(" ");
2148
2301
  if (template.includes(AGENT_INSTRUCTIONS_CORE_PLACEHOLDER)) {
2149
2302
  return template.split(AGENT_INSTRUCTIONS_CORE_PLACEHOLDER).join(core);
2150
2303
  }
@@ -2154,27 +2307,52 @@ function appendSessionInstructions(composed, sessionInstructions) {
2154
2307
  const trimmed = sessionInstructions?.trim();
2155
2308
  return trimmed ? `${composed} ${trimmed}` : composed;
2156
2309
  }
2157
- function appendToolspaceInstructions(composed, toolspaceAvailable) {
2158
- return toolspaceAvailable ? `${composed} ${TOOLSPACE_PROGRAMMATIC_DIRECTIVE}` : composed;
2310
+ function appendPersistentSessionSettings(composed, settings) {
2311
+ if (!settings) {
2312
+ return composed;
2313
+ }
2314
+ const childState = settings.childNotificationsMode ? `; spawned-worker completion notifications are ${settings.childNotificationsMode}` : "";
2315
+ const persistentTools = settings.childNotificationsMode ? "opengeni__set_session_title or opengeni__set_child_notifications_mode" : "opengeni__set_session_title";
2316
+ return `${composed} Persistent session settings already in effect: the display title is ${settings.titleIsSet ? "set" : "not set"}${childState}. These settings persist across turns, continuations, and interruptions. Do not call ${persistentTools} merely to reassert an unchanged value; call them only when the current value actually needs to change.`;
2317
+ }
2318
+ function appendWorkspaceMemory(composed, workspaceMemory) {
2319
+ const trimmed = workspaceMemory?.trim();
2320
+ return trimmed ? `${composed} ${trimmed}` : composed;
2321
+ }
2322
+ function appendToolspaceInstructions(composed, toolspaceAvailable) {
2323
+ return toolspaceAvailable ? `${composed} ${TOOLSPACE_PROGRAMMATIC_DIRECTIVE}` : composed;
2159
2324
  }
2160
2325
  function appendGenesisTitleDirective(instructions, genesisTitleHint) {
2161
2326
  return genesisTitleHint ? `${instructions} ${GENESIS_TITLE_DIRECTIVE}` : instructions;
2162
2327
  }
2163
2328
  var agentFileDownloads = /* @__PURE__ */ new WeakMap();
2164
2329
  var agentRepositoryCloneHooks = /* @__PURE__ */ new WeakMap();
2165
- var agentGitTokenSeed = /* @__PURE__ */ new WeakMap();
2330
+ var agentGitTokenSeeds = /* @__PURE__ */ new WeakMap();
2166
2331
  var agentToolspaceTokenSeed = /* @__PURE__ */ new WeakMap();
2332
+ var agentsNeedingGenesisTitleDirective = /* @__PURE__ */ new WeakSet();
2167
2333
  var agentActiveSandboxBackend = /* @__PURE__ */ new WeakMap();
2334
+ var agentRigSetup = /* @__PURE__ */ new WeakMap();
2335
+ var agentRigCredentialHooks = /* @__PURE__ */ new WeakMap();
2168
2336
  function mcpToolErrorOutput(error) {
2169
2337
  const details = error instanceof Error ? error.message : String(error);
2170
- return { isError: true, content: [{ type: "text", text: `An error occurred while running the tool. Please try again. Error: ${details}` }] };
2338
+ return {
2339
+ isError: true,
2340
+ content: [
2341
+ {
2342
+ type: "text",
2343
+ text: `An error occurred while running the tool. Please try again. Error: ${details}`
2344
+ }
2345
+ ]
2346
+ };
2171
2347
  }
2172
2348
  var mcpToolErrorFunction = ({ error }) => mcpToolErrorOutput(error);
2173
2349
  function buildOpenGeniAgent(settings, resources, options = {}) {
2174
- const compactionMode = options.compactionMode ?? resolveContextCompactionMode(settings);
2175
2350
  const hostedWebSearch = options.hostedWebSearch ?? settings.webSearchEnabled;
2176
2351
  const encryptedReasoning = options.encryptedReasoning ?? settings.openaiReasoningEncryptedContent;
2177
- const contextWindowTokens = options.contextWindowTokens ?? settings.contextWindowTokens;
2352
+ const providerData = {
2353
+ ...encryptedReasoning ? { include: ["reasoning.encrypted_content"] } : {},
2354
+ ...options.promptCacheKey ? { prompt_cache_key: options.promptCacheKey } : {}
2355
+ };
2178
2356
  const hostedTools = hostedWebSearch ? [webSearchTool()] : [];
2179
2357
  const baseConfig = {
2180
2358
  name: "OpenGeni Agent",
@@ -2194,32 +2372,36 @@ function buildOpenGeniAgent(settings, resources, options = {}) {
2194
2372
  // when a toolspace token was minted for this turn (feature enabled + a
2195
2373
  // per-turn seed — the mint gate, which includes selfhosted turns since
2196
2374
  // they now receive the token too) — appendToolspaceInstructions,
2197
- // 3. + the per-session persona instructions (session-specific, so it
2375
+ // 3. + workspace memory working set, ONLY when the workspace setting is on
2376
+ // and the worker resolved a nonblank block — appendWorkspaceMemory,
2377
+ // 4. + the per-session persona instructions (session-specific, so it
2198
2378
  // refines both the workspace persona and the substrate note),
2199
- // 4. + the one-shot genesis title directive (genesis turn only).
2200
- // With no toolspace token, no session instructions, and no genesis hint this
2201
- // is byte-identical to the historical composed instructions.
2202
- instructions: appendGenesisTitleDirective(
2379
+ // 5. + durable session-setting state (title present + child notification
2380
+ // mode), when supplied by the worker,
2381
+ // The genesis title directive is deliberately NOT part of this persistent
2382
+ // string. runAgentStream injects it into the first model call only.
2383
+ instructions: appendPersistentSessionSettings(
2203
2384
  appendSessionInstructions(
2204
- appendToolspaceInstructions(
2205
- composeAgentInstructions(options.instructionsTemplate ?? settings.agentInstructionsTemplate, options.workspaceEnvironment),
2206
- settings.toolspaceEnabled && Boolean(options.toolspaceTokenSeed)
2385
+ appendWorkspaceMemory(
2386
+ appendToolspaceInstructions(
2387
+ composeAgentInstructions(
2388
+ options.instructionsTemplate ?? settings.agentInstructionsTemplate,
2389
+ options.workspaceEnvironment,
2390
+ options.rig
2391
+ ),
2392
+ settings.toolspaceEnabled && Boolean(options.toolspaceTokenSeed)
2393
+ ),
2394
+ options.workspaceMemory
2207
2395
  ),
2208
2396
  options.sessionInstructions
2209
2397
  ),
2210
- options.genesisTitleHint
2398
+ options.persistentSessionSettings
2211
2399
  ),
2212
2400
  modelSettings: {
2213
- reasoning: { effort: options.reasoningEffort ?? settings.openaiReasoningEffort, summary: "detailed" },
2214
- // Server-side compaction (OpenAI platform) requires store=false: the
2215
- // server emits an opaque ENCRYPTED 'compaction' item that round-trips in
2216
- // the request rather than being anchored to a stored response. OpenGeni
2217
- // already runs storeless (provider item ids stripped, encrypted reasoning
2218
- // round-tripped), so this is consistent with the existing design and
2219
- // only set where the server compaction capability is attached. Gated on
2220
- // the RESOLVED compaction mode (registry providers resolve to "client",
2221
- // so they never carry store:false).
2222
- ...compactionMode === "server" ? { store: false } : {},
2401
+ reasoning: {
2402
+ effort: options.reasoningEffort ?? settings.openaiReasoningEffort,
2403
+ summary: "detailed"
2404
+ },
2223
2405
  // Round-trip the encrypted reasoning payload with every call so chains
2224
2406
  // of thought survive without provider-side response storage (which is
2225
2407
  // what stripped provider item ids opt us out of — see
@@ -2228,7 +2410,7 @@ function buildOpenGeniAgent(settings, resources, options = {}) {
2228
2410
  // function tools, which contribute none. Gated on the resolved
2229
2411
  // encryptedReasoning flag: the chat wire API has no encrypted_content
2230
2412
  // field, so registry "chat" providers turn it off.
2231
- ...encryptedReasoning ? { providerData: { include: ["reasoning.encrypted_content"] } } : {}
2413
+ ...Object.keys(providerData).length > 0 ? { providerData } : {}
2232
2414
  },
2233
2415
  // Explicit hosted tools (web_search when enabled). Threaded into BOTH the
2234
2416
  // `new Agent(baseConfig)` path (sandboxBackend === "none") and the
@@ -2246,6 +2428,9 @@ function buildOpenGeniAgent(settings, resources, options = {}) {
2246
2428
  };
2247
2429
  if (settings.sandboxBackend === "none") {
2248
2430
  const agent2 = new Agent(baseConfig);
2431
+ if (options.genesisTitleHint) {
2432
+ agentsNeedingGenesisTitleDirective.add(agent2);
2433
+ }
2249
2434
  maybeInstallCodexToolSearch(agent2, settings, options);
2250
2435
  applyMcpApprovalPolicy(agent2, settings);
2251
2436
  return agent2;
@@ -2253,26 +2438,51 @@ function buildOpenGeniAgent(settings, resources, options = {}) {
2253
2438
  const runAs = sandboxRunAs(settings);
2254
2439
  const agent = new SandboxAgent({
2255
2440
  ...baseConfig,
2256
- defaultManifest: buildManifest(settings, resources, options.sandboxEnvironment, options.fileResourceDownloads),
2441
+ defaultManifest: buildManifest(
2442
+ settings,
2443
+ resources,
2444
+ options.sandboxEnvironment,
2445
+ options.fileResourceDownloads
2446
+ ),
2257
2447
  ...runAs ? { runAs } : {},
2258
2448
  capabilities: buildAgentCapabilities(settings, options.packSkills ?? [], {
2259
- compactionMode,
2260
- contextWindowTokens,
2261
2449
  ...options.structuredToolTransport !== void 0 ? { structuredToolTransport: options.structuredToolTransport } : {},
2262
- ...options.computerToolMode !== void 0 ? { computerToolMode: options.computerToolMode } : {}
2450
+ ...options.computerToolMode !== void 0 ? { computerToolMode: options.computerToolMode } : {},
2451
+ ...options.onComputerUseReady ? { onComputerUseReady: options.onComputerUseReady } : {}
2263
2452
  })
2264
2453
  });
2265
- agentFileDownloads.set(agent, normalizeSandboxFileDownloads(options.fileResourceDownloads ?? []).filter((download) => !download.content));
2266
- agentRepositoryCloneHooks.set(agent, sandboxRepositoryCloneHooks(settings, resources, options.activeSandboxBackend));
2454
+ if (options.genesisTitleHint) {
2455
+ agentsNeedingGenesisTitleDirective.add(agent);
2456
+ }
2457
+ agentFileDownloads.set(
2458
+ agent,
2459
+ normalizeSandboxFileDownloads(options.fileResourceDownloads ?? []).filter(
2460
+ (download) => !download.content
2461
+ )
2462
+ );
2463
+ agentRepositoryCloneHooks.set(
2464
+ agent,
2465
+ sandboxRepositoryCloneHooks(settings, resources, options.activeSandboxBackend)
2466
+ );
2267
2467
  if (options.activeSandboxBackend) {
2268
2468
  agentActiveSandboxBackend.set(agent, options.activeSandboxBackend);
2269
2469
  }
2270
- if (options.gitTokenSeed) {
2271
- agentGitTokenSeed.set(agent, options.gitTokenSeed);
2470
+ const gitTokenSeeds = {
2471
+ ...options.gitTokenSeeds ?? {},
2472
+ ...options.gitTokenSeed ? { github: options.gitTokenSeed } : {}
2473
+ };
2474
+ if (Object.keys(gitTokenSeeds).length > 0) {
2475
+ agentGitTokenSeeds.set(agent, gitTokenSeeds);
2272
2476
  }
2273
2477
  if (options.toolspaceTokenSeed) {
2274
2478
  agentToolspaceTokenSeed.set(agent, options.toolspaceTokenSeed);
2275
2479
  }
2480
+ if (options.rigSetup) {
2481
+ agentRigSetup.set(agent, options.rigSetup);
2482
+ }
2483
+ if (options.rigCredentialHookIds && options.rigCredentialHookIds.length > 0) {
2484
+ agentRigCredentialHooks.set(agent, sandboxLifecycleHooksForIds(options.rigCredentialHookIds));
2485
+ }
2276
2486
  maybeInstallCodexToolSearch(agent, settings, options);
2277
2487
  applyMcpApprovalPolicy(agent, settings);
2278
2488
  return agent;
@@ -2314,7 +2524,12 @@ function installMcpApprovalPolicy(agent, policies) {
2314
2524
  }
2315
2525
  }
2316
2526
  function applyMcpApprovalPolicy(agent, settings) {
2317
- const policies = settings.mcpServers.filter((server) => server.requireApproval === true || Array.isArray(server.requireApproval) && server.requireApproval.length > 0).map((server) => ({ prefix: prefixedMcpToolName(server.id, ""), requireApproval: server.requireApproval })).sort((a, b) => b.prefix.length - a.prefix.length);
2527
+ const policies = settings.mcpServers.filter(
2528
+ (server) => server.requireApproval === true || Array.isArray(server.requireApproval) && server.requireApproval.length > 0
2529
+ ).map((server) => ({
2530
+ prefix: prefixedMcpToolName(server.id, ""),
2531
+ requireApproval: server.requireApproval
2532
+ })).sort((a, b) => b.prefix.length - a.prefix.length);
2318
2533
  if (policies.length === 0) {
2319
2534
  return;
2320
2535
  }
@@ -2327,24 +2542,67 @@ function neutralizeStructuredToolTransport(capability) {
2327
2542
  };
2328
2543
  capability.bindModel = forceFunctionTransport;
2329
2544
  }
2545
+ function withExecOpCorrelation(tools) {
2546
+ return tools.map((capabilityTool) => {
2547
+ if (capabilityTool.type !== "function" || capabilityTool.name !== "exec_command") {
2548
+ return capabilityTool;
2549
+ }
2550
+ const invoke = capabilityTool.invoke;
2551
+ return {
2552
+ ...capabilityTool,
2553
+ invoke: (runContext, input, details) => {
2554
+ const callId = details?.toolCall?.callId;
2555
+ if (!callId) {
2556
+ return invoke(runContext, input, details);
2557
+ }
2558
+ return runWithToolCallCorrelation(callId, () => invoke(runContext, input, details));
2559
+ }
2560
+ };
2561
+ });
2562
+ }
2563
+ function withStructuredViewImageFunctionResults(tools) {
2564
+ return tools.map((capabilityTool) => {
2565
+ if (capabilityTool.type !== "function" || capabilityTool.name !== "view_image") {
2566
+ return capabilityTool;
2567
+ }
2568
+ const invoke = capabilityTool.invoke;
2569
+ return {
2570
+ ...capabilityTool,
2571
+ invoke: async (runContext, input, details) => {
2572
+ const output = await invoke(runContext, input, details);
2573
+ const dataUrl = typeof output === "string" ? output : output && typeof output === "object" && typeof output.text === "string" ? output.text : null;
2574
+ return dataUrl?.startsWith("data:image/") ? { type: "image", image: { url: dataUrl } } : output;
2575
+ }
2576
+ };
2577
+ });
2578
+ }
2330
2579
  function buildAgentCapabilities(settings, packSkills, options = {}) {
2331
- const mode = options.compactionMode ?? resolveContextCompactionMode(settings);
2332
- const contextWindowTokens = options.contextWindowTokens ?? settings.contextWindowTokens;
2333
- const filesystemCapability = filesystem();
2580
+ const filesystemCapability = filesystem({
2581
+ ...options.structuredToolTransport === false ? { configureTools: withStructuredViewImageFunctionResults } : {}
2582
+ });
2334
2583
  if (options.structuredToolTransport === false) {
2335
2584
  neutralizeStructuredToolTransport(filesystemCapability);
2336
2585
  }
2337
- const caps = [filesystemCapability, shell()];
2338
- if (mode === "server") {
2339
- caps.push(compaction({ policy: new StaticCompactionPolicy(contextServerCompactThreshold({ ...settings, contextWindowTokens })) }));
2340
- }
2586
+ const caps = [
2587
+ filesystemCapability,
2588
+ shell({ configureTools: withExecOpCorrelation })
2589
+ ];
2341
2590
  caps.push(skills({ lazyFrom: lazySkillSourceWithPackSkills(packSkills) }));
2342
2591
  if (settings.computerUseEnabled && settings.sandboxDesktopEnabled && desktopCapableBackend(settings.sandboxBackend)) {
2343
2592
  const explicitMode = options.computerToolMode;
2344
2593
  const computerCapability = computerUse({
2345
2594
  dimensions: [settings.streamResolutionWidth, settings.streamResolutionHeight],
2346
2595
  readOnly: settings.computerUseReadOnly,
2347
- ...explicitMode ? { toolMode: explicitMode } : options.structuredToolTransport === false ? { imageFunctionResults: true } : {}
2596
+ ...options.onComputerUseReady ? { onReady: options.onComputerUseReady } : {},
2597
+ ...explicitMode ? { toolMode: explicitMode } : (
2598
+ // Legacy (no explicit mode): on the codex path the function tools deliver
2599
+ // screenshots as a real image the model can see. The ChatGPT/Codex backend
2600
+ // rejects HOSTED tool types but DOES accept `input_image` content items inside a
2601
+ // `function_call_output` (proven by openai/codex codex-rs, whose view_image tool
2602
+ // ships exactly that shape) — so a structured image tool result is seen, where a
2603
+ // text data-URL would be unreadable.
2604
+ options.structuredToolTransport === false ? { imageFunctionResults: true } : {}
2605
+ )
2348
2606
  });
2349
2607
  if (!explicitMode && options.structuredToolTransport === false) {
2350
2608
  neutralizeStructuredToolTransport(computerCapability);
@@ -2363,32 +2621,44 @@ async function prepareAgentTools(settings, tools, options = {}) {
2363
2621
  }, codexConnectorNamespaces };
2364
2622
  }
2365
2623
  const registry = new Map(settings.mcpServers.map((server) => [server.id, server]));
2366
- const servers = await Promise.all(tools.map(async (tool2) => {
2367
- const config = registry.get(tool2.id);
2368
- if (!config) {
2369
- throw new Error(`Unknown MCP server id: ${tool2.id}`);
2370
- }
2371
- const url = firstPartyMcpServerUrlForRun(settings, config, options.workspaceId) ?? config.url;
2372
- const baseFetch = isCodexAppsMcpServer(config) ? codexAppsSanitizingFetch(globalThis.fetch, codexConnectorNamespaces) : globalThis.fetch;
2373
- const fetchImpl = config.connectionRef ? connectionBrokerFetch(baseFetch, config, options) : baseFetch;
2374
- const server = new PrefixedMcpServer(new MCPServerStreamableHttp({
2375
- url,
2376
- name: config.name ?? config.id,
2377
- cacheToolsList: config.cacheToolsList,
2378
- // codex_apps returns connector tools with empty `outputSchema: {}` that the
2379
- // MCP SDK's strict Tool schema rejects (fails the turn during tools/list);
2380
- // sanitize the response on the wire before validation. The namespace Set
2381
- // also captures each tool's original connector namespace (P4 Part B.1).
2382
- ...fetchImpl !== globalThis.fetch ? { fetch: fetchImpl } : {},
2383
- ...await mcpServerRequestInit(settings, config, options),
2384
- ...config.timeoutMs ? {
2385
- timeout: config.timeoutMs,
2386
- clientSessionTimeoutSeconds: Math.ceil(config.timeoutMs / 1e3)
2387
- } : {}
2388
- }), config.id, config.allowedTools);
2389
- const optional = tool2.optional === true;
2390
- return { server, bestEffort: isCodexAppsMcpServer(config) || optional || !!config.connectionRef, optional };
2391
- }));
2624
+ const servers = await Promise.all(
2625
+ tools.map(async (tool2) => {
2626
+ const config = registry.get(tool2.id);
2627
+ if (!config) {
2628
+ throw new Error(`Unknown MCP server id: ${tool2.id}`);
2629
+ }
2630
+ const url = firstPartyMcpServerUrlForRun(settings, config, options.workspaceId) ?? config.url;
2631
+ const baseFetch = isCodexAppsMcpServer(config) ? codexAppsSanitizingFetch(globalThis.fetch, codexConnectorNamespaces) : globalThis.fetch;
2632
+ const fetchImpl = config.connectionRef ? connectionBrokerFetch(baseFetch, config, options) : isFirstPartyMcpServer(settings, config) ? firstPartyAuthFetch(baseFetch, settings, options) : baseFetch;
2633
+ const optional = tool2.optional === true;
2634
+ const bestEffort = isCodexAppsMcpServer(config) || optional || !!config.connectionRef;
2635
+ const server = new PrefixedMcpServer(
2636
+ new MCPServerStreamableHttp({
2637
+ url,
2638
+ name: config.name ?? config.id,
2639
+ cacheToolsList: config.cacheToolsList,
2640
+ // codex_apps returns connector tools with empty `outputSchema: {}` that the
2641
+ // MCP SDK's strict Tool schema rejects (fails the turn during tools/list);
2642
+ // sanitize the response on the wire before validation. The namespace Set
2643
+ // also captures each tool's original connector namespace (P4 Part B.1).
2644
+ ...fetchImpl !== globalThis.fetch ? { fetch: fetchImpl } : {},
2645
+ ...await mcpServerRequestInit(settings, config),
2646
+ ...config.timeoutMs ? {
2647
+ timeout: config.timeoutMs,
2648
+ clientSessionTimeoutSeconds: Math.ceil(config.timeoutMs / 1e3)
2649
+ } : {}
2650
+ }),
2651
+ config.id,
2652
+ config.allowedTools,
2653
+ bestEffort
2654
+ );
2655
+ return {
2656
+ server,
2657
+ bestEffort,
2658
+ optional
2659
+ };
2660
+ })
2661
+ );
2392
2662
  const requiredServers = servers.filter((entry) => !entry.bestEffort).map((entry) => entry.server);
2393
2663
  const bestEffortServers = servers.filter((entry) => entry.bestEffort).map((entry) => entry.server);
2394
2664
  const optionalServerNames = new Set(
@@ -2432,30 +2702,54 @@ function connectionBrokerFetch(baseFetch, config, options) {
2432
2702
  }
2433
2703
  return async (input, init) => {
2434
2704
  const request = await mcpRequestInfo(input, init);
2435
- const first = await resolveConnectionForRequest(options, config.id, connectionRef, request.toolName, false);
2705
+ const first = await resolveConnectionForRequest(
2706
+ options,
2707
+ config.id,
2708
+ connectionRef,
2709
+ request.toolName,
2710
+ false
2711
+ );
2436
2712
  if (first.status === "auth_needed") {
2437
2713
  return await authNeededFetchResponse(options, config.id, request, first, connectionRef);
2438
2714
  }
2439
- const response = await baseFetch(fetchInputForAttempt(input), withConnectionHeaders(input, init, first.headers));
2715
+ const response = await baseFetch(
2716
+ fetchInputForAttempt(input),
2717
+ withConnectionHeaders(input, init, first.headers)
2718
+ );
2440
2719
  if (response.status === 401) {
2441
- const refreshed = await resolveConnectionForRequest(options, config.id, connectionRef, request.toolName, true);
2720
+ const refreshed = await resolveConnectionForRequest(
2721
+ options,
2722
+ config.id,
2723
+ connectionRef,
2724
+ request.toolName,
2725
+ true
2726
+ );
2442
2727
  if (refreshed.status === "auth_needed") {
2443
2728
  return await authNeededFetchResponse(options, config.id, request, refreshed, connectionRef);
2444
2729
  }
2445
- const retry = await baseFetch(fetchInputForAttempt(input), withConnectionHeaders(input, init, refreshed.headers));
2730
+ const retry = await baseFetch(
2731
+ fetchInputForAttempt(input),
2732
+ withConnectionHeaders(input, init, refreshed.headers)
2733
+ );
2446
2734
  if (retry.status === 403) {
2447
2735
  const auth = insufficientScopeAuth(retry.headers, connectionRef, refreshed.connectionId);
2448
2736
  return auth ? await authNeededFetchResponse(options, config.id, request, auth, connectionRef) : retry;
2449
2737
  }
2450
2738
  if (retry.status === 401) {
2451
- return await authNeededFetchResponse(options, config.id, request, {
2452
- status: "auth_needed",
2453
- reason: "expired",
2454
- providerDomain: connectionRef.providerDomain,
2455
- connectionId: refreshed.connectionId,
2456
- ...connectionRef.scopes ? { scopes: connectionRef.scopes } : {},
2457
- ...connectionRef.resource ? { resource: connectionRef.resource } : {}
2458
- }, connectionRef);
2739
+ return await authNeededFetchResponse(
2740
+ options,
2741
+ config.id,
2742
+ request,
2743
+ {
2744
+ status: "auth_needed",
2745
+ reason: "expired",
2746
+ providerDomain: connectionRef.providerDomain,
2747
+ connectionId: refreshed.connectionId,
2748
+ ...connectionRef.scopes ? { scopes: connectionRef.scopes } : {},
2749
+ ...connectionRef.resource ? { resource: connectionRef.resource } : {}
2750
+ },
2751
+ connectionRef
2752
+ );
2459
2753
  }
2460
2754
  return retry;
2461
2755
  }
@@ -2556,7 +2850,9 @@ async function mcpRequestInfo(input, init) {
2556
2850
  }
2557
2851
  }
2558
2852
  function withConnectionHeaders(input, init, authHeaders) {
2559
- const headers = new Headers(init?.headers ?? (input instanceof Request ? input.headers : void 0));
2853
+ const headers = new Headers(
2854
+ init?.headers ?? (input instanceof Request ? input.headers : void 0)
2855
+ );
2560
2856
  for (const [name, value] of Object.entries(authHeaders)) {
2561
2857
  headers.set(name, value);
2562
2858
  }
@@ -2590,17 +2886,20 @@ function parseWwwAuthenticate(header) {
2590
2886
  var MCP_AUTH_NEEDED_ERROR_CODE = -32001;
2591
2887
  var MCP_AUTH_NEEDED_MESSAGE = "Authentication required - a connection link was posted to the session.";
2592
2888
  function mcpToolAuthNeededResponse(id) {
2593
- return new Response(JSON.stringify({
2594
- jsonrpc: "2.0",
2595
- id: id ?? null,
2596
- error: {
2597
- code: MCP_AUTH_NEEDED_ERROR_CODE,
2598
- message: MCP_AUTH_NEEDED_MESSAGE
2599
- }
2600
- }), {
2601
- status: 200,
2602
- headers: { "content-type": "application/json" }
2603
- });
2889
+ return new Response(
2890
+ JSON.stringify({
2891
+ jsonrpc: "2.0",
2892
+ id: id ?? null,
2893
+ error: {
2894
+ code: MCP_AUTH_NEEDED_ERROR_CODE,
2895
+ message: MCP_AUTH_NEEDED_MESSAGE
2896
+ }
2897
+ }),
2898
+ {
2899
+ status: 200,
2900
+ headers: { "content-type": "application/json" }
2901
+ }
2902
+ );
2604
2903
  }
2605
2904
  function isAuthNeededMcpError(error) {
2606
2905
  if (!(error instanceof Error)) {
@@ -2609,19 +2908,35 @@ function isAuthNeededMcpError(error) {
2609
2908
  const code = error.code;
2610
2909
  return code === MCP_AUTH_NEEDED_ERROR_CODE || error.message.includes(MCP_AUTH_NEEDED_MESSAGE);
2611
2910
  }
2612
- async function mcpServerRequestInit(settings, config, options) {
2911
+ function mcpToolUnavailableMessage(reason) {
2912
+ return `This tool is unavailable for the rest of this turn (${reason}). Do not retry it \u2014 continue without it or use another approach.`;
2913
+ }
2914
+ function safeMcpErrorFields(error) {
2915
+ const errorClass = error instanceof Error ? error.constructor.name : typeof error;
2916
+ const raw = error?.code;
2917
+ const status = typeof raw === "number" ? raw : void 0;
2918
+ if (status === void 0) {
2919
+ const altRaw = error?.status;
2920
+ return typeof altRaw === "number" ? { errorClass, status: altRaw } : { errorClass };
2921
+ }
2922
+ return { errorClass, status };
2923
+ }
2924
+ function mcpErrorReason(fields) {
2925
+ return fields.status === void 0 ? fields.errorClass : `${fields.errorClass} ${fields.status}`;
2926
+ }
2927
+ async function mcpServerRequestInit(settings, config) {
2613
2928
  if (isCodexAppsMcpServer(config)) {
2614
2929
  return await codexAppsMcpRequestInit(settings);
2615
2930
  }
2616
2931
  if (isFirstPartyMcpServer(settings, config)) {
2617
- return await firstPartyMcpRequestInit(settings, config, options);
2932
+ return await firstPartyMcpRequestInit(settings, config);
2618
2933
  }
2619
2934
  if (config.headers && Object.keys(config.headers).length > 0) {
2620
2935
  return { requestInit: { headers: { ...config.headers } } };
2621
2936
  }
2622
2937
  return {};
2623
2938
  }
2624
- async function firstPartyMcpRequestInit(settings, config, options) {
2939
+ async function firstPartyMcpRequestInit(settings, config) {
2625
2940
  if (!isFirstPartyMcpServer(settings, config)) {
2626
2941
  return {};
2627
2942
  }
@@ -2629,17 +2944,6 @@ async function firstPartyMcpRequestInit(settings, config, options) {
2629
2944
  if (settings.authRequired && settings.accessKey) {
2630
2945
  headers["x-opengeni-access-key"] = settings.accessKey;
2631
2946
  }
2632
- if (settings.delegationSecret && options.accountId && options.workspaceId) {
2633
- headers.authorization = `Bearer ${await signDelegatedAccessToken(settings.delegationSecret, {
2634
- accountId: options.accountId,
2635
- workspaceId: options.workspaceId,
2636
- subjectId: options.subjectId ?? "worker:first-party-mcp",
2637
- ...options.subjectLabel ? { subjectLabel: options.subjectLabel } : {},
2638
- permissions: options.firstPartyPermissions ?? firstPartyMcpPermissions,
2639
- ...options.sessionId ? { sessionId: options.sessionId } : {},
2640
- exp: Math.floor(Date.now() / 1e3) + 60 * 60
2641
- })}`;
2642
- }
2643
2947
  if (Object.keys(headers).length === 0) {
2644
2948
  return {};
2645
2949
  }
@@ -2649,6 +2953,33 @@ async function firstPartyMcpRequestInit(settings, config, options) {
2649
2953
  }
2650
2954
  };
2651
2955
  }
2956
+ async function signFirstPartyDelegatedBearer(settings, options) {
2957
+ if (!settings.delegationSecret || !options.accountId || !options.workspaceId) {
2958
+ return null;
2959
+ }
2960
+ return await signDelegatedAccessToken(settings.delegationSecret, {
2961
+ accountId: options.accountId,
2962
+ workspaceId: options.workspaceId,
2963
+ subjectId: options.subjectId ?? "worker:first-party-mcp",
2964
+ ...options.subjectLabel ? { subjectLabel: options.subjectLabel } : {},
2965
+ permissions: options.firstPartyPermissions ?? firstPartyMcpPermissions,
2966
+ ...options.sessionId ? { sessionId: options.sessionId } : {},
2967
+ ...options.turnId ? { turnId: options.turnId } : {},
2968
+ exp: Math.floor(Date.now() / 1e3) + 60 * 60
2969
+ });
2970
+ }
2971
+ function firstPartyAuthFetch(baseFetch, settings, options) {
2972
+ return async (input, init) => {
2973
+ const bearer = await signFirstPartyDelegatedBearer(settings, options);
2974
+ if (!bearer) {
2975
+ return await baseFetch(input, init);
2976
+ }
2977
+ return await baseFetch(
2978
+ fetchInputForAttempt(input),
2979
+ withConnectionHeaders(input, init, { authorization: `Bearer ${bearer}` })
2980
+ );
2981
+ };
2982
+ }
2652
2983
  async function codexAppsMcpRequestInit(settings) {
2653
2984
  const ctx = codexRequestStorage.getStore();
2654
2985
  if (!ctx) {
@@ -2688,8 +3019,9 @@ var firstPartyMcpPermissions = [
2688
3019
  "sessions:read",
2689
3020
  "sessions:create",
2690
3021
  "sessions:control",
2691
- "environments:use",
2692
- "environments:manage",
3022
+ "variable-sets:use",
3023
+ "variable-sets:manage",
3024
+ "rigs:use",
2693
3025
  "github:use"
2694
3026
  ];
2695
3027
  function isCodexAppsMcpServer(config) {
@@ -2718,7 +3050,11 @@ function firstPartyMcpServerUrlForRun(settings, config, workspaceId) {
2718
3050
  if (!isFirstPartyMcpServer(settings, config)) {
2719
3051
  return null;
2720
3052
  }
2721
- const rawBase = settings.opengeniMcpUrl?.includes("{workspaceId}") ? settings.opengeniMcpUrl.replaceAll("{workspaceId}", workspaceId) : settings.opengeniMcpUrl ? scopedMcpUrlFromConfiguredBase(settings.opengeniMcpUrl, workspaceId) : firstPartyMcpBaseUrl(settings).replaceAll("{workspaceId}", workspaceId);
3053
+ const rawBase = settings.opengeniMcpUrl?.includes("{workspaceId}") ? settings.opengeniMcpUrl.replaceAll("{workspaceId}", workspaceId) : settings.opengeniMcpUrl ? scopedMcpUrlFromConfiguredBase(settings.opengeniMcpUrl, workspaceId) : (
3054
+ // unset → the shared loopback default (a `{workspaceId}` template owned by
3055
+ // @opengeni/config's firstPartyMcpBaseUrl), scoped to this run's workspace.
3056
+ firstPartyMcpBaseUrl(settings).replaceAll("{workspaceId}", workspaceId)
3057
+ );
2722
3058
  const url = new URL(rawBase);
2723
3059
  if (config.id === "docs") {
2724
3060
  url.pathname = `${url.pathname.replace(/\/+$/, "")}/docs`;
@@ -2755,18 +3091,24 @@ function prefixedMcpToolName(registryId, toolName) {
2755
3091
  return sharedPrefixedMcpToolName(registryId, toolName);
2756
3092
  }
2757
3093
  var PrefixedMcpServer = class {
2758
- constructor(inner, registryId, allowedTools) {
3094
+ constructor(inner, registryId, allowedTools, bestEffort = false) {
2759
3095
  this.inner = inner;
2760
3096
  this.name = registryId;
2761
3097
  this.prefix = prefixedMcpToolName(registryId, "");
2762
3098
  this.cacheToolsList = inner.cacheToolsList;
2763
3099
  this.allowedTools = allowedTools ? new Set(allowedTools) : void 0;
3100
+ this.bestEffort = bestEffort;
2764
3101
  }
2765
- inner;
2766
3102
  cacheToolsList;
2767
3103
  name;
2768
3104
  prefix;
2769
3105
  allowedTools;
3106
+ // Best-effort servers (optional refs, connectionRef-backed capability MCPs,
3107
+ // codex_apps) must never fail a turn: a tools/list throw degrades to zero
3108
+ // tools instead of propagating. Deduplicate the warn so one degraded server
3109
+ // doesn't spam the log when the SDK re-lists across model turns.
3110
+ bestEffort;
3111
+ loggedListToolsFailure = false;
2770
3112
  connect() {
2771
3113
  return this.inner.connect();
2772
3114
  }
@@ -2774,7 +3116,24 @@ var PrefixedMcpServer = class {
2774
3116
  return this.inner.close();
2775
3117
  }
2776
3118
  async listTools() {
2777
- const tools = await this.inner.listTools();
3119
+ let tools;
3120
+ try {
3121
+ tools = await this.inner.listTools();
3122
+ } catch (error) {
3123
+ if (!this.bestEffort) {
3124
+ throw error;
3125
+ }
3126
+ if (!this.loggedListToolsFailure) {
3127
+ this.loggedListToolsFailure = true;
3128
+ console.warn(
3129
+ "[mcp] best-effort server tools/list failed; its tools are unavailable this turn",
3130
+ // Safe surface only (class + numeric status), never the raw error
3131
+ // message/response body — a broker 401/403 body can echo request detail.
3132
+ { serverId: this.name, ...safeMcpErrorFields(error) }
3133
+ );
3134
+ }
3135
+ return [];
3136
+ }
2778
3137
  return tools.filter((tool2) => this.isAllowed(tool2.name)).map((tool2) => ({ ...tool2, name: prefixedMcpToolName(this.name, tool2.name) }));
2779
3138
  }
2780
3139
  async callTool(toolName, args, meta) {
@@ -2788,6 +3147,17 @@ var PrefixedMcpServer = class {
2788
3147
  if (isAuthNeededMcpError(error)) {
2789
3148
  return { isError: true, content: [{ type: "text", text: MCP_AUTH_NEEDED_MESSAGE }] };
2790
3149
  }
3150
+ if (this.bestEffort) {
3151
+ const fields = safeMcpErrorFields(error);
3152
+ console.warn(
3153
+ "[mcp] best-effort server tool call failed; returning an unavailable result for this turn",
3154
+ { serverId: this.name, toolName: unprefixed, ...fields }
3155
+ );
3156
+ return {
3157
+ isError: true,
3158
+ content: [{ type: "text", text: mcpToolUnavailableMessage(mcpErrorReason(fields)) }]
3159
+ };
3160
+ }
2791
3161
  throw error;
2792
3162
  }
2793
3163
  }
@@ -2825,74 +3195,45 @@ var PrefixedMcpServer = class {
2825
3195
  return toolName.slice(this.prefix.length);
2826
3196
  }
2827
3197
  };
2828
- function guardAssembledInput(history, trailing, inputBudgetTokens) {
2829
- if (typeof inputBudgetTokens !== "number" || inputBudgetTokens <= 0) {
2830
- return [...history, trailing];
2831
- }
2832
- const trailingTokens = estimateItemTokens(trailing);
2833
- const guarded = enforceInputBudget(
2834
- history,
2835
- inputBudgetTokens,
2836
- trailingTokens
2837
- );
2838
- if (guarded.trimmed) {
2839
- console.warn(
2840
- `read-path budget guard trimmed ${guarded.droppedCount} oldest history item(s) to fit input budget (${inputBudgetTokens} tokens); the over-budget input was NOT sent`
2841
- );
2842
- }
2843
- return [...guarded.items, trailing];
2844
- }
2845
3198
  async function prepareRunInput(agent, input, options = {}) {
2846
3199
  if (input.kind === "message") {
2847
- if (input.historyItems && input.historyItems.length > 0) {
2848
- const sandboxSessionState2 = input.sandboxEnvelope ? await restoredSandboxSessionStateFromEntry(input.sandboxEnvelope, options.sandboxClient) : void 0;
2849
- const sanitizedHistory2 = sanitizeHistoryItemsForModel(
2850
- input.historyItems
2851
- );
2852
- return {
2853
- // Read-path budget guard: even after the orphan sanitizer, an assembled
2854
- // input can exceed the model window (pre-turn compaction is best-effort
2855
- // and can no-op). Trim the oldest history at a clean turn boundary so an
2856
- // over-budget request is never sent. No-op when no budget is supplied.
2857
- input: guardAssembledInput(
2858
- sanitizedHistory2,
2859
- {
2860
- type: "message",
2861
- role: "user",
2862
- content: input.text
2863
- },
2864
- options.inputBudgetTokens
2865
- ),
2866
- ...sandboxSessionState2 ? { sandboxSessionState: sandboxSessionState2 } : {}
2867
- };
3200
+ const trailingMessages = [];
3201
+ if (input.internalContext?.trim()) {
3202
+ trailingMessages.push({
3203
+ type: "message",
3204
+ role: "system",
3205
+ content: input.internalContext
3206
+ });
2868
3207
  }
2869
- if (!input.serializedRunState || isClearedRunStateBlob(input.serializedRunState)) {
2870
- return { input: input.text };
3208
+ if (input.text?.trim()) {
3209
+ trailingMessages.push({
3210
+ type: "message",
3211
+ role: "user",
3212
+ content: input.text
3213
+ });
3214
+ }
3215
+ if (trailingMessages.length === 0 && (input.historyItems === void 0 || input.historyItems === null || input.historyItems.length === 0)) {
3216
+ throw new Error("Message input requires a user prompt or internal context");
2871
3217
  }
2872
- const state2 = await RunState.fromString(agent, input.serializedRunState);
2873
- const sandboxSessionState = await restoredSandboxSessionState(state2, options.sandboxClient);
2874
3218
  const sanitizedHistory = sanitizeHistoryItemsForModel(
2875
- state2.history
3219
+ input.historyItems ?? []
2876
3220
  );
3221
+ const sandboxSessionState = input.sandboxEnvelope ? await restoredSandboxSessionStateFromEntry(input.sandboxEnvelope, options.sandboxClient) : void 0;
3222
+ const assembled = [...sanitizedHistory, ...trailingMessages];
3223
+ if (assembled.length === 0) {
3224
+ throw new Error("Message input requires durable history or internal context");
3225
+ }
2877
3226
  return {
2878
- // Read-path budget guard (see the items path above): keep an over-budget
2879
- // resumed history off the wire by trimming the oldest turns when a budget
2880
- // is supplied.
2881
- input: guardAssembledInput(
2882
- sanitizedHistory,
2883
- {
2884
- type: "message",
2885
- role: "user",
2886
- content: input.text
2887
- },
2888
- options.inputBudgetTokens
2889
- ),
2890
- ...sandboxSessionState ? { sandboxSessionState } : {},
2891
- serializedRunStateForSandbox: input.serializedRunState
3227
+ // Preserve the SDK's simple first-message form without creating a second
3228
+ // history path: this is merely the zero-history representation.
3229
+ input: sanitizedHistory.length === 0 && !input.internalContext?.trim() && input.text?.trim() ? input.text : assembled,
3230
+ ...sandboxSessionState ? { sandboxSessionState } : {}
2892
3231
  };
2893
3232
  }
2894
3233
  if (isClearedRunStateBlob(input.serializedRunState)) {
2895
- throw new Error("Cannot resume an approval: the session context was cleared, so the awaiting run state no longer exists.");
3234
+ throw new Error(
3235
+ "Cannot resume an approval: the session context was cleared, so the awaiting run state no longer exists."
3236
+ );
2896
3237
  }
2897
3238
  const state = await RunState.fromString(agent, input.serializedRunState);
2898
3239
  const interruptions = state.getInterruptions();
@@ -2908,6 +3249,26 @@ async function prepareRunInput(agent, input, options = {}) {
2908
3249
  return { input: state };
2909
3250
  }
2910
3251
  var GENESIS_TITLE_DIRECTIVE = "This is the first turn of a new session. Before responding to the user, call the opengeni__set_session_title tool with a concise 3-7 word title that summarizes what this session is about, then address the user's request normally.";
3252
+ function oneShotGenesisTitleInputFilter() {
3253
+ let pending = true;
3254
+ return ({ modelData }) => {
3255
+ if (!pending) {
3256
+ return modelData;
3257
+ }
3258
+ pending = false;
3259
+ return {
3260
+ ...modelData,
3261
+ instructions: modelData.instructions ? `${modelData.instructions} ${GENESIS_TITLE_DIRECTIVE}` : GENESIS_TITLE_DIRECTIVE
3262
+ };
3263
+ };
3264
+ }
3265
+ function takeGenesisTitleInputFilter(agent) {
3266
+ if (!agentsNeedingGenesisTitleDirective.has(agent)) {
3267
+ return void 0;
3268
+ }
3269
+ agentsNeedingGenesisTitleDirective.delete(agent);
3270
+ return oneShotGenesisTitleInputFilter();
3271
+ }
2911
3272
  var TOOLSPACE_PROGRAMMATIC_DIRECTIVE = "Every tool on your MCP surface is also callable programmatically from the sandbox shell, so scripts can invoke tools without a model round trip per call. Run `ogtool list` to see the available tools and their input schemas (from tools/list), then `ogtool call <tool-name> '<json-args>'`; equivalently, POST MCP JSON-RPC to $OPENGENI_TOOLSPACE_URL with the bearer token read from $OPENGENI_TOOLSPACE_TOKEN_FILE. Prefer programmatic calls for loops, polling, and bulk filtering: their results stay in the sandbox and do not consume your context window. Tools that require human approval must still be invoked normally \u2014 called programmatically they return a typed error.";
2912
3273
  var stripProviderItemIdsFilter = ({ modelData }) => ({
2913
3274
  ...modelData,
@@ -2925,38 +3286,32 @@ var normalizeComputerCallsFilter = ({ modelData }) => ({
2925
3286
  modelData.input
2926
3287
  )
2927
3288
  });
3289
+ var elideSupersededViewImagesFilter = ({ modelData }) => ({
3290
+ ...modelData,
3291
+ input: elideSupersededViewImagePairs(
3292
+ modelData.input
3293
+ )
3294
+ });
2928
3295
  function contextRobustnessFilterForSettings(settings, options = {}) {
2929
- const inputBudgetTokens = modelCallBudgetTokens(settings);
2930
- const clientCompactionMode = resolveContextCompactionMode(settings) === "client";
2931
- const compactionThresholdTokens = clientCompactionThresholdTokens(settings);
2932
- return ({ modelData }) => {
2933
- const images = elideStaleScreenshotImages(modelData.input);
2934
- if (images.elidedCount > 0) {
2935
- console.warn(
2936
- `per-call image history policy elided ${images.elidedCount} older screenshot image(s), keeping the last ${Math.min(3, images.imageCount)} full image(s)`
2937
- );
2938
- }
2939
- let input = images.items;
2940
- if (inputBudgetTokens !== void 0) {
2941
- const guarded = enforceInputBudget(
2942
- input,
2943
- inputBudgetTokens
2944
- );
2945
- if (guarded.trimmed) {
2946
- console.warn(
2947
- `per-call budget guard trimmed ${guarded.droppedCount} oldest history item(s) to fit input budget (${inputBudgetTokens} tokens); the over-budget model call was NOT sent`
2948
- );
2949
- input = guarded.items;
2950
- }
2951
- }
2952
- if (clientCompactionMode && options.throwOnCompactionNeeded) {
3296
+ const thresholdTokens = compactionThresholdTokens(settings);
3297
+ return async ({ modelData }) => {
3298
+ const input = modelData.input;
3299
+ if (options.throwOnCompactionNeeded) {
2953
3300
  const reported = options.contextCompactionSignalTokens?.();
2954
3301
  const hasReported = typeof reported === "number" && reported > 0;
2955
3302
  const signalTokens = hasReported ? reported : estimateTokens(input);
2956
- if (signalTokens > compactionThresholdTokens) {
3303
+ if (await options.contextCompactionRequested?.()) {
3304
+ throw new CompactionNeededError({
3305
+ signalTokens,
3306
+ thresholdTokens,
3307
+ signalSource: hasReported ? "provider" : "estimate",
3308
+ trigger: "operator"
3309
+ });
3310
+ }
3311
+ if (signalTokens >= thresholdTokens) {
2957
3312
  throw new CompactionNeededError({
2958
3313
  signalTokens,
2959
- thresholdTokens: compactionThresholdTokens,
3314
+ thresholdTokens,
2960
3315
  signalSource: hasReported ? "provider" : "estimate"
2961
3316
  });
2962
3317
  }
@@ -2964,13 +3319,6 @@ function contextRobustnessFilterForSettings(settings, options = {}) {
2964
3319
  return { ...modelData, input };
2965
3320
  };
2966
3321
  }
2967
- function modelCallBudgetTokens(settings) {
2968
- if (resolveContextCompactionMode(settings) !== "client") {
2969
- return void 0;
2970
- }
2971
- const budget = contextInputBudgetTokens(settings);
2972
- return budget > 0 ? budget : void 0;
2973
- }
2974
3322
  function composeCallModelInputFilters(filters) {
2975
3323
  return async (args) => {
2976
3324
  let modelData = args.modelData;
@@ -2981,7 +3329,10 @@ function composeCallModelInputFilters(filters) {
2981
3329
  };
2982
3330
  }
2983
3331
  function callModelInputFilterForSettings(settings, options = {}) {
2984
- const filters = [normalizeComputerCallsFilter];
3332
+ const filters = [
3333
+ normalizeComputerCallsFilter,
3334
+ elideSupersededViewImagesFilter
3335
+ ];
2985
3336
  if (settings.openaiProviderItemIds === "strip") {
2986
3337
  filters.push(stripProviderItemIdsFilter);
2987
3338
  }
@@ -2991,54 +3342,63 @@ function callModelInputFilterForSettings(settings, options = {}) {
2991
3342
  async function runAgentStream(agent, input, settings, overrides = {}) {
2992
3343
  const prepared = typeof input === "string" || input instanceof RunState ? { input } : input;
2993
3344
  const environment = overrides.sandboxEnvironment ?? collectSandboxEnvironment2(settings);
3345
+ const genesisTitleInputFilter = takeGenesisTitleInputFilter(agent);
2994
3346
  if (overrides.ownedSandbox) {
2995
3347
  const { client: ownedClient, session, sessionState } = overrides.ownedSandbox;
2996
3348
  const setupSession = overrides.ownedSandbox.setupSession ?? session;
3349
+ if (!overrides.ownedSandbox.deferredSetup) {
3350
+ await runOwnedSandboxSetup(agent, session, setupSession, {
3351
+ settings,
3352
+ environment,
3353
+ preparedInput: prepared,
3354
+ ...overrides.ownedSandbox.fileDownloadsMaterialized ? { fileDownloadsMaterialized: true } : {},
3355
+ ...overrides.onRuntimeEvent ? { onRuntimeEvent: overrides.onRuntimeEvent } : {}
3356
+ });
3357
+ await overrides.onGitCredentialSessionReady?.(setupSession);
3358
+ }
2997
3359
  const runAs2 = sandboxRunAs(settings);
2998
3360
  const fileDownloads2 = sandboxFileDownloadsForAgent(agent);
2999
3361
  const resourceClient2 = fileDownloads2.length > 0 ? withSandboxFileDownloads(ownedClient, fileDownloads2, {
3000
3362
  ...overrides.onRuntimeEvent ? { onRuntimeEvent: overrides.onRuntimeEvent } : {},
3001
3363
  ...runAs2 ? { runAs: runAs2 } : {}
3002
3364
  }) : ownedClient;
3003
- const ownedGitTokenSeed = gitTokenSeedForAgent(agent);
3365
+ const ownedGitTokenSeeds = gitTokenSeedsForAgent(agent);
3004
3366
  const ownedToolspaceTokenSeed = toolspaceTokenSeedForAgent(agent);
3367
+ const ownedRigSetup = rigSetupDescriptorForAgent(agent);
3005
3368
  const ownedHooks = [
3006
- ...sandboxLifecycleHooksForIds(sandboxLifecycleHookIds(settings)),
3369
+ // M3: rig setup runs FIRST so any tooling it installs is present for the
3370
+ // credential / repository-clone hooks below. The rig's credential hooks are
3371
+ // unioned into the deployment preparation-profile hooks (deduped by id).
3372
+ ...sandboxRigSetupHooksForAgent(agent),
3373
+ ...unionCredentialHooks(
3374
+ sandboxLifecycleHooksForIds(sandboxLifecycleHookIds(settings)),
3375
+ rigCredentialHooksForAgent(agent)
3376
+ ),
3007
3377
  ...sandboxToolspaceTokenHooksForAgent(agent),
3008
- ...sandboxRepositoryCloneHooksForAgent(agent)
3378
+ ...sandboxRepositoryCloneHooksForAgent(agent),
3379
+ ...gitCredentialSessionRegistrationHooks(overrides.onGitCredentialSessionReady)
3009
3380
  ];
3010
3381
  const ownedHookContext = {
3011
3382
  environment,
3012
3383
  ...overrides.onRuntimeEvent ? { onRuntimeEvent: overrides.onRuntimeEvent } : {},
3013
3384
  ...runAs2 ? { runAs: runAs2 } : {},
3014
- ...ownedGitTokenSeed ? { gitTokenSeed: ownedGitTokenSeed } : {},
3015
- ...ownedToolspaceTokenSeed ? { toolspaceTokenSeed: ownedToolspaceTokenSeed } : {}
3385
+ ...ownedGitTokenSeeds ? { gitTokenSeeds: ownedGitTokenSeeds } : {},
3386
+ ...ownedToolspaceTokenSeed ? { toolspaceTokenSeed: ownedToolspaceTokenSeed } : {},
3387
+ ...ownedRigSetup ? { rigSetup: ownedRigSetup } : {}
3016
3388
  };
3017
- if (agentActiveSandboxBackend.get(agent) !== "selfhosted") {
3018
- await runBeforeAgentStartHooks(setupSession, ownedHooks, ownedHookContext);
3019
- if (fileDownloads2.length > 0) {
3020
- await materializeSandboxFileDownloads(setupSession, fileDownloads2, {
3021
- ...overrides.onRuntimeEvent ? { onRuntimeEvent: overrides.onRuntimeEvent } : {},
3022
- ...runAs2 ? { runAs: runAs2 } : {}
3023
- });
3024
- }
3025
- } else {
3026
- const toolspaceHooks = sandboxToolspaceTokenHooksForAgent(agent);
3027
- if (toolspaceHooks.length > 0) {
3028
- await runBeforeAgentStartHooks(setupSession, toolspaceHooks, ownedHookContext);
3029
- }
3030
- }
3031
3389
  const decoratedClient = withSandboxLifecycleHooks(resourceClient2, ownedHooks, ownedHookContext);
3032
3390
  const ownedFilter = composeCallModelInputFilters(
3033
3391
  [
3034
3392
  callModelInputFilterForSettings(settings, {
3035
- throwOnCompactionNeeded: Boolean(overrides.contextCompactionSignalTokens),
3036
- ...overrides.contextCompactionSignalTokens ? { contextCompactionSignalTokens: overrides.contextCompactionSignalTokens } : {}
3393
+ throwOnCompactionNeeded: Boolean(
3394
+ overrides.contextCompactionSignalTokens || overrides.contextCompactionRequested
3395
+ ),
3396
+ ...overrides.contextCompactionSignalTokens ? { contextCompactionSignalTokens: overrides.contextCompactionSignalTokens } : {},
3397
+ ...overrides.contextCompactionRequested ? { contextCompactionRequested: overrides.contextCompactionRequested } : {}
3037
3398
  }),
3399
+ genesisTitleInputFilter,
3038
3400
  overrides.callModelInputFilter
3039
- ].filter(
3040
- (f) => Boolean(f)
3041
- )
3401
+ ].filter((f) => Boolean(f))
3042
3402
  );
3043
3403
  const ownedRunOptions = {
3044
3404
  stream: true,
@@ -3053,44 +3413,66 @@ async function runAgentStream(agent, input, settings, overrides = {}) {
3053
3413
  return await runScopedRunner(settings).run(agent, prepared.input, ownedRunOptions);
3054
3414
  }
3055
3415
  const rawClient = overrides.sandboxClient ?? createSandboxClient(settings, environment);
3056
- const refreshedClient = rawClient ? withManifestRefreshOnResume(rawClient, agent.defaultManifest) : void 0;
3416
+ const refreshedClient = rawClient ? withManifestRefreshOnResume(
3417
+ rawClient,
3418
+ agent.defaultManifest,
3419
+ {
3420
+ ...overrides.onRuntimeEvent ? { onRuntimeEvent: overrides.onRuntimeEvent } : {}
3421
+ }
3422
+ ) : void 0;
3057
3423
  const runAs = sandboxRunAs(settings);
3058
3424
  const fileDownloads = sandboxFileDownloadsForAgent(agent);
3059
3425
  const resourceClient = refreshedClient && fileDownloads.length > 0 ? withSandboxFileDownloads(refreshedClient, fileDownloads, {
3060
3426
  ...overrides.onRuntimeEvent ? { onRuntimeEvent: overrides.onRuntimeEvent } : {},
3061
3427
  ...runAs ? { runAs } : {}
3062
3428
  }) : refreshedClient;
3063
- const gitTokenSeed = gitTokenSeedForAgent(agent);
3429
+ const gitTokenSeeds = gitTokenSeedsForAgent(agent);
3064
3430
  const toolspaceTokenSeed = toolspaceTokenSeedForAgent(agent);
3065
- const client = resourceClient ? withSandboxLifecycleHooks(resourceClient, [
3066
- ...sandboxLifecycleHooksForIds(sandboxLifecycleHookIds(settings)),
3067
- ...sandboxToolspaceTokenHooksForAgent(agent),
3068
- ...sandboxRepositoryCloneHooksForAgent(agent)
3069
- ], {
3070
- environment,
3071
- ...overrides.onRuntimeEvent ? { onRuntimeEvent: overrides.onRuntimeEvent } : {},
3072
- ...runAs ? { runAs } : {},
3073
- ...gitTokenSeed ? { gitTokenSeed } : {},
3074
- ...toolspaceTokenSeed ? { toolspaceTokenSeed } : {}
3075
- }) : void 0;
3076
- const sandboxSessionState = prepared.sandboxSessionState ?? (prepared.serializedRunStateForSandbox && client ? await restoredSandboxSessionState(await RunState.fromString(agent, prepared.serializedRunStateForSandbox), client) : void 0);
3431
+ const legacyRigSetup = rigSetupDescriptorForAgent(agent);
3432
+ const client = resourceClient ? withSandboxLifecycleHooks(
3433
+ resourceClient,
3434
+ [
3435
+ // M3: same rig-setup-first ordering + credential-hook union as the owned
3436
+ // path (this legacy create/resume decoration path is byte-for-byte today
3437
+ // for a rig-less turn — the rig hooks are empty then).
3438
+ ...sandboxRigSetupHooksForAgent(agent),
3439
+ ...unionCredentialHooks(
3440
+ sandboxLifecycleHooksForIds(sandboxLifecycleHookIds(settings)),
3441
+ rigCredentialHooksForAgent(agent)
3442
+ ),
3443
+ ...sandboxToolspaceTokenHooksForAgent(agent),
3444
+ ...sandboxRepositoryCloneHooksForAgent(agent),
3445
+ ...gitCredentialSessionRegistrationHooks(overrides.onGitCredentialSessionReady)
3446
+ ],
3447
+ {
3448
+ environment,
3449
+ ...overrides.onRuntimeEvent ? { onRuntimeEvent: overrides.onRuntimeEvent } : {},
3450
+ ...runAs ? { runAs } : {},
3451
+ ...gitTokenSeeds ? { gitTokenSeeds } : {},
3452
+ ...toolspaceTokenSeed ? { toolspaceTokenSeed } : {},
3453
+ ...legacyRigSetup ? { rigSetup: legacyRigSetup } : {}
3454
+ }
3455
+ ) : void 0;
3456
+ const sandboxSessionState = prepared.sandboxSessionState;
3077
3457
  const callModelInputFilter = composeCallModelInputFilters(
3078
3458
  [
3079
3459
  callModelInputFilterForSettings(settings, {
3080
- throwOnCompactionNeeded: Boolean(overrides.contextCompactionSignalTokens),
3081
- ...overrides.contextCompactionSignalTokens ? { contextCompactionSignalTokens: overrides.contextCompactionSignalTokens } : {}
3460
+ throwOnCompactionNeeded: Boolean(
3461
+ overrides.contextCompactionSignalTokens || overrides.contextCompactionRequested
3462
+ ),
3463
+ ...overrides.contextCompactionSignalTokens ? { contextCompactionSignalTokens: overrides.contextCompactionSignalTokens } : {},
3464
+ ...overrides.contextCompactionRequested ? { contextCompactionRequested: overrides.contextCompactionRequested } : {}
3082
3465
  }),
3466
+ genesisTitleInputFilter,
3083
3467
  overrides.callModelInputFilter
3084
- ].filter(
3085
- (f) => Boolean(f)
3086
- )
3468
+ ].filter((f) => Boolean(f))
3087
3469
  );
3088
3470
  const runOptions = {
3089
3471
  stream: true,
3090
3472
  maxTurns: settings.agentMaxModelCallsPerTurn,
3091
3473
  // Built-in per-call guard chain: normalize computer calls, optionally strip
3092
- // provider ids, elide stale screenshots in every mode, and trim to the input
3093
- // budget on the client-compaction path. This runs for turn-start replay AND
3474
+ // provider ids, trim to the input budget on the client-compaction path, and
3475
+ // raise the proactive compaction signal. This runs for turn-start replay AND
3094
3476
  // every mid-turn follow-up.
3095
3477
  callModelInputFilter
3096
3478
  };
@@ -3102,6 +3484,22 @@ async function runAgentStream(agent, input, settings, overrides = {}) {
3102
3484
  }
3103
3485
  return await runScopedRunner(settings).run(agent, prepared.input, runOptions);
3104
3486
  }
3487
+ function appendSandboxFileDownloadFailureNote(input, failures) {
3488
+ const note = sandboxFileDownloadFailureNote(failures);
3489
+ if (!note) {
3490
+ return;
3491
+ }
3492
+ if (typeof input.input === "string") {
3493
+ input.input = [input.input, "", note].join("\n");
3494
+ return;
3495
+ }
3496
+ if (Array.isArray(input.input)) {
3497
+ input.input = [
3498
+ ...input.input,
3499
+ { type: "message", role: "user", content: note }
3500
+ ];
3501
+ }
3502
+ }
3105
3503
  function runScopedRunner(settings) {
3106
3504
  return new Runner({ modelProvider: new MultiProviderModelProvider(settings) });
3107
3505
  }
@@ -3125,7 +3523,7 @@ function agentsErrorRunState(error) {
3125
3523
  return null;
3126
3524
  }
3127
3525
  }
3128
- function withManifestRefreshOnResume(client, targetManifest) {
3526
+ function withManifestRefreshOnResume(client, targetManifest, context = {}) {
3129
3527
  if (!targetManifest || !client.resume) {
3130
3528
  return client;
3131
3529
  }
@@ -3135,17 +3533,25 @@ function withManifestRefreshOnResume(client, targetManifest) {
3135
3533
  ...client.create ? { create: async (...args) => await client.create(...args) } : {},
3136
3534
  resume: async (state) => {
3137
3535
  const session = await client.resume(state);
3138
- await applyMissingManifestEntries(session, targetManifest);
3536
+ await applyMissingManifestEntries(session, targetManifest, context);
3139
3537
  return session;
3140
3538
  },
3141
3539
  ...client.delete ? { delete: async (state) => await client.delete(state) } : {},
3142
- ...client.serializeSessionState ? { serializeSessionState: async (state, options) => await client.serializeSessionState(state, options) } : {},
3143
- ...client.canPersistOwnedSessionState ? { canPersistOwnedSessionState: async (state) => await client.canPersistOwnedSessionState(state) } : {},
3144
- ...client.canReusePreservedOwnedSession ? { canReusePreservedOwnedSession: async (state) => await client.canReusePreservedOwnedSession(state) } : {},
3145
- ...client.deserializeSessionState ? { deserializeSessionState: async (state) => await client.deserializeSessionState(state) } : {}
3540
+ ...client.serializeSessionState ? {
3541
+ serializeSessionState: async (state, options) => await client.serializeSessionState(state, options)
3542
+ } : {},
3543
+ ...client.canPersistOwnedSessionState ? {
3544
+ canPersistOwnedSessionState: async (state) => await client.canPersistOwnedSessionState(state)
3545
+ } : {},
3546
+ ...client.canReusePreservedOwnedSession ? {
3547
+ canReusePreservedOwnedSession: async (state) => await client.canReusePreservedOwnedSession(state)
3548
+ } : {},
3549
+ ...client.deserializeSessionState ? {
3550
+ deserializeSessionState: async (state) => await client.deserializeSessionState(state)
3551
+ } : {}
3146
3552
  };
3147
3553
  }
3148
- async function applyMissingManifestEntries(session, targetManifest) {
3554
+ async function applyMissingManifestEntries(session, targetManifest, context = {}) {
3149
3555
  const currentManifestValue = session.state?.manifest;
3150
3556
  const currentManifest = currentManifestValue ? ensureManifest(currentManifestValue) : void 0;
3151
3557
  const target = ensureManifest(targetManifest);
@@ -3153,13 +3559,18 @@ async function applyMissingManifestEntries(session, targetManifest) {
3153
3559
  if (Object.keys(target.entries).length === 0) {
3154
3560
  return;
3155
3561
  }
3156
- throw new Error("Resumed sandbox session cannot apply new manifest entries because current manifest state is unavailable");
3562
+ throw new Error(
3563
+ "Resumed sandbox session cannot apply new manifest entries because current manifest state is unavailable"
3564
+ );
3157
3565
  }
3566
+ await reportManifestEnvironmentDrift(currentManifest, target, context);
3158
3567
  if (!session.applyManifest && !session.materializeEntry) {
3159
3568
  if (Object.keys(target.entries).length === 0) {
3160
3569
  return;
3161
3570
  }
3162
- throw new Error("Resumed sandbox session cannot apply new manifest entries because it does not support applyManifest() or materializeEntry()");
3571
+ throw new Error(
3572
+ "Resumed sandbox session cannot apply new manifest entries because it does not support applyManifest() or materializeEntry()"
3573
+ );
3163
3574
  }
3164
3575
  if (Object.keys(target.entries).length === 0) {
3165
3576
  return;
@@ -3180,7 +3591,9 @@ async function applyMissingManifestEntries(session, targetManifest) {
3180
3591
  }
3181
3592
  const environmentChanged = stableJson(currentManifest.environment) !== stableJson(target.environment);
3182
3593
  if (environmentChanged && !session.applyManifest) {
3183
- throw new Error("Resumed sandbox session cannot refresh manifest environment because it does not support applyManifest()");
3594
+ throw new Error(
3595
+ "Resumed sandbox session cannot refresh manifest environment because it does not support applyManifest()"
3596
+ );
3184
3597
  }
3185
3598
  if (Object.keys(entries).length === 0 && !environmentChanged) {
3186
3599
  return;
@@ -3209,6 +3622,114 @@ async function applyMissingManifestEntries(session, targetManifest) {
3209
3622
  ...extraPathGrants.length ? { extraPathGrants } : {}
3210
3623
  });
3211
3624
  }
3625
+ function manifestEnvironmentDrift(current, target) {
3626
+ const currentEnv = current.environment ?? {};
3627
+ const targetEnv = target.environment ?? {};
3628
+ const added = [];
3629
+ const removed = [];
3630
+ const changed = [];
3631
+ for (const key of Object.keys(targetEnv)) {
3632
+ if (!(key in currentEnv)) {
3633
+ added.push(key);
3634
+ } else if (stableJson(currentEnv[key]) !== stableJson(targetEnv[key])) {
3635
+ changed.push(key);
3636
+ }
3637
+ }
3638
+ for (const key of Object.keys(currentEnv)) {
3639
+ if (!(key in targetEnv)) {
3640
+ removed.push(key);
3641
+ }
3642
+ }
3643
+ if (added.length === 0 && removed.length === 0 && changed.length === 0) {
3644
+ return null;
3645
+ }
3646
+ return { added: added.sort(), removed: removed.sort(), changed: changed.sort() };
3647
+ }
3648
+ async function reportManifestEnvironmentDrift(current, target, context) {
3649
+ const drift = manifestEnvironmentDrift(current, target);
3650
+ if (!drift) {
3651
+ return null;
3652
+ }
3653
+ try {
3654
+ await context.onRuntimeEvent?.({ type: "sandbox.env.drift", payload: drift });
3655
+ } catch {
3656
+ }
3657
+ return drift;
3658
+ }
3659
+ async function pinProvidedSessionManifestEnvironment(agent, session, context = {}) {
3660
+ const holder = agent;
3661
+ const currentManifestValue = session.state?.manifest;
3662
+ if (!holder.defaultManifest || !currentManifestValue) {
3663
+ return;
3664
+ }
3665
+ const current = ensureManifest(currentManifestValue);
3666
+ const target = ensureManifest(holder.defaultManifest);
3667
+ const drift = await reportManifestEnvironmentDrift(current, target, context);
3668
+ if (!drift) {
3669
+ return;
3670
+ }
3671
+ holder.defaultManifest = new Manifest({
3672
+ ...target.root ? { root: target.root } : {},
3673
+ entries: target.entries,
3674
+ environment: current.environment,
3675
+ ...target.extraPathGrants?.length ? { extraPathGrants: target.extraPathGrants } : {}
3676
+ });
3677
+ }
3678
+ async function runOwnedSandboxSetup(agent, session, setupSession, opts) {
3679
+ const { settings, environment } = opts;
3680
+ await pinProvidedSessionManifestEnvironment(agent, session, {
3681
+ ...opts.onRuntimeEvent ? { onRuntimeEvent: opts.onRuntimeEvent } : {}
3682
+ });
3683
+ const runAs = sandboxRunAs(settings);
3684
+ const fileDownloads = sandboxFileDownloadsForAgent(agent);
3685
+ const ownedGitTokenSeeds = {
3686
+ ...gitTokenSeedsForAgent(agent) ?? {},
3687
+ ...opts.gitTokenSeedOverride ? { github: opts.gitTokenSeedOverride } : {},
3688
+ ...opts.gitTokenSeedsOverride ?? {}
3689
+ };
3690
+ const ownedToolspaceTokenSeed = toolspaceTokenSeedForAgent(agent);
3691
+ const ownedRigSetup = rigSetupDescriptorForAgent(agent);
3692
+ const ownedHooks = [
3693
+ // M3: rig setup runs FIRST so any tooling it installs is present for the
3694
+ // credential / repository-clone hooks below; the rig's credential hooks are
3695
+ // unioned into the deployment preparation-profile hooks (deduped by id).
3696
+ // This is the LIVE owned-path execution (the provided session skips the
3697
+ // client create/resume decoration), so the rig hooks MUST be here or a
3698
+ // rig-bound turn would start without ever running the frozen setup script.
3699
+ ...sandboxRigSetupHooksForAgent(agent),
3700
+ ...unionCredentialHooks(
3701
+ sandboxLifecycleHooksForIds(sandboxLifecycleHookIds(settings)),
3702
+ rigCredentialHooksForAgent(agent)
3703
+ ),
3704
+ ...sandboxToolspaceTokenHooksForAgent(agent),
3705
+ ...sandboxRepositoryCloneHooksForAgent(agent)
3706
+ ];
3707
+ const ownedHookContext = {
3708
+ environment,
3709
+ ...opts.onRuntimeEvent ? { onRuntimeEvent: opts.onRuntimeEvent } : {},
3710
+ ...runAs ? { runAs } : {},
3711
+ ...Object.keys(ownedGitTokenSeeds).length > 0 ? { gitTokenSeeds: ownedGitTokenSeeds } : {},
3712
+ ...ownedToolspaceTokenSeed ? { toolspaceTokenSeed: ownedToolspaceTokenSeed } : {},
3713
+ ...ownedRigSetup ? { rigSetup: ownedRigSetup } : {}
3714
+ };
3715
+ if (agentActiveSandboxBackend.get(agent) !== "selfhosted") {
3716
+ await runBeforeAgentStartHooks(setupSession, ownedHooks, ownedHookContext);
3717
+ if (fileDownloads.length > 0 && !opts.fileDownloadsMaterialized) {
3718
+ const materialized = await materializeSandboxFileDownloads(setupSession, fileDownloads, {
3719
+ ...opts.onRuntimeEvent ? { onRuntimeEvent: opts.onRuntimeEvent } : {},
3720
+ ...runAs ? { runAs } : {}
3721
+ });
3722
+ if (opts.preparedInput) {
3723
+ appendSandboxFileDownloadFailureNote(opts.preparedInput, materialized.failures);
3724
+ }
3725
+ }
3726
+ } else {
3727
+ const toolspaceHooks = sandboxToolspaceTokenHooksForAgent(agent);
3728
+ if (toolspaceHooks.length > 0) {
3729
+ await runBeforeAgentStartHooks(setupSession, toolspaceHooks, ownedHookContext);
3730
+ }
3731
+ }
3732
+ }
3212
3733
  function mergePathGrants(current, target) {
3213
3734
  const merged = /* @__PURE__ */ new Map();
3214
3735
  for (const grant of [...current ?? [], ...target ?? []]) {
@@ -3232,23 +3753,33 @@ function withSandboxFileDownloads(client, downloads, context = {}) {
3232
3753
  return {
3233
3754
  backendId: client.backendId,
3234
3755
  ...client.supportsDefaultOptions !== void 0 ? { supportsDefaultOptions: client.supportsDefaultOptions } : {},
3235
- ...client.create ? { create: async (...args) => await wrapSession(await client.create(...args)) } : {},
3236
- ...client.resume ? { resume: async (state) => await wrapSession(await client.resume(state)) } : {},
3756
+ ...client.create ? {
3757
+ create: async (...args) => await wrapSession(await client.create(...args))
3758
+ } : {},
3759
+ ...client.resume ? {
3760
+ resume: async (state) => await wrapSession(await client.resume(state))
3761
+ } : {},
3237
3762
  ...client.delete ? { delete: async (state) => await client.delete(state) } : {},
3238
- ...client.serializeSessionState ? { serializeSessionState: async (state, options) => await client.serializeSessionState(state, options) } : {},
3239
- ...client.canPersistOwnedSessionState ? { canPersistOwnedSessionState: async (state) => await client.canPersistOwnedSessionState(state) } : {},
3240
- ...client.canReusePreservedOwnedSession ? { canReusePreservedOwnedSession: async (state) => await client.canReusePreservedOwnedSession(state) } : {},
3241
- ...client.deserializeSessionState ? { deserializeSessionState: async (state) => await client.deserializeSessionState(state) } : {}
3763
+ ...client.serializeSessionState ? {
3764
+ serializeSessionState: async (state, options) => await client.serializeSessionState(state, options)
3765
+ } : {},
3766
+ ...client.canPersistOwnedSessionState ? {
3767
+ canPersistOwnedSessionState: async (state) => await client.canPersistOwnedSessionState(state)
3768
+ } : {},
3769
+ ...client.canReusePreservedOwnedSession ? {
3770
+ canReusePreservedOwnedSession: async (state) => await client.canReusePreservedOwnedSession(state)
3771
+ } : {},
3772
+ ...client.deserializeSessionState ? {
3773
+ deserializeSessionState: async (state) => await client.deserializeSessionState(state)
3774
+ } : {}
3242
3775
  };
3243
3776
  }
3244
3777
  async function materializeSandboxFileDownloads(session, downloads, context = {}) {
3245
3778
  const normalizedDownloads = normalizeSandboxFileDownloads(downloads);
3246
3779
  if (normalizedDownloads.length === 0) {
3247
- return;
3248
- }
3249
- if (!session.exec && !session.execCommand) {
3250
- throw new Error("Sandbox file download materialization requires command execution support");
3780
+ return { failures: [] };
3251
3781
  }
3782
+ const failures = [];
3252
3783
  for (const download of normalizedDownloads) {
3253
3784
  const targetPath = sandboxDownloadTargetPath(download);
3254
3785
  const payload = {
@@ -3257,9 +3788,30 @@ async function materializeSandboxFileDownloads(session, downloads, context = {})
3257
3788
  sizeBytes: download.sizeBytes ?? null,
3258
3789
  expiresAt: download.expiresAt ? new Date(download.expiresAt).toISOString() : null
3259
3790
  };
3260
- await context.onRuntimeEvent?.({ type: "sandbox.operation.started", payload: { name: "file-resource-download", ...payload } });
3791
+ await context.onRuntimeEvent?.({
3792
+ type: "sandbox.operation.started",
3793
+ payload: { name: "file-resource-download", ...payload }
3794
+ });
3795
+ if (!session.exec && !session.execCommand) {
3796
+ const failure = sandboxFileDownloadFailure(
3797
+ download,
3798
+ targetPath,
3799
+ "Sandbox file download materialization requires command execution support"
3800
+ );
3801
+ failures.push(failure);
3802
+ await context.onRuntimeEvent?.({
3803
+ type: "sandbox.operation.failed",
3804
+ payload: {
3805
+ name: "file-resource-download",
3806
+ ...payload,
3807
+ error: failure.reason
3808
+ }
3809
+ });
3810
+ continue;
3811
+ }
3812
+ let result;
3261
3813
  try {
3262
- const result = session.exec ? await session.exec({
3814
+ result = session.exec ? await session.exec({
3263
3815
  cmd: sandboxFileDownloadCommand(download, targetPath),
3264
3816
  workdir: "/workspace",
3265
3817
  ...context.runAs ? { runAs: context.runAs } : {},
@@ -3273,19 +3825,48 @@ async function materializeSandboxFileDownloads(session, downloads, context = {})
3273
3825
  maxOutputTokens: 2e4
3274
3826
  });
3275
3827
  assertSandboxCommandSucceeded(result, `Sandbox file resource download ${download.fileId}`);
3276
- await context.onRuntimeEvent?.({ type: "sandbox.operation.completed", payload: { name: "file-resource-download", ...payload } });
3828
+ await context.onRuntimeEvent?.({
3829
+ type: "sandbox.operation.completed",
3830
+ payload: { name: "file-resource-download", ...payload }
3831
+ });
3277
3832
  } catch (error) {
3833
+ const failure = sandboxFileDownloadFailure(download, targetPath, error, result);
3834
+ failures.push(failure);
3278
3835
  await context.onRuntimeEvent?.({
3279
3836
  type: "sandbox.operation.failed",
3280
3837
  payload: {
3281
3838
  name: "file-resource-download",
3282
3839
  ...payload,
3283
- error: error instanceof Error ? error.message : String(error)
3840
+ error: failure.reason,
3841
+ ...failure.exitCode !== void 0 ? { exitCode: failure.exitCode } : {},
3842
+ ...failure.output ? { output: failure.output } : {}
3284
3843
  }
3285
3844
  });
3286
- throw error;
3287
3845
  }
3288
3846
  }
3847
+ return { failures };
3848
+ }
3849
+ function sandboxFileDownloadFailure(download, targetPath, error, result) {
3850
+ const exitCode = sandboxCommandExitCode(result);
3851
+ const output = sandboxCommandOutput(result);
3852
+ return {
3853
+ fileId: download.fileId,
3854
+ filename: download.filename,
3855
+ path: targetPath,
3856
+ reason: error instanceof Error ? error.message : String(error),
3857
+ ...exitCode !== null ? { exitCode } : {},
3858
+ ...output ? { output } : {}
3859
+ };
3860
+ }
3861
+ function sandboxFileDownloadFailureNote(failures) {
3862
+ if (failures.length === 0) {
3863
+ return "";
3864
+ }
3865
+ return [
3866
+ "The following attached files could not be loaded into the sandbox and are unavailable this turn:",
3867
+ ...failures.map((failure) => `- ${failure.filename} (${failure.reason})`),
3868
+ "Continue without them or tell the user."
3869
+ ].join("\n");
3289
3870
  }
3290
3871
  function sandboxFileDownloadsForAgent(agent) {
3291
3872
  return typeof agent === "object" && agent !== null ? [...agentFileDownloads.get(agent) ?? []] : [];
@@ -3360,6 +3941,9 @@ function normalizeSdkEvent(event) {
3360
3941
  out.push({ type: "agent.message.delta", payload: { text: data.delta } });
3361
3942
  return out;
3362
3943
  }
3944
+ if (data?.type === "response_done") {
3945
+ return out;
3946
+ }
3363
3947
  }
3364
3948
  if (isOpenAIResponsesRawModelStreamEvent(event)) {
3365
3949
  const raw = event.data?.event;
@@ -3418,7 +4002,10 @@ function normalizeSdkEvent(event) {
3418
4002
  type: "agent.toolCall.output",
3419
4003
  payload: {
3420
4004
  id: raw.call_id ?? raw.callId ?? item.id ?? null,
3421
- output: { type: "text", text: disclosed.length > 0 ? `Disclosed tools: ${disclosed.join(", ")}` : "No matching tools found." }
4005
+ output: {
4006
+ type: "text",
4007
+ text: disclosed.length > 0 ? `Disclosed tools: ${disclosed.join(", ")}` : "No matching tools found."
4008
+ }
3422
4009
  }
3423
4010
  });
3424
4011
  } else if (item.type === "message_output_item") {
@@ -3431,6 +4018,9 @@ function normalizeSdkEvent(event) {
3431
4018
  }
3432
4019
  function modelResponseUsageFromSdkEvent(event) {
3433
4020
  const response = modelResponseFromSdkEvent(event);
4021
+ return modelResponseUsageFromResponse(response);
4022
+ }
4023
+ function modelResponseUsageFromResponse(response) {
3434
4024
  const usage = usageFromResponse(response);
3435
4025
  if (!usage) {
3436
4026
  return null;
@@ -3461,25 +4051,51 @@ function usageFromResponse(response) {
3461
4051
  if (!raw || typeof raw !== "object") {
3462
4052
  return null;
3463
4053
  }
4054
+ const record = raw;
3464
4055
  const usage = {
3465
- ...numberProp(raw, "inputTokens", "inputTokens", "input_tokens"),
3466
- ...numberProp(raw, "outputTokens", "outputTokens", "output_tokens"),
3467
- ...numberProp(raw, "totalTokens", "totalTokens", "total_tokens"),
3468
- ...inputTokenDetailsProp(raw)
4056
+ ...numberProp(
4057
+ record,
4058
+ "inputTokens",
4059
+ "inputTokens",
4060
+ "input_tokens",
4061
+ "promptTokens",
4062
+ "prompt_tokens"
4063
+ ),
4064
+ ...numberProp(
4065
+ record,
4066
+ "outputTokens",
4067
+ "outputTokens",
4068
+ "output_tokens",
4069
+ "completionTokens",
4070
+ "completion_tokens"
4071
+ ),
4072
+ ...numberProp(record, "totalTokens", "totalTokens", "total_tokens"),
4073
+ ...inputTokenDetailsProp(record),
4074
+ ...outputTokenDetailsProp(record)
3469
4075
  };
3470
4076
  return Object.keys(usage).length > 0 ? usage : null;
3471
4077
  }
3472
- function numberProp(raw, outputKey, camel, snake) {
3473
- const value = raw[camel] ?? raw[snake];
4078
+ function numberProp(raw, outputKey, ...keys) {
4079
+ const value = keys.map((key) => raw[key]).find((candidate) => candidate !== void 0);
3474
4080
  return typeof value === "number" && Number.isFinite(value) ? { [outputKey]: value } : {};
3475
4081
  }
3476
4082
  function inputTokenDetailsProp(raw) {
3477
- const details = raw.inputTokensDetails ?? raw.input_tokens_details;
4083
+ const details = raw.inputTokensDetails ?? raw.input_tokens_details ?? raw.promptTokensDetails ?? raw.prompt_tokens_details;
3478
4084
  if (!details || typeof details !== "object") {
3479
4085
  return {};
3480
4086
  }
3481
4087
  return { inputTokensDetails: details };
3482
4088
  }
4089
+ function outputTokenDetailsProp(raw) {
4090
+ const details = raw.outputTokensDetails ?? raw.output_tokens_details;
4091
+ const normalized = details ?? raw.completionTokensDetails ?? raw.completion_tokens_details;
4092
+ if (!normalized || typeof normalized !== "object") {
4093
+ return {};
4094
+ }
4095
+ return {
4096
+ outputTokensDetails: normalized
4097
+ };
4098
+ }
3483
4099
  function serializeApprovals(interruptions) {
3484
4100
  return interruptions.map((item) => {
3485
4101
  if (typeof item?.toJSON === "function") {
@@ -3495,7 +4111,12 @@ function serializeApprovals(interruptions) {
3495
4111
  }
3496
4112
  function buildManifest(settings, resources, environment = collectSandboxEnvironment2(settings), fileResourceDownloads = []) {
3497
4113
  const entries = {};
3498
- const downloadsByFileId = new Map(normalizeSandboxFileDownloads(fileResourceDownloads).map((download) => [download.fileId, download]));
4114
+ const downloadsByFileId = new Map(
4115
+ normalizeSandboxFileDownloads(fileResourceDownloads).map((download) => [
4116
+ download.fileId,
4117
+ download
4118
+ ])
4119
+ );
3499
4120
  for (const resource of resources) {
3500
4121
  if (resource.kind === "repository") {
3501
4122
  const url = new URL(resource.uri);
@@ -3528,7 +4149,9 @@ function buildManifest(settings, resources, environment = collectSandboxEnvironm
3528
4149
  }
3529
4150
  function sandboxDownloadDirectory(download, mountPath) {
3530
4151
  if (download.mountPath !== mountPath) {
3531
- throw new Error(`File download materialization path mismatch for ${download.fileId}: expected ${mountPath}, got ${download.mountPath}`);
4152
+ throw new Error(
4153
+ `File download materialization path mismatch for ${download.fileId}: expected ${mountPath}, got ${download.mountPath}`
4154
+ );
3532
4155
  }
3533
4156
  assertSafeSandboxFilename(download.filename, download.fileId);
3534
4157
  if (download.content) {
@@ -3544,7 +4167,9 @@ function objectStorageFileMount(settings, prefix) {
3544
4167
  const nativeBucketMount = CAPABILITY_DESCRIPTORS2[settings.sandboxBackend].nativeBucketMount;
3545
4168
  if (settings.objectStorageBackend === "azure-blob") {
3546
4169
  if (nativeBucketMount) {
3547
- throw new Error("Modal sandbox Azure Blob file resources require pre-signed download materialization because the current OpenAI Agents SDK Modal client does not support Azure Blob mount entries.");
4170
+ throw new Error(
4171
+ "Modal sandbox Azure Blob file resources require pre-signed download materialization because the current OpenAI Agents SDK Modal client does not support Azure Blob mount entries."
4172
+ );
3548
4173
  }
3549
4174
  const config2 = azureBlobMountConfig(settings);
3550
4175
  return azureBlobMount({
@@ -3558,7 +4183,9 @@ function objectStorageFileMount(settings, prefix) {
3558
4183
  });
3559
4184
  }
3560
4185
  if (settings.objectStorageBackend === "aws-s3" || settings.objectStorageBackend === "gcs") {
3561
- throw new Error(`${settings.objectStorageBackend} file resources require pre-signed download materialization`);
4186
+ throw new Error(
4187
+ `${settings.objectStorageBackend} file resources require pre-signed download materialization`
4188
+ );
3562
4189
  }
3563
4190
  const config = s3CompatibleMountConfig(settings);
3564
4191
  return s3Mount({
@@ -3594,7 +4221,10 @@ function azureBlobMountConfig(settings) {
3594
4221
  if (!accountName || !accountKey) {
3595
4222
  throw new Error("File resources require Azure Blob account name and account key");
3596
4223
  }
3597
- const endpointUrl = azureBlobManifestEndpoint(settings.objectStorageAzureEndpoint ?? parsed.BlobEndpoint, accountName);
4224
+ const endpointUrl = azureBlobManifestEndpoint(
4225
+ settings.objectStorageAzureEndpoint ?? parsed.BlobEndpoint,
4226
+ accountName
4227
+ );
3598
4228
  return {
3599
4229
  container: settings.objectStorageBucket,
3600
4230
  accountName,
@@ -3611,10 +4241,12 @@ function azureBlobManifestEndpoint(endpoint, accountName) {
3611
4241
  return normalized === standardAccountEndpoint ? void 0 : normalized;
3612
4242
  }
3613
4243
  function parseAzureConnectionString(value) {
3614
- return Object.fromEntries(value.split(";").map((part) => part.trim()).filter(Boolean).map((part) => {
3615
- const index = part.indexOf("=");
3616
- return index === -1 ? [part, ""] : [part.slice(0, index), part.slice(index + 1)];
3617
- }));
4244
+ return Object.fromEntries(
4245
+ value.split(";").map((part) => part.trim()).filter(Boolean).map((part) => {
4246
+ const index = part.indexOf("=");
4247
+ return index === -1 ? [part, ""] : [part.slice(0, index), part.slice(index + 1)];
4248
+ })
4249
+ );
3618
4250
  }
3619
4251
  function normalizeManifestPath(path) {
3620
4252
  const normalized = path.replace(/^\/+|\/+$/g, "");
@@ -3628,7 +4260,9 @@ function normalizeSandboxFileDownloads(downloads) {
3628
4260
  const mountPath = normalizeManifestPath(download.mountPath);
3629
4261
  assertSafeSandboxFilename(download.filename, download.fileId);
3630
4262
  if (!download.content && !download.url?.trim()) {
3631
- throw new Error(`File download materialization requires content or a URL for ${download.fileId}`);
4263
+ throw new Error(
4264
+ `File download materialization requires content or a URL for ${download.fileId}`
4265
+ );
3632
4266
  }
3633
4267
  return {
3634
4268
  ...download,
@@ -3651,7 +4285,7 @@ function sandboxFileDownloadCommand(download, targetPath) {
3651
4285
  const targetDir = posixPath.dirname(targetPath);
3652
4286
  const tmpPath = `${targetPath}.opengeni-download-$$`;
3653
4287
  return [
3654
- "set -euo pipefail",
4288
+ "set -eu",
3655
4289
  `mkdir -p -- ${shellQuote(targetDir)}`,
3656
4290
  `if [ ! -f ${shellQuote(targetPath)} ]; then`,
3657
4291
  ` tmp=${shellQuote(tmpPath)}`,
@@ -3667,25 +4301,6 @@ function sandboxFileDownloadCommand(download, targetPath) {
3667
4301
  function shellQuote(value) {
3668
4302
  return `'${value.replace(/'/g, "'\\''")}'`;
3669
4303
  }
3670
- async function restoredSandboxSessionState(state, client) {
3671
- if (!client) {
3672
- return void 0;
3673
- }
3674
- const sandboxState = state._sandbox;
3675
- const entry = sandboxState?.sessionsByAgent?.[sandboxState.currentAgentKey] ?? (sandboxState?.currentAgentKey && sandboxState?.sessionState ? {
3676
- backendId: sandboxState.backendId,
3677
- currentAgentKey: sandboxState.currentAgentKey,
3678
- currentAgentName: sandboxState.currentAgentName,
3679
- sessionState: sandboxState.sessionState
3680
- } : void 0);
3681
- if (!entry) {
3682
- return void 0;
3683
- }
3684
- if (client.backendId !== entry.backendId) {
3685
- throw new Error("RunState sandbox backend does not match the configured sandbox client");
3686
- }
3687
- return await deserializeSandboxSessionStateEnvelope(client, entry.sessionState);
3688
- }
3689
4304
  var builtInSandboxLifecycleHooks = {
3690
4305
  "azure-cli-login": {
3691
4306
  id: "azure-cli-login",
@@ -3704,7 +4319,9 @@ function sandboxLifecycleHooksForIds(ids) {
3704
4319
  });
3705
4320
  }
3706
4321
  function applicableBeforeAgentStartHooks(hooks, context) {
3707
- return hooks.filter((hook) => hook.phase === "beforeAgentStart" && (hook.shouldRun?.(context) ?? true));
4322
+ return hooks.filter(
4323
+ (hook) => hook.phase === "beforeAgentStart" && (hook.shouldRun?.(context) ?? true)
4324
+ );
3708
4325
  }
3709
4326
  async function runBeforeAgentStartHooks(session, hooks, context) {
3710
4327
  for (const hook of applicableBeforeAgentStartHooks(hooks, context)) {
@@ -3729,102 +4346,328 @@ function withSandboxLifecycleHooks(client, hooks, context) {
3729
4346
  const wrapped = {
3730
4347
  backendId: client.backendId,
3731
4348
  ...client.supportsDefaultOptions !== void 0 ? { supportsDefaultOptions: client.supportsDefaultOptions } : {},
3732
- ...client.create ? { create: async (...args) => await wrapSession(await client.create(...args)) } : {},
3733
- ...client.resume ? { resume: async (state) => await wrapSession(await client.resume(state)) } : {},
4349
+ ...client.create ? {
4350
+ create: async (...args) => await wrapSession(await client.create(...args))
4351
+ } : {},
4352
+ ...client.resume ? {
4353
+ resume: async (state) => await wrapSession(await client.resume(state))
4354
+ } : {},
3734
4355
  ...client.delete ? { delete: async (state) => await client.delete(state) } : {},
3735
- ...client.serializeSessionState ? { serializeSessionState: async (state, options) => await client.serializeSessionState(state, options) } : {},
3736
- ...client.canPersistOwnedSessionState ? { canPersistOwnedSessionState: async (state) => await client.canPersistOwnedSessionState(state) } : {},
3737
- ...client.canReusePreservedOwnedSession ? { canReusePreservedOwnedSession: async (state) => await client.canReusePreservedOwnedSession(state) } : {},
3738
- ...client.deserializeSessionState ? { deserializeSessionState: async (state) => await client.deserializeSessionState(state) } : {}
4356
+ ...client.serializeSessionState ? {
4357
+ serializeSessionState: async (state, options) => await client.serializeSessionState(state, options)
4358
+ } : {},
4359
+ ...client.canPersistOwnedSessionState ? {
4360
+ canPersistOwnedSessionState: async (state) => await client.canPersistOwnedSessionState(state)
4361
+ } : {},
4362
+ ...client.canReusePreservedOwnedSession ? {
4363
+ canReusePreservedOwnedSession: async (state) => await client.canReusePreservedOwnedSession(state)
4364
+ } : {},
4365
+ ...client.deserializeSessionState ? {
4366
+ deserializeSessionState: async (state) => await client.deserializeSessionState(state)
4367
+ } : {}
3739
4368
  };
3740
4369
  return wrapped;
3741
4370
  }
3742
4371
  function sandboxRepositoryCloneHooksForAgent(agent) {
3743
4372
  return agentRepositoryCloneHooks.get(agent) ?? [];
3744
4373
  }
3745
- function gitTokenSeedForAgent(agent) {
3746
- return agentGitTokenSeed.get(agent);
4374
+ function gitTokenSeedsForAgent(agent) {
4375
+ return agentGitTokenSeeds.get(agent);
3747
4376
  }
3748
4377
  function toolspaceTokenSeedForAgent(agent) {
3749
4378
  return agentToolspaceTokenSeed.get(agent);
3750
4379
  }
3751
4380
  function sandboxToolspaceTokenHooksForAgent(agent) {
3752
- return toolspaceTokenSeedForAgent(agent) ? [{
3753
- id: "toolspace-token",
3754
- phase: "beforeAgentStart",
3755
- run: runToolspaceTokenSeedHook
3756
- }] : [];
4381
+ return toolspaceTokenSeedForAgent(agent) ? [
4382
+ {
4383
+ id: "toolspace-token",
4384
+ phase: "beforeAgentStart",
4385
+ run: runToolspaceTokenSeedHook
4386
+ }
4387
+ ] : [];
4388
+ }
4389
+ function sandboxRigSetupHooksForAgent(agent) {
4390
+ return agentRigSetup.get(agent) ? [
4391
+ {
4392
+ id: "rig-setup",
4393
+ phase: "beforeAgentStart",
4394
+ run: runRigSetupHook
4395
+ }
4396
+ ] : [];
4397
+ }
4398
+ function rigSetupDescriptorForAgent(agent) {
4399
+ return agentRigSetup.get(agent);
4400
+ }
4401
+ function rigCredentialHooksForAgent(agent) {
4402
+ return agentRigCredentialHooks.get(agent) ?? [];
4403
+ }
4404
+ function unionCredentialHooks(deploymentHooks, rigHooks) {
4405
+ if (rigHooks.length === 0) {
4406
+ return deploymentHooks;
4407
+ }
4408
+ const seen = new Set(deploymentHooks.map((hook) => hook.id));
4409
+ return [...deploymentHooks, ...rigHooks.filter((hook) => !seen.has(hook.id))];
4410
+ }
4411
+ function gitCredentialSessionRegistrationHooks(callback) {
4412
+ return callback ? [
4413
+ {
4414
+ id: "git-credential-renewal-registration",
4415
+ phase: "beforeAgentStart",
4416
+ run: async (session) => {
4417
+ await callback(session);
4418
+ }
4419
+ }
4420
+ ] : [];
3757
4421
  }
3758
4422
  function sandboxRepositoryCloneHooks(settings, resources, activeSandboxBackend = settings.sandboxBackend) {
3759
- const repositories = resources.filter((resource) => resource.kind === "repository" && repositoryUsesSandboxClone(settings, resource, activeSandboxBackend));
4423
+ const repositories = resources.filter(
4424
+ (resource) => resource.kind === "repository" && repositoryUsesSandboxClone(settings, resource, activeSandboxBackend)
4425
+ );
3760
4426
  if (repositories.length === 0) {
3761
4427
  return [];
3762
4428
  }
3763
- return [{
3764
- id: "repository-clone",
3765
- phase: "beforeAgentStart",
3766
- run: async (session, context) => {
3767
- await runRepositoryCloneHook(session, repositories, context);
4429
+ return [
4430
+ {
4431
+ id: "repository-clone",
4432
+ phase: "beforeAgentStart",
4433
+ run: async (session, context) => {
4434
+ await runRepositoryCloneHook(session, repositories, context);
4435
+ }
3768
4436
  }
3769
- }];
4437
+ ];
3770
4438
  }
3771
4439
  function repositoryUsesSandboxClone(settings, resource, activeSandboxBackend = settings.sandboxBackend) {
3772
4440
  if (activeSandboxBackend === "selfhosted") {
3773
4441
  return false;
3774
4442
  }
3775
- return settings.sandboxBackend === "modal" || Boolean(resource.githubInstallationId && resource.githubRepositoryId);
4443
+ return settings.sandboxBackend === "modal" || Boolean(resource.githubInstallationId && resource.githubRepositoryId) || Boolean(resource.provider);
3776
4444
  }
3777
- function repositoryCloneCommand(resources) {
3778
- const commands = [
3779
- "set -eu",
3780
- 'export HOME="${HOME:-/workspace}"',
3781
- 'export GIT_TERMINAL_PROMPT="${GIT_TERMINAL_PROMPT:-0}"',
3782
- // TOKEN-BROKER (B1/B2): seed the run-scoped GitHub token into the STABLE token FILE
3783
- // AND provision the git-askpass helper into the box AT SETUP (runtime) BEFORE any
3784
- // clone runs, so GIT_ASKPASS points at a per-box, user-writable script that reads
3785
- // that file for the fetch below. Provisioning the askpass here (rather than relying
3786
- // on a baked image script at /usr/local/bin/opengeni-git-askpass) removes the
3787
- // image-rebuild rollout gate: the askpass is correct on ANY box image, including
3788
- // pre-existing warm boxes on their next turn's clone hook, and no product image has
3789
- // to carry it. The seed rides the per-exec env (OPENGENI_GIT_TOKEN_SEED) — NEVER the
3790
- // box/agent manifest (validateNoEnvironmentDelta must not see a rotating value), so
3791
- // this whole block is a no-op when the seed is absent (e.g. the selfhosted path,
3792
- // which uses its own git creds). The token file lives at $OPENGENI_GIT_TOKEN_FILE
3793
- // (stable, from the shared base) with a $HOME/.opengeni/git-token fallback.
3794
- // $GIT_ASKPASS is on the box manifest env (set by
3795
- // sandboxEnvironmentForRun to $HOME/.opengeni/askpass), so it is available to this
3796
- // exec; the askpass script we write is byte-identical to docker/opengeni-git-askpass
3797
- // and is written via a QUOTED heredoc (<<'ASKPASS_EOF') so NOTHING inside it expands
3798
- // ($1, $HOME, ${OPENGENI_GIT_TOKEN_FILE:-...}, and the literal \n in printf all land
3799
- // verbatim), then chmod 0755 so git can exec it.
3800
- //
3801
- // ATOMIC REWRITE: this block now re-runs at the start of EVERY turn on a warm box
3802
- // that other turn holders may be actively using — an in-flight `git fetch` from a
3803
- // concurrent turn can invoke the askpass (which cats the token file) at any moment.
3804
- // Both files are therefore written to a pid-suffixed temp under umask 077 and
3805
- // renamed into place: rename is atomic, concurrent readers keep the old inode, and
3806
- // the token is never observable world-readable (no post-hoc chmod window).
3807
- 'if [ -n "${OPENGENI_GIT_TOKEN_SEED:-}" ]; then',
3808
- ' seed_umask="$(umask)"',
3809
- " umask 077",
3810
- ' git_token_file="${OPENGENI_GIT_TOKEN_FILE:-$HOME/.opengeni/git-token}"',
3811
- ' mkdir -p "$(dirname "$git_token_file")"',
3812
- ` printf '%s' "$OPENGENI_GIT_TOKEN_SEED" > "$git_token_file.tmp.$$"`,
3813
- ' mv -f "$git_token_file.tmp.$$" "$git_token_file"',
3814
- ' git_askpass="${GIT_ASKPASS:-$HOME/.opengeni/askpass}"',
3815
- ' mkdir -p "$(dirname "$git_askpass")"',
3816
- ` cat > "$git_askpass.tmp.$$" <<'ASKPASS_EOF'`,
4445
+ var GIT_CREDENTIAL_PROVIDERS = [
4446
+ "github",
4447
+ "gitlab",
4448
+ "azure_devops"
4449
+ ];
4450
+ function gitProviderSeedEnv(provider) {
4451
+ return `OPENGENI_GIT_${provider.toUpperCase()}_TOKEN_SEED`;
4452
+ }
4453
+ function gitTokenSeedExportPrefix(seeds) {
4454
+ const lines = [];
4455
+ for (const provider of GIT_CREDENTIAL_PROVIDERS) {
4456
+ const token = seeds[provider];
4457
+ if (!token) {
4458
+ continue;
4459
+ }
4460
+ lines.push(`export ${gitProviderSeedEnv(provider)}=${shellQuote(token)}`);
4461
+ if (provider === "github") {
4462
+ lines.push(`export OPENGENI_GIT_TOKEN_SEED=${shellQuote(token)}`);
4463
+ }
4464
+ }
4465
+ return lines.join("\n");
4466
+ }
4467
+ function repositoryCredentialProvider(resource) {
4468
+ return resource.provider ?? "github";
4469
+ }
4470
+ function gitAskpassHostProviderCaseLines(resources) {
4471
+ const hosts = /* @__PURE__ */ new Map();
4472
+ for (const resource of resources) {
4473
+ try {
4474
+ const hostname = new URL(resource.uri).hostname.toLowerCase();
4475
+ if (!hostname) {
4476
+ continue;
4477
+ }
4478
+ hosts.set(hostname, repositoryCredentialProvider(resource));
4479
+ } catch {
4480
+ }
4481
+ }
4482
+ return [...hosts.entries()].sort(([a], [b]) => a.localeCompare(b)).map(
4483
+ ([hostname, provider]) => ` ${shellQuote(hostname)}) printf '%s\\n' ${provider}; return 0 ;;`
4484
+ );
4485
+ }
4486
+ function gitCredentialTokenWriterCommandLines() {
4487
+ return [
4488
+ // TOKEN-BROKER (B1/B2): seed run-scoped provider tokens into stable files and
4489
+ // atomically replace each provider file. Token VALUES are supplied only by
4490
+ // the per-exec command prefix
4491
+ // (OPENGENI_GIT_*_TOKEN_SEED), never by the box/agent manifest. Helper paths
4492
+ // are stable manifest values from @opengeni/config.
4493
+ "git_provider_token_file() {",
4494
+ ' provider="$1"',
4495
+ ' case "$provider" in',
4496
+ ` github) printf '%s\\n' "\${OPENGENI_GIT_TOKEN_FILE:-$HOME/.opengeni/git-token}" ;;`,
4497
+ ` *) printf '%s\\n' "\${OPENGENI_GIT_CREDENTIALS_DIR:-$HOME/.opengeni/git-credentials}/$provider-token" ;;`,
4498
+ " esac",
4499
+ "}",
4500
+ "write_git_provider_token() {",
4501
+ ' provider="$1"',
4502
+ ' token="$2"',
4503
+ ' [ -n "$token" ] || return 0',
4504
+ ' token_file="$(git_provider_token_file "$provider")"',
4505
+ ' mkdir -p "$(dirname "$token_file")"',
4506
+ ` printf '%s' "$token" > "$token_file.tmp.$$"`,
4507
+ ' mv -f "$token_file.tmp.$$" "$token_file"',
4508
+ ' if [ "$provider" = github ]; then',
4509
+ ' credential_dir="${OPENGENI_GIT_CREDENTIALS_DIR:-$HOME/.opengeni/git-credentials}"',
4510
+ ' mkdir -p "$credential_dir"',
4511
+ ` printf '%s' "$token" > "$credential_dir/github-token.tmp.$$"`,
4512
+ ' mv -f "$credential_dir/github-token.tmp.$$" "$credential_dir/github-token"',
4513
+ " fi",
4514
+ "}",
4515
+ 'seed_umask="$(umask)"',
4516
+ "umask 077",
4517
+ 'write_git_provider_token github "${OPENGENI_GIT_GITHUB_TOKEN_SEED:-${OPENGENI_GIT_TOKEN_SEED:-}}"',
4518
+ 'write_git_provider_token gitlab "${OPENGENI_GIT_GITLAB_TOKEN_SEED:-}"',
4519
+ 'write_git_provider_token azure_devops "${OPENGENI_GIT_AZURE_DEVOPS_TOKEN_SEED:-}"',
4520
+ 'umask "$seed_umask"'
4521
+ ];
4522
+ }
4523
+ function gitCredentialHelperCommandLines(resources = []) {
4524
+ const hostProviderCases = gitAskpassHostProviderCaseLines(resources);
4525
+ return [
4526
+ ...gitCredentialTokenWriterCommandLines(),
4527
+ // Provision git/provider-CLI helpers at SETUP (runtime) before any clone
4528
+ // runs. Renewal updates only token files and deliberately leaves these
4529
+ // repository-specific host mappings intact.
4530
+ 'git_askpass="${GIT_ASKPASS:-$HOME/.opengeni/askpass}"',
4531
+ 'mkdir -p "$(dirname "$git_askpass")"',
4532
+ `cat > "$git_askpass.tmp.$$" <<'ASKPASS_EOF'`,
3817
4533
  "#!/usr/bin/env sh",
4534
+ "prompt_host() {",
4535
+ ` prompt_lower="$(printf '%s\\n' "$1" | tr '[:upper:]' '[:lower:]')"`,
4536
+ ' case "$prompt_lower" in',
4537
+ " *://*) ;;",
4538
+ " *) printf '\\n'; return 0 ;;",
4539
+ " esac",
4540
+ ' rest="${prompt_lower#*://}"',
4541
+ ' rest="${rest#*@}"',
4542
+ ' host="${rest%%/*}"',
4543
+ ' host="${host%%:*}"',
4544
+ ` host="$(printf '%s\\n' "$host" | tr -d "'")"`,
4545
+ ` printf '%s\\n' "$host"`,
4546
+ "}",
4547
+ "provider_for_prompt() {",
4548
+ ' host="$(prompt_host "$1")"',
4549
+ ' case "$host" in',
4550
+ ...hostProviderCases.length > 0 ? hostProviderCases : [' "") : ;;'],
4551
+ " esac",
4552
+ ` case "$(printf '%s\\n' "$1" | tr '[:upper:]' '[:lower:]')" in`,
4553
+ " *github.com*|*githubusercontent.com*) printf '%s\\n' github ;;",
4554
+ " *gitlab*) printf '%s\\n' gitlab ;;",
4555
+ " *dev.azure.com*|*.visualstudio.com*) printf '%s\\n' azure_devops ;;",
4556
+ " *) printf '%s\\n' github ;;",
4557
+ " esac",
4558
+ "}",
4559
+ "token_file_for_provider() {",
4560
+ ' case "$1" in',
4561
+ ` github) printf '%s\\n' "\${OPENGENI_GIT_TOKEN_FILE:-$HOME/.opengeni/git-token}" ;;`,
4562
+ ` *) printf '%s\\n' "\${OPENGENI_GIT_CREDENTIALS_DIR:-$HOME/.opengeni/git-credentials}/$1-token" ;;`,
4563
+ " esac",
4564
+ "}",
4565
+ "username_for_provider() {",
4566
+ ' case "$1" in',
4567
+ ` github) printf '%s\\n' "x-access-token" ;;`,
4568
+ ` gitlab) printf '%s\\n' "oauth2" ;;`,
4569
+ ` azure_devops) printf '%s\\n' "opengeni" ;;`,
4570
+ " *) printf '\\n' ;;",
4571
+ " esac",
4572
+ "}",
4573
+ 'provider="$(provider_for_prompt "$1")"',
3818
4574
  'case "$1" in',
3819
- ` *Username*) printf '%s\\n' "x-access-token" ;;`,
3820
- ` *Password*) cat "\${OPENGENI_GIT_TOKEN_FILE:-$HOME/.opengeni/git-token}" 2>/dev/null || printf '\\n' ;;`,
4575
+ ' *Username*) username_for_provider "$provider" ;;',
4576
+ ` *Password*) cat "$(token_file_for_provider "$provider")" 2>/dev/null || printf '\\n' ;;`,
3821
4577
  " *) printf '\\n' ;;",
3822
4578
  "esac",
3823
4579
  "ASKPASS_EOF",
3824
- ' chmod 0755 "$git_askpass.tmp.$$"',
3825
- ' mv -f "$git_askpass.tmp.$$" "$git_askpass"',
3826
- ' umask "$seed_umask"',
4580
+ 'chmod 0755 "$git_askpass.tmp.$$"',
4581
+ 'mv -f "$git_askpass.tmp.$$" "$git_askpass"',
4582
+ 'wrapper_dir="${OPENGENI_GIT_CLI_WRAPPER_DIR:-$HOME/.opengeni/bin}"',
4583
+ 'mkdir -p "$wrapper_dir"',
4584
+ "for opengeni_git_cli_tool in gh glab az; do",
4585
+ ' wrapper="$wrapper_dir/$opengeni_git_cli_tool"',
4586
+ ` cat > "$wrapper.tmp.$$" <<'CLI_WRAPPER_EOF'`,
4587
+ "#!/usr/bin/env sh",
4588
+ "set -eu",
4589
+ 'tool="${0##*/}"',
4590
+ 'case "$tool" in',
4591
+ " gh) provider=github; token_env=GH_TOKEN ;;",
4592
+ " glab) provider=gitlab; token_env=GITLAB_TOKEN ;;",
4593
+ " az) provider=azure_devops; token_env=AZURE_DEVOPS_EXT_PAT ;;",
4594
+ " *) provider=; token_env= ;;",
4595
+ "esac",
4596
+ 'if [ -n "$provider" ]; then',
4597
+ ' case "$provider" in',
4598
+ ' github) token_file="${OPENGENI_GIT_TOKEN_FILE:-$HOME/.opengeni/git-token}" ;;',
4599
+ ' *) token_file="${OPENGENI_GIT_CREDENTIALS_DIR:-$HOME/.opengeni/git-credentials}/$provider-token" ;;',
4600
+ " esac",
4601
+ ' if [ -f "$token_file" ]; then',
4602
+ ' token="$(cat "$token_file" 2>/dev/null || true)"',
4603
+ ' if [ -n "$token" ]; then',
4604
+ ' case "$token_env" in',
4605
+ ' GH_TOKEN) export GH_TOKEN="$token" ;;',
4606
+ ' GITLAB_TOKEN) export GITLAB_TOKEN="$token" ;;',
4607
+ ' AZURE_DEVOPS_EXT_PAT) export AZURE_DEVOPS_EXT_PAT="$token" ;;',
4608
+ " esac",
4609
+ " fi",
4610
+ " fi",
3827
4611
  "fi",
4612
+ `self_real="$(readlink -f "$0" 2>/dev/null || printf '%s\\n' "$0")"`,
4613
+ 'old_ifs="$IFS"',
4614
+ "IFS=:",
4615
+ "for dir in $PATH; do",
4616
+ ' [ -n "$dir" ] || dir=.',
4617
+ ' candidate="$dir/$tool"',
4618
+ ' [ -x "$candidate" ] || continue',
4619
+ ` candidate_real="$(readlink -f "$candidate" 2>/dev/null || printf '%s\\n' "$candidate")"`,
4620
+ ' [ "$candidate_real" = "$self_real" ] && continue',
4621
+ ' IFS="$old_ifs"',
4622
+ ' exec "$candidate" "$@"',
4623
+ "done",
4624
+ 'IFS="$old_ifs"',
4625
+ `printf '%s\\n' "$tool: real command not found on PATH" >&2`,
4626
+ "exit 127",
4627
+ "CLI_WRAPPER_EOF",
4628
+ ' chmod 0755 "$wrapper.tmp.$$"',
4629
+ ' mv -f "$wrapper.tmp.$$" "$wrapper"',
4630
+ "done"
4631
+ ];
4632
+ }
4633
+ function gitProviderTokenRefreshCommand(seeds) {
4634
+ const seedPrefix = gitTokenSeedExportPrefix(seeds);
4635
+ if (!seedPrefix) {
4636
+ return "";
4637
+ }
4638
+ return [
4639
+ seedPrefix,
4640
+ "set -eu",
4641
+ 'export HOME="${HOME:-/workspace}"',
4642
+ ...gitCredentialTokenWriterCommandLines()
4643
+ ].join("\n");
4644
+ }
4645
+ async function refreshGitProviderTokenFiles(session, seeds, options = {}) {
4646
+ const command = gitProviderTokenRefreshCommand(seeds);
4647
+ if (!command) {
4648
+ return;
4649
+ }
4650
+ const args = {
4651
+ cmd: command,
4652
+ workdir: "/workspace",
4653
+ ...options.runAs ? { runAs: options.runAs } : {},
4654
+ yieldTimeMs: SANDBOX_LIFECYCLE_COMMAND_TIMEOUT_MS,
4655
+ maxOutputTokens: 4e3
4656
+ };
4657
+ if (session.exec) {
4658
+ assertSandboxCommandSucceeded(await session.exec(args), "Git credential refresh");
4659
+ } else if (session.execCommand) {
4660
+ assertSandboxCommandSucceeded(await session.execCommand(args), "Git credential refresh");
4661
+ } else {
4662
+ throw new Error("Sandbox session does not support command execution");
4663
+ }
4664
+ }
4665
+ function repositoryCloneCommand(resources) {
4666
+ const commands = [
4667
+ "set -eu",
4668
+ 'export HOME="${HOME:-/workspace}"',
4669
+ 'export GIT_TERMINAL_PROMPT="${GIT_TERMINAL_PROMPT:-0}"',
4670
+ ...gitCredentialHelperCommandLines(resources),
3828
4671
  "ensure_git() {",
3829
4672
  " if command -v git >/dev/null 2>&1; then",
3830
4673
  " return 0",
@@ -3915,13 +4758,15 @@ function repositoryCloneCommand(resources) {
3915
4758
  const url = new URL(resource.uri);
3916
4759
  const repo = url.pathname.replace(/^\/+|\/+$/g, "").replace(/\.git$/, "");
3917
4760
  const mountPath = normalizeManifestPath(resource.mountPath ?? `repos/${repo}`);
3918
- commands.push([
3919
- "clone_repository",
3920
- shellQuote(posixPath.join("/workspace", mountPath)),
3921
- shellQuote(resource.uri),
3922
- shellQuote(resource.ref),
3923
- shellQuote(resource.subpath ? normalizeManifestPath(resource.subpath) : "")
3924
- ].join(" "));
4761
+ commands.push(
4762
+ [
4763
+ "clone_repository",
4764
+ shellQuote(posixPath.join("/workspace", mountPath)),
4765
+ shellQuote(resource.uri),
4766
+ shellQuote(resource.ref),
4767
+ shellQuote(resource.subpath ? normalizeManifestPath(resource.subpath) : "")
4768
+ ].join(" ")
4769
+ );
3925
4770
  }
3926
4771
  return commands.join("\n");
3927
4772
  }
@@ -3968,11 +4813,124 @@ ${toolspaceTokenSeedCommand()}`;
3968
4813
  throw new Error("Sandbox session does not support command execution");
3969
4814
  }
3970
4815
  }
4816
+ var RIG_SETUP_OUTPUT_TAIL_LIMIT = 4e3;
4817
+ var RIG_SETUP_SKIPPED_SENTINEL = "__OPENGENI_RIG_SETUP_SKIPPED__";
4818
+ function rigSetupScriptCommand(script, versionId, timeoutMs = 6e5, markerRoot = "/var/opengeni") {
4819
+ const timeoutSecs = Math.max(1, Math.ceil(timeoutMs / 1e3));
4820
+ const lockWaitSecs = timeoutSecs + 6;
4821
+ const marker = `${markerRoot.replace(/\/+$/, "")}/rig-setup-${versionId}.done`;
4822
+ return [
4823
+ "set -u",
4824
+ `mkdir -p ${shellQuote(markerRoot)}`,
4825
+ `__OG_RIG_MARKER=${shellQuote(marker)}`,
4826
+ '__OG_RIG_LOCK="$__OG_RIG_MARKER.lock"',
4827
+ `__OG_RIG_TIMEOUT_SECS=${timeoutSecs}`,
4828
+ `__OG_RIG_LOCK_WAIT_SECS=${lockWaitSecs}`,
4829
+ `if [ -f "$__OG_RIG_MARKER" ]; then printf '%s\\n' ${shellQuote(RIG_SETUP_SKIPPED_SENTINEL)}; exit 0; fi`,
4830
+ "while :; do",
4831
+ ' if mkdir "$__OG_RIG_LOCK" 2>/dev/null; then',
4832
+ ` trap 'rm -rf "$__OG_RIG_LOCK"' EXIT`,
4833
+ ` if [ -f "$__OG_RIG_MARKER" ]; then printf '%s\\n' ${shellQuote(RIG_SETUP_SKIPPED_SENTINEL)}; exit 0; fi`,
4834
+ '__OG_RIG_SCRIPT="$(mktemp)"',
4835
+ `cat > "$__OG_RIG_SCRIPT" <<'__OPENGENI_RIG_SETUP_SCRIPT_EOF__'`,
4836
+ script,
4837
+ "__OPENGENI_RIG_SETUP_SCRIPT_EOF__",
4838
+ ' timeout -k 5s "${__OG_RIG_TIMEOUT_SECS}s" bash "$__OG_RIG_SCRIPT"',
4839
+ "__OG_RIG_RC=$?",
4840
+ ' rm -f "$__OG_RIG_SCRIPT"',
4841
+ ' if [ "$__OG_RIG_RC" -eq 0 ]; then touch "$__OG_RIG_MARKER"; fi',
4842
+ ' exit "$__OG_RIG_RC"',
4843
+ " fi",
4844
+ " __OG_RIG_WAITED=0",
4845
+ ' while [ "$__OG_RIG_WAITED" -lt "$__OG_RIG_LOCK_WAIT_SECS" ]; do',
4846
+ ` if [ -f "$__OG_RIG_MARKER" ]; then printf '%s\\n' ${shellQuote(RIG_SETUP_SKIPPED_SENTINEL)}; exit 0; fi`,
4847
+ ' if [ ! -d "$__OG_RIG_LOCK" ]; then break; fi',
4848
+ " sleep 1",
4849
+ " __OG_RIG_WAITED=$((__OG_RIG_WAITED + 1))",
4850
+ " done",
4851
+ ` if [ -f "$__OG_RIG_MARKER" ]; then printf '%s\\n' ${shellQuote(RIG_SETUP_SKIPPED_SENTINEL)}; exit 0; fi`,
4852
+ ' if [ ! -d "$__OG_RIG_LOCK" ]; then continue; fi',
4853
+ ' rmdir "$__OG_RIG_LOCK" 2>/dev/null || true',
4854
+ "done"
4855
+ ].join("\n");
4856
+ }
4857
+ async function runRigSetupHook(session, context = { environment: {} }) {
4858
+ const rigSetup = context.rigSetup;
4859
+ if (!rigSetup) {
4860
+ return;
4861
+ }
4862
+ const payload = {
4863
+ rigId: rigSetup.rigId,
4864
+ versionId: rigSetup.versionId,
4865
+ rigName: rigSetup.rigName
4866
+ };
4867
+ await context.onRuntimeEvent?.({ type: "rig.setup.started", payload });
4868
+ const command = rigSetupScriptCommand(rigSetup.script, rigSetup.versionId, rigSetup.timeoutMs);
4869
+ const execArgs = {
4870
+ cmd: command,
4871
+ workdir: "/workspace",
4872
+ ...context.runAs ? { runAs: context.runAs } : {},
4873
+ // The in-box coreutils timeout is the hard deadline; the SDK yield waits a
4874
+ // little longer so it observes timeout's non-zero exit instead of a live
4875
+ // still-running process.
4876
+ yieldTimeMs: rigSetup.timeoutMs + 7e3,
4877
+ maxOutputTokens: 2e4
4878
+ };
4879
+ let result;
4880
+ try {
4881
+ if (session.exec) {
4882
+ result = await session.exec(execArgs);
4883
+ } else if (session.execCommand) {
4884
+ result = await session.execCommand(execArgs);
4885
+ } else {
4886
+ throw new Error("Sandbox session does not support command execution");
4887
+ }
4888
+ } catch (error) {
4889
+ const message = error instanceof Error ? error.message : String(error);
4890
+ await context.onRuntimeEvent?.({
4891
+ type: "rig.setup.failed",
4892
+ payload: { ...payload, error: message.slice(-RIG_SETUP_OUTPUT_TAIL_LIMIT) }
4893
+ });
4894
+ throw new Error(
4895
+ `Rig setup failed for rig "${rigSetup.rigName}" (version ${rigSetup.versionId}): ${message}`,
4896
+ { cause: error }
4897
+ );
4898
+ }
4899
+ const output = sandboxCommandOutput(result);
4900
+ if (output.includes(RIG_SETUP_SKIPPED_SENTINEL)) {
4901
+ await context.onRuntimeEvent?.({ type: "rig.setup.skipped", payload });
4902
+ return;
4903
+ }
4904
+ const stillRunning = sandboxCommandStillRunning(result);
4905
+ const exitCode = sandboxCommandExitCode(result);
4906
+ if (stillRunning || exitCode === null || exitCode !== 0) {
4907
+ const tail = output.length > RIG_SETUP_OUTPUT_TAIL_LIMIT ? output.slice(-RIG_SETUP_OUTPUT_TAIL_LIMIT) : output;
4908
+ const reason = stillRunning ? `did not finish within the rig setup timeout (${rigSetup.timeoutMs}ms)` : exitCode === null ? "did not report an exit code" : `exited with code ${exitCode}`;
4909
+ const failure = new Error(
4910
+ `Rig setup failed for rig "${rigSetup.rigName}" (version ${rigSetup.versionId}): the setup script ${reason}${tail ? `:
4911
+ ${tail}` : ""}`
4912
+ );
4913
+ await context.onRuntimeEvent?.({
4914
+ type: "rig.setup.failed",
4915
+ payload: { ...payload, error: failure.message.slice(-RIG_SETUP_OUTPUT_TAIL_LIMIT) }
4916
+ });
4917
+ throw failure;
4918
+ }
4919
+ await context.onRuntimeEvent?.({
4920
+ type: "rig.setup.completed",
4921
+ payload: { ...payload, skipped: false }
4922
+ });
4923
+ }
3971
4924
  async function runRepositoryCloneHook(session, resources, context = { environment: {} }) {
3972
4925
  const payload = { name: "repository-clone", repositoryCount: resources.length };
3973
4926
  await context.onRuntimeEvent?.({ type: "sandbox.operation.started", payload });
3974
4927
  try {
3975
- const command = context.gitTokenSeed ? `export OPENGENI_GIT_TOKEN_SEED=${shellQuote(context.gitTokenSeed)}
4928
+ const gitTokenSeeds = {
4929
+ ...context.gitTokenSeeds ?? {},
4930
+ ...context.gitTokenSeed ? { github: context.gitTokenSeed } : {}
4931
+ };
4932
+ const seedPrefix = gitTokenSeedExportPrefix(gitTokenSeeds);
4933
+ const command = seedPrefix ? `${seedPrefix}
3976
4934
  ${repositoryCloneCommand(resources)}` : repositoryCloneCommand(resources);
3977
4935
  if (session.exec) {
3978
4936
  const result = await session.exec({
@@ -4043,6 +5001,10 @@ function sandboxCommandExitCode(result) {
4043
5001
  return null;
4044
5002
  }
4045
5003
  function sandboxCommandOutput(result) {
5004
+ if (typeof result === "string") {
5005
+ const outputIndex = result.indexOf("Output:");
5006
+ return outputIndex >= 0 ? result.slice(outputIndex + "Output:".length).trim() : result.trim();
5007
+ }
4046
5008
  if (!result || typeof result !== "object") {
4047
5009
  return "";
4048
5010
  }
@@ -4052,12 +5014,17 @@ function sandboxCommandOutput(result) {
4052
5014
  function assertSandboxCommandSucceeded(result, operation) {
4053
5015
  const output = sandboxCommandOutput(result);
4054
5016
  if (sandboxCommandStillRunning(result)) {
4055
- throw new Error(`${operation} did not finish before the lifecycle command timeout${output ? `:
4056
- ${output}` : ""}`);
5017
+ throw new Error(
5018
+ `${operation} did not finish before the lifecycle command timeout${output ? `:
5019
+ ${output}` : ""}`
5020
+ );
4057
5021
  }
4058
5022
  const exitCode = sandboxCommandExitCode(result);
4059
5023
  if (exitCode !== null && exitCode !== 0) {
4060
- throw new Error(output || `${operation} failed with exit code ${exitCode}`);
5024
+ throw new Error(
5025
+ `${operation} failed with exit code ${exitCode}${output ? `:
5026
+ ${output}` : ""}`
5027
+ );
4061
5028
  }
4062
5029
  if (exitCode === null) {
4063
5030
  throw new Error(output || `${operation} did not return a command exit code`);
@@ -4142,7 +5109,10 @@ function bundledSkillsDir() {
4142
5109
  return packaged;
4143
5110
  }
4144
5111
  if (!stagedBundledSkillsDir) {
4145
- stagedBundledSkillsDir = stageBundledSkills(packaged, join(process.cwd(), ".opengeni", "bundled_hashicorp_terraform_skills"));
5112
+ stagedBundledSkillsDir = stageBundledSkills(
5113
+ packaged,
5114
+ join(process.cwd(), ".opengeni", "bundled_hashicorp_terraform_skills")
5115
+ );
4146
5116
  }
4147
5117
  return stagedBundledSkillsDir;
4148
5118
  }
@@ -4187,12 +5157,18 @@ function lazySkillSourceWithPackSkills(packSkills) {
4187
5157
  packNameKeys.add(skill.name.toLowerCase());
4188
5158
  packNames.add(skill.name);
4189
5159
  children[skill.name] = packSkillDirEntry(skill);
4190
- packIndex.push({ name: skill.name, description: packSkillDescription(skill), path: skill.name });
5160
+ packIndex.push({
5161
+ name: skill.name,
5162
+ description: packSkillDescription(skill),
5163
+ path: skill.name
5164
+ });
4191
5165
  }
4192
5166
  return {
4193
5167
  source: dir({ children }),
4194
5168
  getIndex: (manifest, skillsPath) => [
4195
- ...(bundled.getIndex?.(manifest, skillsPath) ?? []).filter((entry) => !packNames.has(entry.path ?? entry.name)),
5169
+ ...(bundled.getIndex?.(manifest, skillsPath) ?? []).filter(
5170
+ (entry) => !packNames.has(entry.path ?? entry.name)
5171
+ ),
4196
5172
  ...packIndex
4197
5173
  ]
4198
5174
  };
@@ -4306,7 +5282,9 @@ function sortJson(value) {
4306
5282
  return value.map(sortJson);
4307
5283
  }
4308
5284
  if (value && typeof value === "object") {
4309
- return Object.fromEntries(Object.entries(value).sort(([a], [b]) => a.localeCompare(b)).map(([key, nested]) => [key, sortJson(nested)]));
5285
+ return Object.fromEntries(
5286
+ Object.entries(value).sort(([a], [b]) => a.localeCompare(b)).map(([key, nested]) => [key, sortJson(nested)])
5287
+ );
4310
5288
  }
4311
5289
  return value;
4312
5290
  }
@@ -4335,25 +5313,44 @@ export {
4335
5313
  DISPLAY_STACK_TIMEOUT_MS,
4336
5314
  DisplayStackError,
4337
5315
  DisplayStackUnsupportedError,
5316
+ FAULT_FIELD_WHAT_HAPPENED,
5317
+ FAULT_FIELD_WHAT_PRESERVED,
5318
+ FAULT_FIELD_WHAT_TO_TRY,
5319
+ FAULT_FIELD_WHICH_LAYER,
4338
5320
  GENESIS_TITLE_DIRECTIVE,
4339
5321
  MAX_COMPACTION_THRESHOLD_RATIO,
4340
5322
  MIN_COMPACTION_THRESHOLD_RATIO,
4341
5323
  MaxTurnsExceededError2 as MaxTurnsExceededError,
4342
5324
  MockAgentResponder,
4343
5325
  MultiProviderModelProvider,
5326
+ NEVER_SENT_DETAIL_KEY,
4344
5327
  NatsControlRpc,
5328
+ NatsOpStreamTransport,
5329
+ OP_STREAM_ACK_INTERVAL_MS,
5330
+ OP_STREAM_DEFAULT_WINDOW_BYTES,
5331
+ OP_STREAM_RECONNECT_HOLD_MS,
5332
+ OP_STREAM_SILENCE_TIMEOUT_MS,
5333
+ OpStreamUnavailableError,
4345
5334
  OpenAIChatCompletionsModel2 as OpenAIChatCompletionsModel,
4346
5335
  OpenAIResponsesModel2 as OpenAIResponsesModel,
4347
5336
  PROVIDER_REGISTRY,
5337
+ REPOSITORY_DISCOVERY_LIMIT,
4348
5338
  RecordingError,
4349
5339
  RecordingUnavailableError,
4350
5340
  RoutingSandboxSession,
4351
5341
  RoutingUnsupportedError,
4352
- SCREENSHOT_OMITTED_IMAGE_DATA_URL,
4353
- SCREENSHOT_OMITTED_PLACEHOLDER,
4354
5342
  SELFHOSTED_DEFAULT_TIMEOUT_MS,
5343
+ SELFHOSTED_DRAINING_MAX_RETRIES,
5344
+ SELFHOSTED_EXEC_DRAINING_MAX_RETRIES,
5345
+ SELFHOSTED_IDEMPOTENT_READONLY_OPS,
5346
+ SELFHOSTED_INFRASTRUCTURE_FAULT_CLASSES,
5347
+ SELFHOSTED_NEVER_SENT_MAX_RETRIES,
4355
5348
  SELFHOSTED_RECONNECT_WINDOW_MS,
4356
5349
  SELFHOSTED_RELAY_STREAM_PATH,
5350
+ SELFHOSTED_RETRY_BACKOFF_BASE_MS,
5351
+ SELFHOSTED_RETRY_BACKOFF_CAP_MS,
5352
+ SELFHOSTED_RETRY_BACKOFF_FACTOR,
5353
+ SELFHOSTED_TIMEOUT_MAX_RETRIES,
4357
5354
  STREAM_PORT,
4358
5355
  STREAM_TOKEN_DEFAULT_TTL_SECONDS,
4359
5356
  SUMMARY_BUFFER_TOKENS,
@@ -4362,6 +5359,7 @@ export {
4362
5359
  SandboxComputer,
4363
5360
  SandboxConfigError,
4364
5361
  SandboxProviderUnavailableError,
5362
+ SandboxResumeStateUnavailableError,
4365
5363
  SelfhostedControlError,
4366
5364
  SelfhostedSandboxClient,
4367
5365
  SelfhostedSession,
@@ -4373,11 +5371,14 @@ export {
4373
5371
  TerminalServerError,
4374
5372
  TerminalServerUnsupportedError,
4375
5373
  USER_MESSAGE_TRUNCATION_MARKER,
5374
+ WorkspaceModelPolicyBlockedError,
4376
5375
  agentErrorToControlError,
4377
5376
  agentsErrorRunState,
4378
5377
  appendGenesisTitleDirective,
5378
+ appendPersistentSessionSettings,
4379
5379
  appendSessionInstructions,
4380
5380
  appendToolspaceInstructions,
5381
+ appendWorkspaceMemory,
4381
5382
  applyMissingManifestEntries,
4382
5383
  assertDescriptorRegistryInvariants,
4383
5384
  assertProviderRegistryInvariants,
@@ -4388,7 +5389,6 @@ export {
4388
5389
  buildAgentCapabilities,
4389
5390
  buildCompactionPromptInput,
4390
5391
  buildCompactionReplacementHistory,
4391
- buildDeterministicFallbackCompactionHistory,
4392
5392
  buildDisplayStackScript,
4393
5393
  buildManifest,
4394
5394
  buildModelInstance,
@@ -4401,8 +5401,8 @@ export {
4401
5401
  buildTerminalServerScript,
4402
5402
  callModelInputFilterForSettings,
4403
5403
  clampCompactionThresholdRatio,
4404
- clientCompactionThresholdTokens,
4405
5404
  collectSandboxEnvironment,
5405
+ compactionThresholdTokens,
4406
5406
  composeAgentInstructions,
4407
5407
  computerUse,
4408
5408
  configureOpenAI,
@@ -4413,14 +5413,18 @@ export {
4413
5413
  createProductionAgentRuntime,
4414
5414
  createSandboxClient,
4415
5415
  createSandboxClientForBackend,
4416
- decideClientCompaction,
5416
+ decideCompaction,
5417
+ decideSelfhostedRetry,
4417
5418
  decodeModalSnapshotId,
5419
+ defaultSelfhostedRetryClock,
4418
5420
  deletePriorPersistedSnapshot,
4419
5421
  deleteRecordingArtifacts,
4420
5422
  deserializeSandboxSessionStateEnvelope,
4421
5423
  desktopCapableBackend,
4422
- elideStaleScreenshotImages,
4423
- enforceInputBudget,
5424
+ drainingExhaustedError,
5425
+ drainingMessage,
5426
+ elideSupersededViewImagePairs,
5427
+ elideSupersededViewImagesFilter,
4424
5428
  ensureDisplayStack,
4425
5429
  ensureModalRegistryImage,
4426
5430
  ensureReadableStreamFrom,
@@ -4428,12 +5432,15 @@ export {
4428
5432
  establishSandboxSessionFromEnvelope,
4429
5433
  estimateItemTokens,
4430
5434
  estimateTokens,
5435
+ execDeadlineHint,
4431
5436
  exposeStreamPort,
4432
5437
  extForCodec,
4433
5438
  extractResponseOutputText,
4434
5439
  findCompactionNeededError,
4435
- findKeepBoundary,
5440
+ gitProviderTokenRefreshCommand,
5441
+ inspectRecordingArtifact,
4436
5442
  isCompactionSummary,
5443
+ isEphemeralInternalContext,
4437
5444
  isExecSessionLostBanner,
4438
5445
  isProviderSandboxNotFoundError,
4439
5446
  isSelfhostedProviderNotFoundError,
@@ -4441,52 +5448,71 @@ export {
4441
5448
  isWorkspaceEscapeError,
4442
5449
  lazySkillSourceWithPackSkills,
4443
5450
  makeActiveBackendResolver,
5451
+ manifestEnvironmentDrift,
4444
5452
  materializeSandboxFileDownloads,
4445
5453
  maxTurnsExceededRunState,
4446
5454
  mcpToolErrorOutput,
4447
5455
  mintStreamToken,
4448
5456
  modalSandboxAttributionEnvironment,
4449
5457
  modalSandboxAttributionTags,
5458
+ modelCallUsageTelemetry,
5459
+ modelResponseUsageFromResponse,
4450
5460
  modelResponseUsageFromSdkEvent,
4451
5461
  negotiateCapabilities,
4452
5462
  negotiateSelfhostedCapabilities,
4453
5463
  neutralizeToolSearchItemsInSerializedRunState,
5464
+ nextDurableOpId,
4454
5465
  normalizeComputerCallsFilter,
4455
5466
  normalizeSdkEvent,
4456
5467
  normalizeToolOutputForEvent,
4457
5468
  offlineAgentError,
4458
5469
  offlineControlResponse,
5470
+ oneShotGenesisTitleInputFilter,
5471
+ opAckSubject,
5472
+ opFrameSubject,
4459
5473
  parseExecBannerSessionId,
4460
5474
  parseExposedPorts,
4461
5475
  parseNumstatZ,
4462
5476
  parsePorcelainV2,
4463
5477
  parseUnifiedPatch,
5478
+ payloadTooLargeMessage,
5479
+ pinProvidedSessionManifestEnvironment,
4464
5480
  prefixedMcpToolName,
4465
5481
  prepareAgentTools,
4466
5482
  prepareRunInput,
4467
- readRecordingBytes,
4468
5483
  readWorkspaceArchiveFromEnvelopeSessionState,
4469
5484
  recordingStorageKey,
5485
+ refreshGitProviderTokenFiles,
4470
5486
  renderCompactionPromptInputForChat,
5487
+ renderSelfhostedFault,
4471
5488
  repositoryCloneCommand,
4472
5489
  repositoryUsesSandboxClone,
4473
5490
  resolveTurnModel,
4474
5491
  restoredSandboxSessionStateFromEntry,
5492
+ rigInstructions,
5493
+ rigSetupScriptCommand,
4475
5494
  runAgentStream,
4476
5495
  runAzureCliLoginHook,
4477
5496
  runBeforeAgentStartHooks,
5497
+ runOwnedSandboxSetup,
4478
5498
  runRepositoryCloneHook,
5499
+ runRigSetupHook,
4479
5500
  runToolspaceTokenSeedHook,
5501
+ runWithToolCallCorrelation,
4480
5502
  sandboxCommandExitCode,
4481
5503
  sandboxCommandOutput,
4482
5504
  sandboxCommandStillRunning,
5505
+ sandboxFileDownloadFailureNote,
4483
5506
  sandboxFileDownloadsForAgent,
4484
5507
  sandboxLifecycleHooksForIds,
4485
5508
  sandboxRunAs,
4486
5509
  sandboxStateEntryFromRunState,
4487
5510
  sanitizeHistoryItemsForModel,
5511
+ sanitizeOpIdToken,
4488
5512
  selectBackend,
5513
+ selfhostedFaultClass,
4489
5514
  selfhostedLiveness,
5515
+ selfhostedRetryBackoffMs,
4490
5516
  serializeApprovals,
4491
5517
  serializeEstablishedSandboxEnvelope,
4492
5518
  setSelfhostedApplyDiff,
@@ -4498,6 +5524,7 @@ export {
4498
5524
  stripReasoningIdentityFromSerializedRunState,
4499
5525
  subjectFor,
4500
5526
  summarizeForCompaction,
5527
+ swapTargetEstablishability,
4501
5528
  sweepModalOrphanSandboxes,
4502
5529
  tagModalSandbox,
4503
5530
  tearDownDisplayStack,
@@ -4506,10 +5533,12 @@ export {
4506
5533
  timeoutAgentError,
4507
5534
  timeoutControlResponse,
4508
5535
  toolspaceTokenSeedCommand,
5536
+ uploadRecordingArtifact,
4509
5537
  verifyStreamToken,
4510
5538
  withManifestRefreshOnResume,
4511
5539
  withSandboxFileDownloads,
4512
5540
  withSandboxLifecycleHooks,
5541
+ withStructuredViewImageFunctionResults,
4513
5542
  workspaceEnvironmentInstructions
4514
5543
  };
4515
5544
  //# sourceMappingURL=index.js.map