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