@opengeni/runtime 0.6.0 → 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.
- package/dist/{chunk-BHA2SU3H.js → chunk-RYDCVTMW.js} +1959 -208
- package/dist/chunk-RYDCVTMW.js.map +1 -0
- package/dist/{index-otZkoLYq.d.ts → index-DIiiwQY2.d.ts} +693 -55
- package/dist/index.d.ts +293 -121
- package/dist/index.js +1819 -786
- package/dist/index.js.map +1 -1
- package/dist/sandbox/index.d.ts +1 -1
- package/dist/sandbox/index.js +79 -3
- package/package.json +21 -20
- package/src/codex-tool-search.ts +185 -31
- package/src/context-compaction.ts +78 -243
- package/src/history-sanitizer.ts +172 -20
- package/src/index.ts +2573 -897
- package/src/metrics.ts +28 -2
- package/src/sandbox/capabilities.ts +12 -3
- package/src/sandbox/channel-a.ts +420 -72
- package/src/sandbox/display-stack.ts +18 -4
- package/src/sandbox/errors.ts +13 -0
- package/src/sandbox/index.ts +398 -112
- package/src/sandbox/op-correlation.ts +70 -0
- package/src/sandbox/providers/index.ts +9 -3
- package/src/sandbox/providers/modal.ts +99 -24
- package/src/sandbox/providers/selfhosted.ts +1 -4
- package/src/sandbox/recording.ts +146 -90
- package/src/sandbox/routing/backend-resolver.ts +144 -10
- package/src/sandbox/routing/routing-session.ts +129 -25
- package/src/sandbox/select.ts +60 -8
- package/src/sandbox/selfhosted/capabilities.ts +59 -25
- package/src/sandbox/selfhosted/control-rpc.ts +160 -28
- package/src/sandbox/selfhosted/fault-rendering.ts +226 -0
- package/src/sandbox/selfhosted/op-observer.ts +54 -0
- package/src/sandbox/selfhosted/op-stream.ts +801 -0
- package/src/sandbox/selfhosted/op-testing.ts +468 -0
- package/src/sandbox/selfhosted/op-transport.ts +151 -0
- package/src/sandbox/selfhosted/retry-policy.ts +177 -0
- package/src/sandbox/selfhosted/session.ts +498 -55
- package/src/sandbox/selfhosted/testing.ts +60 -19
- package/src/sandbox/stream-port.ts +17 -5
- package/src/sandbox/stream-token.ts +8 -2
- package/src/sandbox/terminal-server.ts +18 -4
- package/src/sandbox-computer.ts +332 -70
- package/src/usage-telemetry.ts +73 -0
- package/dist/chunk-BHA2SU3H.js.map +0 -1
- package/src/image-history.ts +0 -149
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
|
-
|
|
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-
|
|
137
|
+
} from "./chunk-RYDCVTMW.js";
|
|
100
138
|
|
|
101
139
|
// src/index.ts
|
|
102
|
-
import {
|
|
103
|
-
|
|
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 {
|
|
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.
|
|
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
|
|
399
|
+
return next;
|
|
271
400
|
}
|
|
272
401
|
function stripReasoningEncryptedContent(item) {
|
|
273
402
|
const type = itemType(item);
|
|
@@ -519,106 +648,6 @@ function computerCallNormalizingFetch(base) {
|
|
|
519
648
|
};
|
|
520
649
|
}
|
|
521
650
|
|
|
522
|
-
// src/image-history.ts
|
|
523
|
-
var SCREENSHOT_OMITTED_PLACEHOLDER = "[screenshot omitted: an older desktop frame \u2014 the full image remains in the session event log]";
|
|
524
|
-
var DATA_IMAGE_BASE64_PATTERN = /data:image\/[a-z0-9.+-]+;base64,[a-z0-9+/=_-]+/i;
|
|
525
|
-
function elideStaleScreenshotImages(items, options = {}) {
|
|
526
|
-
const keepLast = Math.max(0, Math.floor(options.keepLast ?? 3));
|
|
527
|
-
const placeholder = options.placeholder ?? SCREENSHOT_OMITTED_PLACEHOLDER;
|
|
528
|
-
const occurrences = [];
|
|
529
|
-
for (let i = 0; i < items.length; i += 1) {
|
|
530
|
-
collectItemImageOccurrences(items[i], [i], placeholder, occurrences);
|
|
531
|
-
}
|
|
532
|
-
const elidedCount = Math.max(0, occurrences.length - keepLast);
|
|
533
|
-
if (elidedCount === 0) {
|
|
534
|
-
return { items: items.slice(), imageCount: occurrences.length, elidedCount: 0 };
|
|
535
|
-
}
|
|
536
|
-
const cloned = structuredClone(items);
|
|
537
|
-
for (const occurrence of occurrences.slice(0, elidedCount)) {
|
|
538
|
-
setPath(cloned, occurrence.path, occurrence.replacement);
|
|
539
|
-
}
|
|
540
|
-
return { items: cloned, imageCount: occurrences.length, elidedCount };
|
|
541
|
-
}
|
|
542
|
-
function collectItemImageOccurrences(item, path, placeholder, out) {
|
|
543
|
-
if (!isRecord(item)) {
|
|
544
|
-
return;
|
|
545
|
-
}
|
|
546
|
-
if (item.type === "message" && (item.role === "user" || item.role === "system")) {
|
|
547
|
-
return;
|
|
548
|
-
}
|
|
549
|
-
if (item.type === "computer_call_result" || item.type === "computer_call_output") {
|
|
550
|
-
collectComputerOutputImages(item, path, placeholder, out);
|
|
551
|
-
return;
|
|
552
|
-
}
|
|
553
|
-
if (item.type === "function_call_result" || item.type === "function_call_output") {
|
|
554
|
-
collectToolResultImages(item.output, [...path, "output"], placeholder, out);
|
|
555
|
-
}
|
|
556
|
-
}
|
|
557
|
-
function collectComputerOutputImages(item, path, placeholder, out) {
|
|
558
|
-
const output = item.output;
|
|
559
|
-
if (!isRecord(output) || output.type !== "computer_screenshot") {
|
|
560
|
-
return;
|
|
561
|
-
}
|
|
562
|
-
for (const key of ["data", "image_url", "imageUrl"]) {
|
|
563
|
-
if (isImageDataUrl(output[key])) {
|
|
564
|
-
out.push({ path: [...path, "output", key], replacement: placeholder });
|
|
565
|
-
return;
|
|
566
|
-
}
|
|
567
|
-
}
|
|
568
|
-
}
|
|
569
|
-
function collectToolResultImages(value, path, placeholder, out) {
|
|
570
|
-
if (typeof value === "string") {
|
|
571
|
-
if (isImageDataUrl(value)) {
|
|
572
|
-
out.push({ path, replacement: placeholder });
|
|
573
|
-
}
|
|
574
|
-
return;
|
|
575
|
-
}
|
|
576
|
-
if (Array.isArray(value)) {
|
|
577
|
-
for (let i = 0; i < value.length; i += 1) {
|
|
578
|
-
collectToolResultImages(value[i], [...path, i], placeholder, out);
|
|
579
|
-
}
|
|
580
|
-
return;
|
|
581
|
-
}
|
|
582
|
-
if (!isRecord(value)) {
|
|
583
|
-
return;
|
|
584
|
-
}
|
|
585
|
-
if (value.type === "input_image") {
|
|
586
|
-
for (const key of ["image", "imageUrl", "image_url"]) {
|
|
587
|
-
if (isImageDataUrl(value[key])) {
|
|
588
|
-
out.push({ path, replacement: { type: "input_text", text: placeholder } });
|
|
589
|
-
return;
|
|
590
|
-
}
|
|
591
|
-
}
|
|
592
|
-
}
|
|
593
|
-
for (const key of ["content", "text", "output"]) {
|
|
594
|
-
if (key in value) {
|
|
595
|
-
collectToolResultImages(value[key], [...path, key], placeholder, out);
|
|
596
|
-
}
|
|
597
|
-
}
|
|
598
|
-
}
|
|
599
|
-
function isImageDataUrl(value) {
|
|
600
|
-
return typeof value === "string" && DATA_IMAGE_BASE64_PATTERN.test(value);
|
|
601
|
-
}
|
|
602
|
-
function isRecord(value) {
|
|
603
|
-
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
604
|
-
}
|
|
605
|
-
function setPath(root, path, value) {
|
|
606
|
-
if (path.length === 0) {
|
|
607
|
-
return;
|
|
608
|
-
}
|
|
609
|
-
let cursor = root;
|
|
610
|
-
for (let i = 0; i < path.length - 1; i += 1) {
|
|
611
|
-
const segment = path[i];
|
|
612
|
-
cursor = Array.isArray(cursor) ? cursor[segment] : cursor[segment];
|
|
613
|
-
}
|
|
614
|
-
const last = path[path.length - 1];
|
|
615
|
-
if (Array.isArray(cursor)) {
|
|
616
|
-
cursor[last] = value;
|
|
617
|
-
} else {
|
|
618
|
-
cursor[last] = value;
|
|
619
|
-
}
|
|
620
|
-
}
|
|
621
|
-
|
|
622
651
|
// src/codex-tool-search.ts
|
|
623
652
|
import { toolSearchTool } from "@openai/agents";
|
|
624
653
|
var CODEX_APPS_TOOL_PREFIX = "codex_apps__";
|
|
@@ -743,7 +772,10 @@ ${sources.map((s) => `- ${s}`).join("\n")}`;
|
|
|
743
772
|
var SEARCH_TOOL_PARAMETERS = {
|
|
744
773
|
type: "object",
|
|
745
774
|
properties: {
|
|
746
|
-
query: {
|
|
775
|
+
query: {
|
|
776
|
+
type: "string",
|
|
777
|
+
description: "Plain-language description of the capability you need."
|
|
778
|
+
},
|
|
747
779
|
limit: { type: "number", description: "Maximum number of tools to return (default 8)." }
|
|
748
780
|
},
|
|
749
781
|
required: ["query"],
|
|
@@ -756,10 +788,28 @@ function codexToolSearchExecutor(args) {
|
|
|
756
788
|
return bm25RankTools(deferred, query, limit);
|
|
757
789
|
}
|
|
758
790
|
var NO_NAMESPACES = /* @__PURE__ */ new Set();
|
|
759
|
-
function
|
|
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) {
|
|
760
810
|
return toolSearchTool({
|
|
761
811
|
execution: "client",
|
|
762
|
-
description
|
|
812
|
+
description,
|
|
763
813
|
parameters: SEARCH_TOOL_PARAMETERS,
|
|
764
814
|
execute: codexToolSearchExecutor
|
|
765
815
|
});
|
|
@@ -768,7 +818,7 @@ function isToolSearchTool(tool2) {
|
|
|
768
818
|
const t = tool2;
|
|
769
819
|
return !!t && (t.name === "tool_search" || t.providerData?.type === "tool_search");
|
|
770
820
|
}
|
|
771
|
-
function applyCodexToolSearch(tools, connectorNamespaces = NO_NAMESPACES) {
|
|
821
|
+
function applyCodexToolSearch(tools, connectorNamespaces = NO_NAMESPACES, descriptionFreeze) {
|
|
772
822
|
for (const tool2 of tools) {
|
|
773
823
|
if (isCodexAppsFunctionTool(tool2) && tool2.deferLoading !== true) {
|
|
774
824
|
tool2.deferLoading = true;
|
|
@@ -777,16 +827,24 @@ function applyCodexToolSearch(tools, connectorNamespaces = NO_NAMESPACES) {
|
|
|
777
827
|
if (tools.some(isToolSearchTool)) {
|
|
778
828
|
return tools;
|
|
779
829
|
}
|
|
780
|
-
|
|
830
|
+
const description = resolveSearchToolDescription(connectorNamespaces, descriptionFreeze);
|
|
831
|
+
return [...tools, buildCodexToolSearchToolFromDescription(description)];
|
|
781
832
|
}
|
|
782
833
|
function installCodexToolSearch(agent, connectorNamespaces = NO_NAMESPACES) {
|
|
834
|
+
installCodexToolSearchWithFreeze(agent, connectorNamespaces, { value: null });
|
|
835
|
+
}
|
|
836
|
+
function installCodexToolSearchWithFreeze(agent, connectorNamespaces, descriptionFreeze) {
|
|
783
837
|
const originalGetAllTools = agent.getAllTools.bind(agent);
|
|
784
|
-
agent.getAllTools =
|
|
838
|
+
agent.getAllTools = async (runContext) => applyCodexToolSearch(
|
|
839
|
+
await originalGetAllTools(runContext),
|
|
840
|
+
connectorNamespaces,
|
|
841
|
+
descriptionFreeze
|
|
842
|
+
);
|
|
785
843
|
const originalClone = agent.clone?.bind(agent);
|
|
786
844
|
if (originalClone) {
|
|
787
845
|
const cloneWithToolSearch = (config) => {
|
|
788
846
|
const cloned = originalClone(config);
|
|
789
|
-
|
|
847
|
+
installCodexToolSearchWithFreeze(cloned, connectorNamespaces, descriptionFreeze);
|
|
790
848
|
return cloned;
|
|
791
849
|
};
|
|
792
850
|
agent.clone = cloneWithToolSearch;
|
|
@@ -797,10 +855,9 @@ function installCodexToolSearch(agent, connectorNamespaces = NO_NAMESPACES) {
|
|
|
797
855
|
var COMPACTION_SUMMARY_MARKER = "opengeni_context_summary";
|
|
798
856
|
var SUMMARY_BUFFER_TOKENS = 2e4;
|
|
799
857
|
var COMPACT_USER_MESSAGE_MAX_TOKENS = 2e4;
|
|
800
|
-
var DEFAULT_COMPACTION_THRESHOLD_RATIO = 0.
|
|
858
|
+
var DEFAULT_COMPACTION_THRESHOLD_RATIO = 0.9;
|
|
801
859
|
var MIN_COMPACTION_THRESHOLD_RATIO = 0.3;
|
|
802
860
|
var MAX_COMPACTION_THRESHOLD_RATIO = 0.9;
|
|
803
|
-
var COMPACTION_FALLBACK_TARGET_RATIO = 0.5;
|
|
804
861
|
var COMPACTION_PROMPT = [
|
|
805
862
|
"You are performing a CONTEXT CHECKPOINT COMPACTION. Create a handoff summary for another LLM that will resume the task.",
|
|
806
863
|
"",
|
|
@@ -836,6 +893,9 @@ function isUserMessage(item) {
|
|
|
836
893
|
function isCompactionSummary(item) {
|
|
837
894
|
return isUserMessage(item) && item[COMPACTION_SUMMARY_MARKER] === true;
|
|
838
895
|
}
|
|
896
|
+
function isEphemeralInternalContext(item) {
|
|
897
|
+
return itemType2(item) === "message" && itemRole(item) === "system";
|
|
898
|
+
}
|
|
839
899
|
function estimateItemTokens(item) {
|
|
840
900
|
let text;
|
|
841
901
|
try {
|
|
@@ -854,13 +914,21 @@ function estimateTokens(items) {
|
|
|
854
914
|
}
|
|
855
915
|
function clampCompactionThresholdRatio(value) {
|
|
856
916
|
const numeric = typeof value === "number" && Number.isFinite(value) ? value : DEFAULT_COMPACTION_THRESHOLD_RATIO;
|
|
857
|
-
return Math.min(
|
|
917
|
+
return Math.min(
|
|
918
|
+
MAX_COMPACTION_THRESHOLD_RATIO,
|
|
919
|
+
Math.max(MIN_COMPACTION_THRESHOLD_RATIO, numeric)
|
|
920
|
+
);
|
|
858
921
|
}
|
|
859
|
-
function
|
|
860
|
-
|
|
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));
|
|
861
929
|
}
|
|
862
|
-
function
|
|
863
|
-
const thresholdTokens =
|
|
930
|
+
function decideCompaction(input) {
|
|
931
|
+
const thresholdTokens = compactionThresholdTokens(input);
|
|
864
932
|
const recorded = typeof input.lastInputTokens === "number" && input.lastInputTokens > 0 ? input.lastInputTokens : 0;
|
|
865
933
|
const signalTokens = recorded > 0 ? recorded : estimateTokens(input.items);
|
|
866
934
|
if (input.items.length === 0) {
|
|
@@ -869,7 +937,7 @@ function decideClientCompaction(input) {
|
|
|
869
937
|
if (input.force) {
|
|
870
938
|
return { shouldCompact: true, reason: "force", signalTokens, thresholdTokens };
|
|
871
939
|
}
|
|
872
|
-
if (signalTokens
|
|
940
|
+
if (signalTokens >= thresholdTokens) {
|
|
873
941
|
return { shouldCompact: true, reason: "above_threshold", signalTokens, thresholdTokens };
|
|
874
942
|
}
|
|
875
943
|
return { shouldCompact: false, reason: "below_threshold", signalTokens, thresholdTokens };
|
|
@@ -878,14 +946,17 @@ var CompactionNeededError = class extends Error {
|
|
|
878
946
|
signalTokens;
|
|
879
947
|
thresholdTokens;
|
|
880
948
|
signalSource;
|
|
949
|
+
trigger;
|
|
881
950
|
constructor(input) {
|
|
951
|
+
const trigger = input.trigger ?? "threshold";
|
|
882
952
|
super(
|
|
883
|
-
`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}`
|
|
884
954
|
);
|
|
885
955
|
this.name = "CompactionNeededError";
|
|
886
956
|
this.signalTokens = input.signalTokens;
|
|
887
957
|
this.thresholdTokens = input.thresholdTokens;
|
|
888
958
|
this.signalSource = input.signalSource;
|
|
959
|
+
this.trigger = trigger;
|
|
889
960
|
}
|
|
890
961
|
};
|
|
891
962
|
function findCompactionNeededError(error, seen = /* @__PURE__ */ new WeakSet()) {
|
|
@@ -902,42 +973,6 @@ function findCompactionNeededError(error, seen = /* @__PURE__ */ new WeakSet())
|
|
|
902
973
|
const record = error;
|
|
903
974
|
return findCompactionNeededError(record.cause, seen) ?? findCompactionNeededError(record.error, seen);
|
|
904
975
|
}
|
|
905
|
-
function findKeepBoundary(items, keepRecentTokens) {
|
|
906
|
-
const boundaries = [];
|
|
907
|
-
for (let i = 0; i < items.length; i += 1) {
|
|
908
|
-
if (isUserMessage(items[i])) {
|
|
909
|
-
boundaries.push(i);
|
|
910
|
-
}
|
|
911
|
-
}
|
|
912
|
-
if (boundaries.length === 0) {
|
|
913
|
-
return 0;
|
|
914
|
-
}
|
|
915
|
-
for (let b = 0; b < boundaries.length; b += 1) {
|
|
916
|
-
const boundary = boundaries[b];
|
|
917
|
-
if (estimateTokens(items.slice(boundary)) <= keepRecentTokens) {
|
|
918
|
-
return boundary;
|
|
919
|
-
}
|
|
920
|
-
}
|
|
921
|
-
return boundaries[boundaries.length - 1];
|
|
922
|
-
}
|
|
923
|
-
function enforceInputBudget(items, maxTokens, trailingTokens = 0) {
|
|
924
|
-
const total = estimateTokens(items) + Math.max(0, trailingTokens);
|
|
925
|
-
if (items.length === 0 || total <= maxTokens) {
|
|
926
|
-
return { items: items.slice(), trimmed: false, droppedCount: 0, estimatedTokens: total };
|
|
927
|
-
}
|
|
928
|
-
const historyBudget = Math.max(0, maxTokens - Math.max(0, trailingTokens));
|
|
929
|
-
const boundary = findKeepBoundary(items, historyBudget);
|
|
930
|
-
if (boundary <= 0) {
|
|
931
|
-
return { items: items.slice(), trimmed: false, droppedCount: 0, estimatedTokens: total };
|
|
932
|
-
}
|
|
933
|
-
const kept = items.slice(boundary);
|
|
934
|
-
return {
|
|
935
|
-
items: kept,
|
|
936
|
-
trimmed: true,
|
|
937
|
-
droppedCount: boundary,
|
|
938
|
-
estimatedTokens: estimateTokens(kept) + Math.max(0, trailingTokens)
|
|
939
|
-
};
|
|
940
|
-
}
|
|
941
976
|
function buildCompactionPromptInput(items) {
|
|
942
977
|
return [
|
|
943
978
|
...items,
|
|
@@ -949,70 +984,27 @@ function buildCompactionPromptInput(items) {
|
|
|
949
984
|
];
|
|
950
985
|
}
|
|
951
986
|
function buildCompactionReplacementHistory(items, summaryBody) {
|
|
952
|
-
const
|
|
953
|
-
|
|
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];
|
|
954
991
|
if (!isUserMessage(item) || isCompactionSummary(item)) {
|
|
955
992
|
continue;
|
|
956
993
|
}
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
}
|
|
962
|
-
function buildDeterministicFallbackCompactionHistory(input) {
|
|
963
|
-
const sourceTokens = Math.max(1, estimateTokens(input.items));
|
|
964
|
-
const requestedTarget = typeof input.targetTokens === "number" && input.targetTokens > 0 ? Math.floor(input.targetTokens) : Math.floor(sourceTokens * COMPACTION_FALLBACK_TARGET_RATIO);
|
|
965
|
-
const targetTokens = Math.max(1, Math.min(requestedTarget, Math.max(1, sourceTokens - 1)));
|
|
966
|
-
const summaryItem = buildSummaryItem([
|
|
967
|
-
"Non-LLM context compaction fallback.",
|
|
968
|
-
input.cause ? `Summarizer failure: ${input.cause}` : "Summarizer failure: unavailable.",
|
|
969
|
-
"Older assistant, tool, reasoning, and image history was dropped deterministically.",
|
|
970
|
-
"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."
|
|
971
|
-
].join("\n"));
|
|
972
|
-
const summaryTokens = estimateItemTokens(summaryItem);
|
|
973
|
-
let remaining = Math.max(0, targetTokens - summaryTokens);
|
|
974
|
-
const retainedInstructions = [];
|
|
975
|
-
for (const item of input.items) {
|
|
976
|
-
if (!isInitialInstructionMessage(item)) {
|
|
977
|
-
if (isUserMessage(item)) {
|
|
978
|
-
break;
|
|
979
|
-
}
|
|
980
|
-
continue;
|
|
981
|
-
}
|
|
982
|
-
if (remaining <= 0) {
|
|
994
|
+
const textTokens = estimatedTextTokens(messageText(item));
|
|
995
|
+
retainedReversed.push(compactMessageToTokenBudget(item, remaining));
|
|
996
|
+
if (textTokens > remaining) {
|
|
997
|
+
remaining = 0;
|
|
983
998
|
break;
|
|
984
999
|
}
|
|
985
|
-
|
|
986
|
-
if (!capped) {
|
|
987
|
-
continue;
|
|
988
|
-
}
|
|
989
|
-
const tokens = estimateItemTokens(capped);
|
|
990
|
-
if (tokens <= remaining) {
|
|
991
|
-
retainedInstructions.push(capped);
|
|
992
|
-
remaining -= tokens;
|
|
993
|
-
}
|
|
994
|
-
}
|
|
995
|
-
const retainedUsers = [];
|
|
996
|
-
for (let index = input.items.length - 1; index >= 0 && remaining > 0; index -= 1) {
|
|
997
|
-
const item = input.items[index];
|
|
998
|
-
if (!isUserMessage(item) || isCompactionSummary(item)) {
|
|
999
|
-
continue;
|
|
1000
|
-
}
|
|
1001
|
-
const capped = compactMessageToEstimatedItemBudget(item, Math.min(COMPACT_USER_MESSAGE_MAX_TOKENS, remaining));
|
|
1002
|
-
if (!capped) {
|
|
1003
|
-
continue;
|
|
1004
|
-
}
|
|
1005
|
-
const tokens = estimateItemTokens(capped);
|
|
1006
|
-
if (tokens <= remaining) {
|
|
1007
|
-
retainedUsers.push(capped);
|
|
1008
|
-
remaining -= tokens;
|
|
1009
|
-
}
|
|
1000
|
+
remaining -= textTokens;
|
|
1010
1001
|
}
|
|
1011
|
-
|
|
1012
|
-
|
|
1002
|
+
const history = retainedReversed.reverse();
|
|
1003
|
+
history.push(buildSummaryItem(summaryBody));
|
|
1004
|
+
return history;
|
|
1013
1005
|
}
|
|
1014
1006
|
function buildSummaryItem(summaryBody) {
|
|
1015
|
-
const trimmed = summaryBody.trim();
|
|
1007
|
+
const trimmed = summaryBody.trim() || "(no summary available)";
|
|
1016
1008
|
return {
|
|
1017
1009
|
type: "message",
|
|
1018
1010
|
role: "user",
|
|
@@ -1021,9 +1013,6 @@ ${trimmed}`,
|
|
|
1021
1013
|
[COMPACTION_SUMMARY_MARKER]: true
|
|
1022
1014
|
};
|
|
1023
1015
|
}
|
|
1024
|
-
function compactUserMessage(item) {
|
|
1025
|
-
return compactMessageToTokenBudget(item, COMPACT_USER_MESSAGE_MAX_TOKENS);
|
|
1026
|
-
}
|
|
1027
1016
|
function compactMessageToTokenBudget(item, maxTokens) {
|
|
1028
1017
|
const text = messageText(item);
|
|
1029
1018
|
const next = { ...item };
|
|
@@ -1034,26 +1023,6 @@ function compactMessageToTokenBudget(item, maxTokens) {
|
|
|
1034
1023
|
next.content = contentWithoutImages(item);
|
|
1035
1024
|
return next;
|
|
1036
1025
|
}
|
|
1037
|
-
function compactMessageToEstimatedItemBudget(item, maxItemTokens) {
|
|
1038
|
-
if (maxItemTokens <= 0) {
|
|
1039
|
-
return null;
|
|
1040
|
-
}
|
|
1041
|
-
const overheadTokens = estimateItemTokens({ ...item, content: "" });
|
|
1042
|
-
let contentBudget = Math.max(0, maxItemTokens - overheadTokens);
|
|
1043
|
-
for (let attempt = 0; attempt < 6; attempt += 1) {
|
|
1044
|
-
const capped = compactMessageToTokenBudget(item, contentBudget);
|
|
1045
|
-
const estimated = estimateItemTokens(capped);
|
|
1046
|
-
if (estimated <= maxItemTokens) {
|
|
1047
|
-
return capped;
|
|
1048
|
-
}
|
|
1049
|
-
const overage = estimated - maxItemTokens;
|
|
1050
|
-
if (contentBudget <= 0) {
|
|
1051
|
-
break;
|
|
1052
|
-
}
|
|
1053
|
-
contentBudget = Math.max(0, contentBudget - Math.max(1, overage));
|
|
1054
|
-
}
|
|
1055
|
-
return null;
|
|
1056
|
-
}
|
|
1057
1026
|
function estimatedTextTokens(text) {
|
|
1058
1027
|
return Math.ceil(text.length / 4);
|
|
1059
1028
|
}
|
|
@@ -1104,46 +1073,25 @@ function messageText(item) {
|
|
|
1104
1073
|
}
|
|
1105
1074
|
return "";
|
|
1106
1075
|
}
|
|
1107
|
-
function
|
|
1108
|
-
|
|
1109
|
-
const role = itemRole(item);
|
|
1110
|
-
return type === "message" && (role === "system" || role === "developer");
|
|
1111
|
-
}
|
|
1112
|
-
function renderCompactionPromptInputForChat(input, options = {}) {
|
|
1113
|
-
const rendered = input.map(renderItem);
|
|
1114
|
-
if (typeof options.maxEstimatedTokens !== "number" || options.maxEstimatedTokens <= 0) {
|
|
1115
|
-
return rendered.join("\n");
|
|
1116
|
-
}
|
|
1117
|
-
const maxChars = Math.max(1, Math.floor(options.maxEstimatedTokens * 4));
|
|
1118
|
-
const last = rendered.at(-1);
|
|
1119
|
-
const prefix = rendered.slice(0, -1);
|
|
1120
|
-
let kept = prefix.slice();
|
|
1121
|
-
while (kept.length > 0 && transcriptLength([...kept, last].filter((line) => line !== void 0)) > maxChars) {
|
|
1122
|
-
kept.shift();
|
|
1123
|
-
}
|
|
1124
|
-
const lines = [...kept, last].filter((line) => line !== void 0);
|
|
1125
|
-
const joined = lines.join("\n");
|
|
1126
|
-
if (joined.length <= maxChars) {
|
|
1127
|
-
return joined;
|
|
1128
|
-
}
|
|
1129
|
-
return truncateMiddleByChars(joined, maxChars);
|
|
1076
|
+
function renderCompactionPromptInputForChat(input) {
|
|
1077
|
+
return input.map(renderItem).join("\n");
|
|
1130
1078
|
}
|
|
1131
1079
|
function renderItem(item) {
|
|
1132
1080
|
const type = itemType2(item) ?? "unknown";
|
|
1133
1081
|
if (type === "message") {
|
|
1134
1082
|
const role = itemRole(item) ?? "assistant";
|
|
1135
|
-
return `[${role}] ${
|
|
1083
|
+
return `[${role}] ${messageText(item)}`;
|
|
1136
1084
|
}
|
|
1137
1085
|
if (type === "reasoning") {
|
|
1138
1086
|
return "[reasoning] (omitted)";
|
|
1139
1087
|
}
|
|
1140
1088
|
if (RESULT_TYPES2.has(type)) {
|
|
1141
|
-
return `[tool_result] ${
|
|
1089
|
+
return `[tool_result] ${resultText(item)}`;
|
|
1142
1090
|
}
|
|
1143
1091
|
if (RESULT_TYPE_BY_CALL_TYPE2[type]) {
|
|
1144
|
-
return `[tool_call ${type}] ${
|
|
1092
|
+
return `[tool_call ${type}] ${callText(item)}`;
|
|
1145
1093
|
}
|
|
1146
|
-
return `[${type}] ${
|
|
1094
|
+
return `[${type}] ${safeStringify(item)}`;
|
|
1147
1095
|
}
|
|
1148
1096
|
function resultText(item) {
|
|
1149
1097
|
const output = item.output;
|
|
@@ -1166,35 +1114,15 @@ function safeStringify(value) {
|
|
|
1166
1114
|
return String(value);
|
|
1167
1115
|
}
|
|
1168
1116
|
}
|
|
1169
|
-
function truncateForTranscript(text, max) {
|
|
1170
|
-
if (text.length <= max) {
|
|
1171
|
-
return text;
|
|
1172
|
-
}
|
|
1173
|
-
return `${text.slice(0, max)}... (${text.length - max} more chars)`;
|
|
1174
|
-
}
|
|
1175
|
-
function transcriptLength(lines) {
|
|
1176
|
-
if (lines.length === 0) {
|
|
1177
|
-
return 0;
|
|
1178
|
-
}
|
|
1179
|
-
return lines.reduce((sum, line) => sum + line.length, 0) + Math.max(0, lines.length - 1);
|
|
1180
|
-
}
|
|
1181
|
-
function truncateMiddleByChars(text, maxChars) {
|
|
1182
|
-
if (text.length <= maxChars) {
|
|
1183
|
-
return text;
|
|
1184
|
-
}
|
|
1185
|
-
if (maxChars <= USER_MESSAGE_TRUNCATION_MARKER.length) {
|
|
1186
|
-
return USER_MESSAGE_TRUNCATION_MARKER.slice(0, maxChars);
|
|
1187
|
-
}
|
|
1188
|
-
const keepChars = maxChars - USER_MESSAGE_TRUNCATION_MARKER.length;
|
|
1189
|
-
const headChars = Math.ceil(keepChars / 2);
|
|
1190
|
-
const tailChars = Math.floor(keepChars / 2);
|
|
1191
|
-
return `${text.slice(0, headChars)}${USER_MESSAGE_TRUNCATION_MARKER}${text.slice(text.length - tailChars)}`;
|
|
1192
|
-
}
|
|
1193
1117
|
|
|
1194
1118
|
// src/sandbox-computer.ts
|
|
1195
1119
|
import { computerTool, tool } from "@openai/agents";
|
|
1196
1120
|
import { Capability } from "@openai/agents/sandbox";
|
|
1197
|
-
import {
|
|
1121
|
+
import {
|
|
1122
|
+
KeyAction,
|
|
1123
|
+
PointerAction,
|
|
1124
|
+
PointerButton
|
|
1125
|
+
} from "@opengeni/agent-proto";
|
|
1198
1126
|
function requireBoundSession(capabilityType, session) {
|
|
1199
1127
|
if (!session) {
|
|
1200
1128
|
throw new ComputerUnavailableError(`capability "${capabilityType}" used before bind(session)`);
|
|
@@ -1242,7 +1170,13 @@ function toKeysym(k) {
|
|
|
1242
1170
|
if (/^f([1-9]|1[0-2])$/.test(low)) return low.toUpperCase();
|
|
1243
1171
|
return low.length === 1 ? low : k;
|
|
1244
1172
|
}
|
|
1245
|
-
var BUTTON_NUM = {
|
|
1173
|
+
var BUTTON_NUM = {
|
|
1174
|
+
left: 1,
|
|
1175
|
+
wheel: 2,
|
|
1176
|
+
right: 3,
|
|
1177
|
+
back: 8,
|
|
1178
|
+
forward: 9
|
|
1179
|
+
};
|
|
1246
1180
|
var ComputerUnavailableError = class extends Error {
|
|
1247
1181
|
constructor(message) {
|
|
1248
1182
|
super(message);
|
|
@@ -1270,9 +1204,6 @@ ${stderr}` : ""}`);
|
|
|
1270
1204
|
this.stderr = stderr;
|
|
1271
1205
|
this.name = "ComputerActionError";
|
|
1272
1206
|
}
|
|
1273
|
-
cmd;
|
|
1274
|
-
exitCode;
|
|
1275
|
-
stderr;
|
|
1276
1207
|
};
|
|
1277
1208
|
var SandboxComputer = class {
|
|
1278
1209
|
environment = "ubuntu";
|
|
@@ -1285,6 +1216,9 @@ var SandboxComputer = class {
|
|
|
1285
1216
|
tmp;
|
|
1286
1217
|
screenshotWarmupBudgetMs;
|
|
1287
1218
|
screenshotRetryDelayMs;
|
|
1219
|
+
prepare;
|
|
1220
|
+
prepared = false;
|
|
1221
|
+
preparation = null;
|
|
1288
1222
|
constructor(session, opts = {}) {
|
|
1289
1223
|
this.session = session;
|
|
1290
1224
|
this.display = opts.display ?? DEFAULT_DISPLAY;
|
|
@@ -1297,15 +1231,34 @@ var SandboxComputer = class {
|
|
|
1297
1231
|
this.tmp = opts.screenshotTmpDir ?? "/tmp";
|
|
1298
1232
|
this.screenshotWarmupBudgetMs = opts.screenshotWarmupBudgetMs ?? SCREENSHOT_WARMUP_BUDGET_MS;
|
|
1299
1233
|
this.screenshotRetryDelayMs = opts.screenshotRetryDelayMs ?? SCREENSHOT_RETRY_DELAY_MS;
|
|
1234
|
+
this.prepare = opts.prepare;
|
|
1300
1235
|
}
|
|
1301
1236
|
/** Rebind to a freshly resumed-by-id session after a box rollover / re-establish. */
|
|
1302
1237
|
rebind(session) {
|
|
1303
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
|
+
}
|
|
1304
1256
|
}
|
|
1305
1257
|
// The single command primitive. Dual-paths exec/execCommand (F1), then uses the
|
|
1306
1258
|
// established string-aware parsers (F2/F3): exitCode from the preamble, and
|
|
1307
1259
|
// "still running" → a retriable failure. Returns the command OUTPUT body.
|
|
1308
1260
|
async x(cmd) {
|
|
1261
|
+
await this.prepareOnce();
|
|
1309
1262
|
const args = {
|
|
1310
1263
|
cmd: `DISPLAY=${this.display} ${cmd}`,
|
|
1311
1264
|
...this.runAs ? { runAs: this.runAs } : {},
|
|
@@ -1353,7 +1306,9 @@ var SandboxComputer = class {
|
|
|
1353
1306
|
await this.x(`scrot --pointer --overwrite ${f}`);
|
|
1354
1307
|
const bytes = await this.readScreenshotBytes(f);
|
|
1355
1308
|
if (bytes.length === 0) {
|
|
1356
|
-
throw new ComputerUnavailableError(
|
|
1309
|
+
throw new ComputerUnavailableError(
|
|
1310
|
+
"scrot produced an empty screenshot (display not up?)"
|
|
1311
|
+
);
|
|
1357
1312
|
}
|
|
1358
1313
|
return Buffer.from(bytes).toString("base64");
|
|
1359
1314
|
} catch (error) {
|
|
@@ -1362,7 +1317,9 @@ var SandboxComputer = class {
|
|
|
1362
1317
|
throw error;
|
|
1363
1318
|
}
|
|
1364
1319
|
} finally {
|
|
1365
|
-
|
|
1320
|
+
if (this.prepared) {
|
|
1321
|
+
await this.x(`rm -f ${f}`).catch(() => void 0);
|
|
1322
|
+
}
|
|
1366
1323
|
}
|
|
1367
1324
|
if (Date.now() + this.screenshotRetryDelayMs >= deadline) {
|
|
1368
1325
|
break;
|
|
@@ -1405,7 +1362,9 @@ var SandboxComputer = class {
|
|
|
1405
1362
|
if (typeof this.session.execCommand === "function") {
|
|
1406
1363
|
return stripExecBanner(await this.session.execCommand(args));
|
|
1407
1364
|
}
|
|
1408
|
-
throw new ComputerUnavailableError(
|
|
1365
|
+
throw new ComputerUnavailableError(
|
|
1366
|
+
"session cannot run commands (no exec/execCommand) \u2014 screenshots unavailable"
|
|
1367
|
+
);
|
|
1409
1368
|
}
|
|
1410
1369
|
// Read the screenshot PNG bytes off the box. A SINGLE `base64 <file>` over Modal's
|
|
1411
1370
|
// exec silently returns "" once the base64 exceeds the exec-stdout buffer cap (a full
|
|
@@ -1542,7 +1501,10 @@ var NativeDesktopComputer = class {
|
|
|
1542
1501
|
}
|
|
1543
1502
|
async pointer(x, y, action, button) {
|
|
1544
1503
|
const n = this.toNative(x, y);
|
|
1545
|
-
await this.session.desktopInput({
|
|
1504
|
+
await this.session.desktopInput({
|
|
1505
|
+
$case: "pointer",
|
|
1506
|
+
pointer: { x: n.x, y: n.y, action, button }
|
|
1507
|
+
});
|
|
1546
1508
|
}
|
|
1547
1509
|
async screenshot() {
|
|
1548
1510
|
let lastError;
|
|
@@ -1556,7 +1518,9 @@ var NativeDesktopComputer = class {
|
|
|
1556
1518
|
try {
|
|
1557
1519
|
const { png, width, height, nativeWidth, nativeHeight } = await this.session.screenshot();
|
|
1558
1520
|
if (png.length === 0) {
|
|
1559
|
-
throw new ComputerUnavailableError(
|
|
1521
|
+
throw new ComputerUnavailableError(
|
|
1522
|
+
"native desktop screenshot returned an empty frame (display not up?)"
|
|
1523
|
+
);
|
|
1560
1524
|
}
|
|
1561
1525
|
this.lastEncoded = [width, height];
|
|
1562
1526
|
this.lastNative = [nativeWidth || width, nativeHeight || height];
|
|
@@ -1570,44 +1534,82 @@ var NativeDesktopComputer = class {
|
|
|
1570
1534
|
}
|
|
1571
1535
|
}
|
|
1572
1536
|
const reason = lastError instanceof Error ? lastError.message : String(lastError);
|
|
1573
|
-
console.warn(
|
|
1537
|
+
console.warn(
|
|
1538
|
+
`[NativeDesktopComputer] screenshot failed after ${attempt} attempt(s): ${reason}`
|
|
1539
|
+
);
|
|
1574
1540
|
if (lastError instanceof Error) {
|
|
1575
1541
|
throw lastError;
|
|
1576
1542
|
}
|
|
1577
|
-
throw new ComputerUnavailableError(
|
|
1543
|
+
throw new ComputerUnavailableError(
|
|
1544
|
+
"native desktop screenshot returned an empty frame (display not up?)"
|
|
1545
|
+
);
|
|
1578
1546
|
}
|
|
1579
1547
|
async click(x, y, button) {
|
|
1580
1548
|
this.guardWrite();
|
|
1581
|
-
await this.pointer(
|
|
1549
|
+
await this.pointer(
|
|
1550
|
+
x,
|
|
1551
|
+
y,
|
|
1552
|
+
PointerAction.POINTER_ACTION_CLICK,
|
|
1553
|
+
POINTER_BUTTON[button] ?? PointerButton.POINTER_BUTTON_LEFT
|
|
1554
|
+
);
|
|
1582
1555
|
}
|
|
1583
1556
|
async doubleClick(x, y) {
|
|
1584
1557
|
this.guardWrite();
|
|
1585
|
-
await this.pointer(
|
|
1558
|
+
await this.pointer(
|
|
1559
|
+
x,
|
|
1560
|
+
y,
|
|
1561
|
+
PointerAction.POINTER_ACTION_DOUBLE_CLICK,
|
|
1562
|
+
PointerButton.POINTER_BUTTON_LEFT
|
|
1563
|
+
);
|
|
1586
1564
|
}
|
|
1587
1565
|
async move(x, y) {
|
|
1588
1566
|
this.guardWrite();
|
|
1589
|
-
await this.pointer(
|
|
1567
|
+
await this.pointer(
|
|
1568
|
+
x,
|
|
1569
|
+
y,
|
|
1570
|
+
PointerAction.POINTER_ACTION_MOVE,
|
|
1571
|
+
PointerButton.POINTER_BUTTON_UNSPECIFIED
|
|
1572
|
+
);
|
|
1590
1573
|
}
|
|
1591
1574
|
async scroll(x, y, sx, sy) {
|
|
1592
1575
|
this.guardWrite();
|
|
1593
1576
|
const n = this.toNative(x, y);
|
|
1594
|
-
await this.session.desktopInput({
|
|
1577
|
+
await this.session.desktopInput({
|
|
1578
|
+
$case: "scroll",
|
|
1579
|
+
scroll: { x: n.x, y: n.y, deltaX: sx, deltaY: sy }
|
|
1580
|
+
});
|
|
1595
1581
|
}
|
|
1596
1582
|
async type(text) {
|
|
1597
1583
|
this.guardWrite();
|
|
1598
|
-
await this.session.desktopInput({
|
|
1584
|
+
await this.session.desktopInput({
|
|
1585
|
+
$case: "key",
|
|
1586
|
+
key: { key: text, isText: true, action: KeyAction.KEY_ACTION_PRESS }
|
|
1587
|
+
});
|
|
1599
1588
|
}
|
|
1600
1589
|
async keypress(keys) {
|
|
1601
1590
|
this.guardWrite();
|
|
1602
|
-
await this.session.desktopInput({
|
|
1591
|
+
await this.session.desktopInput({
|
|
1592
|
+
$case: "key",
|
|
1593
|
+
key: { key: keys.join("+"), isText: false, action: KeyAction.KEY_ACTION_PRESS }
|
|
1594
|
+
});
|
|
1603
1595
|
}
|
|
1604
1596
|
async drag(path) {
|
|
1605
1597
|
this.guardWrite();
|
|
1606
1598
|
if (path.length === 0) return;
|
|
1607
1599
|
const [sx, sy] = path[0];
|
|
1608
|
-
await this.pointer(
|
|
1600
|
+
await this.pointer(
|
|
1601
|
+
sx,
|
|
1602
|
+
sy,
|
|
1603
|
+
PointerAction.POINTER_ACTION_DOWN,
|
|
1604
|
+
PointerButton.POINTER_BUTTON_LEFT
|
|
1605
|
+
);
|
|
1609
1606
|
for (const [px, py] of path.slice(1)) {
|
|
1610
|
-
await this.pointer(
|
|
1607
|
+
await this.pointer(
|
|
1608
|
+
px,
|
|
1609
|
+
py,
|
|
1610
|
+
PointerAction.POINTER_ACTION_MOVE,
|
|
1611
|
+
PointerButton.POINTER_BUTTON_LEFT
|
|
1612
|
+
);
|
|
1611
1613
|
}
|
|
1612
1614
|
const [ex, ey] = path[path.length - 1];
|
|
1613
1615
|
await this.pointer(ex, ey, PointerAction.POINTER_ACTION_UP, PointerButton.POINTER_BUTTON_LEFT);
|
|
@@ -1621,14 +1623,20 @@ function isNativeDesktopSession(session) {
|
|
|
1621
1623
|
return typeof s.desktopInput === "function" && typeof s.screenshot === "function";
|
|
1622
1624
|
}
|
|
1623
1625
|
function sniffScreenshotMediaType(bytes) {
|
|
1624
|
-
if (bytes[0] === 137 && bytes[1] === 80 && bytes[2] === 78 && bytes[3] === 71)
|
|
1626
|
+
if (bytes[0] === 137 && bytes[1] === 80 && bytes[2] === 78 && bytes[3] === 71)
|
|
1627
|
+
return "image/png";
|
|
1625
1628
|
if (bytes[0] === 255 && bytes[1] === 216 && bytes[2] === 255) return "image/jpeg";
|
|
1626
|
-
if (bytes[0] === 71 && bytes[1] === 73 && bytes[2] === 70 && bytes[3] === 56)
|
|
1627
|
-
|
|
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";
|
|
1628
1633
|
return "image/png";
|
|
1629
1634
|
}
|
|
1630
1635
|
function imageOutputFromScreenshotBytes(bytes) {
|
|
1631
|
-
return {
|
|
1636
|
+
return {
|
|
1637
|
+
type: "image",
|
|
1638
|
+
image: { data: Uint8Array.from(bytes), mediaType: sniffScreenshotMediaType(bytes) }
|
|
1639
|
+
};
|
|
1632
1640
|
}
|
|
1633
1641
|
function renderImageForTextTransport(output) {
|
|
1634
1642
|
if (typeof output === "string") return output;
|
|
@@ -1643,8 +1651,14 @@ function supportsStructuredToolOutputTransport(modelInstance) {
|
|
|
1643
1651
|
}
|
|
1644
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.";
|
|
1645
1653
|
var COORD_PROPS = {
|
|
1646
|
-
x: {
|
|
1647
|
-
|
|
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
|
+
}
|
|
1648
1662
|
};
|
|
1649
1663
|
function objectSchema(properties, required) {
|
|
1650
1664
|
return { type: "object", properties, required, additionalProperties: false };
|
|
@@ -1678,14 +1692,24 @@ function computerFunctionTools(computer, readOnly, needsApproval, imageFunctionR
|
|
|
1678
1692
|
name: "computer_click",
|
|
1679
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.",
|
|
1680
1694
|
parameters: objectSchema(
|
|
1681
|
-
{
|
|
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
|
+
},
|
|
1682
1703
|
["x", "y"]
|
|
1683
1704
|
),
|
|
1684
1705
|
strict: false,
|
|
1685
1706
|
...approval,
|
|
1686
1707
|
execute: async (input) => {
|
|
1687
1708
|
const { x, y, button } = input;
|
|
1688
|
-
return write(
|
|
1709
|
+
return write(
|
|
1710
|
+
`clicked ${button ?? "left"} at (${x}, ${y})`,
|
|
1711
|
+
() => computer.click(x, y, button ?? "left")
|
|
1712
|
+
);
|
|
1689
1713
|
}
|
|
1690
1714
|
}),
|
|
1691
1715
|
tool({
|
|
@@ -1716,8 +1740,14 @@ function computerFunctionTools(computer, readOnly, needsApproval, imageFunctionR
|
|
|
1716
1740
|
parameters: objectSchema(
|
|
1717
1741
|
{
|
|
1718
1742
|
...COORD_PROPS,
|
|
1719
|
-
scroll_x: {
|
|
1720
|
-
|
|
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
|
+
}
|
|
1721
1751
|
},
|
|
1722
1752
|
["x", "y", "scroll_x", "scroll_y"]
|
|
1723
1753
|
),
|
|
@@ -1725,13 +1755,19 @@ function computerFunctionTools(computer, readOnly, needsApproval, imageFunctionR
|
|
|
1725
1755
|
...approval,
|
|
1726
1756
|
execute: async (input) => {
|
|
1727
1757
|
const { x, y, scroll_x, scroll_y } = input;
|
|
1728
|
-
return write(
|
|
1758
|
+
return write(
|
|
1759
|
+
`scrolled (${scroll_x}, ${scroll_y}) at (${x}, ${y})`,
|
|
1760
|
+
() => computer.scroll(x, y, scroll_x, scroll_y)
|
|
1761
|
+
);
|
|
1729
1762
|
}
|
|
1730
1763
|
}),
|
|
1731
1764
|
tool({
|
|
1732
1765
|
name: "computer_type",
|
|
1733
1766
|
description: "Type a literal text string at the current keyboard focus. Click the target field first.",
|
|
1734
|
-
parameters: objectSchema(
|
|
1767
|
+
parameters: objectSchema(
|
|
1768
|
+
{ text: { type: "string", description: "The literal text to type" } },
|
|
1769
|
+
["text"]
|
|
1770
|
+
),
|
|
1735
1771
|
strict: false,
|
|
1736
1772
|
...approval,
|
|
1737
1773
|
execute: async (input) => {
|
|
@@ -1743,7 +1779,13 @@ function computerFunctionTools(computer, readOnly, needsApproval, imageFunctionR
|
|
|
1743
1779
|
name: "computer_keypress",
|
|
1744
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.',
|
|
1745
1781
|
parameters: objectSchema(
|
|
1746
|
-
{
|
|
1782
|
+
{
|
|
1783
|
+
keys: {
|
|
1784
|
+
type: "array",
|
|
1785
|
+
items: { type: "string" },
|
|
1786
|
+
description: "Keys pressed together as a chord"
|
|
1787
|
+
}
|
|
1788
|
+
},
|
|
1747
1789
|
["keys"]
|
|
1748
1790
|
),
|
|
1749
1791
|
strict: false,
|
|
@@ -1789,7 +1831,6 @@ var ComputerUseCapability = class extends Capability {
|
|
|
1789
1831
|
super();
|
|
1790
1832
|
this.args = args;
|
|
1791
1833
|
}
|
|
1792
|
-
args;
|
|
1793
1834
|
type = "computer-use";
|
|
1794
1835
|
tools() {
|
|
1795
1836
|
const session = requireBoundSession("computer-use", this._session);
|
|
@@ -1801,22 +1842,45 @@ var ComputerUseCapability = class extends Capability {
|
|
|
1801
1842
|
...this.args.readOnly !== void 0 ? { readOnly: this.args.readOnly } : {},
|
|
1802
1843
|
...this.args.display ? { display: this.args.display } : {},
|
|
1803
1844
|
// The SDK base exposes the bound runAs as a protected field.
|
|
1804
|
-
...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
|
+
}
|
|
1805
1854
|
});
|
|
1806
1855
|
switch (this.args.toolMode) {
|
|
1807
1856
|
case "hosted":
|
|
1808
1857
|
return [this.hostedComputerTool(computer)];
|
|
1809
1858
|
case "function-image":
|
|
1810
|
-
return computerFunctionTools(
|
|
1859
|
+
return computerFunctionTools(
|
|
1860
|
+
computer,
|
|
1861
|
+
this.args.readOnly ?? false,
|
|
1862
|
+
this.args.needsApproval,
|
|
1863
|
+
true
|
|
1864
|
+
);
|
|
1811
1865
|
case "function-text":
|
|
1812
|
-
return computerFunctionTools(
|
|
1866
|
+
return computerFunctionTools(
|
|
1867
|
+
computer,
|
|
1868
|
+
this.args.readOnly ?? false,
|
|
1869
|
+
this.args.needsApproval,
|
|
1870
|
+
false
|
|
1871
|
+
);
|
|
1813
1872
|
case void 0:
|
|
1814
1873
|
break;
|
|
1815
1874
|
}
|
|
1816
1875
|
if (supportsStructuredToolOutputTransport(this._modelInstance)) {
|
|
1817
1876
|
return [this.hostedComputerTool(computer)];
|
|
1818
1877
|
}
|
|
1819
|
-
return computerFunctionTools(
|
|
1878
|
+
return computerFunctionTools(
|
|
1879
|
+
computer,
|
|
1880
|
+
this.args.readOnly ?? false,
|
|
1881
|
+
this.args.needsApproval,
|
|
1882
|
+
this.args.imageFunctionResults ?? false
|
|
1883
|
+
);
|
|
1820
1884
|
}
|
|
1821
1885
|
/** The single HOSTED `computer_use_preview` tool bound to `computer` — identical
|
|
1822
1886
|
* construction for the explicit "hosted" mode and the legacy structured-sniff path. */
|
|
@@ -1830,8 +1894,52 @@ var ComputerUseCapability = class extends Capability {
|
|
|
1830
1894
|
|
|
1831
1895
|
// src/index.ts
|
|
1832
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
|
|
1833
1939
|
import { MaxTurnsExceededError as MaxTurnsExceededError2 } from "@openai/agents";
|
|
1834
|
-
setSelfhostedApplyDiff(
|
|
1940
|
+
setSelfhostedApplyDiff(
|
|
1941
|
+
applyDiff
|
|
1942
|
+
);
|
|
1835
1943
|
ensureReadableStreamFrom();
|
|
1836
1944
|
var SANDBOX_LIFECYCLE_COMMAND_TIMEOUT_MS = 12e4;
|
|
1837
1945
|
function ensureReadableStreamFrom() {
|
|
@@ -1899,7 +2007,7 @@ function buildOpenAIClientFromSettings(settings, providerId = settings.openaiPro
|
|
|
1899
2007
|
defaultQuery: azureOpenAIDefaultQuery(settings, baseURL),
|
|
1900
2008
|
defaultHeaders: settings.azureOpenaiAdToken && !settings.azureOpenaiApiKey ? { Authorization: `Bearer ${settings.azureOpenaiAdToken}` } : void 0,
|
|
1901
2009
|
// Rewrite every outbound /responses computer_call to the ACTIONS-ONLY shape
|
|
1902
|
-
// the GA Azure computer tool
|
|
2010
|
+
// the GA Azure computer tool accepts. This is the lowest reachable
|
|
1903
2011
|
// seam — below the SDK responses converter, which always re-synthesizes BOTH
|
|
1904
2012
|
// `action` and `actions` (rejected 400 "exactly one of action or actions").
|
|
1905
2013
|
// See computerCallNormalizingFetch / rewriteComputerCallsToActionsOnly.
|
|
@@ -1919,19 +2027,35 @@ function buildProviderClient(provider, settings) {
|
|
|
1919
2027
|
if (cached) {
|
|
1920
2028
|
return cached;
|
|
1921
2029
|
}
|
|
1922
|
-
const client = provider.builtin ? buildOpenAIClientFromSettings(settings, provider.id) : provider.kind === "codex-subscription" ?
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
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
|
+
);
|
|
1935
2059
|
providerClientCache.set(provider.id, client);
|
|
1936
2060
|
return client;
|
|
1937
2061
|
}
|
|
@@ -1955,11 +2079,13 @@ var MultiProviderModelProvider = class {
|
|
|
1955
2079
|
constructor(settings) {
|
|
1956
2080
|
this.settings = settings;
|
|
1957
2081
|
}
|
|
1958
|
-
settings;
|
|
1959
2082
|
fallback;
|
|
1960
2083
|
async getModel(modelName) {
|
|
1961
2084
|
if (modelName) {
|
|
1962
|
-
const resolved = resolveTurnModel(
|
|
2085
|
+
const resolved = resolveTurnModel(
|
|
2086
|
+
settingsForRunScopedModelResolution(this.settings, modelName),
|
|
2087
|
+
modelName
|
|
2088
|
+
);
|
|
1963
2089
|
if (resolved) {
|
|
1964
2090
|
if (modelName.startsWith(CODEX_MODEL_ID_PREFIX) && resolved.provider.kind !== "codex-subscription") {
|
|
1965
2091
|
throw new CodexSubscriptionUnavailableError(modelName);
|
|
@@ -1996,6 +2122,14 @@ var CodexSubscriptionUnavailableError = class extends Error {
|
|
|
1996
2122
|
this.name = "CodexSubscriptionUnavailableError";
|
|
1997
2123
|
}
|
|
1998
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
|
+
};
|
|
1999
2133
|
function configureOpenAI(settings) {
|
|
2000
2134
|
setOpenAIResponsesTransport(settings.openaiResponsesTransport);
|
|
2001
2135
|
setTracingDisabled(settings.disableOpenaiTracing || !settings.observabilityOtlpEndpoint);
|
|
@@ -2014,7 +2148,7 @@ function configureOpenAI(settings) {
|
|
|
2014
2148
|
setDefaultModelProvider(router);
|
|
2015
2149
|
}
|
|
2016
2150
|
function instrumentedModelFetch(provider, inner) {
|
|
2017
|
-
return
|
|
2151
|
+
return async (input, init) => {
|
|
2018
2152
|
if (!isModelCallFetch(input)) {
|
|
2019
2153
|
return await inner(input, init);
|
|
2020
2154
|
}
|
|
@@ -2027,7 +2161,7 @@ function instrumentedModelFetch(provider, inner) {
|
|
|
2027
2161
|
recordModelCallMetric(provider, "failed", started);
|
|
2028
2162
|
throw error;
|
|
2029
2163
|
}
|
|
2030
|
-
}
|
|
2164
|
+
};
|
|
2031
2165
|
}
|
|
2032
2166
|
function isModelCallFetch(input) {
|
|
2033
2167
|
const rawUrl = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
|
|
@@ -2053,37 +2187,43 @@ async function summarizeForCompaction(settings, input, options = {}) {
|
|
|
2053
2187
|
const api = options.api ?? "responses";
|
|
2054
2188
|
const model = options.model ?? settings.openaiModel;
|
|
2055
2189
|
const maxTokens = options.maxOutputTokens ?? SUMMARY_BUFFER_TOKENS;
|
|
2056
|
-
|
|
2057
|
-
input
|
|
2058
|
-
|
|
2059
|
-
);
|
|
2060
|
-
try {
|
|
2061
|
-
if (api === "chat") {
|
|
2062
|
-
const completion = await client.chat.completions.create({
|
|
2063
|
-
model,
|
|
2064
|
-
max_tokens: maxTokens,
|
|
2065
|
-
messages: [{ role: "user", content: transcript }]
|
|
2066
|
-
});
|
|
2067
|
-
const text2 = completion.choices?.[0]?.message?.content;
|
|
2068
|
-
const trimmed2 = typeof text2 === "string" ? text2.trim() : "";
|
|
2069
|
-
return trimmed2.length > 0 ? trimmed2 : null;
|
|
2070
|
-
}
|
|
2071
|
-
const response = await client.responses.create({
|
|
2190
|
+
if (api === "chat") {
|
|
2191
|
+
const transcript = renderCompactionPromptInputForChat(input);
|
|
2192
|
+
const completion = await client.chat.completions.create({
|
|
2072
2193
|
model,
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
...settings.openaiProvider === "azure" ? {} : { store: false },
|
|
2077
|
-
max_output_tokens: maxTokens,
|
|
2078
|
-
input: transcript
|
|
2194
|
+
max_tokens: maxTokens,
|
|
2195
|
+
messages: [{ role: "user", content: transcript }],
|
|
2196
|
+
...options.promptCacheKey ? { prompt_cache_key: options.promptCacheKey } : {}
|
|
2079
2197
|
});
|
|
2080
|
-
const
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
return
|
|
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);
|
|
2086
2225
|
}
|
|
2226
|
+
return extractResponseOutputText(response).trim();
|
|
2087
2227
|
}
|
|
2088
2228
|
function extractResponseOutputText(response) {
|
|
2089
2229
|
if (!response || typeof response !== "object") {
|
|
@@ -2120,6 +2260,19 @@ function extractResponseOutputText(response) {
|
|
|
2120
2260
|
}
|
|
2121
2261
|
return parts.join("");
|
|
2122
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
|
+
}
|
|
2123
2276
|
function workspaceEnvironmentInstructions(environment) {
|
|
2124
2277
|
const lines = [
|
|
2125
2278
|
`A workspace environment named "${environment.name}" is attached to this session; its variables are exported in the sandbox shell environment.`
|
|
@@ -2134,14 +2287,17 @@ function workspaceEnvironmentInstructions(environment) {
|
|
|
2134
2287
|
}
|
|
2135
2288
|
return lines;
|
|
2136
2289
|
}
|
|
2137
|
-
function coreInstructions(workspaceEnvironment) {
|
|
2290
|
+
function coreInstructions(workspaceEnvironment, rig) {
|
|
2138
2291
|
return [
|
|
2139
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.",
|
|
2140
|
-
...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) : []
|
|
2141
2297
|
];
|
|
2142
2298
|
}
|
|
2143
|
-
function composeAgentInstructions(template, workspaceEnvironment) {
|
|
2144
|
-
const core = coreInstructions(workspaceEnvironment).join(" ");
|
|
2299
|
+
function composeAgentInstructions(template, workspaceEnvironment, rig) {
|
|
2300
|
+
const core = coreInstructions(workspaceEnvironment, rig).join(" ");
|
|
2145
2301
|
if (template.includes(AGENT_INSTRUCTIONS_CORE_PLACEHOLDER)) {
|
|
2146
2302
|
return template.split(AGENT_INSTRUCTIONS_CORE_PLACEHOLDER).join(core);
|
|
2147
2303
|
}
|
|
@@ -2151,27 +2307,52 @@ function appendSessionInstructions(composed, sessionInstructions) {
|
|
|
2151
2307
|
const trimmed = sessionInstructions?.trim();
|
|
2152
2308
|
return trimmed ? `${composed} ${trimmed}` : composed;
|
|
2153
2309
|
}
|
|
2154
|
-
function
|
|
2155
|
-
|
|
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;
|
|
2156
2324
|
}
|
|
2157
2325
|
function appendGenesisTitleDirective(instructions, genesisTitleHint) {
|
|
2158
2326
|
return genesisTitleHint ? `${instructions} ${GENESIS_TITLE_DIRECTIVE}` : instructions;
|
|
2159
2327
|
}
|
|
2160
2328
|
var agentFileDownloads = /* @__PURE__ */ new WeakMap();
|
|
2161
2329
|
var agentRepositoryCloneHooks = /* @__PURE__ */ new WeakMap();
|
|
2162
|
-
var
|
|
2330
|
+
var agentGitTokenSeeds = /* @__PURE__ */ new WeakMap();
|
|
2163
2331
|
var agentToolspaceTokenSeed = /* @__PURE__ */ new WeakMap();
|
|
2332
|
+
var agentsNeedingGenesisTitleDirective = /* @__PURE__ */ new WeakSet();
|
|
2164
2333
|
var agentActiveSandboxBackend = /* @__PURE__ */ new WeakMap();
|
|
2334
|
+
var agentRigSetup = /* @__PURE__ */ new WeakMap();
|
|
2335
|
+
var agentRigCredentialHooks = /* @__PURE__ */ new WeakMap();
|
|
2165
2336
|
function mcpToolErrorOutput(error) {
|
|
2166
2337
|
const details = error instanceof Error ? error.message : String(error);
|
|
2167
|
-
return {
|
|
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
|
+
};
|
|
2168
2347
|
}
|
|
2169
2348
|
var mcpToolErrorFunction = ({ error }) => mcpToolErrorOutput(error);
|
|
2170
2349
|
function buildOpenGeniAgent(settings, resources, options = {}) {
|
|
2171
|
-
const compactionMode = options.compactionMode ?? resolveContextCompactionMode(settings);
|
|
2172
2350
|
const hostedWebSearch = options.hostedWebSearch ?? settings.webSearchEnabled;
|
|
2173
2351
|
const encryptedReasoning = options.encryptedReasoning ?? settings.openaiReasoningEncryptedContent;
|
|
2174
|
-
const
|
|
2352
|
+
const providerData = {
|
|
2353
|
+
...encryptedReasoning ? { include: ["reasoning.encrypted_content"] } : {},
|
|
2354
|
+
...options.promptCacheKey ? { prompt_cache_key: options.promptCacheKey } : {}
|
|
2355
|
+
};
|
|
2175
2356
|
const hostedTools = hostedWebSearch ? [webSearchTool()] : [];
|
|
2176
2357
|
const baseConfig = {
|
|
2177
2358
|
name: "OpenGeni Agent",
|
|
@@ -2191,32 +2372,36 @@ function buildOpenGeniAgent(settings, resources, options = {}) {
|
|
|
2191
2372
|
// when a toolspace token was minted for this turn (feature enabled + a
|
|
2192
2373
|
// per-turn seed — the mint gate, which includes selfhosted turns since
|
|
2193
2374
|
// they now receive the token too) — appendToolspaceInstructions,
|
|
2194
|
-
// 3. +
|
|
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
|
|
2195
2378
|
// refines both the workspace persona and the substrate note),
|
|
2196
|
-
//
|
|
2197
|
-
//
|
|
2198
|
-
// is
|
|
2199
|
-
|
|
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(
|
|
2200
2384
|
appendSessionInstructions(
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
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
|
|
2204
2395
|
),
|
|
2205
2396
|
options.sessionInstructions
|
|
2206
2397
|
),
|
|
2207
|
-
options.
|
|
2398
|
+
options.persistentSessionSettings
|
|
2208
2399
|
),
|
|
2209
2400
|
modelSettings: {
|
|
2210
|
-
reasoning: {
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
// already runs storeless (provider item ids stripped, encrypted reasoning
|
|
2215
|
-
// round-tripped), so this is consistent with the existing design and
|
|
2216
|
-
// only set where the server compaction capability is attached. Gated on
|
|
2217
|
-
// the RESOLVED compaction mode (registry providers resolve to "client",
|
|
2218
|
-
// so they never carry store:false).
|
|
2219
|
-
...compactionMode === "server" ? { store: false } : {},
|
|
2401
|
+
reasoning: {
|
|
2402
|
+
effort: options.reasoningEffort ?? settings.openaiReasoningEffort,
|
|
2403
|
+
summary: "detailed"
|
|
2404
|
+
},
|
|
2220
2405
|
// Round-trip the encrypted reasoning payload with every call so chains
|
|
2221
2406
|
// of thought survive without provider-side response storage (which is
|
|
2222
2407
|
// what stripped provider item ids opt us out of — see
|
|
@@ -2225,7 +2410,7 @@ function buildOpenGeniAgent(settings, resources, options = {}) {
|
|
|
2225
2410
|
// function tools, which contribute none. Gated on the resolved
|
|
2226
2411
|
// encryptedReasoning flag: the chat wire API has no encrypted_content
|
|
2227
2412
|
// field, so registry "chat" providers turn it off.
|
|
2228
|
-
...
|
|
2413
|
+
...Object.keys(providerData).length > 0 ? { providerData } : {}
|
|
2229
2414
|
},
|
|
2230
2415
|
// Explicit hosted tools (web_search when enabled). Threaded into BOTH the
|
|
2231
2416
|
// `new Agent(baseConfig)` path (sandboxBackend === "none") and the
|
|
@@ -2243,6 +2428,9 @@ function buildOpenGeniAgent(settings, resources, options = {}) {
|
|
|
2243
2428
|
};
|
|
2244
2429
|
if (settings.sandboxBackend === "none") {
|
|
2245
2430
|
const agent2 = new Agent(baseConfig);
|
|
2431
|
+
if (options.genesisTitleHint) {
|
|
2432
|
+
agentsNeedingGenesisTitleDirective.add(agent2);
|
|
2433
|
+
}
|
|
2246
2434
|
maybeInstallCodexToolSearch(agent2, settings, options);
|
|
2247
2435
|
applyMcpApprovalPolicy(agent2, settings);
|
|
2248
2436
|
return agent2;
|
|
@@ -2250,26 +2438,51 @@ function buildOpenGeniAgent(settings, resources, options = {}) {
|
|
|
2250
2438
|
const runAs = sandboxRunAs(settings);
|
|
2251
2439
|
const agent = new SandboxAgent({
|
|
2252
2440
|
...baseConfig,
|
|
2253
|
-
defaultManifest: buildManifest(
|
|
2441
|
+
defaultManifest: buildManifest(
|
|
2442
|
+
settings,
|
|
2443
|
+
resources,
|
|
2444
|
+
options.sandboxEnvironment,
|
|
2445
|
+
options.fileResourceDownloads
|
|
2446
|
+
),
|
|
2254
2447
|
...runAs ? { runAs } : {},
|
|
2255
2448
|
capabilities: buildAgentCapabilities(settings, options.packSkills ?? [], {
|
|
2256
|
-
compactionMode,
|
|
2257
|
-
contextWindowTokens,
|
|
2258
2449
|
...options.structuredToolTransport !== void 0 ? { structuredToolTransport: options.structuredToolTransport } : {},
|
|
2259
|
-
...options.computerToolMode !== void 0 ? { computerToolMode: options.computerToolMode } : {}
|
|
2450
|
+
...options.computerToolMode !== void 0 ? { computerToolMode: options.computerToolMode } : {},
|
|
2451
|
+
...options.onComputerUseReady ? { onComputerUseReady: options.onComputerUseReady } : {}
|
|
2260
2452
|
})
|
|
2261
2453
|
});
|
|
2262
|
-
|
|
2263
|
-
|
|
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
|
+
);
|
|
2264
2467
|
if (options.activeSandboxBackend) {
|
|
2265
2468
|
agentActiveSandboxBackend.set(agent, options.activeSandboxBackend);
|
|
2266
2469
|
}
|
|
2267
|
-
|
|
2268
|
-
|
|
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);
|
|
2269
2476
|
}
|
|
2270
2477
|
if (options.toolspaceTokenSeed) {
|
|
2271
2478
|
agentToolspaceTokenSeed.set(agent, options.toolspaceTokenSeed);
|
|
2272
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
|
+
}
|
|
2273
2486
|
maybeInstallCodexToolSearch(agent, settings, options);
|
|
2274
2487
|
applyMcpApprovalPolicy(agent, settings);
|
|
2275
2488
|
return agent;
|
|
@@ -2311,7 +2524,12 @@ function installMcpApprovalPolicy(agent, policies) {
|
|
|
2311
2524
|
}
|
|
2312
2525
|
}
|
|
2313
2526
|
function applyMcpApprovalPolicy(agent, settings) {
|
|
2314
|
-
const policies = settings.mcpServers.filter(
|
|
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);
|
|
2315
2533
|
if (policies.length === 0) {
|
|
2316
2534
|
return;
|
|
2317
2535
|
}
|
|
@@ -2324,24 +2542,67 @@ function neutralizeStructuredToolTransport(capability) {
|
|
|
2324
2542
|
};
|
|
2325
2543
|
capability.bindModel = forceFunctionTransport;
|
|
2326
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
|
+
}
|
|
2327
2579
|
function buildAgentCapabilities(settings, packSkills, options = {}) {
|
|
2328
|
-
const
|
|
2329
|
-
|
|
2330
|
-
|
|
2580
|
+
const filesystemCapability = filesystem({
|
|
2581
|
+
...options.structuredToolTransport === false ? { configureTools: withStructuredViewImageFunctionResults } : {}
|
|
2582
|
+
});
|
|
2331
2583
|
if (options.structuredToolTransport === false) {
|
|
2332
2584
|
neutralizeStructuredToolTransport(filesystemCapability);
|
|
2333
2585
|
}
|
|
2334
|
-
const caps = [
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2586
|
+
const caps = [
|
|
2587
|
+
filesystemCapability,
|
|
2588
|
+
shell({ configureTools: withExecOpCorrelation })
|
|
2589
|
+
];
|
|
2338
2590
|
caps.push(skills({ lazyFrom: lazySkillSourceWithPackSkills(packSkills) }));
|
|
2339
2591
|
if (settings.computerUseEnabled && settings.sandboxDesktopEnabled && desktopCapableBackend(settings.sandboxBackend)) {
|
|
2340
2592
|
const explicitMode = options.computerToolMode;
|
|
2341
2593
|
const computerCapability = computerUse({
|
|
2342
2594
|
dimensions: [settings.streamResolutionWidth, settings.streamResolutionHeight],
|
|
2343
2595
|
readOnly: settings.computerUseReadOnly,
|
|
2344
|
-
...
|
|
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
|
+
)
|
|
2345
2606
|
});
|
|
2346
2607
|
if (!explicitMode && options.structuredToolTransport === false) {
|
|
2347
2608
|
neutralizeStructuredToolTransport(computerCapability);
|
|
@@ -2360,32 +2621,44 @@ async function prepareAgentTools(settings, tools, options = {}) {
|
|
|
2360
2621
|
}, codexConnectorNamespaces };
|
|
2361
2622
|
}
|
|
2362
2623
|
const registry = new Map(settings.mcpServers.map((server) => [server.id, server]));
|
|
2363
|
-
const servers = await Promise.all(
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
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
|
+
);
|
|
2389
2662
|
const requiredServers = servers.filter((entry) => !entry.bestEffort).map((entry) => entry.server);
|
|
2390
2663
|
const bestEffortServers = servers.filter((entry) => entry.bestEffort).map((entry) => entry.server);
|
|
2391
2664
|
const optionalServerNames = new Set(
|
|
@@ -2429,30 +2702,54 @@ function connectionBrokerFetch(baseFetch, config, options) {
|
|
|
2429
2702
|
}
|
|
2430
2703
|
return async (input, init) => {
|
|
2431
2704
|
const request = await mcpRequestInfo(input, init);
|
|
2432
|
-
const first = await resolveConnectionForRequest(
|
|
2705
|
+
const first = await resolveConnectionForRequest(
|
|
2706
|
+
options,
|
|
2707
|
+
config.id,
|
|
2708
|
+
connectionRef,
|
|
2709
|
+
request.toolName,
|
|
2710
|
+
false
|
|
2711
|
+
);
|
|
2433
2712
|
if (first.status === "auth_needed") {
|
|
2434
2713
|
return await authNeededFetchResponse(options, config.id, request, first, connectionRef);
|
|
2435
2714
|
}
|
|
2436
|
-
const response = await baseFetch(
|
|
2715
|
+
const response = await baseFetch(
|
|
2716
|
+
fetchInputForAttempt(input),
|
|
2717
|
+
withConnectionHeaders(input, init, first.headers)
|
|
2718
|
+
);
|
|
2437
2719
|
if (response.status === 401) {
|
|
2438
|
-
const refreshed = await resolveConnectionForRequest(
|
|
2720
|
+
const refreshed = await resolveConnectionForRequest(
|
|
2721
|
+
options,
|
|
2722
|
+
config.id,
|
|
2723
|
+
connectionRef,
|
|
2724
|
+
request.toolName,
|
|
2725
|
+
true
|
|
2726
|
+
);
|
|
2439
2727
|
if (refreshed.status === "auth_needed") {
|
|
2440
2728
|
return await authNeededFetchResponse(options, config.id, request, refreshed, connectionRef);
|
|
2441
2729
|
}
|
|
2442
|
-
const retry = await baseFetch(
|
|
2730
|
+
const retry = await baseFetch(
|
|
2731
|
+
fetchInputForAttempt(input),
|
|
2732
|
+
withConnectionHeaders(input, init, refreshed.headers)
|
|
2733
|
+
);
|
|
2443
2734
|
if (retry.status === 403) {
|
|
2444
2735
|
const auth = insufficientScopeAuth(retry.headers, connectionRef, refreshed.connectionId);
|
|
2445
2736
|
return auth ? await authNeededFetchResponse(options, config.id, request, auth, connectionRef) : retry;
|
|
2446
2737
|
}
|
|
2447
2738
|
if (retry.status === 401) {
|
|
2448
|
-
return await authNeededFetchResponse(
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
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
|
+
);
|
|
2456
2753
|
}
|
|
2457
2754
|
return retry;
|
|
2458
2755
|
}
|
|
@@ -2553,7 +2850,9 @@ async function mcpRequestInfo(input, init) {
|
|
|
2553
2850
|
}
|
|
2554
2851
|
}
|
|
2555
2852
|
function withConnectionHeaders(input, init, authHeaders) {
|
|
2556
|
-
const headers = new Headers(
|
|
2853
|
+
const headers = new Headers(
|
|
2854
|
+
init?.headers ?? (input instanceof Request ? input.headers : void 0)
|
|
2855
|
+
);
|
|
2557
2856
|
for (const [name, value] of Object.entries(authHeaders)) {
|
|
2558
2857
|
headers.set(name, value);
|
|
2559
2858
|
}
|
|
@@ -2587,17 +2886,20 @@ function parseWwwAuthenticate(header) {
|
|
|
2587
2886
|
var MCP_AUTH_NEEDED_ERROR_CODE = -32001;
|
|
2588
2887
|
var MCP_AUTH_NEEDED_MESSAGE = "Authentication required - a connection link was posted to the session.";
|
|
2589
2888
|
function mcpToolAuthNeededResponse(id) {
|
|
2590
|
-
return new Response(
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
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
|
+
);
|
|
2601
2903
|
}
|
|
2602
2904
|
function isAuthNeededMcpError(error) {
|
|
2603
2905
|
if (!(error instanceof Error)) {
|
|
@@ -2606,19 +2908,35 @@ function isAuthNeededMcpError(error) {
|
|
|
2606
2908
|
const code = error.code;
|
|
2607
2909
|
return code === MCP_AUTH_NEEDED_ERROR_CODE || error.message.includes(MCP_AUTH_NEEDED_MESSAGE);
|
|
2608
2910
|
}
|
|
2609
|
-
|
|
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) {
|
|
2610
2928
|
if (isCodexAppsMcpServer(config)) {
|
|
2611
2929
|
return await codexAppsMcpRequestInit(settings);
|
|
2612
2930
|
}
|
|
2613
2931
|
if (isFirstPartyMcpServer(settings, config)) {
|
|
2614
|
-
return await firstPartyMcpRequestInit(settings, config
|
|
2932
|
+
return await firstPartyMcpRequestInit(settings, config);
|
|
2615
2933
|
}
|
|
2616
2934
|
if (config.headers && Object.keys(config.headers).length > 0) {
|
|
2617
2935
|
return { requestInit: { headers: { ...config.headers } } };
|
|
2618
2936
|
}
|
|
2619
2937
|
return {};
|
|
2620
2938
|
}
|
|
2621
|
-
async function firstPartyMcpRequestInit(settings, config
|
|
2939
|
+
async function firstPartyMcpRequestInit(settings, config) {
|
|
2622
2940
|
if (!isFirstPartyMcpServer(settings, config)) {
|
|
2623
2941
|
return {};
|
|
2624
2942
|
}
|
|
@@ -2626,17 +2944,6 @@ async function firstPartyMcpRequestInit(settings, config, options) {
|
|
|
2626
2944
|
if (settings.authRequired && settings.accessKey) {
|
|
2627
2945
|
headers["x-opengeni-access-key"] = settings.accessKey;
|
|
2628
2946
|
}
|
|
2629
|
-
if (settings.delegationSecret && options.accountId && options.workspaceId) {
|
|
2630
|
-
headers.authorization = `Bearer ${await signDelegatedAccessToken(settings.delegationSecret, {
|
|
2631
|
-
accountId: options.accountId,
|
|
2632
|
-
workspaceId: options.workspaceId,
|
|
2633
|
-
subjectId: options.subjectId ?? "worker:first-party-mcp",
|
|
2634
|
-
...options.subjectLabel ? { subjectLabel: options.subjectLabel } : {},
|
|
2635
|
-
permissions: options.firstPartyPermissions ?? firstPartyMcpPermissions,
|
|
2636
|
-
...options.sessionId ? { sessionId: options.sessionId } : {},
|
|
2637
|
-
exp: Math.floor(Date.now() / 1e3) + 60 * 60
|
|
2638
|
-
})}`;
|
|
2639
|
-
}
|
|
2640
2947
|
if (Object.keys(headers).length === 0) {
|
|
2641
2948
|
return {};
|
|
2642
2949
|
}
|
|
@@ -2646,6 +2953,33 @@ async function firstPartyMcpRequestInit(settings, config, options) {
|
|
|
2646
2953
|
}
|
|
2647
2954
|
};
|
|
2648
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
|
+
}
|
|
2649
2983
|
async function codexAppsMcpRequestInit(settings) {
|
|
2650
2984
|
const ctx = codexRequestStorage.getStore();
|
|
2651
2985
|
if (!ctx) {
|
|
@@ -2685,8 +3019,9 @@ var firstPartyMcpPermissions = [
|
|
|
2685
3019
|
"sessions:read",
|
|
2686
3020
|
"sessions:create",
|
|
2687
3021
|
"sessions:control",
|
|
2688
|
-
"
|
|
2689
|
-
"
|
|
3022
|
+
"variable-sets:use",
|
|
3023
|
+
"variable-sets:manage",
|
|
3024
|
+
"rigs:use",
|
|
2690
3025
|
"github:use"
|
|
2691
3026
|
];
|
|
2692
3027
|
function isCodexAppsMcpServer(config) {
|
|
@@ -2715,7 +3050,11 @@ function firstPartyMcpServerUrlForRun(settings, config, workspaceId) {
|
|
|
2715
3050
|
if (!isFirstPartyMcpServer(settings, config)) {
|
|
2716
3051
|
return null;
|
|
2717
3052
|
}
|
|
2718
|
-
const rawBase = settings.opengeniMcpUrl?.includes("{workspaceId}") ? settings.opengeniMcpUrl.replaceAll("{workspaceId}", workspaceId) : settings.opengeniMcpUrl ? scopedMcpUrlFromConfiguredBase(settings.opengeniMcpUrl, 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
|
+
);
|
|
2719
3058
|
const url = new URL(rawBase);
|
|
2720
3059
|
if (config.id === "docs") {
|
|
2721
3060
|
url.pathname = `${url.pathname.replace(/\/+$/, "")}/docs`;
|
|
@@ -2752,18 +3091,24 @@ function prefixedMcpToolName(registryId, toolName) {
|
|
|
2752
3091
|
return sharedPrefixedMcpToolName(registryId, toolName);
|
|
2753
3092
|
}
|
|
2754
3093
|
var PrefixedMcpServer = class {
|
|
2755
|
-
constructor(inner, registryId, allowedTools) {
|
|
3094
|
+
constructor(inner, registryId, allowedTools, bestEffort = false) {
|
|
2756
3095
|
this.inner = inner;
|
|
2757
3096
|
this.name = registryId;
|
|
2758
3097
|
this.prefix = prefixedMcpToolName(registryId, "");
|
|
2759
3098
|
this.cacheToolsList = inner.cacheToolsList;
|
|
2760
3099
|
this.allowedTools = allowedTools ? new Set(allowedTools) : void 0;
|
|
3100
|
+
this.bestEffort = bestEffort;
|
|
2761
3101
|
}
|
|
2762
|
-
inner;
|
|
2763
3102
|
cacheToolsList;
|
|
2764
3103
|
name;
|
|
2765
3104
|
prefix;
|
|
2766
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;
|
|
2767
3112
|
connect() {
|
|
2768
3113
|
return this.inner.connect();
|
|
2769
3114
|
}
|
|
@@ -2771,7 +3116,24 @@ var PrefixedMcpServer = class {
|
|
|
2771
3116
|
return this.inner.close();
|
|
2772
3117
|
}
|
|
2773
3118
|
async listTools() {
|
|
2774
|
-
|
|
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
|
+
}
|
|
2775
3137
|
return tools.filter((tool2) => this.isAllowed(tool2.name)).map((tool2) => ({ ...tool2, name: prefixedMcpToolName(this.name, tool2.name) }));
|
|
2776
3138
|
}
|
|
2777
3139
|
async callTool(toolName, args, meta) {
|
|
@@ -2785,6 +3147,17 @@ var PrefixedMcpServer = class {
|
|
|
2785
3147
|
if (isAuthNeededMcpError(error)) {
|
|
2786
3148
|
return { isError: true, content: [{ type: "text", text: MCP_AUTH_NEEDED_MESSAGE }] };
|
|
2787
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
|
+
}
|
|
2788
3161
|
throw error;
|
|
2789
3162
|
}
|
|
2790
3163
|
}
|
|
@@ -2822,74 +3195,45 @@ var PrefixedMcpServer = class {
|
|
|
2822
3195
|
return toolName.slice(this.prefix.length);
|
|
2823
3196
|
}
|
|
2824
3197
|
};
|
|
2825
|
-
function guardAssembledInput(history, trailing, inputBudgetTokens) {
|
|
2826
|
-
if (typeof inputBudgetTokens !== "number" || inputBudgetTokens <= 0) {
|
|
2827
|
-
return [...history, trailing];
|
|
2828
|
-
}
|
|
2829
|
-
const trailingTokens = estimateItemTokens(trailing);
|
|
2830
|
-
const guarded = enforceInputBudget(
|
|
2831
|
-
history,
|
|
2832
|
-
inputBudgetTokens,
|
|
2833
|
-
trailingTokens
|
|
2834
|
-
);
|
|
2835
|
-
if (guarded.trimmed) {
|
|
2836
|
-
console.warn(
|
|
2837
|
-
`read-path budget guard trimmed ${guarded.droppedCount} oldest history item(s) to fit input budget (${inputBudgetTokens} tokens); the over-budget input was NOT sent`
|
|
2838
|
-
);
|
|
2839
|
-
}
|
|
2840
|
-
return [...guarded.items, trailing];
|
|
2841
|
-
}
|
|
2842
3198
|
async function prepareRunInput(agent, input, options = {}) {
|
|
2843
3199
|
if (input.kind === "message") {
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
role: "user",
|
|
2859
|
-
content: input.text
|
|
2860
|
-
},
|
|
2861
|
-
options.inputBudgetTokens
|
|
2862
|
-
),
|
|
2863
|
-
...sandboxSessionState2 ? { sandboxSessionState: sandboxSessionState2 } : {}
|
|
2864
|
-
};
|
|
3200
|
+
const trailingMessages = [];
|
|
3201
|
+
if (input.internalContext?.trim()) {
|
|
3202
|
+
trailingMessages.push({
|
|
3203
|
+
type: "message",
|
|
3204
|
+
role: "system",
|
|
3205
|
+
content: input.internalContext
|
|
3206
|
+
});
|
|
3207
|
+
}
|
|
3208
|
+
if (input.text?.trim()) {
|
|
3209
|
+
trailingMessages.push({
|
|
3210
|
+
type: "message",
|
|
3211
|
+
role: "user",
|
|
3212
|
+
content: input.text
|
|
3213
|
+
});
|
|
2865
3214
|
}
|
|
2866
|
-
if (
|
|
2867
|
-
|
|
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");
|
|
2868
3217
|
}
|
|
2869
|
-
const state2 = await RunState.fromString(agent, input.serializedRunState);
|
|
2870
|
-
const sandboxSessionState = await restoredSandboxSessionState(state2, options.sandboxClient);
|
|
2871
3218
|
const sanitizedHistory = sanitizeHistoryItemsForModel(
|
|
2872
|
-
|
|
3219
|
+
input.historyItems ?? []
|
|
2873
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
|
+
}
|
|
2874
3226
|
return {
|
|
2875
|
-
//
|
|
2876
|
-
//
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
sanitizedHistory,
|
|
2880
|
-
{
|
|
2881
|
-
type: "message",
|
|
2882
|
-
role: "user",
|
|
2883
|
-
content: input.text
|
|
2884
|
-
},
|
|
2885
|
-
options.inputBudgetTokens
|
|
2886
|
-
),
|
|
2887
|
-
...sandboxSessionState ? { sandboxSessionState } : {},
|
|
2888
|
-
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 } : {}
|
|
2889
3231
|
};
|
|
2890
3232
|
}
|
|
2891
3233
|
if (isClearedRunStateBlob(input.serializedRunState)) {
|
|
2892
|
-
throw new Error(
|
|
3234
|
+
throw new Error(
|
|
3235
|
+
"Cannot resume an approval: the session context was cleared, so the awaiting run state no longer exists."
|
|
3236
|
+
);
|
|
2893
3237
|
}
|
|
2894
3238
|
const state = await RunState.fromString(agent, input.serializedRunState);
|
|
2895
3239
|
const interruptions = state.getInterruptions();
|
|
@@ -2905,6 +3249,26 @@ async function prepareRunInput(agent, input, options = {}) {
|
|
|
2905
3249
|
return { input: state };
|
|
2906
3250
|
}
|
|
2907
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
|
+
}
|
|
2908
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.";
|
|
2909
3273
|
var stripProviderItemIdsFilter = ({ modelData }) => ({
|
|
2910
3274
|
...modelData,
|
|
@@ -2922,38 +3286,32 @@ var normalizeComputerCallsFilter = ({ modelData }) => ({
|
|
|
2922
3286
|
modelData.input
|
|
2923
3287
|
)
|
|
2924
3288
|
});
|
|
3289
|
+
var elideSupersededViewImagesFilter = ({ modelData }) => ({
|
|
3290
|
+
...modelData,
|
|
3291
|
+
input: elideSupersededViewImagePairs(
|
|
3292
|
+
modelData.input
|
|
3293
|
+
)
|
|
3294
|
+
});
|
|
2925
3295
|
function contextRobustnessFilterForSettings(settings, options = {}) {
|
|
2926
|
-
const
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
const images = elideStaleScreenshotImages(modelData.input);
|
|
2931
|
-
if (images.elidedCount > 0) {
|
|
2932
|
-
console.warn(
|
|
2933
|
-
`per-call image history policy elided ${images.elidedCount} older screenshot image(s), keeping the last ${Math.min(3, images.imageCount)} full image(s)`
|
|
2934
|
-
);
|
|
2935
|
-
}
|
|
2936
|
-
let input = images.items;
|
|
2937
|
-
if (inputBudgetTokens !== void 0) {
|
|
2938
|
-
const guarded = enforceInputBudget(
|
|
2939
|
-
input,
|
|
2940
|
-
inputBudgetTokens
|
|
2941
|
-
);
|
|
2942
|
-
if (guarded.trimmed) {
|
|
2943
|
-
console.warn(
|
|
2944
|
-
`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`
|
|
2945
|
-
);
|
|
2946
|
-
input = guarded.items;
|
|
2947
|
-
}
|
|
2948
|
-
}
|
|
2949
|
-
if (clientCompactionMode && options.throwOnCompactionNeeded) {
|
|
3296
|
+
const thresholdTokens = compactionThresholdTokens(settings);
|
|
3297
|
+
return async ({ modelData }) => {
|
|
3298
|
+
const input = modelData.input;
|
|
3299
|
+
if (options.throwOnCompactionNeeded) {
|
|
2950
3300
|
const reported = options.contextCompactionSignalTokens?.();
|
|
2951
3301
|
const hasReported = typeof reported === "number" && reported > 0;
|
|
2952
3302
|
const signalTokens = hasReported ? reported : estimateTokens(input);
|
|
2953
|
-
if (
|
|
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) {
|
|
2954
3312
|
throw new CompactionNeededError({
|
|
2955
3313
|
signalTokens,
|
|
2956
|
-
thresholdTokens
|
|
3314
|
+
thresholdTokens,
|
|
2957
3315
|
signalSource: hasReported ? "provider" : "estimate"
|
|
2958
3316
|
});
|
|
2959
3317
|
}
|
|
@@ -2961,13 +3319,6 @@ function contextRobustnessFilterForSettings(settings, options = {}) {
|
|
|
2961
3319
|
return { ...modelData, input };
|
|
2962
3320
|
};
|
|
2963
3321
|
}
|
|
2964
|
-
function modelCallBudgetTokens(settings) {
|
|
2965
|
-
if (resolveContextCompactionMode(settings) !== "client") {
|
|
2966
|
-
return void 0;
|
|
2967
|
-
}
|
|
2968
|
-
const budget = contextInputBudgetTokens(settings);
|
|
2969
|
-
return budget > 0 ? budget : void 0;
|
|
2970
|
-
}
|
|
2971
3322
|
function composeCallModelInputFilters(filters) {
|
|
2972
3323
|
return async (args) => {
|
|
2973
3324
|
let modelData = args.modelData;
|
|
@@ -2978,7 +3329,10 @@ function composeCallModelInputFilters(filters) {
|
|
|
2978
3329
|
};
|
|
2979
3330
|
}
|
|
2980
3331
|
function callModelInputFilterForSettings(settings, options = {}) {
|
|
2981
|
-
const filters = [
|
|
3332
|
+
const filters = [
|
|
3333
|
+
normalizeComputerCallsFilter,
|
|
3334
|
+
elideSupersededViewImagesFilter
|
|
3335
|
+
];
|
|
2982
3336
|
if (settings.openaiProviderItemIds === "strip") {
|
|
2983
3337
|
filters.push(stripProviderItemIdsFilter);
|
|
2984
3338
|
}
|
|
@@ -2988,54 +3342,63 @@ function callModelInputFilterForSettings(settings, options = {}) {
|
|
|
2988
3342
|
async function runAgentStream(agent, input, settings, overrides = {}) {
|
|
2989
3343
|
const prepared = typeof input === "string" || input instanceof RunState ? { input } : input;
|
|
2990
3344
|
const environment = overrides.sandboxEnvironment ?? collectSandboxEnvironment2(settings);
|
|
3345
|
+
const genesisTitleInputFilter = takeGenesisTitleInputFilter(agent);
|
|
2991
3346
|
if (overrides.ownedSandbox) {
|
|
2992
3347
|
const { client: ownedClient, session, sessionState } = overrides.ownedSandbox;
|
|
2993
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
|
+
}
|
|
2994
3359
|
const runAs2 = sandboxRunAs(settings);
|
|
2995
3360
|
const fileDownloads2 = sandboxFileDownloadsForAgent(agent);
|
|
2996
3361
|
const resourceClient2 = fileDownloads2.length > 0 ? withSandboxFileDownloads(ownedClient, fileDownloads2, {
|
|
2997
3362
|
...overrides.onRuntimeEvent ? { onRuntimeEvent: overrides.onRuntimeEvent } : {},
|
|
2998
3363
|
...runAs2 ? { runAs: runAs2 } : {}
|
|
2999
3364
|
}) : ownedClient;
|
|
3000
|
-
const
|
|
3365
|
+
const ownedGitTokenSeeds = gitTokenSeedsForAgent(agent);
|
|
3001
3366
|
const ownedToolspaceTokenSeed = toolspaceTokenSeedForAgent(agent);
|
|
3367
|
+
const ownedRigSetup = rigSetupDescriptorForAgent(agent);
|
|
3002
3368
|
const ownedHooks = [
|
|
3003
|
-
|
|
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
|
+
),
|
|
3004
3377
|
...sandboxToolspaceTokenHooksForAgent(agent),
|
|
3005
|
-
...sandboxRepositoryCloneHooksForAgent(agent)
|
|
3378
|
+
...sandboxRepositoryCloneHooksForAgent(agent),
|
|
3379
|
+
...gitCredentialSessionRegistrationHooks(overrides.onGitCredentialSessionReady)
|
|
3006
3380
|
];
|
|
3007
3381
|
const ownedHookContext = {
|
|
3008
3382
|
environment,
|
|
3009
3383
|
...overrides.onRuntimeEvent ? { onRuntimeEvent: overrides.onRuntimeEvent } : {},
|
|
3010
3384
|
...runAs2 ? { runAs: runAs2 } : {},
|
|
3011
|
-
...
|
|
3012
|
-
...ownedToolspaceTokenSeed ? { toolspaceTokenSeed: ownedToolspaceTokenSeed } : {}
|
|
3385
|
+
...ownedGitTokenSeeds ? { gitTokenSeeds: ownedGitTokenSeeds } : {},
|
|
3386
|
+
...ownedToolspaceTokenSeed ? { toolspaceTokenSeed: ownedToolspaceTokenSeed } : {},
|
|
3387
|
+
...ownedRigSetup ? { rigSetup: ownedRigSetup } : {}
|
|
3013
3388
|
};
|
|
3014
|
-
if (agentActiveSandboxBackend.get(agent) !== "selfhosted") {
|
|
3015
|
-
await runBeforeAgentStartHooks(setupSession, ownedHooks, ownedHookContext);
|
|
3016
|
-
if (fileDownloads2.length > 0) {
|
|
3017
|
-
await materializeSandboxFileDownloads(setupSession, fileDownloads2, {
|
|
3018
|
-
...overrides.onRuntimeEvent ? { onRuntimeEvent: overrides.onRuntimeEvent } : {},
|
|
3019
|
-
...runAs2 ? { runAs: runAs2 } : {}
|
|
3020
|
-
});
|
|
3021
|
-
}
|
|
3022
|
-
} else {
|
|
3023
|
-
const toolspaceHooks = sandboxToolspaceTokenHooksForAgent(agent);
|
|
3024
|
-
if (toolspaceHooks.length > 0) {
|
|
3025
|
-
await runBeforeAgentStartHooks(setupSession, toolspaceHooks, ownedHookContext);
|
|
3026
|
-
}
|
|
3027
|
-
}
|
|
3028
3389
|
const decoratedClient = withSandboxLifecycleHooks(resourceClient2, ownedHooks, ownedHookContext);
|
|
3029
3390
|
const ownedFilter = composeCallModelInputFilters(
|
|
3030
3391
|
[
|
|
3031
3392
|
callModelInputFilterForSettings(settings, {
|
|
3032
|
-
throwOnCompactionNeeded: Boolean(
|
|
3033
|
-
|
|
3393
|
+
throwOnCompactionNeeded: Boolean(
|
|
3394
|
+
overrides.contextCompactionSignalTokens || overrides.contextCompactionRequested
|
|
3395
|
+
),
|
|
3396
|
+
...overrides.contextCompactionSignalTokens ? { contextCompactionSignalTokens: overrides.contextCompactionSignalTokens } : {},
|
|
3397
|
+
...overrides.contextCompactionRequested ? { contextCompactionRequested: overrides.contextCompactionRequested } : {}
|
|
3034
3398
|
}),
|
|
3399
|
+
genesisTitleInputFilter,
|
|
3035
3400
|
overrides.callModelInputFilter
|
|
3036
|
-
].filter(
|
|
3037
|
-
(f) => Boolean(f)
|
|
3038
|
-
)
|
|
3401
|
+
].filter((f) => Boolean(f))
|
|
3039
3402
|
);
|
|
3040
3403
|
const ownedRunOptions = {
|
|
3041
3404
|
stream: true,
|
|
@@ -3050,44 +3413,66 @@ async function runAgentStream(agent, input, settings, overrides = {}) {
|
|
|
3050
3413
|
return await runScopedRunner(settings).run(agent, prepared.input, ownedRunOptions);
|
|
3051
3414
|
}
|
|
3052
3415
|
const rawClient = overrides.sandboxClient ?? createSandboxClient(settings, environment);
|
|
3053
|
-
const refreshedClient = rawClient ? withManifestRefreshOnResume(
|
|
3416
|
+
const refreshedClient = rawClient ? withManifestRefreshOnResume(
|
|
3417
|
+
rawClient,
|
|
3418
|
+
agent.defaultManifest,
|
|
3419
|
+
{
|
|
3420
|
+
...overrides.onRuntimeEvent ? { onRuntimeEvent: overrides.onRuntimeEvent } : {}
|
|
3421
|
+
}
|
|
3422
|
+
) : void 0;
|
|
3054
3423
|
const runAs = sandboxRunAs(settings);
|
|
3055
3424
|
const fileDownloads = sandboxFileDownloadsForAgent(agent);
|
|
3056
3425
|
const resourceClient = refreshedClient && fileDownloads.length > 0 ? withSandboxFileDownloads(refreshedClient, fileDownloads, {
|
|
3057
3426
|
...overrides.onRuntimeEvent ? { onRuntimeEvent: overrides.onRuntimeEvent } : {},
|
|
3058
3427
|
...runAs ? { runAs } : {}
|
|
3059
3428
|
}) : refreshedClient;
|
|
3060
|
-
const
|
|
3429
|
+
const gitTokenSeeds = gitTokenSeedsForAgent(agent);
|
|
3061
3430
|
const toolspaceTokenSeed = toolspaceTokenSeedForAgent(agent);
|
|
3062
|
-
const
|
|
3063
|
-
|
|
3064
|
-
|
|
3065
|
-
|
|
3066
|
-
|
|
3067
|
-
|
|
3068
|
-
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
|
|
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;
|
|
3074
3457
|
const callModelInputFilter = composeCallModelInputFilters(
|
|
3075
3458
|
[
|
|
3076
3459
|
callModelInputFilterForSettings(settings, {
|
|
3077
|
-
throwOnCompactionNeeded: Boolean(
|
|
3078
|
-
|
|
3460
|
+
throwOnCompactionNeeded: Boolean(
|
|
3461
|
+
overrides.contextCompactionSignalTokens || overrides.contextCompactionRequested
|
|
3462
|
+
),
|
|
3463
|
+
...overrides.contextCompactionSignalTokens ? { contextCompactionSignalTokens: overrides.contextCompactionSignalTokens } : {},
|
|
3464
|
+
...overrides.contextCompactionRequested ? { contextCompactionRequested: overrides.contextCompactionRequested } : {}
|
|
3079
3465
|
}),
|
|
3466
|
+
genesisTitleInputFilter,
|
|
3080
3467
|
overrides.callModelInputFilter
|
|
3081
|
-
].filter(
|
|
3082
|
-
(f) => Boolean(f)
|
|
3083
|
-
)
|
|
3468
|
+
].filter((f) => Boolean(f))
|
|
3084
3469
|
);
|
|
3085
3470
|
const runOptions = {
|
|
3086
3471
|
stream: true,
|
|
3087
3472
|
maxTurns: settings.agentMaxModelCallsPerTurn,
|
|
3088
3473
|
// Built-in per-call guard chain: normalize computer calls, optionally strip
|
|
3089
|
-
// provider ids,
|
|
3090
|
-
//
|
|
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
|
|
3091
3476
|
// every mid-turn follow-up.
|
|
3092
3477
|
callModelInputFilter
|
|
3093
3478
|
};
|
|
@@ -3099,6 +3484,22 @@ async function runAgentStream(agent, input, settings, overrides = {}) {
|
|
|
3099
3484
|
}
|
|
3100
3485
|
return await runScopedRunner(settings).run(agent, prepared.input, runOptions);
|
|
3101
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
|
+
}
|
|
3102
3503
|
function runScopedRunner(settings) {
|
|
3103
3504
|
return new Runner({ modelProvider: new MultiProviderModelProvider(settings) });
|
|
3104
3505
|
}
|
|
@@ -3122,7 +3523,7 @@ function agentsErrorRunState(error) {
|
|
|
3122
3523
|
return null;
|
|
3123
3524
|
}
|
|
3124
3525
|
}
|
|
3125
|
-
function withManifestRefreshOnResume(client, targetManifest) {
|
|
3526
|
+
function withManifestRefreshOnResume(client, targetManifest, context = {}) {
|
|
3126
3527
|
if (!targetManifest || !client.resume) {
|
|
3127
3528
|
return client;
|
|
3128
3529
|
}
|
|
@@ -3132,17 +3533,25 @@ function withManifestRefreshOnResume(client, targetManifest) {
|
|
|
3132
3533
|
...client.create ? { create: async (...args) => await client.create(...args) } : {},
|
|
3133
3534
|
resume: async (state) => {
|
|
3134
3535
|
const session = await client.resume(state);
|
|
3135
|
-
await applyMissingManifestEntries(session, targetManifest);
|
|
3536
|
+
await applyMissingManifestEntries(session, targetManifest, context);
|
|
3136
3537
|
return session;
|
|
3137
3538
|
},
|
|
3138
3539
|
...client.delete ? { delete: async (state) => await client.delete(state) } : {},
|
|
3139
|
-
...client.serializeSessionState ? {
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
...client.
|
|
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
|
+
} : {}
|
|
3143
3552
|
};
|
|
3144
3553
|
}
|
|
3145
|
-
async function applyMissingManifestEntries(session, targetManifest) {
|
|
3554
|
+
async function applyMissingManifestEntries(session, targetManifest, context = {}) {
|
|
3146
3555
|
const currentManifestValue = session.state?.manifest;
|
|
3147
3556
|
const currentManifest = currentManifestValue ? ensureManifest(currentManifestValue) : void 0;
|
|
3148
3557
|
const target = ensureManifest(targetManifest);
|
|
@@ -3150,13 +3559,18 @@ async function applyMissingManifestEntries(session, targetManifest) {
|
|
|
3150
3559
|
if (Object.keys(target.entries).length === 0) {
|
|
3151
3560
|
return;
|
|
3152
3561
|
}
|
|
3153
|
-
throw new Error(
|
|
3562
|
+
throw new Error(
|
|
3563
|
+
"Resumed sandbox session cannot apply new manifest entries because current manifest state is unavailable"
|
|
3564
|
+
);
|
|
3154
3565
|
}
|
|
3566
|
+
await reportManifestEnvironmentDrift(currentManifest, target, context);
|
|
3155
3567
|
if (!session.applyManifest && !session.materializeEntry) {
|
|
3156
3568
|
if (Object.keys(target.entries).length === 0) {
|
|
3157
3569
|
return;
|
|
3158
3570
|
}
|
|
3159
|
-
throw new Error(
|
|
3571
|
+
throw new Error(
|
|
3572
|
+
"Resumed sandbox session cannot apply new manifest entries because it does not support applyManifest() or materializeEntry()"
|
|
3573
|
+
);
|
|
3160
3574
|
}
|
|
3161
3575
|
if (Object.keys(target.entries).length === 0) {
|
|
3162
3576
|
return;
|
|
@@ -3177,7 +3591,9 @@ async function applyMissingManifestEntries(session, targetManifest) {
|
|
|
3177
3591
|
}
|
|
3178
3592
|
const environmentChanged = stableJson(currentManifest.environment) !== stableJson(target.environment);
|
|
3179
3593
|
if (environmentChanged && !session.applyManifest) {
|
|
3180
|
-
throw new Error(
|
|
3594
|
+
throw new Error(
|
|
3595
|
+
"Resumed sandbox session cannot refresh manifest environment because it does not support applyManifest()"
|
|
3596
|
+
);
|
|
3181
3597
|
}
|
|
3182
3598
|
if (Object.keys(entries).length === 0 && !environmentChanged) {
|
|
3183
3599
|
return;
|
|
@@ -3206,6 +3622,114 @@ async function applyMissingManifestEntries(session, targetManifest) {
|
|
|
3206
3622
|
...extraPathGrants.length ? { extraPathGrants } : {}
|
|
3207
3623
|
});
|
|
3208
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
|
+
}
|
|
3209
3733
|
function mergePathGrants(current, target) {
|
|
3210
3734
|
const merged = /* @__PURE__ */ new Map();
|
|
3211
3735
|
for (const grant of [...current ?? [], ...target ?? []]) {
|
|
@@ -3229,23 +3753,33 @@ function withSandboxFileDownloads(client, downloads, context = {}) {
|
|
|
3229
3753
|
return {
|
|
3230
3754
|
backendId: client.backendId,
|
|
3231
3755
|
...client.supportsDefaultOptions !== void 0 ? { supportsDefaultOptions: client.supportsDefaultOptions } : {},
|
|
3232
|
-
...client.create ? {
|
|
3233
|
-
|
|
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
|
+
} : {},
|
|
3234
3762
|
...client.delete ? { delete: async (state) => await client.delete(state) } : {},
|
|
3235
|
-
...client.serializeSessionState ? {
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
...client.
|
|
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
|
+
} : {}
|
|
3239
3775
|
};
|
|
3240
3776
|
}
|
|
3241
3777
|
async function materializeSandboxFileDownloads(session, downloads, context = {}) {
|
|
3242
3778
|
const normalizedDownloads = normalizeSandboxFileDownloads(downloads);
|
|
3243
3779
|
if (normalizedDownloads.length === 0) {
|
|
3244
|
-
return;
|
|
3245
|
-
}
|
|
3246
|
-
if (!session.exec && !session.execCommand) {
|
|
3247
|
-
throw new Error("Sandbox file download materialization requires command execution support");
|
|
3780
|
+
return { failures: [] };
|
|
3248
3781
|
}
|
|
3782
|
+
const failures = [];
|
|
3249
3783
|
for (const download of normalizedDownloads) {
|
|
3250
3784
|
const targetPath = sandboxDownloadTargetPath(download);
|
|
3251
3785
|
const payload = {
|
|
@@ -3254,9 +3788,30 @@ async function materializeSandboxFileDownloads(session, downloads, context = {})
|
|
|
3254
3788
|
sizeBytes: download.sizeBytes ?? null,
|
|
3255
3789
|
expiresAt: download.expiresAt ? new Date(download.expiresAt).toISOString() : null
|
|
3256
3790
|
};
|
|
3257
|
-
await context.onRuntimeEvent?.({
|
|
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;
|
|
3258
3813
|
try {
|
|
3259
|
-
|
|
3814
|
+
result = session.exec ? await session.exec({
|
|
3260
3815
|
cmd: sandboxFileDownloadCommand(download, targetPath),
|
|
3261
3816
|
workdir: "/workspace",
|
|
3262
3817
|
...context.runAs ? { runAs: context.runAs } : {},
|
|
@@ -3270,19 +3825,48 @@ async function materializeSandboxFileDownloads(session, downloads, context = {})
|
|
|
3270
3825
|
maxOutputTokens: 2e4
|
|
3271
3826
|
});
|
|
3272
3827
|
assertSandboxCommandSucceeded(result, `Sandbox file resource download ${download.fileId}`);
|
|
3273
|
-
await context.onRuntimeEvent?.({
|
|
3828
|
+
await context.onRuntimeEvent?.({
|
|
3829
|
+
type: "sandbox.operation.completed",
|
|
3830
|
+
payload: { name: "file-resource-download", ...payload }
|
|
3831
|
+
});
|
|
3274
3832
|
} catch (error) {
|
|
3833
|
+
const failure = sandboxFileDownloadFailure(download, targetPath, error, result);
|
|
3834
|
+
failures.push(failure);
|
|
3275
3835
|
await context.onRuntimeEvent?.({
|
|
3276
3836
|
type: "sandbox.operation.failed",
|
|
3277
3837
|
payload: {
|
|
3278
3838
|
name: "file-resource-download",
|
|
3279
3839
|
...payload,
|
|
3280
|
-
error:
|
|
3840
|
+
error: failure.reason,
|
|
3841
|
+
...failure.exitCode !== void 0 ? { exitCode: failure.exitCode } : {},
|
|
3842
|
+
...failure.output ? { output: failure.output } : {}
|
|
3281
3843
|
}
|
|
3282
3844
|
});
|
|
3283
|
-
throw error;
|
|
3284
3845
|
}
|
|
3285
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");
|
|
3286
3870
|
}
|
|
3287
3871
|
function sandboxFileDownloadsForAgent(agent) {
|
|
3288
3872
|
return typeof agent === "object" && agent !== null ? [...agentFileDownloads.get(agent) ?? []] : [];
|
|
@@ -3357,6 +3941,9 @@ function normalizeSdkEvent(event) {
|
|
|
3357
3941
|
out.push({ type: "agent.message.delta", payload: { text: data.delta } });
|
|
3358
3942
|
return out;
|
|
3359
3943
|
}
|
|
3944
|
+
if (data?.type === "response_done") {
|
|
3945
|
+
return out;
|
|
3946
|
+
}
|
|
3360
3947
|
}
|
|
3361
3948
|
if (isOpenAIResponsesRawModelStreamEvent(event)) {
|
|
3362
3949
|
const raw = event.data?.event;
|
|
@@ -3415,7 +4002,10 @@ function normalizeSdkEvent(event) {
|
|
|
3415
4002
|
type: "agent.toolCall.output",
|
|
3416
4003
|
payload: {
|
|
3417
4004
|
id: raw.call_id ?? raw.callId ?? item.id ?? null,
|
|
3418
|
-
output: {
|
|
4005
|
+
output: {
|
|
4006
|
+
type: "text",
|
|
4007
|
+
text: disclosed.length > 0 ? `Disclosed tools: ${disclosed.join(", ")}` : "No matching tools found."
|
|
4008
|
+
}
|
|
3419
4009
|
}
|
|
3420
4010
|
});
|
|
3421
4011
|
} else if (item.type === "message_output_item") {
|
|
@@ -3428,6 +4018,9 @@ function normalizeSdkEvent(event) {
|
|
|
3428
4018
|
}
|
|
3429
4019
|
function modelResponseUsageFromSdkEvent(event) {
|
|
3430
4020
|
const response = modelResponseFromSdkEvent(event);
|
|
4021
|
+
return modelResponseUsageFromResponse(response);
|
|
4022
|
+
}
|
|
4023
|
+
function modelResponseUsageFromResponse(response) {
|
|
3431
4024
|
const usage = usageFromResponse(response);
|
|
3432
4025
|
if (!usage) {
|
|
3433
4026
|
return null;
|
|
@@ -3458,25 +4051,51 @@ function usageFromResponse(response) {
|
|
|
3458
4051
|
if (!raw || typeof raw !== "object") {
|
|
3459
4052
|
return null;
|
|
3460
4053
|
}
|
|
4054
|
+
const record = raw;
|
|
3461
4055
|
const usage = {
|
|
3462
|
-
...numberProp(
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
|
|
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)
|
|
3466
4075
|
};
|
|
3467
4076
|
return Object.keys(usage).length > 0 ? usage : null;
|
|
3468
4077
|
}
|
|
3469
|
-
function numberProp(raw, outputKey,
|
|
3470
|
-
const value = raw[
|
|
4078
|
+
function numberProp(raw, outputKey, ...keys) {
|
|
4079
|
+
const value = keys.map((key) => raw[key]).find((candidate) => candidate !== void 0);
|
|
3471
4080
|
return typeof value === "number" && Number.isFinite(value) ? { [outputKey]: value } : {};
|
|
3472
4081
|
}
|
|
3473
4082
|
function inputTokenDetailsProp(raw) {
|
|
3474
|
-
const details = raw.inputTokensDetails ?? raw.input_tokens_details;
|
|
4083
|
+
const details = raw.inputTokensDetails ?? raw.input_tokens_details ?? raw.promptTokensDetails ?? raw.prompt_tokens_details;
|
|
3475
4084
|
if (!details || typeof details !== "object") {
|
|
3476
4085
|
return {};
|
|
3477
4086
|
}
|
|
3478
4087
|
return { inputTokensDetails: details };
|
|
3479
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
|
+
}
|
|
3480
4099
|
function serializeApprovals(interruptions) {
|
|
3481
4100
|
return interruptions.map((item) => {
|
|
3482
4101
|
if (typeof item?.toJSON === "function") {
|
|
@@ -3492,7 +4111,12 @@ function serializeApprovals(interruptions) {
|
|
|
3492
4111
|
}
|
|
3493
4112
|
function buildManifest(settings, resources, environment = collectSandboxEnvironment2(settings), fileResourceDownloads = []) {
|
|
3494
4113
|
const entries = {};
|
|
3495
|
-
const downloadsByFileId = new Map(
|
|
4114
|
+
const downloadsByFileId = new Map(
|
|
4115
|
+
normalizeSandboxFileDownloads(fileResourceDownloads).map((download) => [
|
|
4116
|
+
download.fileId,
|
|
4117
|
+
download
|
|
4118
|
+
])
|
|
4119
|
+
);
|
|
3496
4120
|
for (const resource of resources) {
|
|
3497
4121
|
if (resource.kind === "repository") {
|
|
3498
4122
|
const url = new URL(resource.uri);
|
|
@@ -3525,7 +4149,9 @@ function buildManifest(settings, resources, environment = collectSandboxEnvironm
|
|
|
3525
4149
|
}
|
|
3526
4150
|
function sandboxDownloadDirectory(download, mountPath) {
|
|
3527
4151
|
if (download.mountPath !== mountPath) {
|
|
3528
|
-
throw new Error(
|
|
4152
|
+
throw new Error(
|
|
4153
|
+
`File download materialization path mismatch for ${download.fileId}: expected ${mountPath}, got ${download.mountPath}`
|
|
4154
|
+
);
|
|
3529
4155
|
}
|
|
3530
4156
|
assertSafeSandboxFilename(download.filename, download.fileId);
|
|
3531
4157
|
if (download.content) {
|
|
@@ -3541,7 +4167,9 @@ function objectStorageFileMount(settings, prefix) {
|
|
|
3541
4167
|
const nativeBucketMount = CAPABILITY_DESCRIPTORS2[settings.sandboxBackend].nativeBucketMount;
|
|
3542
4168
|
if (settings.objectStorageBackend === "azure-blob") {
|
|
3543
4169
|
if (nativeBucketMount) {
|
|
3544
|
-
throw new Error(
|
|
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
|
+
);
|
|
3545
4173
|
}
|
|
3546
4174
|
const config2 = azureBlobMountConfig(settings);
|
|
3547
4175
|
return azureBlobMount({
|
|
@@ -3555,7 +4183,9 @@ function objectStorageFileMount(settings, prefix) {
|
|
|
3555
4183
|
});
|
|
3556
4184
|
}
|
|
3557
4185
|
if (settings.objectStorageBackend === "aws-s3" || settings.objectStorageBackend === "gcs") {
|
|
3558
|
-
throw new Error(
|
|
4186
|
+
throw new Error(
|
|
4187
|
+
`${settings.objectStorageBackend} file resources require pre-signed download materialization`
|
|
4188
|
+
);
|
|
3559
4189
|
}
|
|
3560
4190
|
const config = s3CompatibleMountConfig(settings);
|
|
3561
4191
|
return s3Mount({
|
|
@@ -3591,7 +4221,10 @@ function azureBlobMountConfig(settings) {
|
|
|
3591
4221
|
if (!accountName || !accountKey) {
|
|
3592
4222
|
throw new Error("File resources require Azure Blob account name and account key");
|
|
3593
4223
|
}
|
|
3594
|
-
const endpointUrl = azureBlobManifestEndpoint(
|
|
4224
|
+
const endpointUrl = azureBlobManifestEndpoint(
|
|
4225
|
+
settings.objectStorageAzureEndpoint ?? parsed.BlobEndpoint,
|
|
4226
|
+
accountName
|
|
4227
|
+
);
|
|
3595
4228
|
return {
|
|
3596
4229
|
container: settings.objectStorageBucket,
|
|
3597
4230
|
accountName,
|
|
@@ -3608,10 +4241,12 @@ function azureBlobManifestEndpoint(endpoint, accountName) {
|
|
|
3608
4241
|
return normalized === standardAccountEndpoint ? void 0 : normalized;
|
|
3609
4242
|
}
|
|
3610
4243
|
function parseAzureConnectionString(value) {
|
|
3611
|
-
return Object.fromEntries(
|
|
3612
|
-
|
|
3613
|
-
|
|
3614
|
-
|
|
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
|
+
);
|
|
3615
4250
|
}
|
|
3616
4251
|
function normalizeManifestPath(path) {
|
|
3617
4252
|
const normalized = path.replace(/^\/+|\/+$/g, "");
|
|
@@ -3625,7 +4260,9 @@ function normalizeSandboxFileDownloads(downloads) {
|
|
|
3625
4260
|
const mountPath = normalizeManifestPath(download.mountPath);
|
|
3626
4261
|
assertSafeSandboxFilename(download.filename, download.fileId);
|
|
3627
4262
|
if (!download.content && !download.url?.trim()) {
|
|
3628
|
-
throw new Error(
|
|
4263
|
+
throw new Error(
|
|
4264
|
+
`File download materialization requires content or a URL for ${download.fileId}`
|
|
4265
|
+
);
|
|
3629
4266
|
}
|
|
3630
4267
|
return {
|
|
3631
4268
|
...download,
|
|
@@ -3648,7 +4285,7 @@ function sandboxFileDownloadCommand(download, targetPath) {
|
|
|
3648
4285
|
const targetDir = posixPath.dirname(targetPath);
|
|
3649
4286
|
const tmpPath = `${targetPath}.opengeni-download-$$`;
|
|
3650
4287
|
return [
|
|
3651
|
-
"set -
|
|
4288
|
+
"set -eu",
|
|
3652
4289
|
`mkdir -p -- ${shellQuote(targetDir)}`,
|
|
3653
4290
|
`if [ ! -f ${shellQuote(targetPath)} ]; then`,
|
|
3654
4291
|
` tmp=${shellQuote(tmpPath)}`,
|
|
@@ -3664,25 +4301,6 @@ function sandboxFileDownloadCommand(download, targetPath) {
|
|
|
3664
4301
|
function shellQuote(value) {
|
|
3665
4302
|
return `'${value.replace(/'/g, "'\\''")}'`;
|
|
3666
4303
|
}
|
|
3667
|
-
async function restoredSandboxSessionState(state, client) {
|
|
3668
|
-
if (!client) {
|
|
3669
|
-
return void 0;
|
|
3670
|
-
}
|
|
3671
|
-
const sandboxState = state._sandbox;
|
|
3672
|
-
const entry = sandboxState?.sessionsByAgent?.[sandboxState.currentAgentKey] ?? (sandboxState?.currentAgentKey && sandboxState?.sessionState ? {
|
|
3673
|
-
backendId: sandboxState.backendId,
|
|
3674
|
-
currentAgentKey: sandboxState.currentAgentKey,
|
|
3675
|
-
currentAgentName: sandboxState.currentAgentName,
|
|
3676
|
-
sessionState: sandboxState.sessionState
|
|
3677
|
-
} : void 0);
|
|
3678
|
-
if (!entry) {
|
|
3679
|
-
return void 0;
|
|
3680
|
-
}
|
|
3681
|
-
if (client.backendId !== entry.backendId) {
|
|
3682
|
-
throw new Error("RunState sandbox backend does not match the configured sandbox client");
|
|
3683
|
-
}
|
|
3684
|
-
return await deserializeSandboxSessionStateEnvelope(client, entry.sessionState);
|
|
3685
|
-
}
|
|
3686
4304
|
var builtInSandboxLifecycleHooks = {
|
|
3687
4305
|
"azure-cli-login": {
|
|
3688
4306
|
id: "azure-cli-login",
|
|
@@ -3701,7 +4319,9 @@ function sandboxLifecycleHooksForIds(ids) {
|
|
|
3701
4319
|
});
|
|
3702
4320
|
}
|
|
3703
4321
|
function applicableBeforeAgentStartHooks(hooks, context) {
|
|
3704
|
-
return hooks.filter(
|
|
4322
|
+
return hooks.filter(
|
|
4323
|
+
(hook) => hook.phase === "beforeAgentStart" && (hook.shouldRun?.(context) ?? true)
|
|
4324
|
+
);
|
|
3705
4325
|
}
|
|
3706
4326
|
async function runBeforeAgentStartHooks(session, hooks, context) {
|
|
3707
4327
|
for (const hook of applicableBeforeAgentStartHooks(hooks, context)) {
|
|
@@ -3726,102 +4346,328 @@ function withSandboxLifecycleHooks(client, hooks, context) {
|
|
|
3726
4346
|
const wrapped = {
|
|
3727
4347
|
backendId: client.backendId,
|
|
3728
4348
|
...client.supportsDefaultOptions !== void 0 ? { supportsDefaultOptions: client.supportsDefaultOptions } : {},
|
|
3729
|
-
...client.create ? {
|
|
3730
|
-
|
|
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
|
+
} : {},
|
|
3731
4355
|
...client.delete ? { delete: async (state) => await client.delete(state) } : {},
|
|
3732
|
-
...client.serializeSessionState ? {
|
|
3733
|
-
|
|
3734
|
-
|
|
3735
|
-
...client.
|
|
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
|
+
} : {}
|
|
3736
4368
|
};
|
|
3737
4369
|
return wrapped;
|
|
3738
4370
|
}
|
|
3739
4371
|
function sandboxRepositoryCloneHooksForAgent(agent) {
|
|
3740
4372
|
return agentRepositoryCloneHooks.get(agent) ?? [];
|
|
3741
4373
|
}
|
|
3742
|
-
function
|
|
3743
|
-
return
|
|
4374
|
+
function gitTokenSeedsForAgent(agent) {
|
|
4375
|
+
return agentGitTokenSeeds.get(agent);
|
|
3744
4376
|
}
|
|
3745
4377
|
function toolspaceTokenSeedForAgent(agent) {
|
|
3746
4378
|
return agentToolspaceTokenSeed.get(agent);
|
|
3747
4379
|
}
|
|
3748
4380
|
function sandboxToolspaceTokenHooksForAgent(agent) {
|
|
3749
|
-
return toolspaceTokenSeedForAgent(agent) ? [
|
|
3750
|
-
|
|
3751
|
-
|
|
3752
|
-
|
|
3753
|
-
|
|
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
|
+
] : [];
|
|
3754
4421
|
}
|
|
3755
4422
|
function sandboxRepositoryCloneHooks(settings, resources, activeSandboxBackend = settings.sandboxBackend) {
|
|
3756
|
-
const repositories = resources.filter(
|
|
4423
|
+
const repositories = resources.filter(
|
|
4424
|
+
(resource) => resource.kind === "repository" && repositoryUsesSandboxClone(settings, resource, activeSandboxBackend)
|
|
4425
|
+
);
|
|
3757
4426
|
if (repositories.length === 0) {
|
|
3758
4427
|
return [];
|
|
3759
4428
|
}
|
|
3760
|
-
return [
|
|
3761
|
-
|
|
3762
|
-
|
|
3763
|
-
|
|
3764
|
-
|
|
4429
|
+
return [
|
|
4430
|
+
{
|
|
4431
|
+
id: "repository-clone",
|
|
4432
|
+
phase: "beforeAgentStart",
|
|
4433
|
+
run: async (session, context) => {
|
|
4434
|
+
await runRepositoryCloneHook(session, repositories, context);
|
|
4435
|
+
}
|
|
3765
4436
|
}
|
|
3766
|
-
|
|
4437
|
+
];
|
|
3767
4438
|
}
|
|
3768
4439
|
function repositoryUsesSandboxClone(settings, resource, activeSandboxBackend = settings.sandboxBackend) {
|
|
3769
4440
|
if (activeSandboxBackend === "selfhosted") {
|
|
3770
4441
|
return false;
|
|
3771
4442
|
}
|
|
3772
|
-
return settings.sandboxBackend === "modal" || Boolean(resource.githubInstallationId && resource.githubRepositoryId);
|
|
4443
|
+
return settings.sandboxBackend === "modal" || Boolean(resource.githubInstallationId && resource.githubRepositoryId) || Boolean(resource.provider);
|
|
3773
4444
|
}
|
|
3774
|
-
|
|
3775
|
-
|
|
3776
|
-
|
|
3777
|
-
|
|
3778
|
-
|
|
3779
|
-
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
|
|
3783
|
-
|
|
3784
|
-
|
|
3785
|
-
|
|
3786
|
-
|
|
3787
|
-
|
|
3788
|
-
|
|
3789
|
-
|
|
3790
|
-
|
|
3791
|
-
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
|
|
3801
|
-
|
|
3802
|
-
|
|
3803
|
-
|
|
3804
|
-
|
|
3805
|
-
|
|
3806
|
-
|
|
3807
|
-
|
|
3808
|
-
|
|
3809
|
-
|
|
3810
|
-
|
|
3811
|
-
|
|
3812
|
-
|
|
3813
|
-
|
|
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'`,
|
|
3814
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")"',
|
|
3815
4574
|
'case "$1" in',
|
|
3816
|
-
|
|
3817
|
-
` *Password*) cat "
|
|
4575
|
+
' *Username*) username_for_provider "$provider" ;;',
|
|
4576
|
+
` *Password*) cat "$(token_file_for_provider "$provider")" 2>/dev/null || printf '\\n' ;;`,
|
|
3818
4577
|
" *) printf '\\n' ;;",
|
|
3819
4578
|
"esac",
|
|
3820
4579
|
"ASKPASS_EOF",
|
|
3821
|
-
'
|
|
3822
|
-
'
|
|
3823
|
-
'
|
|
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",
|
|
3824
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),
|
|
3825
4671
|
"ensure_git() {",
|
|
3826
4672
|
" if command -v git >/dev/null 2>&1; then",
|
|
3827
4673
|
" return 0",
|
|
@@ -3912,13 +4758,15 @@ function repositoryCloneCommand(resources) {
|
|
|
3912
4758
|
const url = new URL(resource.uri);
|
|
3913
4759
|
const repo = url.pathname.replace(/^\/+|\/+$/g, "").replace(/\.git$/, "");
|
|
3914
4760
|
const mountPath = normalizeManifestPath(resource.mountPath ?? `repos/${repo}`);
|
|
3915
|
-
commands.push(
|
|
3916
|
-
|
|
3917
|
-
|
|
3918
|
-
|
|
3919
|
-
|
|
3920
|
-
|
|
3921
|
-
|
|
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
|
+
);
|
|
3922
4770
|
}
|
|
3923
4771
|
return commands.join("\n");
|
|
3924
4772
|
}
|
|
@@ -3965,11 +4813,124 @@ ${toolspaceTokenSeedCommand()}`;
|
|
|
3965
4813
|
throw new Error("Sandbox session does not support command execution");
|
|
3966
4814
|
}
|
|
3967
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
|
+
}
|
|
3968
4924
|
async function runRepositoryCloneHook(session, resources, context = { environment: {} }) {
|
|
3969
4925
|
const payload = { name: "repository-clone", repositoryCount: resources.length };
|
|
3970
4926
|
await context.onRuntimeEvent?.({ type: "sandbox.operation.started", payload });
|
|
3971
4927
|
try {
|
|
3972
|
-
const
|
|
4928
|
+
const gitTokenSeeds = {
|
|
4929
|
+
...context.gitTokenSeeds ?? {},
|
|
4930
|
+
...context.gitTokenSeed ? { github: context.gitTokenSeed } : {}
|
|
4931
|
+
};
|
|
4932
|
+
const seedPrefix = gitTokenSeedExportPrefix(gitTokenSeeds);
|
|
4933
|
+
const command = seedPrefix ? `${seedPrefix}
|
|
3973
4934
|
${repositoryCloneCommand(resources)}` : repositoryCloneCommand(resources);
|
|
3974
4935
|
if (session.exec) {
|
|
3975
4936
|
const result = await session.exec({
|
|
@@ -4040,6 +5001,10 @@ function sandboxCommandExitCode(result) {
|
|
|
4040
5001
|
return null;
|
|
4041
5002
|
}
|
|
4042
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
|
+
}
|
|
4043
5008
|
if (!result || typeof result !== "object") {
|
|
4044
5009
|
return "";
|
|
4045
5010
|
}
|
|
@@ -4049,12 +5014,17 @@ function sandboxCommandOutput(result) {
|
|
|
4049
5014
|
function assertSandboxCommandSucceeded(result, operation) {
|
|
4050
5015
|
const output = sandboxCommandOutput(result);
|
|
4051
5016
|
if (sandboxCommandStillRunning(result)) {
|
|
4052
|
-
throw new Error(
|
|
4053
|
-
${output
|
|
5017
|
+
throw new Error(
|
|
5018
|
+
`${operation} did not finish before the lifecycle command timeout${output ? `:
|
|
5019
|
+
${output}` : ""}`
|
|
5020
|
+
);
|
|
4054
5021
|
}
|
|
4055
5022
|
const exitCode = sandboxCommandExitCode(result);
|
|
4056
5023
|
if (exitCode !== null && exitCode !== 0) {
|
|
4057
|
-
throw new Error(
|
|
5024
|
+
throw new Error(
|
|
5025
|
+
`${operation} failed with exit code ${exitCode}${output ? `:
|
|
5026
|
+
${output}` : ""}`
|
|
5027
|
+
);
|
|
4058
5028
|
}
|
|
4059
5029
|
if (exitCode === null) {
|
|
4060
5030
|
throw new Error(output || `${operation} did not return a command exit code`);
|
|
@@ -4139,7 +5109,10 @@ function bundledSkillsDir() {
|
|
|
4139
5109
|
return packaged;
|
|
4140
5110
|
}
|
|
4141
5111
|
if (!stagedBundledSkillsDir) {
|
|
4142
|
-
stagedBundledSkillsDir = stageBundledSkills(
|
|
5112
|
+
stagedBundledSkillsDir = stageBundledSkills(
|
|
5113
|
+
packaged,
|
|
5114
|
+
join(process.cwd(), ".opengeni", "bundled_hashicorp_terraform_skills")
|
|
5115
|
+
);
|
|
4143
5116
|
}
|
|
4144
5117
|
return stagedBundledSkillsDir;
|
|
4145
5118
|
}
|
|
@@ -4184,12 +5157,18 @@ function lazySkillSourceWithPackSkills(packSkills) {
|
|
|
4184
5157
|
packNameKeys.add(skill.name.toLowerCase());
|
|
4185
5158
|
packNames.add(skill.name);
|
|
4186
5159
|
children[skill.name] = packSkillDirEntry(skill);
|
|
4187
|
-
packIndex.push({
|
|
5160
|
+
packIndex.push({
|
|
5161
|
+
name: skill.name,
|
|
5162
|
+
description: packSkillDescription(skill),
|
|
5163
|
+
path: skill.name
|
|
5164
|
+
});
|
|
4188
5165
|
}
|
|
4189
5166
|
return {
|
|
4190
5167
|
source: dir({ children }),
|
|
4191
5168
|
getIndex: (manifest, skillsPath) => [
|
|
4192
|
-
...(bundled.getIndex?.(manifest, skillsPath) ?? []).filter(
|
|
5169
|
+
...(bundled.getIndex?.(manifest, skillsPath) ?? []).filter(
|
|
5170
|
+
(entry) => !packNames.has(entry.path ?? entry.name)
|
|
5171
|
+
),
|
|
4193
5172
|
...packIndex
|
|
4194
5173
|
]
|
|
4195
5174
|
};
|
|
@@ -4303,7 +5282,9 @@ function sortJson(value) {
|
|
|
4303
5282
|
return value.map(sortJson);
|
|
4304
5283
|
}
|
|
4305
5284
|
if (value && typeof value === "object") {
|
|
4306
|
-
return Object.fromEntries(
|
|
5285
|
+
return Object.fromEntries(
|
|
5286
|
+
Object.entries(value).sort(([a], [b]) => a.localeCompare(b)).map(([key, nested]) => [key, sortJson(nested)])
|
|
5287
|
+
);
|
|
4307
5288
|
}
|
|
4308
5289
|
return value;
|
|
4309
5290
|
}
|
|
@@ -4332,24 +5313,44 @@ export {
|
|
|
4332
5313
|
DISPLAY_STACK_TIMEOUT_MS,
|
|
4333
5314
|
DisplayStackError,
|
|
4334
5315
|
DisplayStackUnsupportedError,
|
|
5316
|
+
FAULT_FIELD_WHAT_HAPPENED,
|
|
5317
|
+
FAULT_FIELD_WHAT_PRESERVED,
|
|
5318
|
+
FAULT_FIELD_WHAT_TO_TRY,
|
|
5319
|
+
FAULT_FIELD_WHICH_LAYER,
|
|
4335
5320
|
GENESIS_TITLE_DIRECTIVE,
|
|
4336
5321
|
MAX_COMPACTION_THRESHOLD_RATIO,
|
|
4337
5322
|
MIN_COMPACTION_THRESHOLD_RATIO,
|
|
4338
5323
|
MaxTurnsExceededError2 as MaxTurnsExceededError,
|
|
4339
5324
|
MockAgentResponder,
|
|
4340
5325
|
MultiProviderModelProvider,
|
|
5326
|
+
NEVER_SENT_DETAIL_KEY,
|
|
4341
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,
|
|
4342
5334
|
OpenAIChatCompletionsModel2 as OpenAIChatCompletionsModel,
|
|
4343
5335
|
OpenAIResponsesModel2 as OpenAIResponsesModel,
|
|
4344
5336
|
PROVIDER_REGISTRY,
|
|
5337
|
+
REPOSITORY_DISCOVERY_LIMIT,
|
|
4345
5338
|
RecordingError,
|
|
4346
5339
|
RecordingUnavailableError,
|
|
4347
5340
|
RoutingSandboxSession,
|
|
4348
5341
|
RoutingUnsupportedError,
|
|
4349
|
-
SCREENSHOT_OMITTED_PLACEHOLDER,
|
|
4350
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,
|
|
4351
5348
|
SELFHOSTED_RECONNECT_WINDOW_MS,
|
|
4352
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,
|
|
4353
5354
|
STREAM_PORT,
|
|
4354
5355
|
STREAM_TOKEN_DEFAULT_TTL_SECONDS,
|
|
4355
5356
|
SUMMARY_BUFFER_TOKENS,
|
|
@@ -4358,6 +5359,7 @@ export {
|
|
|
4358
5359
|
SandboxComputer,
|
|
4359
5360
|
SandboxConfigError,
|
|
4360
5361
|
SandboxProviderUnavailableError,
|
|
5362
|
+
SandboxResumeStateUnavailableError,
|
|
4361
5363
|
SelfhostedControlError,
|
|
4362
5364
|
SelfhostedSandboxClient,
|
|
4363
5365
|
SelfhostedSession,
|
|
@@ -4369,11 +5371,14 @@ export {
|
|
|
4369
5371
|
TerminalServerError,
|
|
4370
5372
|
TerminalServerUnsupportedError,
|
|
4371
5373
|
USER_MESSAGE_TRUNCATION_MARKER,
|
|
5374
|
+
WorkspaceModelPolicyBlockedError,
|
|
4372
5375
|
agentErrorToControlError,
|
|
4373
5376
|
agentsErrorRunState,
|
|
4374
5377
|
appendGenesisTitleDirective,
|
|
5378
|
+
appendPersistentSessionSettings,
|
|
4375
5379
|
appendSessionInstructions,
|
|
4376
5380
|
appendToolspaceInstructions,
|
|
5381
|
+
appendWorkspaceMemory,
|
|
4377
5382
|
applyMissingManifestEntries,
|
|
4378
5383
|
assertDescriptorRegistryInvariants,
|
|
4379
5384
|
assertProviderRegistryInvariants,
|
|
@@ -4384,7 +5389,6 @@ export {
|
|
|
4384
5389
|
buildAgentCapabilities,
|
|
4385
5390
|
buildCompactionPromptInput,
|
|
4386
5391
|
buildCompactionReplacementHistory,
|
|
4387
|
-
buildDeterministicFallbackCompactionHistory,
|
|
4388
5392
|
buildDisplayStackScript,
|
|
4389
5393
|
buildManifest,
|
|
4390
5394
|
buildModelInstance,
|
|
@@ -4397,8 +5401,8 @@ export {
|
|
|
4397
5401
|
buildTerminalServerScript,
|
|
4398
5402
|
callModelInputFilterForSettings,
|
|
4399
5403
|
clampCompactionThresholdRatio,
|
|
4400
|
-
clientCompactionThresholdTokens,
|
|
4401
5404
|
collectSandboxEnvironment,
|
|
5405
|
+
compactionThresholdTokens,
|
|
4402
5406
|
composeAgentInstructions,
|
|
4403
5407
|
computerUse,
|
|
4404
5408
|
configureOpenAI,
|
|
@@ -4409,14 +5413,18 @@ export {
|
|
|
4409
5413
|
createProductionAgentRuntime,
|
|
4410
5414
|
createSandboxClient,
|
|
4411
5415
|
createSandboxClientForBackend,
|
|
4412
|
-
|
|
5416
|
+
decideCompaction,
|
|
5417
|
+
decideSelfhostedRetry,
|
|
4413
5418
|
decodeModalSnapshotId,
|
|
5419
|
+
defaultSelfhostedRetryClock,
|
|
4414
5420
|
deletePriorPersistedSnapshot,
|
|
4415
5421
|
deleteRecordingArtifacts,
|
|
4416
5422
|
deserializeSandboxSessionStateEnvelope,
|
|
4417
5423
|
desktopCapableBackend,
|
|
4418
|
-
|
|
4419
|
-
|
|
5424
|
+
drainingExhaustedError,
|
|
5425
|
+
drainingMessage,
|
|
5426
|
+
elideSupersededViewImagePairs,
|
|
5427
|
+
elideSupersededViewImagesFilter,
|
|
4420
5428
|
ensureDisplayStack,
|
|
4421
5429
|
ensureModalRegistryImage,
|
|
4422
5430
|
ensureReadableStreamFrom,
|
|
@@ -4424,12 +5432,15 @@ export {
|
|
|
4424
5432
|
establishSandboxSessionFromEnvelope,
|
|
4425
5433
|
estimateItemTokens,
|
|
4426
5434
|
estimateTokens,
|
|
5435
|
+
execDeadlineHint,
|
|
4427
5436
|
exposeStreamPort,
|
|
4428
5437
|
extForCodec,
|
|
4429
5438
|
extractResponseOutputText,
|
|
4430
5439
|
findCompactionNeededError,
|
|
4431
|
-
|
|
5440
|
+
gitProviderTokenRefreshCommand,
|
|
5441
|
+
inspectRecordingArtifact,
|
|
4432
5442
|
isCompactionSummary,
|
|
5443
|
+
isEphemeralInternalContext,
|
|
4433
5444
|
isExecSessionLostBanner,
|
|
4434
5445
|
isProviderSandboxNotFoundError,
|
|
4435
5446
|
isSelfhostedProviderNotFoundError,
|
|
@@ -4437,52 +5448,71 @@ export {
|
|
|
4437
5448
|
isWorkspaceEscapeError,
|
|
4438
5449
|
lazySkillSourceWithPackSkills,
|
|
4439
5450
|
makeActiveBackendResolver,
|
|
5451
|
+
manifestEnvironmentDrift,
|
|
4440
5452
|
materializeSandboxFileDownloads,
|
|
4441
5453
|
maxTurnsExceededRunState,
|
|
4442
5454
|
mcpToolErrorOutput,
|
|
4443
5455
|
mintStreamToken,
|
|
4444
5456
|
modalSandboxAttributionEnvironment,
|
|
4445
5457
|
modalSandboxAttributionTags,
|
|
5458
|
+
modelCallUsageTelemetry,
|
|
5459
|
+
modelResponseUsageFromResponse,
|
|
4446
5460
|
modelResponseUsageFromSdkEvent,
|
|
4447
5461
|
negotiateCapabilities,
|
|
4448
5462
|
negotiateSelfhostedCapabilities,
|
|
4449
5463
|
neutralizeToolSearchItemsInSerializedRunState,
|
|
5464
|
+
nextDurableOpId,
|
|
4450
5465
|
normalizeComputerCallsFilter,
|
|
4451
5466
|
normalizeSdkEvent,
|
|
4452
5467
|
normalizeToolOutputForEvent,
|
|
4453
5468
|
offlineAgentError,
|
|
4454
5469
|
offlineControlResponse,
|
|
5470
|
+
oneShotGenesisTitleInputFilter,
|
|
5471
|
+
opAckSubject,
|
|
5472
|
+
opFrameSubject,
|
|
4455
5473
|
parseExecBannerSessionId,
|
|
4456
5474
|
parseExposedPorts,
|
|
4457
5475
|
parseNumstatZ,
|
|
4458
5476
|
parsePorcelainV2,
|
|
4459
5477
|
parseUnifiedPatch,
|
|
5478
|
+
payloadTooLargeMessage,
|
|
5479
|
+
pinProvidedSessionManifestEnvironment,
|
|
4460
5480
|
prefixedMcpToolName,
|
|
4461
5481
|
prepareAgentTools,
|
|
4462
5482
|
prepareRunInput,
|
|
4463
|
-
readRecordingBytes,
|
|
4464
5483
|
readWorkspaceArchiveFromEnvelopeSessionState,
|
|
4465
5484
|
recordingStorageKey,
|
|
5485
|
+
refreshGitProviderTokenFiles,
|
|
4466
5486
|
renderCompactionPromptInputForChat,
|
|
5487
|
+
renderSelfhostedFault,
|
|
4467
5488
|
repositoryCloneCommand,
|
|
4468
5489
|
repositoryUsesSandboxClone,
|
|
4469
5490
|
resolveTurnModel,
|
|
4470
5491
|
restoredSandboxSessionStateFromEntry,
|
|
5492
|
+
rigInstructions,
|
|
5493
|
+
rigSetupScriptCommand,
|
|
4471
5494
|
runAgentStream,
|
|
4472
5495
|
runAzureCliLoginHook,
|
|
4473
5496
|
runBeforeAgentStartHooks,
|
|
5497
|
+
runOwnedSandboxSetup,
|
|
4474
5498
|
runRepositoryCloneHook,
|
|
5499
|
+
runRigSetupHook,
|
|
4475
5500
|
runToolspaceTokenSeedHook,
|
|
5501
|
+
runWithToolCallCorrelation,
|
|
4476
5502
|
sandboxCommandExitCode,
|
|
4477
5503
|
sandboxCommandOutput,
|
|
4478
5504
|
sandboxCommandStillRunning,
|
|
5505
|
+
sandboxFileDownloadFailureNote,
|
|
4479
5506
|
sandboxFileDownloadsForAgent,
|
|
4480
5507
|
sandboxLifecycleHooksForIds,
|
|
4481
5508
|
sandboxRunAs,
|
|
4482
5509
|
sandboxStateEntryFromRunState,
|
|
4483
5510
|
sanitizeHistoryItemsForModel,
|
|
5511
|
+
sanitizeOpIdToken,
|
|
4484
5512
|
selectBackend,
|
|
5513
|
+
selfhostedFaultClass,
|
|
4485
5514
|
selfhostedLiveness,
|
|
5515
|
+
selfhostedRetryBackoffMs,
|
|
4486
5516
|
serializeApprovals,
|
|
4487
5517
|
serializeEstablishedSandboxEnvelope,
|
|
4488
5518
|
setSelfhostedApplyDiff,
|
|
@@ -4494,6 +5524,7 @@ export {
|
|
|
4494
5524
|
stripReasoningIdentityFromSerializedRunState,
|
|
4495
5525
|
subjectFor,
|
|
4496
5526
|
summarizeForCompaction,
|
|
5527
|
+
swapTargetEstablishability,
|
|
4497
5528
|
sweepModalOrphanSandboxes,
|
|
4498
5529
|
tagModalSandbox,
|
|
4499
5530
|
tearDownDisplayStack,
|
|
@@ -4502,10 +5533,12 @@ export {
|
|
|
4502
5533
|
timeoutAgentError,
|
|
4503
5534
|
timeoutControlResponse,
|
|
4504
5535
|
toolspaceTokenSeedCommand,
|
|
5536
|
+
uploadRecordingArtifact,
|
|
4505
5537
|
verifyStreamToken,
|
|
4506
5538
|
withManifestRefreshOnResume,
|
|
4507
5539
|
withSandboxFileDownloads,
|
|
4508
5540
|
withSandboxLifecycleHooks,
|
|
5541
|
+
withStructuredViewImageFunctionResults,
|
|
4509
5542
|
workspaceEnvironmentInstructions
|
|
4510
5543
|
};
|
|
4511
5544
|
//# sourceMappingURL=index.js.map
|