@opengeni/runtime 0.6.1 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +293 -123
- package/dist/index.js +1819 -790
- package/dist/index.js.map +1 -1
- package/dist/sandbox/index.d.ts +1 -1
- package/dist/sandbox/index.js +79 -3
- package/package.json +21 -20
- package/src/codex-tool-search.ts +185 -31
- package/src/context-compaction.ts +78 -243
- package/src/history-sanitizer.ts +172 -20
- package/src/index.ts +2573 -898
- 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,52 @@ 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
|
+
const childState = settings.childNotificationsMode ? `; spawned-worker completion notifications are ${settings.childNotificationsMode}` : "";
|
|
2315
|
+
const persistentTools = settings.childNotificationsMode ? "opengeni__set_session_title or opengeni__set_child_notifications_mode" : "opengeni__set_session_title";
|
|
2316
|
+
return `${composed} Persistent session settings already in effect: the display title is ${settings.titleIsSet ? "set" : "not set"}${childState}. These settings persist across turns, continuations, and interruptions. Do not call ${persistentTools} merely to reassert an unchanged value; call them only when the current value actually needs to change.`;
|
|
2317
|
+
}
|
|
2318
|
+
function appendWorkspaceMemory(composed, workspaceMemory) {
|
|
2319
|
+
const trimmed = workspaceMemory?.trim();
|
|
2320
|
+
return trimmed ? `${composed} ${trimmed}` : composed;
|
|
2321
|
+
}
|
|
2322
|
+
function appendToolspaceInstructions(composed, toolspaceAvailable) {
|
|
2323
|
+
return toolspaceAvailable ? `${composed} ${TOOLSPACE_PROGRAMMATIC_DIRECTIVE}` : composed;
|
|
2159
2324
|
}
|
|
2160
2325
|
function appendGenesisTitleDirective(instructions, genesisTitleHint) {
|
|
2161
2326
|
return genesisTitleHint ? `${instructions} ${GENESIS_TITLE_DIRECTIVE}` : instructions;
|
|
2162
2327
|
}
|
|
2163
2328
|
var agentFileDownloads = /* @__PURE__ */ new WeakMap();
|
|
2164
2329
|
var agentRepositoryCloneHooks = /* @__PURE__ */ new WeakMap();
|
|
2165
|
-
var
|
|
2330
|
+
var agentGitTokenSeeds = /* @__PURE__ */ new WeakMap();
|
|
2166
2331
|
var agentToolspaceTokenSeed = /* @__PURE__ */ new WeakMap();
|
|
2332
|
+
var agentsNeedingGenesisTitleDirective = /* @__PURE__ */ new WeakSet();
|
|
2167
2333
|
var agentActiveSandboxBackend = /* @__PURE__ */ new WeakMap();
|
|
2334
|
+
var agentRigSetup = /* @__PURE__ */ new WeakMap();
|
|
2335
|
+
var agentRigCredentialHooks = /* @__PURE__ */ new WeakMap();
|
|
2168
2336
|
function mcpToolErrorOutput(error) {
|
|
2169
2337
|
const details = error instanceof Error ? error.message : String(error);
|
|
2170
|
-
return {
|
|
2338
|
+
return {
|
|
2339
|
+
isError: true,
|
|
2340
|
+
content: [
|
|
2341
|
+
{
|
|
2342
|
+
type: "text",
|
|
2343
|
+
text: `An error occurred while running the tool. Please try again. Error: ${details}`
|
|
2344
|
+
}
|
|
2345
|
+
]
|
|
2346
|
+
};
|
|
2171
2347
|
}
|
|
2172
2348
|
var mcpToolErrorFunction = ({ error }) => mcpToolErrorOutput(error);
|
|
2173
2349
|
function buildOpenGeniAgent(settings, resources, options = {}) {
|
|
2174
|
-
const compactionMode = options.compactionMode ?? resolveContextCompactionMode(settings);
|
|
2175
2350
|
const hostedWebSearch = options.hostedWebSearch ?? settings.webSearchEnabled;
|
|
2176
2351
|
const encryptedReasoning = options.encryptedReasoning ?? settings.openaiReasoningEncryptedContent;
|
|
2177
|
-
const
|
|
2352
|
+
const providerData = {
|
|
2353
|
+
...encryptedReasoning ? { include: ["reasoning.encrypted_content"] } : {},
|
|
2354
|
+
...options.promptCacheKey ? { prompt_cache_key: options.promptCacheKey } : {}
|
|
2355
|
+
};
|
|
2178
2356
|
const hostedTools = hostedWebSearch ? [webSearchTool()] : [];
|
|
2179
2357
|
const baseConfig = {
|
|
2180
2358
|
name: "OpenGeni Agent",
|
|
@@ -2194,32 +2372,36 @@ function buildOpenGeniAgent(settings, resources, options = {}) {
|
|
|
2194
2372
|
// when a toolspace token was minted for this turn (feature enabled + a
|
|
2195
2373
|
// per-turn seed — the mint gate, which includes selfhosted turns since
|
|
2196
2374
|
// they now receive the token too) — appendToolspaceInstructions,
|
|
2197
|
-
// 3. +
|
|
2375
|
+
// 3. + workspace memory working set, ONLY when the workspace setting is on
|
|
2376
|
+
// and the worker resolved a nonblank block — appendWorkspaceMemory,
|
|
2377
|
+
// 4. + the per-session persona instructions (session-specific, so it
|
|
2198
2378
|
// refines both the workspace persona and the substrate note),
|
|
2199
|
-
//
|
|
2200
|
-
//
|
|
2201
|
-
// is
|
|
2202
|
-
|
|
2379
|
+
// 5. + durable session-setting state (title present + child notification
|
|
2380
|
+
// mode), when supplied by the worker,
|
|
2381
|
+
// The genesis title directive is deliberately NOT part of this persistent
|
|
2382
|
+
// string. runAgentStream injects it into the first model call only.
|
|
2383
|
+
instructions: appendPersistentSessionSettings(
|
|
2203
2384
|
appendSessionInstructions(
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2385
|
+
appendWorkspaceMemory(
|
|
2386
|
+
appendToolspaceInstructions(
|
|
2387
|
+
composeAgentInstructions(
|
|
2388
|
+
options.instructionsTemplate ?? settings.agentInstructionsTemplate,
|
|
2389
|
+
options.workspaceEnvironment,
|
|
2390
|
+
options.rig
|
|
2391
|
+
),
|
|
2392
|
+
settings.toolspaceEnabled && Boolean(options.toolspaceTokenSeed)
|
|
2393
|
+
),
|
|
2394
|
+
options.workspaceMemory
|
|
2207
2395
|
),
|
|
2208
2396
|
options.sessionInstructions
|
|
2209
2397
|
),
|
|
2210
|
-
options.
|
|
2398
|
+
options.persistentSessionSettings
|
|
2211
2399
|
),
|
|
2212
2400
|
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 } : {},
|
|
2401
|
+
reasoning: {
|
|
2402
|
+
effort: options.reasoningEffort ?? settings.openaiReasoningEffort,
|
|
2403
|
+
summary: "detailed"
|
|
2404
|
+
},
|
|
2223
2405
|
// Round-trip the encrypted reasoning payload with every call so chains
|
|
2224
2406
|
// of thought survive without provider-side response storage (which is
|
|
2225
2407
|
// what stripped provider item ids opt us out of — see
|
|
@@ -2228,7 +2410,7 @@ function buildOpenGeniAgent(settings, resources, options = {}) {
|
|
|
2228
2410
|
// function tools, which contribute none. Gated on the resolved
|
|
2229
2411
|
// encryptedReasoning flag: the chat wire API has no encrypted_content
|
|
2230
2412
|
// field, so registry "chat" providers turn it off.
|
|
2231
|
-
...
|
|
2413
|
+
...Object.keys(providerData).length > 0 ? { providerData } : {}
|
|
2232
2414
|
},
|
|
2233
2415
|
// Explicit hosted tools (web_search when enabled). Threaded into BOTH the
|
|
2234
2416
|
// `new Agent(baseConfig)` path (sandboxBackend === "none") and the
|
|
@@ -2246,6 +2428,9 @@ function buildOpenGeniAgent(settings, resources, options = {}) {
|
|
|
2246
2428
|
};
|
|
2247
2429
|
if (settings.sandboxBackend === "none") {
|
|
2248
2430
|
const agent2 = new Agent(baseConfig);
|
|
2431
|
+
if (options.genesisTitleHint) {
|
|
2432
|
+
agentsNeedingGenesisTitleDirective.add(agent2);
|
|
2433
|
+
}
|
|
2249
2434
|
maybeInstallCodexToolSearch(agent2, settings, options);
|
|
2250
2435
|
applyMcpApprovalPolicy(agent2, settings);
|
|
2251
2436
|
return agent2;
|
|
@@ -2253,26 +2438,51 @@ function buildOpenGeniAgent(settings, resources, options = {}) {
|
|
|
2253
2438
|
const runAs = sandboxRunAs(settings);
|
|
2254
2439
|
const agent = new SandboxAgent({
|
|
2255
2440
|
...baseConfig,
|
|
2256
|
-
defaultManifest: buildManifest(
|
|
2441
|
+
defaultManifest: buildManifest(
|
|
2442
|
+
settings,
|
|
2443
|
+
resources,
|
|
2444
|
+
options.sandboxEnvironment,
|
|
2445
|
+
options.fileResourceDownloads
|
|
2446
|
+
),
|
|
2257
2447
|
...runAs ? { runAs } : {},
|
|
2258
2448
|
capabilities: buildAgentCapabilities(settings, options.packSkills ?? [], {
|
|
2259
|
-
compactionMode,
|
|
2260
|
-
contextWindowTokens,
|
|
2261
2449
|
...options.structuredToolTransport !== void 0 ? { structuredToolTransport: options.structuredToolTransport } : {},
|
|
2262
|
-
...options.computerToolMode !== void 0 ? { computerToolMode: options.computerToolMode } : {}
|
|
2450
|
+
...options.computerToolMode !== void 0 ? { computerToolMode: options.computerToolMode } : {},
|
|
2451
|
+
...options.onComputerUseReady ? { onComputerUseReady: options.onComputerUseReady } : {}
|
|
2263
2452
|
})
|
|
2264
2453
|
});
|
|
2265
|
-
|
|
2266
|
-
|
|
2454
|
+
if (options.genesisTitleHint) {
|
|
2455
|
+
agentsNeedingGenesisTitleDirective.add(agent);
|
|
2456
|
+
}
|
|
2457
|
+
agentFileDownloads.set(
|
|
2458
|
+
agent,
|
|
2459
|
+
normalizeSandboxFileDownloads(options.fileResourceDownloads ?? []).filter(
|
|
2460
|
+
(download) => !download.content
|
|
2461
|
+
)
|
|
2462
|
+
);
|
|
2463
|
+
agentRepositoryCloneHooks.set(
|
|
2464
|
+
agent,
|
|
2465
|
+
sandboxRepositoryCloneHooks(settings, resources, options.activeSandboxBackend)
|
|
2466
|
+
);
|
|
2267
2467
|
if (options.activeSandboxBackend) {
|
|
2268
2468
|
agentActiveSandboxBackend.set(agent, options.activeSandboxBackend);
|
|
2269
2469
|
}
|
|
2270
|
-
|
|
2271
|
-
|
|
2470
|
+
const gitTokenSeeds = {
|
|
2471
|
+
...options.gitTokenSeeds ?? {},
|
|
2472
|
+
...options.gitTokenSeed ? { github: options.gitTokenSeed } : {}
|
|
2473
|
+
};
|
|
2474
|
+
if (Object.keys(gitTokenSeeds).length > 0) {
|
|
2475
|
+
agentGitTokenSeeds.set(agent, gitTokenSeeds);
|
|
2272
2476
|
}
|
|
2273
2477
|
if (options.toolspaceTokenSeed) {
|
|
2274
2478
|
agentToolspaceTokenSeed.set(agent, options.toolspaceTokenSeed);
|
|
2275
2479
|
}
|
|
2480
|
+
if (options.rigSetup) {
|
|
2481
|
+
agentRigSetup.set(agent, options.rigSetup);
|
|
2482
|
+
}
|
|
2483
|
+
if (options.rigCredentialHookIds && options.rigCredentialHookIds.length > 0) {
|
|
2484
|
+
agentRigCredentialHooks.set(agent, sandboxLifecycleHooksForIds(options.rigCredentialHookIds));
|
|
2485
|
+
}
|
|
2276
2486
|
maybeInstallCodexToolSearch(agent, settings, options);
|
|
2277
2487
|
applyMcpApprovalPolicy(agent, settings);
|
|
2278
2488
|
return agent;
|
|
@@ -2314,7 +2524,12 @@ function installMcpApprovalPolicy(agent, policies) {
|
|
|
2314
2524
|
}
|
|
2315
2525
|
}
|
|
2316
2526
|
function applyMcpApprovalPolicy(agent, settings) {
|
|
2317
|
-
const policies = settings.mcpServers.filter(
|
|
2527
|
+
const policies = settings.mcpServers.filter(
|
|
2528
|
+
(server) => server.requireApproval === true || Array.isArray(server.requireApproval) && server.requireApproval.length > 0
|
|
2529
|
+
).map((server) => ({
|
|
2530
|
+
prefix: prefixedMcpToolName(server.id, ""),
|
|
2531
|
+
requireApproval: server.requireApproval
|
|
2532
|
+
})).sort((a, b) => b.prefix.length - a.prefix.length);
|
|
2318
2533
|
if (policies.length === 0) {
|
|
2319
2534
|
return;
|
|
2320
2535
|
}
|
|
@@ -2327,24 +2542,67 @@ function neutralizeStructuredToolTransport(capability) {
|
|
|
2327
2542
|
};
|
|
2328
2543
|
capability.bindModel = forceFunctionTransport;
|
|
2329
2544
|
}
|
|
2545
|
+
function withExecOpCorrelation(tools) {
|
|
2546
|
+
return tools.map((capabilityTool) => {
|
|
2547
|
+
if (capabilityTool.type !== "function" || capabilityTool.name !== "exec_command") {
|
|
2548
|
+
return capabilityTool;
|
|
2549
|
+
}
|
|
2550
|
+
const invoke = capabilityTool.invoke;
|
|
2551
|
+
return {
|
|
2552
|
+
...capabilityTool,
|
|
2553
|
+
invoke: (runContext, input, details) => {
|
|
2554
|
+
const callId = details?.toolCall?.callId;
|
|
2555
|
+
if (!callId) {
|
|
2556
|
+
return invoke(runContext, input, details);
|
|
2557
|
+
}
|
|
2558
|
+
return runWithToolCallCorrelation(callId, () => invoke(runContext, input, details));
|
|
2559
|
+
}
|
|
2560
|
+
};
|
|
2561
|
+
});
|
|
2562
|
+
}
|
|
2563
|
+
function withStructuredViewImageFunctionResults(tools) {
|
|
2564
|
+
return tools.map((capabilityTool) => {
|
|
2565
|
+
if (capabilityTool.type !== "function" || capabilityTool.name !== "view_image") {
|
|
2566
|
+
return capabilityTool;
|
|
2567
|
+
}
|
|
2568
|
+
const invoke = capabilityTool.invoke;
|
|
2569
|
+
return {
|
|
2570
|
+
...capabilityTool,
|
|
2571
|
+
invoke: async (runContext, input, details) => {
|
|
2572
|
+
const output = await invoke(runContext, input, details);
|
|
2573
|
+
const dataUrl = typeof output === "string" ? output : output && typeof output === "object" && typeof output.text === "string" ? output.text : null;
|
|
2574
|
+
return dataUrl?.startsWith("data:image/") ? { type: "image", image: { url: dataUrl } } : output;
|
|
2575
|
+
}
|
|
2576
|
+
};
|
|
2577
|
+
});
|
|
2578
|
+
}
|
|
2330
2579
|
function buildAgentCapabilities(settings, packSkills, options = {}) {
|
|
2331
|
-
const
|
|
2332
|
-
|
|
2333
|
-
|
|
2580
|
+
const filesystemCapability = filesystem({
|
|
2581
|
+
...options.structuredToolTransport === false ? { configureTools: withStructuredViewImageFunctionResults } : {}
|
|
2582
|
+
});
|
|
2334
2583
|
if (options.structuredToolTransport === false) {
|
|
2335
2584
|
neutralizeStructuredToolTransport(filesystemCapability);
|
|
2336
2585
|
}
|
|
2337
|
-
const caps = [
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2586
|
+
const caps = [
|
|
2587
|
+
filesystemCapability,
|
|
2588
|
+
shell({ configureTools: withExecOpCorrelation })
|
|
2589
|
+
];
|
|
2341
2590
|
caps.push(skills({ lazyFrom: lazySkillSourceWithPackSkills(packSkills) }));
|
|
2342
2591
|
if (settings.computerUseEnabled && settings.sandboxDesktopEnabled && desktopCapableBackend(settings.sandboxBackend)) {
|
|
2343
2592
|
const explicitMode = options.computerToolMode;
|
|
2344
2593
|
const computerCapability = computerUse({
|
|
2345
2594
|
dimensions: [settings.streamResolutionWidth, settings.streamResolutionHeight],
|
|
2346
2595
|
readOnly: settings.computerUseReadOnly,
|
|
2347
|
-
...
|
|
2596
|
+
...options.onComputerUseReady ? { onReady: options.onComputerUseReady } : {},
|
|
2597
|
+
...explicitMode ? { toolMode: explicitMode } : (
|
|
2598
|
+
// Legacy (no explicit mode): on the codex path the function tools deliver
|
|
2599
|
+
// screenshots as a real image the model can see. The ChatGPT/Codex backend
|
|
2600
|
+
// rejects HOSTED tool types but DOES accept `input_image` content items inside a
|
|
2601
|
+
// `function_call_output` (proven by openai/codex codex-rs, whose view_image tool
|
|
2602
|
+
// ships exactly that shape) — so a structured image tool result is seen, where a
|
|
2603
|
+
// text data-URL would be unreadable.
|
|
2604
|
+
options.structuredToolTransport === false ? { imageFunctionResults: true } : {}
|
|
2605
|
+
)
|
|
2348
2606
|
});
|
|
2349
2607
|
if (!explicitMode && options.structuredToolTransport === false) {
|
|
2350
2608
|
neutralizeStructuredToolTransport(computerCapability);
|
|
@@ -2363,32 +2621,44 @@ async function prepareAgentTools(settings, tools, options = {}) {
|
|
|
2363
2621
|
}, codexConnectorNamespaces };
|
|
2364
2622
|
}
|
|
2365
2623
|
const registry = new Map(settings.mcpServers.map((server) => [server.id, server]));
|
|
2366
|
-
const servers = await Promise.all(
|
|
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
|
-
|
|
2624
|
+
const servers = await Promise.all(
|
|
2625
|
+
tools.map(async (tool2) => {
|
|
2626
|
+
const config = registry.get(tool2.id);
|
|
2627
|
+
if (!config) {
|
|
2628
|
+
throw new Error(`Unknown MCP server id: ${tool2.id}`);
|
|
2629
|
+
}
|
|
2630
|
+
const url = firstPartyMcpServerUrlForRun(settings, config, options.workspaceId) ?? config.url;
|
|
2631
|
+
const baseFetch = isCodexAppsMcpServer(config) ? codexAppsSanitizingFetch(globalThis.fetch, codexConnectorNamespaces) : globalThis.fetch;
|
|
2632
|
+
const fetchImpl = config.connectionRef ? connectionBrokerFetch(baseFetch, config, options) : isFirstPartyMcpServer(settings, config) ? firstPartyAuthFetch(baseFetch, settings, options) : baseFetch;
|
|
2633
|
+
const optional = tool2.optional === true;
|
|
2634
|
+
const bestEffort = isCodexAppsMcpServer(config) || optional || !!config.connectionRef;
|
|
2635
|
+
const server = new PrefixedMcpServer(
|
|
2636
|
+
new MCPServerStreamableHttp({
|
|
2637
|
+
url,
|
|
2638
|
+
name: config.name ?? config.id,
|
|
2639
|
+
cacheToolsList: config.cacheToolsList,
|
|
2640
|
+
// codex_apps returns connector tools with empty `outputSchema: {}` that the
|
|
2641
|
+
// MCP SDK's strict Tool schema rejects (fails the turn during tools/list);
|
|
2642
|
+
// sanitize the response on the wire before validation. The namespace Set
|
|
2643
|
+
// also captures each tool's original connector namespace (P4 Part B.1).
|
|
2644
|
+
...fetchImpl !== globalThis.fetch ? { fetch: fetchImpl } : {},
|
|
2645
|
+
...await mcpServerRequestInit(settings, config),
|
|
2646
|
+
...config.timeoutMs ? {
|
|
2647
|
+
timeout: config.timeoutMs,
|
|
2648
|
+
clientSessionTimeoutSeconds: Math.ceil(config.timeoutMs / 1e3)
|
|
2649
|
+
} : {}
|
|
2650
|
+
}),
|
|
2651
|
+
config.id,
|
|
2652
|
+
config.allowedTools,
|
|
2653
|
+
bestEffort
|
|
2654
|
+
);
|
|
2655
|
+
return {
|
|
2656
|
+
server,
|
|
2657
|
+
bestEffort,
|
|
2658
|
+
optional
|
|
2659
|
+
};
|
|
2660
|
+
})
|
|
2661
|
+
);
|
|
2392
2662
|
const requiredServers = servers.filter((entry) => !entry.bestEffort).map((entry) => entry.server);
|
|
2393
2663
|
const bestEffortServers = servers.filter((entry) => entry.bestEffort).map((entry) => entry.server);
|
|
2394
2664
|
const optionalServerNames = new Set(
|
|
@@ -2432,30 +2702,54 @@ function connectionBrokerFetch(baseFetch, config, options) {
|
|
|
2432
2702
|
}
|
|
2433
2703
|
return async (input, init) => {
|
|
2434
2704
|
const request = await mcpRequestInfo(input, init);
|
|
2435
|
-
const first = await resolveConnectionForRequest(
|
|
2705
|
+
const first = await resolveConnectionForRequest(
|
|
2706
|
+
options,
|
|
2707
|
+
config.id,
|
|
2708
|
+
connectionRef,
|
|
2709
|
+
request.toolName,
|
|
2710
|
+
false
|
|
2711
|
+
);
|
|
2436
2712
|
if (first.status === "auth_needed") {
|
|
2437
2713
|
return await authNeededFetchResponse(options, config.id, request, first, connectionRef);
|
|
2438
2714
|
}
|
|
2439
|
-
const response = await baseFetch(
|
|
2715
|
+
const response = await baseFetch(
|
|
2716
|
+
fetchInputForAttempt(input),
|
|
2717
|
+
withConnectionHeaders(input, init, first.headers)
|
|
2718
|
+
);
|
|
2440
2719
|
if (response.status === 401) {
|
|
2441
|
-
const refreshed = await resolveConnectionForRequest(
|
|
2720
|
+
const refreshed = await resolveConnectionForRequest(
|
|
2721
|
+
options,
|
|
2722
|
+
config.id,
|
|
2723
|
+
connectionRef,
|
|
2724
|
+
request.toolName,
|
|
2725
|
+
true
|
|
2726
|
+
);
|
|
2442
2727
|
if (refreshed.status === "auth_needed") {
|
|
2443
2728
|
return await authNeededFetchResponse(options, config.id, request, refreshed, connectionRef);
|
|
2444
2729
|
}
|
|
2445
|
-
const retry = await baseFetch(
|
|
2730
|
+
const retry = await baseFetch(
|
|
2731
|
+
fetchInputForAttempt(input),
|
|
2732
|
+
withConnectionHeaders(input, init, refreshed.headers)
|
|
2733
|
+
);
|
|
2446
2734
|
if (retry.status === 403) {
|
|
2447
2735
|
const auth = insufficientScopeAuth(retry.headers, connectionRef, refreshed.connectionId);
|
|
2448
2736
|
return auth ? await authNeededFetchResponse(options, config.id, request, auth, connectionRef) : retry;
|
|
2449
2737
|
}
|
|
2450
2738
|
if (retry.status === 401) {
|
|
2451
|
-
return await authNeededFetchResponse(
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2739
|
+
return await authNeededFetchResponse(
|
|
2740
|
+
options,
|
|
2741
|
+
config.id,
|
|
2742
|
+
request,
|
|
2743
|
+
{
|
|
2744
|
+
status: "auth_needed",
|
|
2745
|
+
reason: "expired",
|
|
2746
|
+
providerDomain: connectionRef.providerDomain,
|
|
2747
|
+
connectionId: refreshed.connectionId,
|
|
2748
|
+
...connectionRef.scopes ? { scopes: connectionRef.scopes } : {},
|
|
2749
|
+
...connectionRef.resource ? { resource: connectionRef.resource } : {}
|
|
2750
|
+
},
|
|
2751
|
+
connectionRef
|
|
2752
|
+
);
|
|
2459
2753
|
}
|
|
2460
2754
|
return retry;
|
|
2461
2755
|
}
|
|
@@ -2556,7 +2850,9 @@ async function mcpRequestInfo(input, init) {
|
|
|
2556
2850
|
}
|
|
2557
2851
|
}
|
|
2558
2852
|
function withConnectionHeaders(input, init, authHeaders) {
|
|
2559
|
-
const headers = new Headers(
|
|
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,33 @@ async function firstPartyMcpRequestInit(settings, config, options) {
|
|
|
2649
2953
|
}
|
|
2650
2954
|
};
|
|
2651
2955
|
}
|
|
2956
|
+
async function signFirstPartyDelegatedBearer(settings, options) {
|
|
2957
|
+
if (!settings.delegationSecret || !options.accountId || !options.workspaceId) {
|
|
2958
|
+
return null;
|
|
2959
|
+
}
|
|
2960
|
+
return await signDelegatedAccessToken(settings.delegationSecret, {
|
|
2961
|
+
accountId: options.accountId,
|
|
2962
|
+
workspaceId: options.workspaceId,
|
|
2963
|
+
subjectId: options.subjectId ?? "worker:first-party-mcp",
|
|
2964
|
+
...options.subjectLabel ? { subjectLabel: options.subjectLabel } : {},
|
|
2965
|
+
permissions: options.firstPartyPermissions ?? firstPartyMcpPermissions,
|
|
2966
|
+
...options.sessionId ? { sessionId: options.sessionId } : {},
|
|
2967
|
+
...options.turnId ? { turnId: options.turnId } : {},
|
|
2968
|
+
exp: Math.floor(Date.now() / 1e3) + 60 * 60
|
|
2969
|
+
});
|
|
2970
|
+
}
|
|
2971
|
+
function firstPartyAuthFetch(baseFetch, settings, options) {
|
|
2972
|
+
return async (input, init) => {
|
|
2973
|
+
const bearer = await signFirstPartyDelegatedBearer(settings, options);
|
|
2974
|
+
if (!bearer) {
|
|
2975
|
+
return await baseFetch(input, init);
|
|
2976
|
+
}
|
|
2977
|
+
return await baseFetch(
|
|
2978
|
+
fetchInputForAttempt(input),
|
|
2979
|
+
withConnectionHeaders(input, init, { authorization: `Bearer ${bearer}` })
|
|
2980
|
+
);
|
|
2981
|
+
};
|
|
2982
|
+
}
|
|
2652
2983
|
async function codexAppsMcpRequestInit(settings) {
|
|
2653
2984
|
const ctx = codexRequestStorage.getStore();
|
|
2654
2985
|
if (!ctx) {
|
|
@@ -2688,8 +3019,9 @@ var firstPartyMcpPermissions = [
|
|
|
2688
3019
|
"sessions:read",
|
|
2689
3020
|
"sessions:create",
|
|
2690
3021
|
"sessions:control",
|
|
2691
|
-
"
|
|
2692
|
-
"
|
|
3022
|
+
"variable-sets:use",
|
|
3023
|
+
"variable-sets:manage",
|
|
3024
|
+
"rigs:use",
|
|
2693
3025
|
"github:use"
|
|
2694
3026
|
];
|
|
2695
3027
|
function isCodexAppsMcpServer(config) {
|
|
@@ -2718,7 +3050,11 @@ function firstPartyMcpServerUrlForRun(settings, config, workspaceId) {
|
|
|
2718
3050
|
if (!isFirstPartyMcpServer(settings, config)) {
|
|
2719
3051
|
return null;
|
|
2720
3052
|
}
|
|
2721
|
-
const rawBase = settings.opengeniMcpUrl?.includes("{workspaceId}") ? settings.opengeniMcpUrl.replaceAll("{workspaceId}", workspaceId) : settings.opengeniMcpUrl ? scopedMcpUrlFromConfiguredBase(settings.opengeniMcpUrl, workspaceId) :
|
|
3053
|
+
const rawBase = settings.opengeniMcpUrl?.includes("{workspaceId}") ? settings.opengeniMcpUrl.replaceAll("{workspaceId}", workspaceId) : settings.opengeniMcpUrl ? scopedMcpUrlFromConfiguredBase(settings.opengeniMcpUrl, workspaceId) : (
|
|
3054
|
+
// unset → the shared loopback default (a `{workspaceId}` template owned by
|
|
3055
|
+
// @opengeni/config's firstPartyMcpBaseUrl), scoped to this run's workspace.
|
|
3056
|
+
firstPartyMcpBaseUrl(settings).replaceAll("{workspaceId}", workspaceId)
|
|
3057
|
+
);
|
|
2722
3058
|
const url = new URL(rawBase);
|
|
2723
3059
|
if (config.id === "docs") {
|
|
2724
3060
|
url.pathname = `${url.pathname.replace(/\/+$/, "")}/docs`;
|
|
@@ -2755,18 +3091,24 @@ function prefixedMcpToolName(registryId, toolName) {
|
|
|
2755
3091
|
return sharedPrefixedMcpToolName(registryId, toolName);
|
|
2756
3092
|
}
|
|
2757
3093
|
var PrefixedMcpServer = class {
|
|
2758
|
-
constructor(inner, registryId, allowedTools) {
|
|
3094
|
+
constructor(inner, registryId, allowedTools, bestEffort = false) {
|
|
2759
3095
|
this.inner = inner;
|
|
2760
3096
|
this.name = registryId;
|
|
2761
3097
|
this.prefix = prefixedMcpToolName(registryId, "");
|
|
2762
3098
|
this.cacheToolsList = inner.cacheToolsList;
|
|
2763
3099
|
this.allowedTools = allowedTools ? new Set(allowedTools) : void 0;
|
|
3100
|
+
this.bestEffort = bestEffort;
|
|
2764
3101
|
}
|
|
2765
|
-
inner;
|
|
2766
3102
|
cacheToolsList;
|
|
2767
3103
|
name;
|
|
2768
3104
|
prefix;
|
|
2769
3105
|
allowedTools;
|
|
3106
|
+
// Best-effort servers (optional refs, connectionRef-backed capability MCPs,
|
|
3107
|
+
// codex_apps) must never fail a turn: a tools/list throw degrades to zero
|
|
3108
|
+
// tools instead of propagating. Deduplicate the warn so one degraded server
|
|
3109
|
+
// doesn't spam the log when the SDK re-lists across model turns.
|
|
3110
|
+
bestEffort;
|
|
3111
|
+
loggedListToolsFailure = false;
|
|
2770
3112
|
connect() {
|
|
2771
3113
|
return this.inner.connect();
|
|
2772
3114
|
}
|
|
@@ -2774,7 +3116,24 @@ var PrefixedMcpServer = class {
|
|
|
2774
3116
|
return this.inner.close();
|
|
2775
3117
|
}
|
|
2776
3118
|
async listTools() {
|
|
2777
|
-
|
|
3119
|
+
let tools;
|
|
3120
|
+
try {
|
|
3121
|
+
tools = await this.inner.listTools();
|
|
3122
|
+
} catch (error) {
|
|
3123
|
+
if (!this.bestEffort) {
|
|
3124
|
+
throw error;
|
|
3125
|
+
}
|
|
3126
|
+
if (!this.loggedListToolsFailure) {
|
|
3127
|
+
this.loggedListToolsFailure = true;
|
|
3128
|
+
console.warn(
|
|
3129
|
+
"[mcp] best-effort server tools/list failed; its tools are unavailable this turn",
|
|
3130
|
+
// Safe surface only (class + numeric status), never the raw error
|
|
3131
|
+
// message/response body — a broker 401/403 body can echo request detail.
|
|
3132
|
+
{ serverId: this.name, ...safeMcpErrorFields(error) }
|
|
3133
|
+
);
|
|
3134
|
+
}
|
|
3135
|
+
return [];
|
|
3136
|
+
}
|
|
2778
3137
|
return tools.filter((tool2) => this.isAllowed(tool2.name)).map((tool2) => ({ ...tool2, name: prefixedMcpToolName(this.name, tool2.name) }));
|
|
2779
3138
|
}
|
|
2780
3139
|
async callTool(toolName, args, meta) {
|
|
@@ -2788,6 +3147,17 @@ var PrefixedMcpServer = class {
|
|
|
2788
3147
|
if (isAuthNeededMcpError(error)) {
|
|
2789
3148
|
return { isError: true, content: [{ type: "text", text: MCP_AUTH_NEEDED_MESSAGE }] };
|
|
2790
3149
|
}
|
|
3150
|
+
if (this.bestEffort) {
|
|
3151
|
+
const fields = safeMcpErrorFields(error);
|
|
3152
|
+
console.warn(
|
|
3153
|
+
"[mcp] best-effort server tool call failed; returning an unavailable result for this turn",
|
|
3154
|
+
{ serverId: this.name, toolName: unprefixed, ...fields }
|
|
3155
|
+
);
|
|
3156
|
+
return {
|
|
3157
|
+
isError: true,
|
|
3158
|
+
content: [{ type: "text", text: mcpToolUnavailableMessage(mcpErrorReason(fields)) }]
|
|
3159
|
+
};
|
|
3160
|
+
}
|
|
2791
3161
|
throw error;
|
|
2792
3162
|
}
|
|
2793
3163
|
}
|
|
@@ -2825,74 +3195,45 @@ var PrefixedMcpServer = class {
|
|
|
2825
3195
|
return toolName.slice(this.prefix.length);
|
|
2826
3196
|
}
|
|
2827
3197
|
};
|
|
2828
|
-
function guardAssembledInput(history, trailing, inputBudgetTokens) {
|
|
2829
|
-
if (typeof inputBudgetTokens !== "number" || inputBudgetTokens <= 0) {
|
|
2830
|
-
return [...history, trailing];
|
|
2831
|
-
}
|
|
2832
|
-
const trailingTokens = estimateItemTokens(trailing);
|
|
2833
|
-
const guarded = enforceInputBudget(
|
|
2834
|
-
history,
|
|
2835
|
-
inputBudgetTokens,
|
|
2836
|
-
trailingTokens
|
|
2837
|
-
);
|
|
2838
|
-
if (guarded.trimmed) {
|
|
2839
|
-
console.warn(
|
|
2840
|
-
`read-path budget guard trimmed ${guarded.droppedCount} oldest history item(s) to fit input budget (${inputBudgetTokens} tokens); the over-budget input was NOT sent`
|
|
2841
|
-
);
|
|
2842
|
-
}
|
|
2843
|
-
return [...guarded.items, trailing];
|
|
2844
|
-
}
|
|
2845
3198
|
async function prepareRunInput(agent, input, options = {}) {
|
|
2846
3199
|
if (input.kind === "message") {
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
// input can exceed the model window (pre-turn compaction is best-effort
|
|
2855
|
-
// and can no-op). Trim the oldest history at a clean turn boundary so an
|
|
2856
|
-
// over-budget request is never sent. No-op when no budget is supplied.
|
|
2857
|
-
input: guardAssembledInput(
|
|
2858
|
-
sanitizedHistory2,
|
|
2859
|
-
{
|
|
2860
|
-
type: "message",
|
|
2861
|
-
role: "user",
|
|
2862
|
-
content: input.text
|
|
2863
|
-
},
|
|
2864
|
-
options.inputBudgetTokens
|
|
2865
|
-
),
|
|
2866
|
-
...sandboxSessionState2 ? { sandboxSessionState: sandboxSessionState2 } : {}
|
|
2867
|
-
};
|
|
3200
|
+
const trailingMessages = [];
|
|
3201
|
+
if (input.internalContext?.trim()) {
|
|
3202
|
+
trailingMessages.push({
|
|
3203
|
+
type: "message",
|
|
3204
|
+
role: "system",
|
|
3205
|
+
content: input.internalContext
|
|
3206
|
+
});
|
|
2868
3207
|
}
|
|
2869
|
-
if (
|
|
2870
|
-
|
|
3208
|
+
if (input.text?.trim()) {
|
|
3209
|
+
trailingMessages.push({
|
|
3210
|
+
type: "message",
|
|
3211
|
+
role: "user",
|
|
3212
|
+
content: input.text
|
|
3213
|
+
});
|
|
3214
|
+
}
|
|
3215
|
+
if (trailingMessages.length === 0 && (input.historyItems === void 0 || input.historyItems === null || input.historyItems.length === 0)) {
|
|
3216
|
+
throw new Error("Message input requires a user prompt or internal context");
|
|
2871
3217
|
}
|
|
2872
|
-
const state2 = await RunState.fromString(agent, input.serializedRunState);
|
|
2873
|
-
const sandboxSessionState = await restoredSandboxSessionState(state2, options.sandboxClient);
|
|
2874
3218
|
const sanitizedHistory = sanitizeHistoryItemsForModel(
|
|
2875
|
-
|
|
3219
|
+
input.historyItems ?? []
|
|
2876
3220
|
);
|
|
3221
|
+
const sandboxSessionState = input.sandboxEnvelope ? await restoredSandboxSessionStateFromEntry(input.sandboxEnvelope, options.sandboxClient) : void 0;
|
|
3222
|
+
const assembled = [...sanitizedHistory, ...trailingMessages];
|
|
3223
|
+
if (assembled.length === 0) {
|
|
3224
|
+
throw new Error("Message input requires durable history or internal context");
|
|
3225
|
+
}
|
|
2877
3226
|
return {
|
|
2878
|
-
//
|
|
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
|
|
3227
|
+
// Preserve the SDK's simple first-message form without creating a second
|
|
3228
|
+
// history path: this is merely the zero-history representation.
|
|
3229
|
+
input: sanitizedHistory.length === 0 && !input.internalContext?.trim() && input.text?.trim() ? input.text : assembled,
|
|
3230
|
+
...sandboxSessionState ? { sandboxSessionState } : {}
|
|
2892
3231
|
};
|
|
2893
3232
|
}
|
|
2894
3233
|
if (isClearedRunStateBlob(input.serializedRunState)) {
|
|
2895
|
-
throw new Error(
|
|
3234
|
+
throw new Error(
|
|
3235
|
+
"Cannot resume an approval: the session context was cleared, so the awaiting run state no longer exists."
|
|
3236
|
+
);
|
|
2896
3237
|
}
|
|
2897
3238
|
const state = await RunState.fromString(agent, input.serializedRunState);
|
|
2898
3239
|
const interruptions = state.getInterruptions();
|
|
@@ -2908,6 +3249,26 @@ async function prepareRunInput(agent, input, options = {}) {
|
|
|
2908
3249
|
return { input: state };
|
|
2909
3250
|
}
|
|
2910
3251
|
var GENESIS_TITLE_DIRECTIVE = "This is the first turn of a new session. Before responding to the user, call the opengeni__set_session_title tool with a concise 3-7 word title that summarizes what this session is about, then address the user's request normally.";
|
|
3252
|
+
function oneShotGenesisTitleInputFilter() {
|
|
3253
|
+
let pending = true;
|
|
3254
|
+
return ({ modelData }) => {
|
|
3255
|
+
if (!pending) {
|
|
3256
|
+
return modelData;
|
|
3257
|
+
}
|
|
3258
|
+
pending = false;
|
|
3259
|
+
return {
|
|
3260
|
+
...modelData,
|
|
3261
|
+
instructions: modelData.instructions ? `${modelData.instructions} ${GENESIS_TITLE_DIRECTIVE}` : GENESIS_TITLE_DIRECTIVE
|
|
3262
|
+
};
|
|
3263
|
+
};
|
|
3264
|
+
}
|
|
3265
|
+
function takeGenesisTitleInputFilter(agent) {
|
|
3266
|
+
if (!agentsNeedingGenesisTitleDirective.has(agent)) {
|
|
3267
|
+
return void 0;
|
|
3268
|
+
}
|
|
3269
|
+
agentsNeedingGenesisTitleDirective.delete(agent);
|
|
3270
|
+
return oneShotGenesisTitleInputFilter();
|
|
3271
|
+
}
|
|
2911
3272
|
var TOOLSPACE_PROGRAMMATIC_DIRECTIVE = "Every tool on your MCP surface is also callable programmatically from the sandbox shell, so scripts can invoke tools without a model round trip per call. Run `ogtool list` to see the available tools and their input schemas (from tools/list), then `ogtool call <tool-name> '<json-args>'`; equivalently, POST MCP JSON-RPC to $OPENGENI_TOOLSPACE_URL with the bearer token read from $OPENGENI_TOOLSPACE_TOKEN_FILE. Prefer programmatic calls for loops, polling, and bulk filtering: their results stay in the sandbox and do not consume your context window. Tools that require human approval must still be invoked normally \u2014 called programmatically they return a typed error.";
|
|
2912
3273
|
var stripProviderItemIdsFilter = ({ modelData }) => ({
|
|
2913
3274
|
...modelData,
|
|
@@ -2925,38 +3286,32 @@ var normalizeComputerCallsFilter = ({ modelData }) => ({
|
|
|
2925
3286
|
modelData.input
|
|
2926
3287
|
)
|
|
2927
3288
|
});
|
|
3289
|
+
var elideSupersededViewImagesFilter = ({ modelData }) => ({
|
|
3290
|
+
...modelData,
|
|
3291
|
+
input: elideSupersededViewImagePairs(
|
|
3292
|
+
modelData.input
|
|
3293
|
+
)
|
|
3294
|
+
});
|
|
2928
3295
|
function contextRobustnessFilterForSettings(settings, options = {}) {
|
|
2929
|
-
const
|
|
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) {
|
|
3296
|
+
const thresholdTokens = compactionThresholdTokens(settings);
|
|
3297
|
+
return async ({ modelData }) => {
|
|
3298
|
+
const input = modelData.input;
|
|
3299
|
+
if (options.throwOnCompactionNeeded) {
|
|
2953
3300
|
const reported = options.contextCompactionSignalTokens?.();
|
|
2954
3301
|
const hasReported = typeof reported === "number" && reported > 0;
|
|
2955
3302
|
const signalTokens = hasReported ? reported : estimateTokens(input);
|
|
2956
|
-
if (
|
|
3303
|
+
if (await options.contextCompactionRequested?.()) {
|
|
3304
|
+
throw new CompactionNeededError({
|
|
3305
|
+
signalTokens,
|
|
3306
|
+
thresholdTokens,
|
|
3307
|
+
signalSource: hasReported ? "provider" : "estimate",
|
|
3308
|
+
trigger: "operator"
|
|
3309
|
+
});
|
|
3310
|
+
}
|
|
3311
|
+
if (signalTokens >= thresholdTokens) {
|
|
2957
3312
|
throw new CompactionNeededError({
|
|
2958
3313
|
signalTokens,
|
|
2959
|
-
thresholdTokens
|
|
3314
|
+
thresholdTokens,
|
|
2960
3315
|
signalSource: hasReported ? "provider" : "estimate"
|
|
2961
3316
|
});
|
|
2962
3317
|
}
|
|
@@ -2964,13 +3319,6 @@ function contextRobustnessFilterForSettings(settings, options = {}) {
|
|
|
2964
3319
|
return { ...modelData, input };
|
|
2965
3320
|
};
|
|
2966
3321
|
}
|
|
2967
|
-
function modelCallBudgetTokens(settings) {
|
|
2968
|
-
if (resolveContextCompactionMode(settings) !== "client") {
|
|
2969
|
-
return void 0;
|
|
2970
|
-
}
|
|
2971
|
-
const budget = contextInputBudgetTokens(settings);
|
|
2972
|
-
return budget > 0 ? budget : void 0;
|
|
2973
|
-
}
|
|
2974
3322
|
function composeCallModelInputFilters(filters) {
|
|
2975
3323
|
return async (args) => {
|
|
2976
3324
|
let modelData = args.modelData;
|
|
@@ -2981,7 +3329,10 @@ function composeCallModelInputFilters(filters) {
|
|
|
2981
3329
|
};
|
|
2982
3330
|
}
|
|
2983
3331
|
function callModelInputFilterForSettings(settings, options = {}) {
|
|
2984
|
-
const filters = [
|
|
3332
|
+
const filters = [
|
|
3333
|
+
normalizeComputerCallsFilter,
|
|
3334
|
+
elideSupersededViewImagesFilter
|
|
3335
|
+
];
|
|
2985
3336
|
if (settings.openaiProviderItemIds === "strip") {
|
|
2986
3337
|
filters.push(stripProviderItemIdsFilter);
|
|
2987
3338
|
}
|
|
@@ -2991,54 +3342,63 @@ function callModelInputFilterForSettings(settings, options = {}) {
|
|
|
2991
3342
|
async function runAgentStream(agent, input, settings, overrides = {}) {
|
|
2992
3343
|
const prepared = typeof input === "string" || input instanceof RunState ? { input } : input;
|
|
2993
3344
|
const environment = overrides.sandboxEnvironment ?? collectSandboxEnvironment2(settings);
|
|
3345
|
+
const genesisTitleInputFilter = takeGenesisTitleInputFilter(agent);
|
|
2994
3346
|
if (overrides.ownedSandbox) {
|
|
2995
3347
|
const { client: ownedClient, session, sessionState } = overrides.ownedSandbox;
|
|
2996
3348
|
const setupSession = overrides.ownedSandbox.setupSession ?? session;
|
|
3349
|
+
if (!overrides.ownedSandbox.deferredSetup) {
|
|
3350
|
+
await runOwnedSandboxSetup(agent, session, setupSession, {
|
|
3351
|
+
settings,
|
|
3352
|
+
environment,
|
|
3353
|
+
preparedInput: prepared,
|
|
3354
|
+
...overrides.ownedSandbox.fileDownloadsMaterialized ? { fileDownloadsMaterialized: true } : {},
|
|
3355
|
+
...overrides.onRuntimeEvent ? { onRuntimeEvent: overrides.onRuntimeEvent } : {}
|
|
3356
|
+
});
|
|
3357
|
+
await overrides.onGitCredentialSessionReady?.(setupSession);
|
|
3358
|
+
}
|
|
2997
3359
|
const runAs2 = sandboxRunAs(settings);
|
|
2998
3360
|
const fileDownloads2 = sandboxFileDownloadsForAgent(agent);
|
|
2999
3361
|
const resourceClient2 = fileDownloads2.length > 0 ? withSandboxFileDownloads(ownedClient, fileDownloads2, {
|
|
3000
3362
|
...overrides.onRuntimeEvent ? { onRuntimeEvent: overrides.onRuntimeEvent } : {},
|
|
3001
3363
|
...runAs2 ? { runAs: runAs2 } : {}
|
|
3002
3364
|
}) : ownedClient;
|
|
3003
|
-
const
|
|
3365
|
+
const ownedGitTokenSeeds = gitTokenSeedsForAgent(agent);
|
|
3004
3366
|
const ownedToolspaceTokenSeed = toolspaceTokenSeedForAgent(agent);
|
|
3367
|
+
const ownedRigSetup = rigSetupDescriptorForAgent(agent);
|
|
3005
3368
|
const ownedHooks = [
|
|
3006
|
-
|
|
3369
|
+
// M3: rig setup runs FIRST so any tooling it installs is present for the
|
|
3370
|
+
// credential / repository-clone hooks below. The rig's credential hooks are
|
|
3371
|
+
// unioned into the deployment preparation-profile hooks (deduped by id).
|
|
3372
|
+
...sandboxRigSetupHooksForAgent(agent),
|
|
3373
|
+
...unionCredentialHooks(
|
|
3374
|
+
sandboxLifecycleHooksForIds(sandboxLifecycleHookIds(settings)),
|
|
3375
|
+
rigCredentialHooksForAgent(agent)
|
|
3376
|
+
),
|
|
3007
3377
|
...sandboxToolspaceTokenHooksForAgent(agent),
|
|
3008
|
-
...sandboxRepositoryCloneHooksForAgent(agent)
|
|
3378
|
+
...sandboxRepositoryCloneHooksForAgent(agent),
|
|
3379
|
+
...gitCredentialSessionRegistrationHooks(overrides.onGitCredentialSessionReady)
|
|
3009
3380
|
];
|
|
3010
3381
|
const ownedHookContext = {
|
|
3011
3382
|
environment,
|
|
3012
3383
|
...overrides.onRuntimeEvent ? { onRuntimeEvent: overrides.onRuntimeEvent } : {},
|
|
3013
3384
|
...runAs2 ? { runAs: runAs2 } : {},
|
|
3014
|
-
...
|
|
3015
|
-
...ownedToolspaceTokenSeed ? { toolspaceTokenSeed: ownedToolspaceTokenSeed } : {}
|
|
3385
|
+
...ownedGitTokenSeeds ? { gitTokenSeeds: ownedGitTokenSeeds } : {},
|
|
3386
|
+
...ownedToolspaceTokenSeed ? { toolspaceTokenSeed: ownedToolspaceTokenSeed } : {},
|
|
3387
|
+
...ownedRigSetup ? { rigSetup: ownedRigSetup } : {}
|
|
3016
3388
|
};
|
|
3017
|
-
if (agentActiveSandboxBackend.get(agent) !== "selfhosted") {
|
|
3018
|
-
await runBeforeAgentStartHooks(setupSession, ownedHooks, ownedHookContext);
|
|
3019
|
-
if (fileDownloads2.length > 0) {
|
|
3020
|
-
await materializeSandboxFileDownloads(setupSession, fileDownloads2, {
|
|
3021
|
-
...overrides.onRuntimeEvent ? { onRuntimeEvent: overrides.onRuntimeEvent } : {},
|
|
3022
|
-
...runAs2 ? { runAs: runAs2 } : {}
|
|
3023
|
-
});
|
|
3024
|
-
}
|
|
3025
|
-
} else {
|
|
3026
|
-
const toolspaceHooks = sandboxToolspaceTokenHooksForAgent(agent);
|
|
3027
|
-
if (toolspaceHooks.length > 0) {
|
|
3028
|
-
await runBeforeAgentStartHooks(setupSession, toolspaceHooks, ownedHookContext);
|
|
3029
|
-
}
|
|
3030
|
-
}
|
|
3031
3389
|
const decoratedClient = withSandboxLifecycleHooks(resourceClient2, ownedHooks, ownedHookContext);
|
|
3032
3390
|
const ownedFilter = composeCallModelInputFilters(
|
|
3033
3391
|
[
|
|
3034
3392
|
callModelInputFilterForSettings(settings, {
|
|
3035
|
-
throwOnCompactionNeeded: Boolean(
|
|
3036
|
-
|
|
3393
|
+
throwOnCompactionNeeded: Boolean(
|
|
3394
|
+
overrides.contextCompactionSignalTokens || overrides.contextCompactionRequested
|
|
3395
|
+
),
|
|
3396
|
+
...overrides.contextCompactionSignalTokens ? { contextCompactionSignalTokens: overrides.contextCompactionSignalTokens } : {},
|
|
3397
|
+
...overrides.contextCompactionRequested ? { contextCompactionRequested: overrides.contextCompactionRequested } : {}
|
|
3037
3398
|
}),
|
|
3399
|
+
genesisTitleInputFilter,
|
|
3038
3400
|
overrides.callModelInputFilter
|
|
3039
|
-
].filter(
|
|
3040
|
-
(f) => Boolean(f)
|
|
3041
|
-
)
|
|
3401
|
+
].filter((f) => Boolean(f))
|
|
3042
3402
|
);
|
|
3043
3403
|
const ownedRunOptions = {
|
|
3044
3404
|
stream: true,
|
|
@@ -3053,44 +3413,66 @@ async function runAgentStream(agent, input, settings, overrides = {}) {
|
|
|
3053
3413
|
return await runScopedRunner(settings).run(agent, prepared.input, ownedRunOptions);
|
|
3054
3414
|
}
|
|
3055
3415
|
const rawClient = overrides.sandboxClient ?? createSandboxClient(settings, environment);
|
|
3056
|
-
const refreshedClient = rawClient ? withManifestRefreshOnResume(
|
|
3416
|
+
const refreshedClient = rawClient ? withManifestRefreshOnResume(
|
|
3417
|
+
rawClient,
|
|
3418
|
+
agent.defaultManifest,
|
|
3419
|
+
{
|
|
3420
|
+
...overrides.onRuntimeEvent ? { onRuntimeEvent: overrides.onRuntimeEvent } : {}
|
|
3421
|
+
}
|
|
3422
|
+
) : void 0;
|
|
3057
3423
|
const runAs = sandboxRunAs(settings);
|
|
3058
3424
|
const fileDownloads = sandboxFileDownloadsForAgent(agent);
|
|
3059
3425
|
const resourceClient = refreshedClient && fileDownloads.length > 0 ? withSandboxFileDownloads(refreshedClient, fileDownloads, {
|
|
3060
3426
|
...overrides.onRuntimeEvent ? { onRuntimeEvent: overrides.onRuntimeEvent } : {},
|
|
3061
3427
|
...runAs ? { runAs } : {}
|
|
3062
3428
|
}) : refreshedClient;
|
|
3063
|
-
const
|
|
3429
|
+
const gitTokenSeeds = gitTokenSeedsForAgent(agent);
|
|
3064
3430
|
const toolspaceTokenSeed = toolspaceTokenSeedForAgent(agent);
|
|
3065
|
-
const
|
|
3066
|
-
|
|
3067
|
-
|
|
3068
|
-
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
|
|
3075
|
-
|
|
3076
|
-
|
|
3431
|
+
const legacyRigSetup = rigSetupDescriptorForAgent(agent);
|
|
3432
|
+
const client = resourceClient ? withSandboxLifecycleHooks(
|
|
3433
|
+
resourceClient,
|
|
3434
|
+
[
|
|
3435
|
+
// M3: same rig-setup-first ordering + credential-hook union as the owned
|
|
3436
|
+
// path (this legacy create/resume decoration path is byte-for-byte today
|
|
3437
|
+
// for a rig-less turn — the rig hooks are empty then).
|
|
3438
|
+
...sandboxRigSetupHooksForAgent(agent),
|
|
3439
|
+
...unionCredentialHooks(
|
|
3440
|
+
sandboxLifecycleHooksForIds(sandboxLifecycleHookIds(settings)),
|
|
3441
|
+
rigCredentialHooksForAgent(agent)
|
|
3442
|
+
),
|
|
3443
|
+
...sandboxToolspaceTokenHooksForAgent(agent),
|
|
3444
|
+
...sandboxRepositoryCloneHooksForAgent(agent),
|
|
3445
|
+
...gitCredentialSessionRegistrationHooks(overrides.onGitCredentialSessionReady)
|
|
3446
|
+
],
|
|
3447
|
+
{
|
|
3448
|
+
environment,
|
|
3449
|
+
...overrides.onRuntimeEvent ? { onRuntimeEvent: overrides.onRuntimeEvent } : {},
|
|
3450
|
+
...runAs ? { runAs } : {},
|
|
3451
|
+
...gitTokenSeeds ? { gitTokenSeeds } : {},
|
|
3452
|
+
...toolspaceTokenSeed ? { toolspaceTokenSeed } : {},
|
|
3453
|
+
...legacyRigSetup ? { rigSetup: legacyRigSetup } : {}
|
|
3454
|
+
}
|
|
3455
|
+
) : void 0;
|
|
3456
|
+
const sandboxSessionState = prepared.sandboxSessionState;
|
|
3077
3457
|
const callModelInputFilter = composeCallModelInputFilters(
|
|
3078
3458
|
[
|
|
3079
3459
|
callModelInputFilterForSettings(settings, {
|
|
3080
|
-
throwOnCompactionNeeded: Boolean(
|
|
3081
|
-
|
|
3460
|
+
throwOnCompactionNeeded: Boolean(
|
|
3461
|
+
overrides.contextCompactionSignalTokens || overrides.contextCompactionRequested
|
|
3462
|
+
),
|
|
3463
|
+
...overrides.contextCompactionSignalTokens ? { contextCompactionSignalTokens: overrides.contextCompactionSignalTokens } : {},
|
|
3464
|
+
...overrides.contextCompactionRequested ? { contextCompactionRequested: overrides.contextCompactionRequested } : {}
|
|
3082
3465
|
}),
|
|
3466
|
+
genesisTitleInputFilter,
|
|
3083
3467
|
overrides.callModelInputFilter
|
|
3084
|
-
].filter(
|
|
3085
|
-
(f) => Boolean(f)
|
|
3086
|
-
)
|
|
3468
|
+
].filter((f) => Boolean(f))
|
|
3087
3469
|
);
|
|
3088
3470
|
const runOptions = {
|
|
3089
3471
|
stream: true,
|
|
3090
3472
|
maxTurns: settings.agentMaxModelCallsPerTurn,
|
|
3091
3473
|
// Built-in per-call guard chain: normalize computer calls, optionally strip
|
|
3092
|
-
// provider ids,
|
|
3093
|
-
//
|
|
3474
|
+
// provider ids, trim to the input budget on the client-compaction path, and
|
|
3475
|
+
// raise the proactive compaction signal. This runs for turn-start replay AND
|
|
3094
3476
|
// every mid-turn follow-up.
|
|
3095
3477
|
callModelInputFilter
|
|
3096
3478
|
};
|
|
@@ -3102,6 +3484,22 @@ async function runAgentStream(agent, input, settings, overrides = {}) {
|
|
|
3102
3484
|
}
|
|
3103
3485
|
return await runScopedRunner(settings).run(agent, prepared.input, runOptions);
|
|
3104
3486
|
}
|
|
3487
|
+
function appendSandboxFileDownloadFailureNote(input, failures) {
|
|
3488
|
+
const note = sandboxFileDownloadFailureNote(failures);
|
|
3489
|
+
if (!note) {
|
|
3490
|
+
return;
|
|
3491
|
+
}
|
|
3492
|
+
if (typeof input.input === "string") {
|
|
3493
|
+
input.input = [input.input, "", note].join("\n");
|
|
3494
|
+
return;
|
|
3495
|
+
}
|
|
3496
|
+
if (Array.isArray(input.input)) {
|
|
3497
|
+
input.input = [
|
|
3498
|
+
...input.input,
|
|
3499
|
+
{ type: "message", role: "user", content: note }
|
|
3500
|
+
];
|
|
3501
|
+
}
|
|
3502
|
+
}
|
|
3105
3503
|
function runScopedRunner(settings) {
|
|
3106
3504
|
return new Runner({ modelProvider: new MultiProviderModelProvider(settings) });
|
|
3107
3505
|
}
|
|
@@ -3125,7 +3523,7 @@ function agentsErrorRunState(error) {
|
|
|
3125
3523
|
return null;
|
|
3126
3524
|
}
|
|
3127
3525
|
}
|
|
3128
|
-
function withManifestRefreshOnResume(client, targetManifest) {
|
|
3526
|
+
function withManifestRefreshOnResume(client, targetManifest, context = {}) {
|
|
3129
3527
|
if (!targetManifest || !client.resume) {
|
|
3130
3528
|
return client;
|
|
3131
3529
|
}
|
|
@@ -3135,17 +3533,25 @@ function withManifestRefreshOnResume(client, targetManifest) {
|
|
|
3135
3533
|
...client.create ? { create: async (...args) => await client.create(...args) } : {},
|
|
3136
3534
|
resume: async (state) => {
|
|
3137
3535
|
const session = await client.resume(state);
|
|
3138
|
-
await applyMissingManifestEntries(session, targetManifest);
|
|
3536
|
+
await applyMissingManifestEntries(session, targetManifest, context);
|
|
3139
3537
|
return session;
|
|
3140
3538
|
},
|
|
3141
3539
|
...client.delete ? { delete: async (state) => await client.delete(state) } : {},
|
|
3142
|
-
...client.serializeSessionState ? {
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
...client.
|
|
3540
|
+
...client.serializeSessionState ? {
|
|
3541
|
+
serializeSessionState: async (state, options) => await client.serializeSessionState(state, options)
|
|
3542
|
+
} : {},
|
|
3543
|
+
...client.canPersistOwnedSessionState ? {
|
|
3544
|
+
canPersistOwnedSessionState: async (state) => await client.canPersistOwnedSessionState(state)
|
|
3545
|
+
} : {},
|
|
3546
|
+
...client.canReusePreservedOwnedSession ? {
|
|
3547
|
+
canReusePreservedOwnedSession: async (state) => await client.canReusePreservedOwnedSession(state)
|
|
3548
|
+
} : {},
|
|
3549
|
+
...client.deserializeSessionState ? {
|
|
3550
|
+
deserializeSessionState: async (state) => await client.deserializeSessionState(state)
|
|
3551
|
+
} : {}
|
|
3146
3552
|
};
|
|
3147
3553
|
}
|
|
3148
|
-
async function applyMissingManifestEntries(session, targetManifest) {
|
|
3554
|
+
async function applyMissingManifestEntries(session, targetManifest, context = {}) {
|
|
3149
3555
|
const currentManifestValue = session.state?.manifest;
|
|
3150
3556
|
const currentManifest = currentManifestValue ? ensureManifest(currentManifestValue) : void 0;
|
|
3151
3557
|
const target = ensureManifest(targetManifest);
|
|
@@ -3153,13 +3559,18 @@ async function applyMissingManifestEntries(session, targetManifest) {
|
|
|
3153
3559
|
if (Object.keys(target.entries).length === 0) {
|
|
3154
3560
|
return;
|
|
3155
3561
|
}
|
|
3156
|
-
throw new Error(
|
|
3562
|
+
throw new Error(
|
|
3563
|
+
"Resumed sandbox session cannot apply new manifest entries because current manifest state is unavailable"
|
|
3564
|
+
);
|
|
3157
3565
|
}
|
|
3566
|
+
await reportManifestEnvironmentDrift(currentManifest, target, context);
|
|
3158
3567
|
if (!session.applyManifest && !session.materializeEntry) {
|
|
3159
3568
|
if (Object.keys(target.entries).length === 0) {
|
|
3160
3569
|
return;
|
|
3161
3570
|
}
|
|
3162
|
-
throw new Error(
|
|
3571
|
+
throw new Error(
|
|
3572
|
+
"Resumed sandbox session cannot apply new manifest entries because it does not support applyManifest() or materializeEntry()"
|
|
3573
|
+
);
|
|
3163
3574
|
}
|
|
3164
3575
|
if (Object.keys(target.entries).length === 0) {
|
|
3165
3576
|
return;
|
|
@@ -3180,7 +3591,9 @@ async function applyMissingManifestEntries(session, targetManifest) {
|
|
|
3180
3591
|
}
|
|
3181
3592
|
const environmentChanged = stableJson(currentManifest.environment) !== stableJson(target.environment);
|
|
3182
3593
|
if (environmentChanged && !session.applyManifest) {
|
|
3183
|
-
throw new Error(
|
|
3594
|
+
throw new Error(
|
|
3595
|
+
"Resumed sandbox session cannot refresh manifest environment because it does not support applyManifest()"
|
|
3596
|
+
);
|
|
3184
3597
|
}
|
|
3185
3598
|
if (Object.keys(entries).length === 0 && !environmentChanged) {
|
|
3186
3599
|
return;
|
|
@@ -3209,6 +3622,114 @@ async function applyMissingManifestEntries(session, targetManifest) {
|
|
|
3209
3622
|
...extraPathGrants.length ? { extraPathGrants } : {}
|
|
3210
3623
|
});
|
|
3211
3624
|
}
|
|
3625
|
+
function manifestEnvironmentDrift(current, target) {
|
|
3626
|
+
const currentEnv = current.environment ?? {};
|
|
3627
|
+
const targetEnv = target.environment ?? {};
|
|
3628
|
+
const added = [];
|
|
3629
|
+
const removed = [];
|
|
3630
|
+
const changed = [];
|
|
3631
|
+
for (const key of Object.keys(targetEnv)) {
|
|
3632
|
+
if (!(key in currentEnv)) {
|
|
3633
|
+
added.push(key);
|
|
3634
|
+
} else if (stableJson(currentEnv[key]) !== stableJson(targetEnv[key])) {
|
|
3635
|
+
changed.push(key);
|
|
3636
|
+
}
|
|
3637
|
+
}
|
|
3638
|
+
for (const key of Object.keys(currentEnv)) {
|
|
3639
|
+
if (!(key in targetEnv)) {
|
|
3640
|
+
removed.push(key);
|
|
3641
|
+
}
|
|
3642
|
+
}
|
|
3643
|
+
if (added.length === 0 && removed.length === 0 && changed.length === 0) {
|
|
3644
|
+
return null;
|
|
3645
|
+
}
|
|
3646
|
+
return { added: added.sort(), removed: removed.sort(), changed: changed.sort() };
|
|
3647
|
+
}
|
|
3648
|
+
async function reportManifestEnvironmentDrift(current, target, context) {
|
|
3649
|
+
const drift = manifestEnvironmentDrift(current, target);
|
|
3650
|
+
if (!drift) {
|
|
3651
|
+
return null;
|
|
3652
|
+
}
|
|
3653
|
+
try {
|
|
3654
|
+
await context.onRuntimeEvent?.({ type: "sandbox.env.drift", payload: drift });
|
|
3655
|
+
} catch {
|
|
3656
|
+
}
|
|
3657
|
+
return drift;
|
|
3658
|
+
}
|
|
3659
|
+
async function pinProvidedSessionManifestEnvironment(agent, session, context = {}) {
|
|
3660
|
+
const holder = agent;
|
|
3661
|
+
const currentManifestValue = session.state?.manifest;
|
|
3662
|
+
if (!holder.defaultManifest || !currentManifestValue) {
|
|
3663
|
+
return;
|
|
3664
|
+
}
|
|
3665
|
+
const current = ensureManifest(currentManifestValue);
|
|
3666
|
+
const target = ensureManifest(holder.defaultManifest);
|
|
3667
|
+
const drift = await reportManifestEnvironmentDrift(current, target, context);
|
|
3668
|
+
if (!drift) {
|
|
3669
|
+
return;
|
|
3670
|
+
}
|
|
3671
|
+
holder.defaultManifest = new Manifest({
|
|
3672
|
+
...target.root ? { root: target.root } : {},
|
|
3673
|
+
entries: target.entries,
|
|
3674
|
+
environment: current.environment,
|
|
3675
|
+
...target.extraPathGrants?.length ? { extraPathGrants: target.extraPathGrants } : {}
|
|
3676
|
+
});
|
|
3677
|
+
}
|
|
3678
|
+
async function runOwnedSandboxSetup(agent, session, setupSession, opts) {
|
|
3679
|
+
const { settings, environment } = opts;
|
|
3680
|
+
await pinProvidedSessionManifestEnvironment(agent, session, {
|
|
3681
|
+
...opts.onRuntimeEvent ? { onRuntimeEvent: opts.onRuntimeEvent } : {}
|
|
3682
|
+
});
|
|
3683
|
+
const runAs = sandboxRunAs(settings);
|
|
3684
|
+
const fileDownloads = sandboxFileDownloadsForAgent(agent);
|
|
3685
|
+
const ownedGitTokenSeeds = {
|
|
3686
|
+
...gitTokenSeedsForAgent(agent) ?? {},
|
|
3687
|
+
...opts.gitTokenSeedOverride ? { github: opts.gitTokenSeedOverride } : {},
|
|
3688
|
+
...opts.gitTokenSeedsOverride ?? {}
|
|
3689
|
+
};
|
|
3690
|
+
const ownedToolspaceTokenSeed = toolspaceTokenSeedForAgent(agent);
|
|
3691
|
+
const ownedRigSetup = rigSetupDescriptorForAgent(agent);
|
|
3692
|
+
const ownedHooks = [
|
|
3693
|
+
// M3: rig setup runs FIRST so any tooling it installs is present for the
|
|
3694
|
+
// credential / repository-clone hooks below; the rig's credential hooks are
|
|
3695
|
+
// unioned into the deployment preparation-profile hooks (deduped by id).
|
|
3696
|
+
// This is the LIVE owned-path execution (the provided session skips the
|
|
3697
|
+
// client create/resume decoration), so the rig hooks MUST be here or a
|
|
3698
|
+
// rig-bound turn would start without ever running the frozen setup script.
|
|
3699
|
+
...sandboxRigSetupHooksForAgent(agent),
|
|
3700
|
+
...unionCredentialHooks(
|
|
3701
|
+
sandboxLifecycleHooksForIds(sandboxLifecycleHookIds(settings)),
|
|
3702
|
+
rigCredentialHooksForAgent(agent)
|
|
3703
|
+
),
|
|
3704
|
+
...sandboxToolspaceTokenHooksForAgent(agent),
|
|
3705
|
+
...sandboxRepositoryCloneHooksForAgent(agent)
|
|
3706
|
+
];
|
|
3707
|
+
const ownedHookContext = {
|
|
3708
|
+
environment,
|
|
3709
|
+
...opts.onRuntimeEvent ? { onRuntimeEvent: opts.onRuntimeEvent } : {},
|
|
3710
|
+
...runAs ? { runAs } : {},
|
|
3711
|
+
...Object.keys(ownedGitTokenSeeds).length > 0 ? { gitTokenSeeds: ownedGitTokenSeeds } : {},
|
|
3712
|
+
...ownedToolspaceTokenSeed ? { toolspaceTokenSeed: ownedToolspaceTokenSeed } : {},
|
|
3713
|
+
...ownedRigSetup ? { rigSetup: ownedRigSetup } : {}
|
|
3714
|
+
};
|
|
3715
|
+
if (agentActiveSandboxBackend.get(agent) !== "selfhosted") {
|
|
3716
|
+
await runBeforeAgentStartHooks(setupSession, ownedHooks, ownedHookContext);
|
|
3717
|
+
if (fileDownloads.length > 0 && !opts.fileDownloadsMaterialized) {
|
|
3718
|
+
const materialized = await materializeSandboxFileDownloads(setupSession, fileDownloads, {
|
|
3719
|
+
...opts.onRuntimeEvent ? { onRuntimeEvent: opts.onRuntimeEvent } : {},
|
|
3720
|
+
...runAs ? { runAs } : {}
|
|
3721
|
+
});
|
|
3722
|
+
if (opts.preparedInput) {
|
|
3723
|
+
appendSandboxFileDownloadFailureNote(opts.preparedInput, materialized.failures);
|
|
3724
|
+
}
|
|
3725
|
+
}
|
|
3726
|
+
} else {
|
|
3727
|
+
const toolspaceHooks = sandboxToolspaceTokenHooksForAgent(agent);
|
|
3728
|
+
if (toolspaceHooks.length > 0) {
|
|
3729
|
+
await runBeforeAgentStartHooks(setupSession, toolspaceHooks, ownedHookContext);
|
|
3730
|
+
}
|
|
3731
|
+
}
|
|
3732
|
+
}
|
|
3212
3733
|
function mergePathGrants(current, target) {
|
|
3213
3734
|
const merged = /* @__PURE__ */ new Map();
|
|
3214
3735
|
for (const grant of [...current ?? [], ...target ?? []]) {
|
|
@@ -3232,23 +3753,33 @@ function withSandboxFileDownloads(client, downloads, context = {}) {
|
|
|
3232
3753
|
return {
|
|
3233
3754
|
backendId: client.backendId,
|
|
3234
3755
|
...client.supportsDefaultOptions !== void 0 ? { supportsDefaultOptions: client.supportsDefaultOptions } : {},
|
|
3235
|
-
...client.create ? {
|
|
3236
|
-
|
|
3756
|
+
...client.create ? {
|
|
3757
|
+
create: async (...args) => await wrapSession(await client.create(...args))
|
|
3758
|
+
} : {},
|
|
3759
|
+
...client.resume ? {
|
|
3760
|
+
resume: async (state) => await wrapSession(await client.resume(state))
|
|
3761
|
+
} : {},
|
|
3237
3762
|
...client.delete ? { delete: async (state) => await client.delete(state) } : {},
|
|
3238
|
-
...client.serializeSessionState ? {
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
...client.
|
|
3763
|
+
...client.serializeSessionState ? {
|
|
3764
|
+
serializeSessionState: async (state, options) => await client.serializeSessionState(state, options)
|
|
3765
|
+
} : {},
|
|
3766
|
+
...client.canPersistOwnedSessionState ? {
|
|
3767
|
+
canPersistOwnedSessionState: async (state) => await client.canPersistOwnedSessionState(state)
|
|
3768
|
+
} : {},
|
|
3769
|
+
...client.canReusePreservedOwnedSession ? {
|
|
3770
|
+
canReusePreservedOwnedSession: async (state) => await client.canReusePreservedOwnedSession(state)
|
|
3771
|
+
} : {},
|
|
3772
|
+
...client.deserializeSessionState ? {
|
|
3773
|
+
deserializeSessionState: async (state) => await client.deserializeSessionState(state)
|
|
3774
|
+
} : {}
|
|
3242
3775
|
};
|
|
3243
3776
|
}
|
|
3244
3777
|
async function materializeSandboxFileDownloads(session, downloads, context = {}) {
|
|
3245
3778
|
const normalizedDownloads = normalizeSandboxFileDownloads(downloads);
|
|
3246
3779
|
if (normalizedDownloads.length === 0) {
|
|
3247
|
-
return;
|
|
3248
|
-
}
|
|
3249
|
-
if (!session.exec && !session.execCommand) {
|
|
3250
|
-
throw new Error("Sandbox file download materialization requires command execution support");
|
|
3780
|
+
return { failures: [] };
|
|
3251
3781
|
}
|
|
3782
|
+
const failures = [];
|
|
3252
3783
|
for (const download of normalizedDownloads) {
|
|
3253
3784
|
const targetPath = sandboxDownloadTargetPath(download);
|
|
3254
3785
|
const payload = {
|
|
@@ -3257,9 +3788,30 @@ async function materializeSandboxFileDownloads(session, downloads, context = {})
|
|
|
3257
3788
|
sizeBytes: download.sizeBytes ?? null,
|
|
3258
3789
|
expiresAt: download.expiresAt ? new Date(download.expiresAt).toISOString() : null
|
|
3259
3790
|
};
|
|
3260
|
-
await context.onRuntimeEvent?.({
|
|
3791
|
+
await context.onRuntimeEvent?.({
|
|
3792
|
+
type: "sandbox.operation.started",
|
|
3793
|
+
payload: { name: "file-resource-download", ...payload }
|
|
3794
|
+
});
|
|
3795
|
+
if (!session.exec && !session.execCommand) {
|
|
3796
|
+
const failure = sandboxFileDownloadFailure(
|
|
3797
|
+
download,
|
|
3798
|
+
targetPath,
|
|
3799
|
+
"Sandbox file download materialization requires command execution support"
|
|
3800
|
+
);
|
|
3801
|
+
failures.push(failure);
|
|
3802
|
+
await context.onRuntimeEvent?.({
|
|
3803
|
+
type: "sandbox.operation.failed",
|
|
3804
|
+
payload: {
|
|
3805
|
+
name: "file-resource-download",
|
|
3806
|
+
...payload,
|
|
3807
|
+
error: failure.reason
|
|
3808
|
+
}
|
|
3809
|
+
});
|
|
3810
|
+
continue;
|
|
3811
|
+
}
|
|
3812
|
+
let result;
|
|
3261
3813
|
try {
|
|
3262
|
-
|
|
3814
|
+
result = session.exec ? await session.exec({
|
|
3263
3815
|
cmd: sandboxFileDownloadCommand(download, targetPath),
|
|
3264
3816
|
workdir: "/workspace",
|
|
3265
3817
|
...context.runAs ? { runAs: context.runAs } : {},
|
|
@@ -3273,19 +3825,48 @@ async function materializeSandboxFileDownloads(session, downloads, context = {})
|
|
|
3273
3825
|
maxOutputTokens: 2e4
|
|
3274
3826
|
});
|
|
3275
3827
|
assertSandboxCommandSucceeded(result, `Sandbox file resource download ${download.fileId}`);
|
|
3276
|
-
await context.onRuntimeEvent?.({
|
|
3828
|
+
await context.onRuntimeEvent?.({
|
|
3829
|
+
type: "sandbox.operation.completed",
|
|
3830
|
+
payload: { name: "file-resource-download", ...payload }
|
|
3831
|
+
});
|
|
3277
3832
|
} catch (error) {
|
|
3833
|
+
const failure = sandboxFileDownloadFailure(download, targetPath, error, result);
|
|
3834
|
+
failures.push(failure);
|
|
3278
3835
|
await context.onRuntimeEvent?.({
|
|
3279
3836
|
type: "sandbox.operation.failed",
|
|
3280
3837
|
payload: {
|
|
3281
3838
|
name: "file-resource-download",
|
|
3282
3839
|
...payload,
|
|
3283
|
-
error:
|
|
3840
|
+
error: failure.reason,
|
|
3841
|
+
...failure.exitCode !== void 0 ? { exitCode: failure.exitCode } : {},
|
|
3842
|
+
...failure.output ? { output: failure.output } : {}
|
|
3284
3843
|
}
|
|
3285
3844
|
});
|
|
3286
|
-
throw error;
|
|
3287
3845
|
}
|
|
3288
3846
|
}
|
|
3847
|
+
return { failures };
|
|
3848
|
+
}
|
|
3849
|
+
function sandboxFileDownloadFailure(download, targetPath, error, result) {
|
|
3850
|
+
const exitCode = sandboxCommandExitCode(result);
|
|
3851
|
+
const output = sandboxCommandOutput(result);
|
|
3852
|
+
return {
|
|
3853
|
+
fileId: download.fileId,
|
|
3854
|
+
filename: download.filename,
|
|
3855
|
+
path: targetPath,
|
|
3856
|
+
reason: error instanceof Error ? error.message : String(error),
|
|
3857
|
+
...exitCode !== null ? { exitCode } : {},
|
|
3858
|
+
...output ? { output } : {}
|
|
3859
|
+
};
|
|
3860
|
+
}
|
|
3861
|
+
function sandboxFileDownloadFailureNote(failures) {
|
|
3862
|
+
if (failures.length === 0) {
|
|
3863
|
+
return "";
|
|
3864
|
+
}
|
|
3865
|
+
return [
|
|
3866
|
+
"The following attached files could not be loaded into the sandbox and are unavailable this turn:",
|
|
3867
|
+
...failures.map((failure) => `- ${failure.filename} (${failure.reason})`),
|
|
3868
|
+
"Continue without them or tell the user."
|
|
3869
|
+
].join("\n");
|
|
3289
3870
|
}
|
|
3290
3871
|
function sandboxFileDownloadsForAgent(agent) {
|
|
3291
3872
|
return typeof agent === "object" && agent !== null ? [...agentFileDownloads.get(agent) ?? []] : [];
|
|
@@ -3360,6 +3941,9 @@ function normalizeSdkEvent(event) {
|
|
|
3360
3941
|
out.push({ type: "agent.message.delta", payload: { text: data.delta } });
|
|
3361
3942
|
return out;
|
|
3362
3943
|
}
|
|
3944
|
+
if (data?.type === "response_done") {
|
|
3945
|
+
return out;
|
|
3946
|
+
}
|
|
3363
3947
|
}
|
|
3364
3948
|
if (isOpenAIResponsesRawModelStreamEvent(event)) {
|
|
3365
3949
|
const raw = event.data?.event;
|
|
@@ -3418,7 +4002,10 @@ function normalizeSdkEvent(event) {
|
|
|
3418
4002
|
type: "agent.toolCall.output",
|
|
3419
4003
|
payload: {
|
|
3420
4004
|
id: raw.call_id ?? raw.callId ?? item.id ?? null,
|
|
3421
|
-
output: {
|
|
4005
|
+
output: {
|
|
4006
|
+
type: "text",
|
|
4007
|
+
text: disclosed.length > 0 ? `Disclosed tools: ${disclosed.join(", ")}` : "No matching tools found."
|
|
4008
|
+
}
|
|
3422
4009
|
}
|
|
3423
4010
|
});
|
|
3424
4011
|
} else if (item.type === "message_output_item") {
|
|
@@ -3431,6 +4018,9 @@ function normalizeSdkEvent(event) {
|
|
|
3431
4018
|
}
|
|
3432
4019
|
function modelResponseUsageFromSdkEvent(event) {
|
|
3433
4020
|
const response = modelResponseFromSdkEvent(event);
|
|
4021
|
+
return modelResponseUsageFromResponse(response);
|
|
4022
|
+
}
|
|
4023
|
+
function modelResponseUsageFromResponse(response) {
|
|
3434
4024
|
const usage = usageFromResponse(response);
|
|
3435
4025
|
if (!usage) {
|
|
3436
4026
|
return null;
|
|
@@ -3461,25 +4051,51 @@ function usageFromResponse(response) {
|
|
|
3461
4051
|
if (!raw || typeof raw !== "object") {
|
|
3462
4052
|
return null;
|
|
3463
4053
|
}
|
|
4054
|
+
const record = raw;
|
|
3464
4055
|
const usage = {
|
|
3465
|
-
...numberProp(
|
|
3466
|
-
|
|
3467
|
-
|
|
3468
|
-
|
|
4056
|
+
...numberProp(
|
|
4057
|
+
record,
|
|
4058
|
+
"inputTokens",
|
|
4059
|
+
"inputTokens",
|
|
4060
|
+
"input_tokens",
|
|
4061
|
+
"promptTokens",
|
|
4062
|
+
"prompt_tokens"
|
|
4063
|
+
),
|
|
4064
|
+
...numberProp(
|
|
4065
|
+
record,
|
|
4066
|
+
"outputTokens",
|
|
4067
|
+
"outputTokens",
|
|
4068
|
+
"output_tokens",
|
|
4069
|
+
"completionTokens",
|
|
4070
|
+
"completion_tokens"
|
|
4071
|
+
),
|
|
4072
|
+
...numberProp(record, "totalTokens", "totalTokens", "total_tokens"),
|
|
4073
|
+
...inputTokenDetailsProp(record),
|
|
4074
|
+
...outputTokenDetailsProp(record)
|
|
3469
4075
|
};
|
|
3470
4076
|
return Object.keys(usage).length > 0 ? usage : null;
|
|
3471
4077
|
}
|
|
3472
|
-
function numberProp(raw, outputKey,
|
|
3473
|
-
const value = raw[
|
|
4078
|
+
function numberProp(raw, outputKey, ...keys) {
|
|
4079
|
+
const value = keys.map((key) => raw[key]).find((candidate) => candidate !== void 0);
|
|
3474
4080
|
return typeof value === "number" && Number.isFinite(value) ? { [outputKey]: value } : {};
|
|
3475
4081
|
}
|
|
3476
4082
|
function inputTokenDetailsProp(raw) {
|
|
3477
|
-
const details = raw.inputTokensDetails ?? raw.input_tokens_details;
|
|
4083
|
+
const details = raw.inputTokensDetails ?? raw.input_tokens_details ?? raw.promptTokensDetails ?? raw.prompt_tokens_details;
|
|
3478
4084
|
if (!details || typeof details !== "object") {
|
|
3479
4085
|
return {};
|
|
3480
4086
|
}
|
|
3481
4087
|
return { inputTokensDetails: details };
|
|
3482
4088
|
}
|
|
4089
|
+
function outputTokenDetailsProp(raw) {
|
|
4090
|
+
const details = raw.outputTokensDetails ?? raw.output_tokens_details;
|
|
4091
|
+
const normalized = details ?? raw.completionTokensDetails ?? raw.completion_tokens_details;
|
|
4092
|
+
if (!normalized || typeof normalized !== "object") {
|
|
4093
|
+
return {};
|
|
4094
|
+
}
|
|
4095
|
+
return {
|
|
4096
|
+
outputTokensDetails: normalized
|
|
4097
|
+
};
|
|
4098
|
+
}
|
|
3483
4099
|
function serializeApprovals(interruptions) {
|
|
3484
4100
|
return interruptions.map((item) => {
|
|
3485
4101
|
if (typeof item?.toJSON === "function") {
|
|
@@ -3495,7 +4111,12 @@ function serializeApprovals(interruptions) {
|
|
|
3495
4111
|
}
|
|
3496
4112
|
function buildManifest(settings, resources, environment = collectSandboxEnvironment2(settings), fileResourceDownloads = []) {
|
|
3497
4113
|
const entries = {};
|
|
3498
|
-
const downloadsByFileId = new Map(
|
|
4114
|
+
const downloadsByFileId = new Map(
|
|
4115
|
+
normalizeSandboxFileDownloads(fileResourceDownloads).map((download) => [
|
|
4116
|
+
download.fileId,
|
|
4117
|
+
download
|
|
4118
|
+
])
|
|
4119
|
+
);
|
|
3499
4120
|
for (const resource of resources) {
|
|
3500
4121
|
if (resource.kind === "repository") {
|
|
3501
4122
|
const url = new URL(resource.uri);
|
|
@@ -3528,7 +4149,9 @@ function buildManifest(settings, resources, environment = collectSandboxEnvironm
|
|
|
3528
4149
|
}
|
|
3529
4150
|
function sandboxDownloadDirectory(download, mountPath) {
|
|
3530
4151
|
if (download.mountPath !== mountPath) {
|
|
3531
|
-
throw new Error(
|
|
4152
|
+
throw new Error(
|
|
4153
|
+
`File download materialization path mismatch for ${download.fileId}: expected ${mountPath}, got ${download.mountPath}`
|
|
4154
|
+
);
|
|
3532
4155
|
}
|
|
3533
4156
|
assertSafeSandboxFilename(download.filename, download.fileId);
|
|
3534
4157
|
if (download.content) {
|
|
@@ -3544,7 +4167,9 @@ function objectStorageFileMount(settings, prefix) {
|
|
|
3544
4167
|
const nativeBucketMount = CAPABILITY_DESCRIPTORS2[settings.sandboxBackend].nativeBucketMount;
|
|
3545
4168
|
if (settings.objectStorageBackend === "azure-blob") {
|
|
3546
4169
|
if (nativeBucketMount) {
|
|
3547
|
-
throw new Error(
|
|
4170
|
+
throw new Error(
|
|
4171
|
+
"Modal sandbox Azure Blob file resources require pre-signed download materialization because the current OpenAI Agents SDK Modal client does not support Azure Blob mount entries."
|
|
4172
|
+
);
|
|
3548
4173
|
}
|
|
3549
4174
|
const config2 = azureBlobMountConfig(settings);
|
|
3550
4175
|
return azureBlobMount({
|
|
@@ -3558,7 +4183,9 @@ function objectStorageFileMount(settings, prefix) {
|
|
|
3558
4183
|
});
|
|
3559
4184
|
}
|
|
3560
4185
|
if (settings.objectStorageBackend === "aws-s3" || settings.objectStorageBackend === "gcs") {
|
|
3561
|
-
throw new Error(
|
|
4186
|
+
throw new Error(
|
|
4187
|
+
`${settings.objectStorageBackend} file resources require pre-signed download materialization`
|
|
4188
|
+
);
|
|
3562
4189
|
}
|
|
3563
4190
|
const config = s3CompatibleMountConfig(settings);
|
|
3564
4191
|
return s3Mount({
|
|
@@ -3594,7 +4221,10 @@ function azureBlobMountConfig(settings) {
|
|
|
3594
4221
|
if (!accountName || !accountKey) {
|
|
3595
4222
|
throw new Error("File resources require Azure Blob account name and account key");
|
|
3596
4223
|
}
|
|
3597
|
-
const endpointUrl = azureBlobManifestEndpoint(
|
|
4224
|
+
const endpointUrl = azureBlobManifestEndpoint(
|
|
4225
|
+
settings.objectStorageAzureEndpoint ?? parsed.BlobEndpoint,
|
|
4226
|
+
accountName
|
|
4227
|
+
);
|
|
3598
4228
|
return {
|
|
3599
4229
|
container: settings.objectStorageBucket,
|
|
3600
4230
|
accountName,
|
|
@@ -3611,10 +4241,12 @@ function azureBlobManifestEndpoint(endpoint, accountName) {
|
|
|
3611
4241
|
return normalized === standardAccountEndpoint ? void 0 : normalized;
|
|
3612
4242
|
}
|
|
3613
4243
|
function parseAzureConnectionString(value) {
|
|
3614
|
-
return Object.fromEntries(
|
|
3615
|
-
|
|
3616
|
-
|
|
3617
|
-
|
|
4244
|
+
return Object.fromEntries(
|
|
4245
|
+
value.split(";").map((part) => part.trim()).filter(Boolean).map((part) => {
|
|
4246
|
+
const index = part.indexOf("=");
|
|
4247
|
+
return index === -1 ? [part, ""] : [part.slice(0, index), part.slice(index + 1)];
|
|
4248
|
+
})
|
|
4249
|
+
);
|
|
3618
4250
|
}
|
|
3619
4251
|
function normalizeManifestPath(path) {
|
|
3620
4252
|
const normalized = path.replace(/^\/+|\/+$/g, "");
|
|
@@ -3628,7 +4260,9 @@ function normalizeSandboxFileDownloads(downloads) {
|
|
|
3628
4260
|
const mountPath = normalizeManifestPath(download.mountPath);
|
|
3629
4261
|
assertSafeSandboxFilename(download.filename, download.fileId);
|
|
3630
4262
|
if (!download.content && !download.url?.trim()) {
|
|
3631
|
-
throw new Error(
|
|
4263
|
+
throw new Error(
|
|
4264
|
+
`File download materialization requires content or a URL for ${download.fileId}`
|
|
4265
|
+
);
|
|
3632
4266
|
}
|
|
3633
4267
|
return {
|
|
3634
4268
|
...download,
|
|
@@ -3651,7 +4285,7 @@ function sandboxFileDownloadCommand(download, targetPath) {
|
|
|
3651
4285
|
const targetDir = posixPath.dirname(targetPath);
|
|
3652
4286
|
const tmpPath = `${targetPath}.opengeni-download-$$`;
|
|
3653
4287
|
return [
|
|
3654
|
-
"set -
|
|
4288
|
+
"set -eu",
|
|
3655
4289
|
`mkdir -p -- ${shellQuote(targetDir)}`,
|
|
3656
4290
|
`if [ ! -f ${shellQuote(targetPath)} ]; then`,
|
|
3657
4291
|
` tmp=${shellQuote(tmpPath)}`,
|
|
@@ -3667,25 +4301,6 @@ function sandboxFileDownloadCommand(download, targetPath) {
|
|
|
3667
4301
|
function shellQuote(value) {
|
|
3668
4302
|
return `'${value.replace(/'/g, "'\\''")}'`;
|
|
3669
4303
|
}
|
|
3670
|
-
async function restoredSandboxSessionState(state, client) {
|
|
3671
|
-
if (!client) {
|
|
3672
|
-
return void 0;
|
|
3673
|
-
}
|
|
3674
|
-
const sandboxState = state._sandbox;
|
|
3675
|
-
const entry = sandboxState?.sessionsByAgent?.[sandboxState.currentAgentKey] ?? (sandboxState?.currentAgentKey && sandboxState?.sessionState ? {
|
|
3676
|
-
backendId: sandboxState.backendId,
|
|
3677
|
-
currentAgentKey: sandboxState.currentAgentKey,
|
|
3678
|
-
currentAgentName: sandboxState.currentAgentName,
|
|
3679
|
-
sessionState: sandboxState.sessionState
|
|
3680
|
-
} : void 0);
|
|
3681
|
-
if (!entry) {
|
|
3682
|
-
return void 0;
|
|
3683
|
-
}
|
|
3684
|
-
if (client.backendId !== entry.backendId) {
|
|
3685
|
-
throw new Error("RunState sandbox backend does not match the configured sandbox client");
|
|
3686
|
-
}
|
|
3687
|
-
return await deserializeSandboxSessionStateEnvelope(client, entry.sessionState);
|
|
3688
|
-
}
|
|
3689
4304
|
var builtInSandboxLifecycleHooks = {
|
|
3690
4305
|
"azure-cli-login": {
|
|
3691
4306
|
id: "azure-cli-login",
|
|
@@ -3704,7 +4319,9 @@ function sandboxLifecycleHooksForIds(ids) {
|
|
|
3704
4319
|
});
|
|
3705
4320
|
}
|
|
3706
4321
|
function applicableBeforeAgentStartHooks(hooks, context) {
|
|
3707
|
-
return hooks.filter(
|
|
4322
|
+
return hooks.filter(
|
|
4323
|
+
(hook) => hook.phase === "beforeAgentStart" && (hook.shouldRun?.(context) ?? true)
|
|
4324
|
+
);
|
|
3708
4325
|
}
|
|
3709
4326
|
async function runBeforeAgentStartHooks(session, hooks, context) {
|
|
3710
4327
|
for (const hook of applicableBeforeAgentStartHooks(hooks, context)) {
|
|
@@ -3729,102 +4346,328 @@ function withSandboxLifecycleHooks(client, hooks, context) {
|
|
|
3729
4346
|
const wrapped = {
|
|
3730
4347
|
backendId: client.backendId,
|
|
3731
4348
|
...client.supportsDefaultOptions !== void 0 ? { supportsDefaultOptions: client.supportsDefaultOptions } : {},
|
|
3732
|
-
...client.create ? {
|
|
3733
|
-
|
|
4349
|
+
...client.create ? {
|
|
4350
|
+
create: async (...args) => await wrapSession(await client.create(...args))
|
|
4351
|
+
} : {},
|
|
4352
|
+
...client.resume ? {
|
|
4353
|
+
resume: async (state) => await wrapSession(await client.resume(state))
|
|
4354
|
+
} : {},
|
|
3734
4355
|
...client.delete ? { delete: async (state) => await client.delete(state) } : {},
|
|
3735
|
-
...client.serializeSessionState ? {
|
|
3736
|
-
|
|
3737
|
-
|
|
3738
|
-
...client.
|
|
4356
|
+
...client.serializeSessionState ? {
|
|
4357
|
+
serializeSessionState: async (state, options) => await client.serializeSessionState(state, options)
|
|
4358
|
+
} : {},
|
|
4359
|
+
...client.canPersistOwnedSessionState ? {
|
|
4360
|
+
canPersistOwnedSessionState: async (state) => await client.canPersistOwnedSessionState(state)
|
|
4361
|
+
} : {},
|
|
4362
|
+
...client.canReusePreservedOwnedSession ? {
|
|
4363
|
+
canReusePreservedOwnedSession: async (state) => await client.canReusePreservedOwnedSession(state)
|
|
4364
|
+
} : {},
|
|
4365
|
+
...client.deserializeSessionState ? {
|
|
4366
|
+
deserializeSessionState: async (state) => await client.deserializeSessionState(state)
|
|
4367
|
+
} : {}
|
|
3739
4368
|
};
|
|
3740
4369
|
return wrapped;
|
|
3741
4370
|
}
|
|
3742
4371
|
function sandboxRepositoryCloneHooksForAgent(agent) {
|
|
3743
4372
|
return agentRepositoryCloneHooks.get(agent) ?? [];
|
|
3744
4373
|
}
|
|
3745
|
-
function
|
|
3746
|
-
return
|
|
4374
|
+
function gitTokenSeedsForAgent(agent) {
|
|
4375
|
+
return agentGitTokenSeeds.get(agent);
|
|
3747
4376
|
}
|
|
3748
4377
|
function toolspaceTokenSeedForAgent(agent) {
|
|
3749
4378
|
return agentToolspaceTokenSeed.get(agent);
|
|
3750
4379
|
}
|
|
3751
4380
|
function sandboxToolspaceTokenHooksForAgent(agent) {
|
|
3752
|
-
return toolspaceTokenSeedForAgent(agent) ? [
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
|
|
3756
|
-
|
|
4381
|
+
return toolspaceTokenSeedForAgent(agent) ? [
|
|
4382
|
+
{
|
|
4383
|
+
id: "toolspace-token",
|
|
4384
|
+
phase: "beforeAgentStart",
|
|
4385
|
+
run: runToolspaceTokenSeedHook
|
|
4386
|
+
}
|
|
4387
|
+
] : [];
|
|
4388
|
+
}
|
|
4389
|
+
function sandboxRigSetupHooksForAgent(agent) {
|
|
4390
|
+
return agentRigSetup.get(agent) ? [
|
|
4391
|
+
{
|
|
4392
|
+
id: "rig-setup",
|
|
4393
|
+
phase: "beforeAgentStart",
|
|
4394
|
+
run: runRigSetupHook
|
|
4395
|
+
}
|
|
4396
|
+
] : [];
|
|
4397
|
+
}
|
|
4398
|
+
function rigSetupDescriptorForAgent(agent) {
|
|
4399
|
+
return agentRigSetup.get(agent);
|
|
4400
|
+
}
|
|
4401
|
+
function rigCredentialHooksForAgent(agent) {
|
|
4402
|
+
return agentRigCredentialHooks.get(agent) ?? [];
|
|
4403
|
+
}
|
|
4404
|
+
function unionCredentialHooks(deploymentHooks, rigHooks) {
|
|
4405
|
+
if (rigHooks.length === 0) {
|
|
4406
|
+
return deploymentHooks;
|
|
4407
|
+
}
|
|
4408
|
+
const seen = new Set(deploymentHooks.map((hook) => hook.id));
|
|
4409
|
+
return [...deploymentHooks, ...rigHooks.filter((hook) => !seen.has(hook.id))];
|
|
4410
|
+
}
|
|
4411
|
+
function gitCredentialSessionRegistrationHooks(callback) {
|
|
4412
|
+
return callback ? [
|
|
4413
|
+
{
|
|
4414
|
+
id: "git-credential-renewal-registration",
|
|
4415
|
+
phase: "beforeAgentStart",
|
|
4416
|
+
run: async (session) => {
|
|
4417
|
+
await callback(session);
|
|
4418
|
+
}
|
|
4419
|
+
}
|
|
4420
|
+
] : [];
|
|
3757
4421
|
}
|
|
3758
4422
|
function sandboxRepositoryCloneHooks(settings, resources, activeSandboxBackend = settings.sandboxBackend) {
|
|
3759
|
-
const repositories = resources.filter(
|
|
4423
|
+
const repositories = resources.filter(
|
|
4424
|
+
(resource) => resource.kind === "repository" && repositoryUsesSandboxClone(settings, resource, activeSandboxBackend)
|
|
4425
|
+
);
|
|
3760
4426
|
if (repositories.length === 0) {
|
|
3761
4427
|
return [];
|
|
3762
4428
|
}
|
|
3763
|
-
return [
|
|
3764
|
-
|
|
3765
|
-
|
|
3766
|
-
|
|
3767
|
-
|
|
4429
|
+
return [
|
|
4430
|
+
{
|
|
4431
|
+
id: "repository-clone",
|
|
4432
|
+
phase: "beforeAgentStart",
|
|
4433
|
+
run: async (session, context) => {
|
|
4434
|
+
await runRepositoryCloneHook(session, repositories, context);
|
|
4435
|
+
}
|
|
3768
4436
|
}
|
|
3769
|
-
|
|
4437
|
+
];
|
|
3770
4438
|
}
|
|
3771
4439
|
function repositoryUsesSandboxClone(settings, resource, activeSandboxBackend = settings.sandboxBackend) {
|
|
3772
4440
|
if (activeSandboxBackend === "selfhosted") {
|
|
3773
4441
|
return false;
|
|
3774
4442
|
}
|
|
3775
|
-
return settings.sandboxBackend === "modal" || Boolean(resource.githubInstallationId && resource.githubRepositoryId);
|
|
4443
|
+
return settings.sandboxBackend === "modal" || Boolean(resource.githubInstallationId && resource.githubRepositoryId) || Boolean(resource.provider);
|
|
3776
4444
|
}
|
|
3777
|
-
|
|
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
|
-
|
|
4445
|
+
var GIT_CREDENTIAL_PROVIDERS = [
|
|
4446
|
+
"github",
|
|
4447
|
+
"gitlab",
|
|
4448
|
+
"azure_devops"
|
|
4449
|
+
];
|
|
4450
|
+
function gitProviderSeedEnv(provider) {
|
|
4451
|
+
return `OPENGENI_GIT_${provider.toUpperCase()}_TOKEN_SEED`;
|
|
4452
|
+
}
|
|
4453
|
+
function gitTokenSeedExportPrefix(seeds) {
|
|
4454
|
+
const lines = [];
|
|
4455
|
+
for (const provider of GIT_CREDENTIAL_PROVIDERS) {
|
|
4456
|
+
const token = seeds[provider];
|
|
4457
|
+
if (!token) {
|
|
4458
|
+
continue;
|
|
4459
|
+
}
|
|
4460
|
+
lines.push(`export ${gitProviderSeedEnv(provider)}=${shellQuote(token)}`);
|
|
4461
|
+
if (provider === "github") {
|
|
4462
|
+
lines.push(`export OPENGENI_GIT_TOKEN_SEED=${shellQuote(token)}`);
|
|
4463
|
+
}
|
|
4464
|
+
}
|
|
4465
|
+
return lines.join("\n");
|
|
4466
|
+
}
|
|
4467
|
+
function repositoryCredentialProvider(resource) {
|
|
4468
|
+
return resource.provider ?? "github";
|
|
4469
|
+
}
|
|
4470
|
+
function gitAskpassHostProviderCaseLines(resources) {
|
|
4471
|
+
const hosts = /* @__PURE__ */ new Map();
|
|
4472
|
+
for (const resource of resources) {
|
|
4473
|
+
try {
|
|
4474
|
+
const hostname = new URL(resource.uri).hostname.toLowerCase();
|
|
4475
|
+
if (!hostname) {
|
|
4476
|
+
continue;
|
|
4477
|
+
}
|
|
4478
|
+
hosts.set(hostname, repositoryCredentialProvider(resource));
|
|
4479
|
+
} catch {
|
|
4480
|
+
}
|
|
4481
|
+
}
|
|
4482
|
+
return [...hosts.entries()].sort(([a], [b]) => a.localeCompare(b)).map(
|
|
4483
|
+
([hostname, provider]) => ` ${shellQuote(hostname)}) printf '%s\\n' ${provider}; return 0 ;;`
|
|
4484
|
+
);
|
|
4485
|
+
}
|
|
4486
|
+
function gitCredentialTokenWriterCommandLines() {
|
|
4487
|
+
return [
|
|
4488
|
+
// TOKEN-BROKER (B1/B2): seed run-scoped provider tokens into stable files and
|
|
4489
|
+
// atomically replace each provider file. Token VALUES are supplied only by
|
|
4490
|
+
// the per-exec command prefix
|
|
4491
|
+
// (OPENGENI_GIT_*_TOKEN_SEED), never by the box/agent manifest. Helper paths
|
|
4492
|
+
// are stable manifest values from @opengeni/config.
|
|
4493
|
+
"git_provider_token_file() {",
|
|
4494
|
+
' provider="$1"',
|
|
4495
|
+
' case "$provider" in',
|
|
4496
|
+
` github) printf '%s\\n' "\${OPENGENI_GIT_TOKEN_FILE:-$HOME/.opengeni/git-token}" ;;`,
|
|
4497
|
+
` *) printf '%s\\n' "\${OPENGENI_GIT_CREDENTIALS_DIR:-$HOME/.opengeni/git-credentials}/$provider-token" ;;`,
|
|
4498
|
+
" esac",
|
|
4499
|
+
"}",
|
|
4500
|
+
"write_git_provider_token() {",
|
|
4501
|
+
' provider="$1"',
|
|
4502
|
+
' token="$2"',
|
|
4503
|
+
' [ -n "$token" ] || return 0',
|
|
4504
|
+
' token_file="$(git_provider_token_file "$provider")"',
|
|
4505
|
+
' mkdir -p "$(dirname "$token_file")"',
|
|
4506
|
+
` printf '%s' "$token" > "$token_file.tmp.$$"`,
|
|
4507
|
+
' mv -f "$token_file.tmp.$$" "$token_file"',
|
|
4508
|
+
' if [ "$provider" = github ]; then',
|
|
4509
|
+
' credential_dir="${OPENGENI_GIT_CREDENTIALS_DIR:-$HOME/.opengeni/git-credentials}"',
|
|
4510
|
+
' mkdir -p "$credential_dir"',
|
|
4511
|
+
` printf '%s' "$token" > "$credential_dir/github-token.tmp.$$"`,
|
|
4512
|
+
' mv -f "$credential_dir/github-token.tmp.$$" "$credential_dir/github-token"',
|
|
4513
|
+
" fi",
|
|
4514
|
+
"}",
|
|
4515
|
+
'seed_umask="$(umask)"',
|
|
4516
|
+
"umask 077",
|
|
4517
|
+
'write_git_provider_token github "${OPENGENI_GIT_GITHUB_TOKEN_SEED:-${OPENGENI_GIT_TOKEN_SEED:-}}"',
|
|
4518
|
+
'write_git_provider_token gitlab "${OPENGENI_GIT_GITLAB_TOKEN_SEED:-}"',
|
|
4519
|
+
'write_git_provider_token azure_devops "${OPENGENI_GIT_AZURE_DEVOPS_TOKEN_SEED:-}"',
|
|
4520
|
+
'umask "$seed_umask"'
|
|
4521
|
+
];
|
|
4522
|
+
}
|
|
4523
|
+
function gitCredentialHelperCommandLines(resources = []) {
|
|
4524
|
+
const hostProviderCases = gitAskpassHostProviderCaseLines(resources);
|
|
4525
|
+
return [
|
|
4526
|
+
...gitCredentialTokenWriterCommandLines(),
|
|
4527
|
+
// Provision git/provider-CLI helpers at SETUP (runtime) before any clone
|
|
4528
|
+
// runs. Renewal updates only token files and deliberately leaves these
|
|
4529
|
+
// repository-specific host mappings intact.
|
|
4530
|
+
'git_askpass="${GIT_ASKPASS:-$HOME/.opengeni/askpass}"',
|
|
4531
|
+
'mkdir -p "$(dirname "$git_askpass")"',
|
|
4532
|
+
`cat > "$git_askpass.tmp.$$" <<'ASKPASS_EOF'`,
|
|
3817
4533
|
"#!/usr/bin/env sh",
|
|
4534
|
+
"prompt_host() {",
|
|
4535
|
+
` prompt_lower="$(printf '%s\\n' "$1" | tr '[:upper:]' '[:lower:]')"`,
|
|
4536
|
+
' case "$prompt_lower" in',
|
|
4537
|
+
" *://*) ;;",
|
|
4538
|
+
" *) printf '\\n'; return 0 ;;",
|
|
4539
|
+
" esac",
|
|
4540
|
+
' rest="${prompt_lower#*://}"',
|
|
4541
|
+
' rest="${rest#*@}"',
|
|
4542
|
+
' host="${rest%%/*}"',
|
|
4543
|
+
' host="${host%%:*}"',
|
|
4544
|
+
` host="$(printf '%s\\n' "$host" | tr -d "'")"`,
|
|
4545
|
+
` printf '%s\\n' "$host"`,
|
|
4546
|
+
"}",
|
|
4547
|
+
"provider_for_prompt() {",
|
|
4548
|
+
' host="$(prompt_host "$1")"',
|
|
4549
|
+
' case "$host" in',
|
|
4550
|
+
...hostProviderCases.length > 0 ? hostProviderCases : [' "") : ;;'],
|
|
4551
|
+
" esac",
|
|
4552
|
+
` case "$(printf '%s\\n' "$1" | tr '[:upper:]' '[:lower:]')" in`,
|
|
4553
|
+
" *github.com*|*githubusercontent.com*) printf '%s\\n' github ;;",
|
|
4554
|
+
" *gitlab*) printf '%s\\n' gitlab ;;",
|
|
4555
|
+
" *dev.azure.com*|*.visualstudio.com*) printf '%s\\n' azure_devops ;;",
|
|
4556
|
+
" *) printf '%s\\n' github ;;",
|
|
4557
|
+
" esac",
|
|
4558
|
+
"}",
|
|
4559
|
+
"token_file_for_provider() {",
|
|
4560
|
+
' case "$1" in',
|
|
4561
|
+
` github) printf '%s\\n' "\${OPENGENI_GIT_TOKEN_FILE:-$HOME/.opengeni/git-token}" ;;`,
|
|
4562
|
+
` *) printf '%s\\n' "\${OPENGENI_GIT_CREDENTIALS_DIR:-$HOME/.opengeni/git-credentials}/$1-token" ;;`,
|
|
4563
|
+
" esac",
|
|
4564
|
+
"}",
|
|
4565
|
+
"username_for_provider() {",
|
|
4566
|
+
' case "$1" in',
|
|
4567
|
+
` github) printf '%s\\n' "x-access-token" ;;`,
|
|
4568
|
+
` gitlab) printf '%s\\n' "oauth2" ;;`,
|
|
4569
|
+
` azure_devops) printf '%s\\n' "opengeni" ;;`,
|
|
4570
|
+
" *) printf '\\n' ;;",
|
|
4571
|
+
" esac",
|
|
4572
|
+
"}",
|
|
4573
|
+
'provider="$(provider_for_prompt "$1")"',
|
|
3818
4574
|
'case "$1" in',
|
|
3819
|
-
|
|
3820
|
-
` *Password*) cat "
|
|
4575
|
+
' *Username*) username_for_provider "$provider" ;;',
|
|
4576
|
+
` *Password*) cat "$(token_file_for_provider "$provider")" 2>/dev/null || printf '\\n' ;;`,
|
|
3821
4577
|
" *) printf '\\n' ;;",
|
|
3822
4578
|
"esac",
|
|
3823
4579
|
"ASKPASS_EOF",
|
|
3824
|
-
'
|
|
3825
|
-
'
|
|
3826
|
-
'
|
|
4580
|
+
'chmod 0755 "$git_askpass.tmp.$$"',
|
|
4581
|
+
'mv -f "$git_askpass.tmp.$$" "$git_askpass"',
|
|
4582
|
+
'wrapper_dir="${OPENGENI_GIT_CLI_WRAPPER_DIR:-$HOME/.opengeni/bin}"',
|
|
4583
|
+
'mkdir -p "$wrapper_dir"',
|
|
4584
|
+
"for opengeni_git_cli_tool in gh glab az; do",
|
|
4585
|
+
' wrapper="$wrapper_dir/$opengeni_git_cli_tool"',
|
|
4586
|
+
` cat > "$wrapper.tmp.$$" <<'CLI_WRAPPER_EOF'`,
|
|
4587
|
+
"#!/usr/bin/env sh",
|
|
4588
|
+
"set -eu",
|
|
4589
|
+
'tool="${0##*/}"',
|
|
4590
|
+
'case "$tool" in',
|
|
4591
|
+
" gh) provider=github; token_env=GH_TOKEN ;;",
|
|
4592
|
+
" glab) provider=gitlab; token_env=GITLAB_TOKEN ;;",
|
|
4593
|
+
" az) provider=azure_devops; token_env=AZURE_DEVOPS_EXT_PAT ;;",
|
|
4594
|
+
" *) provider=; token_env= ;;",
|
|
4595
|
+
"esac",
|
|
4596
|
+
'if [ -n "$provider" ]; then',
|
|
4597
|
+
' case "$provider" in',
|
|
4598
|
+
' github) token_file="${OPENGENI_GIT_TOKEN_FILE:-$HOME/.opengeni/git-token}" ;;',
|
|
4599
|
+
' *) token_file="${OPENGENI_GIT_CREDENTIALS_DIR:-$HOME/.opengeni/git-credentials}/$provider-token" ;;',
|
|
4600
|
+
" esac",
|
|
4601
|
+
' if [ -f "$token_file" ]; then',
|
|
4602
|
+
' token="$(cat "$token_file" 2>/dev/null || true)"',
|
|
4603
|
+
' if [ -n "$token" ]; then',
|
|
4604
|
+
' case "$token_env" in',
|
|
4605
|
+
' GH_TOKEN) export GH_TOKEN="$token" ;;',
|
|
4606
|
+
' GITLAB_TOKEN) export GITLAB_TOKEN="$token" ;;',
|
|
4607
|
+
' AZURE_DEVOPS_EXT_PAT) export AZURE_DEVOPS_EXT_PAT="$token" ;;',
|
|
4608
|
+
" esac",
|
|
4609
|
+
" fi",
|
|
4610
|
+
" fi",
|
|
3827
4611
|
"fi",
|
|
4612
|
+
`self_real="$(readlink -f "$0" 2>/dev/null || printf '%s\\n' "$0")"`,
|
|
4613
|
+
'old_ifs="$IFS"',
|
|
4614
|
+
"IFS=:",
|
|
4615
|
+
"for dir in $PATH; do",
|
|
4616
|
+
' [ -n "$dir" ] || dir=.',
|
|
4617
|
+
' candidate="$dir/$tool"',
|
|
4618
|
+
' [ -x "$candidate" ] || continue',
|
|
4619
|
+
` candidate_real="$(readlink -f "$candidate" 2>/dev/null || printf '%s\\n' "$candidate")"`,
|
|
4620
|
+
' [ "$candidate_real" = "$self_real" ] && continue',
|
|
4621
|
+
' IFS="$old_ifs"',
|
|
4622
|
+
' exec "$candidate" "$@"',
|
|
4623
|
+
"done",
|
|
4624
|
+
'IFS="$old_ifs"',
|
|
4625
|
+
`printf '%s\\n' "$tool: real command not found on PATH" >&2`,
|
|
4626
|
+
"exit 127",
|
|
4627
|
+
"CLI_WRAPPER_EOF",
|
|
4628
|
+
' chmod 0755 "$wrapper.tmp.$$"',
|
|
4629
|
+
' mv -f "$wrapper.tmp.$$" "$wrapper"',
|
|
4630
|
+
"done"
|
|
4631
|
+
];
|
|
4632
|
+
}
|
|
4633
|
+
function gitProviderTokenRefreshCommand(seeds) {
|
|
4634
|
+
const seedPrefix = gitTokenSeedExportPrefix(seeds);
|
|
4635
|
+
if (!seedPrefix) {
|
|
4636
|
+
return "";
|
|
4637
|
+
}
|
|
4638
|
+
return [
|
|
4639
|
+
seedPrefix,
|
|
4640
|
+
"set -eu",
|
|
4641
|
+
'export HOME="${HOME:-/workspace}"',
|
|
4642
|
+
...gitCredentialTokenWriterCommandLines()
|
|
4643
|
+
].join("\n");
|
|
4644
|
+
}
|
|
4645
|
+
async function refreshGitProviderTokenFiles(session, seeds, options = {}) {
|
|
4646
|
+
const command = gitProviderTokenRefreshCommand(seeds);
|
|
4647
|
+
if (!command) {
|
|
4648
|
+
return;
|
|
4649
|
+
}
|
|
4650
|
+
const args = {
|
|
4651
|
+
cmd: command,
|
|
4652
|
+
workdir: "/workspace",
|
|
4653
|
+
...options.runAs ? { runAs: options.runAs } : {},
|
|
4654
|
+
yieldTimeMs: SANDBOX_LIFECYCLE_COMMAND_TIMEOUT_MS,
|
|
4655
|
+
maxOutputTokens: 4e3
|
|
4656
|
+
};
|
|
4657
|
+
if (session.exec) {
|
|
4658
|
+
assertSandboxCommandSucceeded(await session.exec(args), "Git credential refresh");
|
|
4659
|
+
} else if (session.execCommand) {
|
|
4660
|
+
assertSandboxCommandSucceeded(await session.execCommand(args), "Git credential refresh");
|
|
4661
|
+
} else {
|
|
4662
|
+
throw new Error("Sandbox session does not support command execution");
|
|
4663
|
+
}
|
|
4664
|
+
}
|
|
4665
|
+
function repositoryCloneCommand(resources) {
|
|
4666
|
+
const commands = [
|
|
4667
|
+
"set -eu",
|
|
4668
|
+
'export HOME="${HOME:-/workspace}"',
|
|
4669
|
+
'export GIT_TERMINAL_PROMPT="${GIT_TERMINAL_PROMPT:-0}"',
|
|
4670
|
+
...gitCredentialHelperCommandLines(resources),
|
|
3828
4671
|
"ensure_git() {",
|
|
3829
4672
|
" if command -v git >/dev/null 2>&1; then",
|
|
3830
4673
|
" return 0",
|
|
@@ -3915,13 +4758,15 @@ function repositoryCloneCommand(resources) {
|
|
|
3915
4758
|
const url = new URL(resource.uri);
|
|
3916
4759
|
const repo = url.pathname.replace(/^\/+|\/+$/g, "").replace(/\.git$/, "");
|
|
3917
4760
|
const mountPath = normalizeManifestPath(resource.mountPath ?? `repos/${repo}`);
|
|
3918
|
-
commands.push(
|
|
3919
|
-
|
|
3920
|
-
|
|
3921
|
-
|
|
3922
|
-
|
|
3923
|
-
|
|
3924
|
-
|
|
4761
|
+
commands.push(
|
|
4762
|
+
[
|
|
4763
|
+
"clone_repository",
|
|
4764
|
+
shellQuote(posixPath.join("/workspace", mountPath)),
|
|
4765
|
+
shellQuote(resource.uri),
|
|
4766
|
+
shellQuote(resource.ref),
|
|
4767
|
+
shellQuote(resource.subpath ? normalizeManifestPath(resource.subpath) : "")
|
|
4768
|
+
].join(" ")
|
|
4769
|
+
);
|
|
3925
4770
|
}
|
|
3926
4771
|
return commands.join("\n");
|
|
3927
4772
|
}
|
|
@@ -3968,11 +4813,124 @@ ${toolspaceTokenSeedCommand()}`;
|
|
|
3968
4813
|
throw new Error("Sandbox session does not support command execution");
|
|
3969
4814
|
}
|
|
3970
4815
|
}
|
|
4816
|
+
var RIG_SETUP_OUTPUT_TAIL_LIMIT = 4e3;
|
|
4817
|
+
var RIG_SETUP_SKIPPED_SENTINEL = "__OPENGENI_RIG_SETUP_SKIPPED__";
|
|
4818
|
+
function rigSetupScriptCommand(script, versionId, timeoutMs = 6e5, markerRoot = "/var/opengeni") {
|
|
4819
|
+
const timeoutSecs = Math.max(1, Math.ceil(timeoutMs / 1e3));
|
|
4820
|
+
const lockWaitSecs = timeoutSecs + 6;
|
|
4821
|
+
const marker = `${markerRoot.replace(/\/+$/, "")}/rig-setup-${versionId}.done`;
|
|
4822
|
+
return [
|
|
4823
|
+
"set -u",
|
|
4824
|
+
`mkdir -p ${shellQuote(markerRoot)}`,
|
|
4825
|
+
`__OG_RIG_MARKER=${shellQuote(marker)}`,
|
|
4826
|
+
'__OG_RIG_LOCK="$__OG_RIG_MARKER.lock"',
|
|
4827
|
+
`__OG_RIG_TIMEOUT_SECS=${timeoutSecs}`,
|
|
4828
|
+
`__OG_RIG_LOCK_WAIT_SECS=${lockWaitSecs}`,
|
|
4829
|
+
`if [ -f "$__OG_RIG_MARKER" ]; then printf '%s\\n' ${shellQuote(RIG_SETUP_SKIPPED_SENTINEL)}; exit 0; fi`,
|
|
4830
|
+
"while :; do",
|
|
4831
|
+
' if mkdir "$__OG_RIG_LOCK" 2>/dev/null; then',
|
|
4832
|
+
` trap 'rm -rf "$__OG_RIG_LOCK"' EXIT`,
|
|
4833
|
+
` if [ -f "$__OG_RIG_MARKER" ]; then printf '%s\\n' ${shellQuote(RIG_SETUP_SKIPPED_SENTINEL)}; exit 0; fi`,
|
|
4834
|
+
'__OG_RIG_SCRIPT="$(mktemp)"',
|
|
4835
|
+
`cat > "$__OG_RIG_SCRIPT" <<'__OPENGENI_RIG_SETUP_SCRIPT_EOF__'`,
|
|
4836
|
+
script,
|
|
4837
|
+
"__OPENGENI_RIG_SETUP_SCRIPT_EOF__",
|
|
4838
|
+
' timeout -k 5s "${__OG_RIG_TIMEOUT_SECS}s" bash "$__OG_RIG_SCRIPT"',
|
|
4839
|
+
"__OG_RIG_RC=$?",
|
|
4840
|
+
' rm -f "$__OG_RIG_SCRIPT"',
|
|
4841
|
+
' if [ "$__OG_RIG_RC" -eq 0 ]; then touch "$__OG_RIG_MARKER"; fi',
|
|
4842
|
+
' exit "$__OG_RIG_RC"',
|
|
4843
|
+
" fi",
|
|
4844
|
+
" __OG_RIG_WAITED=0",
|
|
4845
|
+
' while [ "$__OG_RIG_WAITED" -lt "$__OG_RIG_LOCK_WAIT_SECS" ]; do',
|
|
4846
|
+
` if [ -f "$__OG_RIG_MARKER" ]; then printf '%s\\n' ${shellQuote(RIG_SETUP_SKIPPED_SENTINEL)}; exit 0; fi`,
|
|
4847
|
+
' if [ ! -d "$__OG_RIG_LOCK" ]; then break; fi',
|
|
4848
|
+
" sleep 1",
|
|
4849
|
+
" __OG_RIG_WAITED=$((__OG_RIG_WAITED + 1))",
|
|
4850
|
+
" done",
|
|
4851
|
+
` if [ -f "$__OG_RIG_MARKER" ]; then printf '%s\\n' ${shellQuote(RIG_SETUP_SKIPPED_SENTINEL)}; exit 0; fi`,
|
|
4852
|
+
' if [ ! -d "$__OG_RIG_LOCK" ]; then continue; fi',
|
|
4853
|
+
' rmdir "$__OG_RIG_LOCK" 2>/dev/null || true',
|
|
4854
|
+
"done"
|
|
4855
|
+
].join("\n");
|
|
4856
|
+
}
|
|
4857
|
+
async function runRigSetupHook(session, context = { environment: {} }) {
|
|
4858
|
+
const rigSetup = context.rigSetup;
|
|
4859
|
+
if (!rigSetup) {
|
|
4860
|
+
return;
|
|
4861
|
+
}
|
|
4862
|
+
const payload = {
|
|
4863
|
+
rigId: rigSetup.rigId,
|
|
4864
|
+
versionId: rigSetup.versionId,
|
|
4865
|
+
rigName: rigSetup.rigName
|
|
4866
|
+
};
|
|
4867
|
+
await context.onRuntimeEvent?.({ type: "rig.setup.started", payload });
|
|
4868
|
+
const command = rigSetupScriptCommand(rigSetup.script, rigSetup.versionId, rigSetup.timeoutMs);
|
|
4869
|
+
const execArgs = {
|
|
4870
|
+
cmd: command,
|
|
4871
|
+
workdir: "/workspace",
|
|
4872
|
+
...context.runAs ? { runAs: context.runAs } : {},
|
|
4873
|
+
// The in-box coreutils timeout is the hard deadline; the SDK yield waits a
|
|
4874
|
+
// little longer so it observes timeout's non-zero exit instead of a live
|
|
4875
|
+
// still-running process.
|
|
4876
|
+
yieldTimeMs: rigSetup.timeoutMs + 7e3,
|
|
4877
|
+
maxOutputTokens: 2e4
|
|
4878
|
+
};
|
|
4879
|
+
let result;
|
|
4880
|
+
try {
|
|
4881
|
+
if (session.exec) {
|
|
4882
|
+
result = await session.exec(execArgs);
|
|
4883
|
+
} else if (session.execCommand) {
|
|
4884
|
+
result = await session.execCommand(execArgs);
|
|
4885
|
+
} else {
|
|
4886
|
+
throw new Error("Sandbox session does not support command execution");
|
|
4887
|
+
}
|
|
4888
|
+
} catch (error) {
|
|
4889
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
4890
|
+
await context.onRuntimeEvent?.({
|
|
4891
|
+
type: "rig.setup.failed",
|
|
4892
|
+
payload: { ...payload, error: message.slice(-RIG_SETUP_OUTPUT_TAIL_LIMIT) }
|
|
4893
|
+
});
|
|
4894
|
+
throw new Error(
|
|
4895
|
+
`Rig setup failed for rig "${rigSetup.rigName}" (version ${rigSetup.versionId}): ${message}`,
|
|
4896
|
+
{ cause: error }
|
|
4897
|
+
);
|
|
4898
|
+
}
|
|
4899
|
+
const output = sandboxCommandOutput(result);
|
|
4900
|
+
if (output.includes(RIG_SETUP_SKIPPED_SENTINEL)) {
|
|
4901
|
+
await context.onRuntimeEvent?.({ type: "rig.setup.skipped", payload });
|
|
4902
|
+
return;
|
|
4903
|
+
}
|
|
4904
|
+
const stillRunning = sandboxCommandStillRunning(result);
|
|
4905
|
+
const exitCode = sandboxCommandExitCode(result);
|
|
4906
|
+
if (stillRunning || exitCode === null || exitCode !== 0) {
|
|
4907
|
+
const tail = output.length > RIG_SETUP_OUTPUT_TAIL_LIMIT ? output.slice(-RIG_SETUP_OUTPUT_TAIL_LIMIT) : output;
|
|
4908
|
+
const reason = stillRunning ? `did not finish within the rig setup timeout (${rigSetup.timeoutMs}ms)` : exitCode === null ? "did not report an exit code" : `exited with code ${exitCode}`;
|
|
4909
|
+
const failure = new Error(
|
|
4910
|
+
`Rig setup failed for rig "${rigSetup.rigName}" (version ${rigSetup.versionId}): the setup script ${reason}${tail ? `:
|
|
4911
|
+
${tail}` : ""}`
|
|
4912
|
+
);
|
|
4913
|
+
await context.onRuntimeEvent?.({
|
|
4914
|
+
type: "rig.setup.failed",
|
|
4915
|
+
payload: { ...payload, error: failure.message.slice(-RIG_SETUP_OUTPUT_TAIL_LIMIT) }
|
|
4916
|
+
});
|
|
4917
|
+
throw failure;
|
|
4918
|
+
}
|
|
4919
|
+
await context.onRuntimeEvent?.({
|
|
4920
|
+
type: "rig.setup.completed",
|
|
4921
|
+
payload: { ...payload, skipped: false }
|
|
4922
|
+
});
|
|
4923
|
+
}
|
|
3971
4924
|
async function runRepositoryCloneHook(session, resources, context = { environment: {} }) {
|
|
3972
4925
|
const payload = { name: "repository-clone", repositoryCount: resources.length };
|
|
3973
4926
|
await context.onRuntimeEvent?.({ type: "sandbox.operation.started", payload });
|
|
3974
4927
|
try {
|
|
3975
|
-
const
|
|
4928
|
+
const gitTokenSeeds = {
|
|
4929
|
+
...context.gitTokenSeeds ?? {},
|
|
4930
|
+
...context.gitTokenSeed ? { github: context.gitTokenSeed } : {}
|
|
4931
|
+
};
|
|
4932
|
+
const seedPrefix = gitTokenSeedExportPrefix(gitTokenSeeds);
|
|
4933
|
+
const command = seedPrefix ? `${seedPrefix}
|
|
3976
4934
|
${repositoryCloneCommand(resources)}` : repositoryCloneCommand(resources);
|
|
3977
4935
|
if (session.exec) {
|
|
3978
4936
|
const result = await session.exec({
|
|
@@ -4043,6 +5001,10 @@ function sandboxCommandExitCode(result) {
|
|
|
4043
5001
|
return null;
|
|
4044
5002
|
}
|
|
4045
5003
|
function sandboxCommandOutput(result) {
|
|
5004
|
+
if (typeof result === "string") {
|
|
5005
|
+
const outputIndex = result.indexOf("Output:");
|
|
5006
|
+
return outputIndex >= 0 ? result.slice(outputIndex + "Output:".length).trim() : result.trim();
|
|
5007
|
+
}
|
|
4046
5008
|
if (!result || typeof result !== "object") {
|
|
4047
5009
|
return "";
|
|
4048
5010
|
}
|
|
@@ -4052,12 +5014,17 @@ function sandboxCommandOutput(result) {
|
|
|
4052
5014
|
function assertSandboxCommandSucceeded(result, operation) {
|
|
4053
5015
|
const output = sandboxCommandOutput(result);
|
|
4054
5016
|
if (sandboxCommandStillRunning(result)) {
|
|
4055
|
-
throw new Error(
|
|
4056
|
-
${output
|
|
5017
|
+
throw new Error(
|
|
5018
|
+
`${operation} did not finish before the lifecycle command timeout${output ? `:
|
|
5019
|
+
${output}` : ""}`
|
|
5020
|
+
);
|
|
4057
5021
|
}
|
|
4058
5022
|
const exitCode = sandboxCommandExitCode(result);
|
|
4059
5023
|
if (exitCode !== null && exitCode !== 0) {
|
|
4060
|
-
throw new Error(
|
|
5024
|
+
throw new Error(
|
|
5025
|
+
`${operation} failed with exit code ${exitCode}${output ? `:
|
|
5026
|
+
${output}` : ""}`
|
|
5027
|
+
);
|
|
4061
5028
|
}
|
|
4062
5029
|
if (exitCode === null) {
|
|
4063
5030
|
throw new Error(output || `${operation} did not return a command exit code`);
|
|
@@ -4142,7 +5109,10 @@ function bundledSkillsDir() {
|
|
|
4142
5109
|
return packaged;
|
|
4143
5110
|
}
|
|
4144
5111
|
if (!stagedBundledSkillsDir) {
|
|
4145
|
-
stagedBundledSkillsDir = stageBundledSkills(
|
|
5112
|
+
stagedBundledSkillsDir = stageBundledSkills(
|
|
5113
|
+
packaged,
|
|
5114
|
+
join(process.cwd(), ".opengeni", "bundled_hashicorp_terraform_skills")
|
|
5115
|
+
);
|
|
4146
5116
|
}
|
|
4147
5117
|
return stagedBundledSkillsDir;
|
|
4148
5118
|
}
|
|
@@ -4187,12 +5157,18 @@ function lazySkillSourceWithPackSkills(packSkills) {
|
|
|
4187
5157
|
packNameKeys.add(skill.name.toLowerCase());
|
|
4188
5158
|
packNames.add(skill.name);
|
|
4189
5159
|
children[skill.name] = packSkillDirEntry(skill);
|
|
4190
|
-
packIndex.push({
|
|
5160
|
+
packIndex.push({
|
|
5161
|
+
name: skill.name,
|
|
5162
|
+
description: packSkillDescription(skill),
|
|
5163
|
+
path: skill.name
|
|
5164
|
+
});
|
|
4191
5165
|
}
|
|
4192
5166
|
return {
|
|
4193
5167
|
source: dir({ children }),
|
|
4194
5168
|
getIndex: (manifest, skillsPath) => [
|
|
4195
|
-
...(bundled.getIndex?.(manifest, skillsPath) ?? []).filter(
|
|
5169
|
+
...(bundled.getIndex?.(manifest, skillsPath) ?? []).filter(
|
|
5170
|
+
(entry) => !packNames.has(entry.path ?? entry.name)
|
|
5171
|
+
),
|
|
4196
5172
|
...packIndex
|
|
4197
5173
|
]
|
|
4198
5174
|
};
|
|
@@ -4306,7 +5282,9 @@ function sortJson(value) {
|
|
|
4306
5282
|
return value.map(sortJson);
|
|
4307
5283
|
}
|
|
4308
5284
|
if (value && typeof value === "object") {
|
|
4309
|
-
return Object.fromEntries(
|
|
5285
|
+
return Object.fromEntries(
|
|
5286
|
+
Object.entries(value).sort(([a], [b]) => a.localeCompare(b)).map(([key, nested]) => [key, sortJson(nested)])
|
|
5287
|
+
);
|
|
4310
5288
|
}
|
|
4311
5289
|
return value;
|
|
4312
5290
|
}
|
|
@@ -4335,25 +5313,44 @@ export {
|
|
|
4335
5313
|
DISPLAY_STACK_TIMEOUT_MS,
|
|
4336
5314
|
DisplayStackError,
|
|
4337
5315
|
DisplayStackUnsupportedError,
|
|
5316
|
+
FAULT_FIELD_WHAT_HAPPENED,
|
|
5317
|
+
FAULT_FIELD_WHAT_PRESERVED,
|
|
5318
|
+
FAULT_FIELD_WHAT_TO_TRY,
|
|
5319
|
+
FAULT_FIELD_WHICH_LAYER,
|
|
4338
5320
|
GENESIS_TITLE_DIRECTIVE,
|
|
4339
5321
|
MAX_COMPACTION_THRESHOLD_RATIO,
|
|
4340
5322
|
MIN_COMPACTION_THRESHOLD_RATIO,
|
|
4341
5323
|
MaxTurnsExceededError2 as MaxTurnsExceededError,
|
|
4342
5324
|
MockAgentResponder,
|
|
4343
5325
|
MultiProviderModelProvider,
|
|
5326
|
+
NEVER_SENT_DETAIL_KEY,
|
|
4344
5327
|
NatsControlRpc,
|
|
5328
|
+
NatsOpStreamTransport,
|
|
5329
|
+
OP_STREAM_ACK_INTERVAL_MS,
|
|
5330
|
+
OP_STREAM_DEFAULT_WINDOW_BYTES,
|
|
5331
|
+
OP_STREAM_RECONNECT_HOLD_MS,
|
|
5332
|
+
OP_STREAM_SILENCE_TIMEOUT_MS,
|
|
5333
|
+
OpStreamUnavailableError,
|
|
4345
5334
|
OpenAIChatCompletionsModel2 as OpenAIChatCompletionsModel,
|
|
4346
5335
|
OpenAIResponsesModel2 as OpenAIResponsesModel,
|
|
4347
5336
|
PROVIDER_REGISTRY,
|
|
5337
|
+
REPOSITORY_DISCOVERY_LIMIT,
|
|
4348
5338
|
RecordingError,
|
|
4349
5339
|
RecordingUnavailableError,
|
|
4350
5340
|
RoutingSandboxSession,
|
|
4351
5341
|
RoutingUnsupportedError,
|
|
4352
|
-
SCREENSHOT_OMITTED_IMAGE_DATA_URL,
|
|
4353
|
-
SCREENSHOT_OMITTED_PLACEHOLDER,
|
|
4354
5342
|
SELFHOSTED_DEFAULT_TIMEOUT_MS,
|
|
5343
|
+
SELFHOSTED_DRAINING_MAX_RETRIES,
|
|
5344
|
+
SELFHOSTED_EXEC_DRAINING_MAX_RETRIES,
|
|
5345
|
+
SELFHOSTED_IDEMPOTENT_READONLY_OPS,
|
|
5346
|
+
SELFHOSTED_INFRASTRUCTURE_FAULT_CLASSES,
|
|
5347
|
+
SELFHOSTED_NEVER_SENT_MAX_RETRIES,
|
|
4355
5348
|
SELFHOSTED_RECONNECT_WINDOW_MS,
|
|
4356
5349
|
SELFHOSTED_RELAY_STREAM_PATH,
|
|
5350
|
+
SELFHOSTED_RETRY_BACKOFF_BASE_MS,
|
|
5351
|
+
SELFHOSTED_RETRY_BACKOFF_CAP_MS,
|
|
5352
|
+
SELFHOSTED_RETRY_BACKOFF_FACTOR,
|
|
5353
|
+
SELFHOSTED_TIMEOUT_MAX_RETRIES,
|
|
4357
5354
|
STREAM_PORT,
|
|
4358
5355
|
STREAM_TOKEN_DEFAULT_TTL_SECONDS,
|
|
4359
5356
|
SUMMARY_BUFFER_TOKENS,
|
|
@@ -4362,6 +5359,7 @@ export {
|
|
|
4362
5359
|
SandboxComputer,
|
|
4363
5360
|
SandboxConfigError,
|
|
4364
5361
|
SandboxProviderUnavailableError,
|
|
5362
|
+
SandboxResumeStateUnavailableError,
|
|
4365
5363
|
SelfhostedControlError,
|
|
4366
5364
|
SelfhostedSandboxClient,
|
|
4367
5365
|
SelfhostedSession,
|
|
@@ -4373,11 +5371,14 @@ export {
|
|
|
4373
5371
|
TerminalServerError,
|
|
4374
5372
|
TerminalServerUnsupportedError,
|
|
4375
5373
|
USER_MESSAGE_TRUNCATION_MARKER,
|
|
5374
|
+
WorkspaceModelPolicyBlockedError,
|
|
4376
5375
|
agentErrorToControlError,
|
|
4377
5376
|
agentsErrorRunState,
|
|
4378
5377
|
appendGenesisTitleDirective,
|
|
5378
|
+
appendPersistentSessionSettings,
|
|
4379
5379
|
appendSessionInstructions,
|
|
4380
5380
|
appendToolspaceInstructions,
|
|
5381
|
+
appendWorkspaceMemory,
|
|
4381
5382
|
applyMissingManifestEntries,
|
|
4382
5383
|
assertDescriptorRegistryInvariants,
|
|
4383
5384
|
assertProviderRegistryInvariants,
|
|
@@ -4388,7 +5389,6 @@ export {
|
|
|
4388
5389
|
buildAgentCapabilities,
|
|
4389
5390
|
buildCompactionPromptInput,
|
|
4390
5391
|
buildCompactionReplacementHistory,
|
|
4391
|
-
buildDeterministicFallbackCompactionHistory,
|
|
4392
5392
|
buildDisplayStackScript,
|
|
4393
5393
|
buildManifest,
|
|
4394
5394
|
buildModelInstance,
|
|
@@ -4401,8 +5401,8 @@ export {
|
|
|
4401
5401
|
buildTerminalServerScript,
|
|
4402
5402
|
callModelInputFilterForSettings,
|
|
4403
5403
|
clampCompactionThresholdRatio,
|
|
4404
|
-
clientCompactionThresholdTokens,
|
|
4405
5404
|
collectSandboxEnvironment,
|
|
5405
|
+
compactionThresholdTokens,
|
|
4406
5406
|
composeAgentInstructions,
|
|
4407
5407
|
computerUse,
|
|
4408
5408
|
configureOpenAI,
|
|
@@ -4413,14 +5413,18 @@ export {
|
|
|
4413
5413
|
createProductionAgentRuntime,
|
|
4414
5414
|
createSandboxClient,
|
|
4415
5415
|
createSandboxClientForBackend,
|
|
4416
|
-
|
|
5416
|
+
decideCompaction,
|
|
5417
|
+
decideSelfhostedRetry,
|
|
4417
5418
|
decodeModalSnapshotId,
|
|
5419
|
+
defaultSelfhostedRetryClock,
|
|
4418
5420
|
deletePriorPersistedSnapshot,
|
|
4419
5421
|
deleteRecordingArtifacts,
|
|
4420
5422
|
deserializeSandboxSessionStateEnvelope,
|
|
4421
5423
|
desktopCapableBackend,
|
|
4422
|
-
|
|
4423
|
-
|
|
5424
|
+
drainingExhaustedError,
|
|
5425
|
+
drainingMessage,
|
|
5426
|
+
elideSupersededViewImagePairs,
|
|
5427
|
+
elideSupersededViewImagesFilter,
|
|
4424
5428
|
ensureDisplayStack,
|
|
4425
5429
|
ensureModalRegistryImage,
|
|
4426
5430
|
ensureReadableStreamFrom,
|
|
@@ -4428,12 +5432,15 @@ export {
|
|
|
4428
5432
|
establishSandboxSessionFromEnvelope,
|
|
4429
5433
|
estimateItemTokens,
|
|
4430
5434
|
estimateTokens,
|
|
5435
|
+
execDeadlineHint,
|
|
4431
5436
|
exposeStreamPort,
|
|
4432
5437
|
extForCodec,
|
|
4433
5438
|
extractResponseOutputText,
|
|
4434
5439
|
findCompactionNeededError,
|
|
4435
|
-
|
|
5440
|
+
gitProviderTokenRefreshCommand,
|
|
5441
|
+
inspectRecordingArtifact,
|
|
4436
5442
|
isCompactionSummary,
|
|
5443
|
+
isEphemeralInternalContext,
|
|
4437
5444
|
isExecSessionLostBanner,
|
|
4438
5445
|
isProviderSandboxNotFoundError,
|
|
4439
5446
|
isSelfhostedProviderNotFoundError,
|
|
@@ -4441,52 +5448,71 @@ export {
|
|
|
4441
5448
|
isWorkspaceEscapeError,
|
|
4442
5449
|
lazySkillSourceWithPackSkills,
|
|
4443
5450
|
makeActiveBackendResolver,
|
|
5451
|
+
manifestEnvironmentDrift,
|
|
4444
5452
|
materializeSandboxFileDownloads,
|
|
4445
5453
|
maxTurnsExceededRunState,
|
|
4446
5454
|
mcpToolErrorOutput,
|
|
4447
5455
|
mintStreamToken,
|
|
4448
5456
|
modalSandboxAttributionEnvironment,
|
|
4449
5457
|
modalSandboxAttributionTags,
|
|
5458
|
+
modelCallUsageTelemetry,
|
|
5459
|
+
modelResponseUsageFromResponse,
|
|
4450
5460
|
modelResponseUsageFromSdkEvent,
|
|
4451
5461
|
negotiateCapabilities,
|
|
4452
5462
|
negotiateSelfhostedCapabilities,
|
|
4453
5463
|
neutralizeToolSearchItemsInSerializedRunState,
|
|
5464
|
+
nextDurableOpId,
|
|
4454
5465
|
normalizeComputerCallsFilter,
|
|
4455
5466
|
normalizeSdkEvent,
|
|
4456
5467
|
normalizeToolOutputForEvent,
|
|
4457
5468
|
offlineAgentError,
|
|
4458
5469
|
offlineControlResponse,
|
|
5470
|
+
oneShotGenesisTitleInputFilter,
|
|
5471
|
+
opAckSubject,
|
|
5472
|
+
opFrameSubject,
|
|
4459
5473
|
parseExecBannerSessionId,
|
|
4460
5474
|
parseExposedPorts,
|
|
4461
5475
|
parseNumstatZ,
|
|
4462
5476
|
parsePorcelainV2,
|
|
4463
5477
|
parseUnifiedPatch,
|
|
5478
|
+
payloadTooLargeMessage,
|
|
5479
|
+
pinProvidedSessionManifestEnvironment,
|
|
4464
5480
|
prefixedMcpToolName,
|
|
4465
5481
|
prepareAgentTools,
|
|
4466
5482
|
prepareRunInput,
|
|
4467
|
-
readRecordingBytes,
|
|
4468
5483
|
readWorkspaceArchiveFromEnvelopeSessionState,
|
|
4469
5484
|
recordingStorageKey,
|
|
5485
|
+
refreshGitProviderTokenFiles,
|
|
4470
5486
|
renderCompactionPromptInputForChat,
|
|
5487
|
+
renderSelfhostedFault,
|
|
4471
5488
|
repositoryCloneCommand,
|
|
4472
5489
|
repositoryUsesSandboxClone,
|
|
4473
5490
|
resolveTurnModel,
|
|
4474
5491
|
restoredSandboxSessionStateFromEntry,
|
|
5492
|
+
rigInstructions,
|
|
5493
|
+
rigSetupScriptCommand,
|
|
4475
5494
|
runAgentStream,
|
|
4476
5495
|
runAzureCliLoginHook,
|
|
4477
5496
|
runBeforeAgentStartHooks,
|
|
5497
|
+
runOwnedSandboxSetup,
|
|
4478
5498
|
runRepositoryCloneHook,
|
|
5499
|
+
runRigSetupHook,
|
|
4479
5500
|
runToolspaceTokenSeedHook,
|
|
5501
|
+
runWithToolCallCorrelation,
|
|
4480
5502
|
sandboxCommandExitCode,
|
|
4481
5503
|
sandboxCommandOutput,
|
|
4482
5504
|
sandboxCommandStillRunning,
|
|
5505
|
+
sandboxFileDownloadFailureNote,
|
|
4483
5506
|
sandboxFileDownloadsForAgent,
|
|
4484
5507
|
sandboxLifecycleHooksForIds,
|
|
4485
5508
|
sandboxRunAs,
|
|
4486
5509
|
sandboxStateEntryFromRunState,
|
|
4487
5510
|
sanitizeHistoryItemsForModel,
|
|
5511
|
+
sanitizeOpIdToken,
|
|
4488
5512
|
selectBackend,
|
|
5513
|
+
selfhostedFaultClass,
|
|
4489
5514
|
selfhostedLiveness,
|
|
5515
|
+
selfhostedRetryBackoffMs,
|
|
4490
5516
|
serializeApprovals,
|
|
4491
5517
|
serializeEstablishedSandboxEnvelope,
|
|
4492
5518
|
setSelfhostedApplyDiff,
|
|
@@ -4498,6 +5524,7 @@ export {
|
|
|
4498
5524
|
stripReasoningIdentityFromSerializedRunState,
|
|
4499
5525
|
subjectFor,
|
|
4500
5526
|
summarizeForCompaction,
|
|
5527
|
+
swapTargetEstablishability,
|
|
4501
5528
|
sweepModalOrphanSandboxes,
|
|
4502
5529
|
tagModalSandbox,
|
|
4503
5530
|
tearDownDisplayStack,
|
|
@@ -4506,10 +5533,12 @@ export {
|
|
|
4506
5533
|
timeoutAgentError,
|
|
4507
5534
|
timeoutControlResponse,
|
|
4508
5535
|
toolspaceTokenSeedCommand,
|
|
5536
|
+
uploadRecordingArtifact,
|
|
4509
5537
|
verifyStreamToken,
|
|
4510
5538
|
withManifestRefreshOnResume,
|
|
4511
5539
|
withSandboxFileDownloads,
|
|
4512
5540
|
withSandboxLifecycleHooks,
|
|
5541
|
+
withStructuredViewImageFunctionResults,
|
|
4513
5542
|
workspaceEnvironmentInstructions
|
|
4514
5543
|
};
|
|
4515
5544
|
//# sourceMappingURL=index.js.map
|