@opengeni/runtime 0.14.1 → 0.14.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-4YJ5Z52Z.js → chunk-7E2ZSCZK.js} +6 -6
- package/dist/{chunk-4YJ5Z52Z.js.map → chunk-7E2ZSCZK.js.map} +1 -1
- package/dist/{chunk-HHWIZHQM.js → chunk-GNEGANUF.js} +942 -300
- package/dist/chunk-GNEGANUF.js.map +1 -0
- package/dist/codex-tool-search.d.ts +100 -0
- package/dist/context-compaction.d.ts +236 -0
- package/dist/history-sanitizer.d.ts +320 -0
- package/dist/index.d.ts +186 -718
- package/dist/index.js +612 -126
- package/dist/index.js.map +1 -1
- package/dist/{localSources-T3HZ6KY4.js → localSources-BXHBKGF4.js} +2 -2
- package/dist/mcp-network.d.ts +32 -35
- package/dist/metrics.d.ts +33 -0
- package/dist/sandbox/capabilities.d.ts +11 -0
- package/dist/sandbox/channel-a.d.ts +226 -0
- package/dist/sandbox/display-stack.d.ts +99 -0
- package/dist/sandbox/errors.d.ts +16 -0
- package/dist/sandbox/exec-banner.d.ts +19 -0
- package/dist/sandbox/index.d.ts +190 -6
- package/dist/sandbox/index.js +20 -4
- package/dist/sandbox/op-correlation.d.ts +24 -0
- package/dist/sandbox/provider-errors.d.ts +32 -0
- package/dist/sandbox/provider-operation-gate.d.ts +12 -0
- package/dist/sandbox/providers/blaxel.d.ts +2 -0
- package/dist/sandbox/providers/cloudflare.d.ts +2 -0
- package/dist/sandbox/providers/daytona.d.ts +2 -0
- package/dist/sandbox/providers/docker.d.ts +2 -0
- package/dist/sandbox/providers/e2b.d.ts +2 -0
- package/dist/sandbox/providers/index.d.ts +11 -0
- package/dist/sandbox/providers/local.d.ts +2 -0
- package/dist/sandbox/providers/modal.d.ts +132 -0
- package/dist/sandbox/providers/none.d.ts +2 -0
- package/dist/sandbox/providers/runloop.d.ts +2 -0
- package/dist/sandbox/providers/selfhosted.d.ts +2 -0
- package/dist/sandbox/providers/types.d.ts +30 -0
- package/dist/sandbox/providers/vercel.d.ts +2 -0
- package/dist/sandbox/recording.d.ts +80 -0
- package/dist/sandbox/routing/backend-resolver.d.ts +173 -0
- package/dist/sandbox/routing/routing-session.d.ts +401 -0
- package/dist/sandbox/run-credentials.d.ts +65 -0
- package/dist/sandbox/select.d.ts +120 -0
- package/dist/sandbox/selfhosted/capabilities.d.ts +91 -0
- package/dist/sandbox/selfhosted/control-rpc.d.ts +175 -0
- package/dist/sandbox/selfhosted/fault-rendering.d.ts +40 -0
- package/dist/sandbox/selfhosted/op-observer.d.ts +40 -0
- package/dist/sandbox/selfhosted/op-stream.d.ts +104 -0
- package/dist/sandbox/selfhosted/op-testing.d.ts +108 -0
- package/dist/sandbox/selfhosted/op-transport.d.ts +60 -0
- package/dist/sandbox/selfhosted/retry-policy.d.ts +106 -0
- package/dist/sandbox/selfhosted/session.d.ts +552 -0
- package/dist/sandbox/selfhosted/testing.d.ts +48 -0
- package/dist/sandbox/stream-port.d.ts +80 -0
- package/dist/sandbox/stream-token.d.ts +44 -0
- package/dist/sandbox/terminal-server.d.ts +67 -0
- package/dist/sandbox/toolspace-token.d.ts +23 -0
- package/dist/sandbox/turn-tool-cancellation.d.ts +72 -0
- package/dist/sandbox/workspace-archive.d.ts +39 -0
- package/dist/sandbox-computer.d.ts +227 -0
- package/dist/screenshot-error-card.d.ts +1 -0
- package/dist/skill-library.d.ts +10 -12
- package/dist/usage-telemetry.d.ts +45 -0
- package/dist/workspace-skills.d.ts +31 -0
- package/package.json +7 -7
- package/src/context-compaction.ts +600 -6
- package/src/history-sanitizer.ts +10 -92
- package/src/index.ts +301 -62
- package/src/sandbox/channel-a.ts +267 -81
- package/src/sandbox/index.ts +56 -94
- package/src/sandbox/provider-operation-gate.ts +110 -0
- package/src/sandbox/providers/docker.ts +13 -1
- package/src/sandbox/providers/modal.ts +433 -3
- package/src/sandbox/routing/routing-session.ts +84 -8
- package/src/sandbox/selfhosted/session.ts +7 -6
- package/src/sandbox/turn-tool-cancellation.ts +47 -7
- package/src/sandbox/workspace-archive.ts +176 -73
- package/src/sandbox-computer.ts +25 -75
- package/dist/chunk-HHWIZHQM.js.map +0 -1
- package/dist/index-BraazrAO.d.ts +0 -2931
- /package/dist/{localSources-T3HZ6KY4.js.map → localSources-BXHBKGF4.js.map} +0 -0
package/src/history-sanitizer.ts
CHANGED
|
@@ -36,6 +36,16 @@ import {
|
|
|
36
36
|
/** A history item is any JSON object; we only inspect a few discriminator fields. */
|
|
37
37
|
export type HistoryItem = Record<string, unknown>;
|
|
38
38
|
|
|
39
|
+
/**
|
|
40
|
+
* @deprecated Model-visible history is append-only outside the explicit
|
|
41
|
+
* durable compaction transition. This compatibility identity function performs
|
|
42
|
+
* no elision and returns the exact input array with every item untouched and in
|
|
43
|
+
* its original order.
|
|
44
|
+
*/
|
|
45
|
+
export function elideSupersededViewImagePairs<T extends HistoryItem>(items: readonly T[]): T[] {
|
|
46
|
+
return items as T[];
|
|
47
|
+
}
|
|
48
|
+
|
|
39
49
|
/**
|
|
40
50
|
* Tool-call item types and the result-item type that settles them. Kept in
|
|
41
51
|
* sync with the SDK's `TOOL_CALL_RESULT_TYPE_BY_CALL_TYPE`; `function_call` is
|
|
@@ -103,98 +113,6 @@ function callIdOf(item: unknown): string | undefined {
|
|
|
103
113
|
return undefined;
|
|
104
114
|
}
|
|
105
115
|
|
|
106
|
-
/**
|
|
107
|
-
* Return the normalized local path from a sandbox `view_image` function call.
|
|
108
|
-
* Invalid/partial calls are deliberately ignored: the filter below only
|
|
109
|
-
* removes fully-paired calls whose replacement is known to be valid.
|
|
110
|
-
*/
|
|
111
|
-
function viewImagePathOf(item: unknown): string | undefined {
|
|
112
|
-
if (!item || typeof item !== "object") {
|
|
113
|
-
return undefined;
|
|
114
|
-
}
|
|
115
|
-
const record = item as { type?: unknown; name?: unknown; arguments?: unknown };
|
|
116
|
-
if (
|
|
117
|
-
record.type !== "function_call" ||
|
|
118
|
-
record.name !== "view_image" ||
|
|
119
|
-
typeof record.arguments !== "string"
|
|
120
|
-
) {
|
|
121
|
-
return undefined;
|
|
122
|
-
}
|
|
123
|
-
try {
|
|
124
|
-
const parsed = JSON.parse(record.arguments) as { path?: unknown };
|
|
125
|
-
return typeof parsed.path === "string" && parsed.path.length > 0 ? parsed.path : undefined;
|
|
126
|
-
} catch {
|
|
127
|
-
return undefined;
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
/**
|
|
132
|
-
* Remove superseded `view_image` call/result pairs for the SAME local path,
|
|
133
|
-
* retaining the newest fully-paired observation. A `view_image` result can be
|
|
134
|
-
* hundreds of kilobytes of base64. When a model re-opens the same attachment,
|
|
135
|
-
* replaying every older copy on each subsequent model call grows input
|
|
136
|
-
* quadratically and can trap a turn in context-compaction/requeue recovery.
|
|
137
|
-
*
|
|
138
|
-
* This is state-based rather than count-based: every distinct path is kept,
|
|
139
|
-
* and an older observation is removed only after a newer successful pair for
|
|
140
|
-
* that exact path exists. The reasoning items immediately attached to a
|
|
141
|
-
* removed call are removed with it, preserving Responses API item validity.
|
|
142
|
-
*/
|
|
143
|
-
export function elideSupersededViewImagePairs<T extends HistoryItem>(items: readonly T[]): T[] {
|
|
144
|
-
if (items.length === 0) {
|
|
145
|
-
return [];
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
const callIndexById = new Map<string, number>();
|
|
149
|
-
const resultIndexById = new Map<string, number>();
|
|
150
|
-
items.forEach((item, index) => {
|
|
151
|
-
const callId = callIdOf(item);
|
|
152
|
-
if (!callId) {
|
|
153
|
-
return;
|
|
154
|
-
}
|
|
155
|
-
if (itemType(item) === "function_call") {
|
|
156
|
-
callIndexById.set(callId, index);
|
|
157
|
-
} else if (
|
|
158
|
-
itemType(item) === "function_call_result" ||
|
|
159
|
-
itemType(item) === "function_call_output"
|
|
160
|
-
) {
|
|
161
|
-
resultIndexById.set(callId, index);
|
|
162
|
-
}
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
const pairedByPath = new Map<string, Array<{ callIndex: number; resultIndex: number }>>();
|
|
166
|
-
for (const [callId, callIndex] of callIndexById) {
|
|
167
|
-
const path = viewImagePathOf(items[callIndex]);
|
|
168
|
-
const resultIndex = resultIndexById.get(callId);
|
|
169
|
-
if (!path || resultIndex === undefined || resultIndex <= callIndex) {
|
|
170
|
-
continue;
|
|
171
|
-
}
|
|
172
|
-
const pairs = pairedByPath.get(path) ?? [];
|
|
173
|
-
pairs.push({ callIndex, resultIndex });
|
|
174
|
-
pairedByPath.set(path, pairs);
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
const dropped = new Set<number>();
|
|
178
|
-
for (const pairs of pairedByPath.values()) {
|
|
179
|
-
if (pairs.length < 2) {
|
|
180
|
-
continue;
|
|
181
|
-
}
|
|
182
|
-
pairs.sort((left, right) => left.callIndex - right.callIndex);
|
|
183
|
-
for (const pair of pairs.slice(0, -1)) {
|
|
184
|
-
dropped.add(pair.callIndex);
|
|
185
|
-
dropped.add(pair.resultIndex);
|
|
186
|
-
for (let index = pair.callIndex - 1; index >= 0; index -= 1) {
|
|
187
|
-
if (itemType(items[index]) !== "reasoning") {
|
|
188
|
-
break;
|
|
189
|
-
}
|
|
190
|
-
dropped.add(index);
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
return dropped.size === 0 ? items.slice() : items.filter((_item, index) => !dropped.has(index));
|
|
196
|
-
}
|
|
197
|
-
|
|
198
116
|
/**
|
|
199
117
|
* Sanitize a replayed history item list into a sequence the Responses API
|
|
200
118
|
* accepts. Pure: returns a new array of the same item references in order,
|
package/src/index.ts
CHANGED
|
@@ -35,6 +35,7 @@ import {
|
|
|
35
35
|
type McpServerConnectionRef,
|
|
36
36
|
type Permission,
|
|
37
37
|
type FirstPartyMcpToolName,
|
|
38
|
+
type LatencyMode,
|
|
38
39
|
type ReasoningEffort,
|
|
39
40
|
type ResourceRef,
|
|
40
41
|
type SessionEventType,
|
|
@@ -148,7 +149,6 @@ import { fileURLToPath } from "node:url";
|
|
|
148
149
|
|
|
149
150
|
import {
|
|
150
151
|
computerCallNormalizingFetch,
|
|
151
|
-
elideSupersededViewImagePairs,
|
|
152
152
|
normalizeComputerCallActions,
|
|
153
153
|
sanitizeHistoryItemsForModel,
|
|
154
154
|
} from "./history-sanitizer";
|
|
@@ -158,6 +158,8 @@ import {
|
|
|
158
158
|
CompactionProviderResponseError,
|
|
159
159
|
EmptyCompactionSummaryError,
|
|
160
160
|
SUMMARY_BUFFER_TOKENS,
|
|
161
|
+
buildRemoteCompactionV2PromptInput,
|
|
162
|
+
extractRemoteCompactionV2OutputItem,
|
|
161
163
|
compactionThresholdTokens,
|
|
162
164
|
estimateCompleteModelInput,
|
|
163
165
|
estimateSerializedValueTokens,
|
|
@@ -280,13 +282,24 @@ export {
|
|
|
280
282
|
clampCompactionThresholdRatio,
|
|
281
283
|
decideCompaction,
|
|
282
284
|
buildSummaryItem,
|
|
285
|
+
buildRemoteCompactionV2PromptInput,
|
|
286
|
+
buildRemoteV2ReplacementHistory,
|
|
287
|
+
extractRemoteCompactionV2OutputItem,
|
|
283
288
|
findCompactionNeededError,
|
|
284
289
|
isCompactionSummary,
|
|
290
|
+
isRemoteCompactionItem,
|
|
291
|
+
isRetainedRemoteV2Message,
|
|
285
292
|
latestCompactionReplacementFingerprint,
|
|
286
293
|
prepareCompactionPromptInput,
|
|
287
294
|
isUserMessage,
|
|
288
295
|
estimateTokens,
|
|
296
|
+
estimateTokensBreakdown,
|
|
289
297
|
estimateItemTokens,
|
|
298
|
+
estimateItemTokenBreakdown,
|
|
299
|
+
estimateOpaqueEncryptedModelVisibleBytes,
|
|
300
|
+
estimateOpaqueEncryptedTokens,
|
|
301
|
+
opaqueEncryptedContentLength,
|
|
302
|
+
estimateNativeImageTokens,
|
|
290
303
|
estimateCompleteModelInput,
|
|
291
304
|
estimateSerializedValueTokens,
|
|
292
305
|
hasModelGeneratedItem,
|
|
@@ -294,15 +307,23 @@ export {
|
|
|
294
307
|
COMPACTION_SUMMARY_MARKER,
|
|
295
308
|
COMPACTION_PROMPT,
|
|
296
309
|
COMPACT_USER_MESSAGE_MAX_TOKENS,
|
|
310
|
+
REMOTE_V2_RETAINED_MESSAGE_TOKEN_BUDGET,
|
|
311
|
+
REMOTE_COMPACTION_V2_IMPLEMENTATION,
|
|
312
|
+
REMOTE_COMPACTION_V2_BETA_FEATURE,
|
|
297
313
|
DEFAULT_COMPACTION_THRESHOLD_RATIO,
|
|
298
314
|
MIN_COMPACTION_THRESHOLD_RATIO,
|
|
299
315
|
MAX_COMPACTION_THRESHOLD_RATIO,
|
|
300
316
|
SUMMARY_BUFFER_TOKENS,
|
|
301
317
|
SUMMARY_PREFIX,
|
|
302
318
|
USER_MESSAGE_TRUNCATION_MARKER,
|
|
319
|
+
UNKNOWN_IMAGE_TOKENS,
|
|
320
|
+
MAX_NATIVE_IMAGE_TOKENS,
|
|
303
321
|
} from "./context-compaction";
|
|
304
322
|
export type {
|
|
305
323
|
CompactionDecision,
|
|
324
|
+
ModelInputTokenBreakdown,
|
|
325
|
+
NativeImageEstimateReason,
|
|
326
|
+
NativeImageTokenEstimate,
|
|
306
327
|
CompactionItem,
|
|
307
328
|
PreparedCompactionPromptInput,
|
|
308
329
|
} from "./context-compaction";
|
|
@@ -329,6 +350,7 @@ export type NormalizedRuntimeEvent = {
|
|
|
329
350
|
|
|
330
351
|
export type ModelResponseUsage = {
|
|
331
352
|
responseId?: string;
|
|
353
|
+
serviceTier?: string;
|
|
332
354
|
usage: {
|
|
333
355
|
inputTokens?: number;
|
|
334
356
|
outputTokens?: number;
|
|
@@ -1005,6 +1027,67 @@ export async function summarizeForCompaction(
|
|
|
1005
1027
|
return summary;
|
|
1006
1028
|
}
|
|
1007
1029
|
|
|
1030
|
+
/**
|
|
1031
|
+
* Codex remote compaction v2: send active history + `compaction_trigger`, collect
|
|
1032
|
+
* exactly one `{ type: "compaction", encrypted_content }` output item.
|
|
1033
|
+
* Must run inside Codex ALS with `remote_compaction_v2` beta + turn metadata.
|
|
1034
|
+
*/
|
|
1035
|
+
export async function requestRemoteCompactionV2(
|
|
1036
|
+
settings: Settings,
|
|
1037
|
+
input: Array<Record<string, unknown>>,
|
|
1038
|
+
options: {
|
|
1039
|
+
client: OpenAI;
|
|
1040
|
+
model: string;
|
|
1041
|
+
promptCacheKey?: string;
|
|
1042
|
+
onUsage?: (usage: ModelResponseUsage) => void | Promise<void>;
|
|
1043
|
+
},
|
|
1044
|
+
): Promise<Record<string, unknown>> {
|
|
1045
|
+
const promptInput = buildRemoteCompactionV2PromptInput(input);
|
|
1046
|
+
const request: ModelRequest = {
|
|
1047
|
+
systemInstructions: "",
|
|
1048
|
+
input: promptInput as AgentInputItem[],
|
|
1049
|
+
modelSettings: {
|
|
1050
|
+
// Azure rejects store:false; Codex transport enforces store:false itself.
|
|
1051
|
+
...(settings.openaiProvider === "azure" ? {} : { store: false }),
|
|
1052
|
+
...(options.promptCacheKey
|
|
1053
|
+
? { providerData: { prompt_cache_key: options.promptCacheKey } }
|
|
1054
|
+
: {}),
|
|
1055
|
+
},
|
|
1056
|
+
tools: [],
|
|
1057
|
+
toolsExplicitlyProvided: true,
|
|
1058
|
+
outputType: "text",
|
|
1059
|
+
handoffs: [],
|
|
1060
|
+
tracing: false,
|
|
1061
|
+
};
|
|
1062
|
+
let response: unknown;
|
|
1063
|
+
try {
|
|
1064
|
+
response = await new CompactionResponsesModel(options.client, options.model).fetchResponse(
|
|
1065
|
+
request,
|
|
1066
|
+
);
|
|
1067
|
+
} catch (error) {
|
|
1068
|
+
throw new CompactionProviderResponseError(compactionProviderFailureDiagnostics(error), error);
|
|
1069
|
+
}
|
|
1070
|
+
const usage = modelResponseUsageFromResponse(response);
|
|
1071
|
+
if (usage) {
|
|
1072
|
+
await options.onUsage?.(usage);
|
|
1073
|
+
}
|
|
1074
|
+
if (isFailedCompactionProviderResponse(response)) {
|
|
1075
|
+
throw new CompactionProviderResponseError(compactionProviderFailureDiagnostics(response));
|
|
1076
|
+
}
|
|
1077
|
+
try {
|
|
1078
|
+
return extractRemoteCompactionV2OutputItem(response);
|
|
1079
|
+
} catch (error) {
|
|
1080
|
+
if (error instanceof EmptyCompactionSummaryError) {
|
|
1081
|
+
throw new EmptyCompactionSummaryError({
|
|
1082
|
+
...compactionResponseDiagnostics(response, ""),
|
|
1083
|
+
...error.diagnostics,
|
|
1084
|
+
stage: "remote_v2_extract",
|
|
1085
|
+
});
|
|
1086
|
+
}
|
|
1087
|
+
throw error;
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1008
1091
|
function isFailedCompactionProviderResponse(response: unknown): boolean {
|
|
1009
1092
|
if (!response || typeof response !== "object") return false;
|
|
1010
1093
|
const record = response as Record<string, unknown>;
|
|
@@ -1293,6 +1376,10 @@ export type BuildAgentOptions = {
|
|
|
1293
1376
|
response: HumanInputResponse;
|
|
1294
1377
|
};
|
|
1295
1378
|
reasoningEffort?: ReasoningEffort;
|
|
1379
|
+
/** Product latency selection frozen onto this turn. */
|
|
1380
|
+
latencyMode?: LatencyMode;
|
|
1381
|
+
/** Provider-specific wire value resolved by the worker (`fast` or `priority`). */
|
|
1382
|
+
serviceTier?: "fast" | "priority";
|
|
1296
1383
|
// Per-turn gating overrides for the multi-provider path. Each defaults to
|
|
1297
1384
|
// today's settings-derived behaviour when omitted, so the legacy
|
|
1298
1385
|
// global-client callers (no model resolution) are byte-for-byte unchanged.
|
|
@@ -1323,8 +1410,7 @@ export type BuildAgentOptions = {
|
|
|
1323
1410
|
// EXPLICIT computer-use tool transport, decided where provider identity is
|
|
1324
1411
|
// authoritative (the worker's model resolution — agent-turn.ts). Threaded into
|
|
1325
1412
|
// buildAgentCapabilities → computerUse({toolMode}) so tool selection never rests
|
|
1326
|
-
// on the SDK's constructor-name sniff.
|
|
1327
|
-
// `structuredToolTransport` neutralize path is preserved byte-for-byte.
|
|
1413
|
+
// on the SDK's constructor-name sniff. Omitted means disabled/fail-closed.
|
|
1328
1414
|
computerToolMode?: ComputerToolMode;
|
|
1329
1415
|
/**
|
|
1330
1416
|
* Invoked once, immediately before the first real computer action and after
|
|
@@ -1765,9 +1851,14 @@ export function buildOpenGeniAgent(
|
|
|
1765
1851
|
// resolved provider's api/window/web-search instead.
|
|
1766
1852
|
const hostedWebSearch = options.hostedWebSearch ?? settings.webSearchEnabled;
|
|
1767
1853
|
const encryptedReasoning = options.encryptedReasoning ?? settings.openaiReasoningEncryptedContent;
|
|
1854
|
+
// Wire value must be provider-mapped by the caller (OpenAI `fast`, Azure/Codex
|
|
1855
|
+
// `priority`). Do not fall back to latencyMode itself — that would send
|
|
1856
|
+
// invalid Azure tiers.
|
|
1857
|
+
const serviceTier = options.serviceTier;
|
|
1768
1858
|
const providerData = {
|
|
1769
1859
|
...(encryptedReasoning ? { include: ["reasoning.encrypted_content"] } : {}),
|
|
1770
1860
|
...(options.promptCacheKey ? { prompt_cache_key: options.promptCacheKey } : {}),
|
|
1861
|
+
...(serviceTier ? { service_tier: serviceTier } : {}),
|
|
1771
1862
|
};
|
|
1772
1863
|
// Native hosted tools attached to every constructed agent. webSearchEnabled
|
|
1773
1864
|
// is ON by default and provider-unconditional on the built-in path (the live
|
|
@@ -2252,10 +2343,8 @@ export function buildAgentCapabilities(
|
|
|
2252
2343
|
sessionSkills?: PackSkill[];
|
|
2253
2344
|
workspaceSkillPaths?: readonly WorkspaceSkillSearchPath[];
|
|
2254
2345
|
structuredToolTransport?: boolean;
|
|
2255
|
-
// EXPLICIT computer-use transport (see BuildAgentOptions.computerToolMode).
|
|
2256
|
-
//
|
|
2257
|
-
// without the constructor-name sniff. When absent, the legacy neutralize +
|
|
2258
|
-
// imageFunctionResults path (driven by structuredToolTransport) is unchanged.
|
|
2346
|
+
// EXPLICIT computer-use transport (see BuildAgentOptions.computerToolMode).
|
|
2347
|
+
// Omitted/unproven transport fails closed with no computer tools.
|
|
2259
2348
|
computerToolMode?: ComputerToolMode;
|
|
2260
2349
|
onComputerUseReady?: (session: SandboxSessionLike) => Promise<void>;
|
|
2261
2350
|
turnCancellationSignal?: AbortSignal;
|
|
@@ -2328,37 +2417,14 @@ export function buildAgentCapabilities(
|
|
|
2328
2417
|
// FUNCTION `computer_*` tools on the text transport. The ChatGPT/Codex backend
|
|
2329
2418
|
// rejects hosted tool types (only function/custom/web_search accepted).
|
|
2330
2419
|
//
|
|
2331
|
-
//
|
|
2332
|
-
//
|
|
2333
|
-
// tool selection then never depends on the SDK's model-instance constructor-name
|
|
2334
|
-
// sniff (which a wrapped/proxied model would defeat, silently 400ing a
|
|
2335
|
-
// chat-completions provider handed the hosted tool). When ABSENT, the legacy path
|
|
2336
|
-
// is preserved byte-for-byte: on the codex path (structuredToolTransport === false)
|
|
2337
|
-
// we set imageFunctionResults and neutralize the capability's model binding — the
|
|
2338
|
-
// SAME trick used for filesystem above — so `tools()` sees no model instance and
|
|
2339
|
-
// emits the function tools the backend can call, instead of suppressing the tier.
|
|
2340
|
-
const explicitMode = options.computerToolMode;
|
|
2420
|
+
// The worker declares an explicit mode from authoritative provider resolution.
|
|
2421
|
+
// Exported/public callers that omit it are unproven and therefore disabled.
|
|
2341
2422
|
const computerCapability = computerUse({
|
|
2342
2423
|
dimensions: [settings.streamResolutionWidth, settings.streamResolutionHeight],
|
|
2343
2424
|
readOnly: settings.computerUseReadOnly,
|
|
2344
2425
|
...(options.onComputerUseReady ? { onReady: options.onComputerUseReady } : {}),
|
|
2345
|
-
|
|
2346
|
-
? { toolMode: explicitMode }
|
|
2347
|
-
: // Legacy (no explicit mode): on the codex path the function tools deliver
|
|
2348
|
-
// screenshots as a real image the model can see. The ChatGPT/Codex backend
|
|
2349
|
-
// rejects HOSTED tool types but DOES accept `input_image` content items inside a
|
|
2350
|
-
// `function_call_output` (proven by openai/codex codex-rs, whose view_image tool
|
|
2351
|
-
// ships exactly that shape) — so a structured image tool result is seen, where a
|
|
2352
|
-
// text data-URL would be unreadable.
|
|
2353
|
-
options.structuredToolTransport === false
|
|
2354
|
-
? { imageFunctionResults: true }
|
|
2355
|
-
: {}),
|
|
2426
|
+
toolMode: options.computerToolMode ?? "disabled",
|
|
2356
2427
|
});
|
|
2357
|
-
// Neutralize ONLY on the legacy sniff path. With an explicit toolMode the mode
|
|
2358
|
-
// already forces the function tools, so the constructor-name override is moot.
|
|
2359
|
-
if (!explicitMode && options.structuredToolTransport === false) {
|
|
2360
|
-
neutralizeStructuredToolTransport(computerCapability);
|
|
2361
|
-
}
|
|
2362
2428
|
caps.push(computerCapability as unknown as ReturnType<typeof Capabilities.default>[number]);
|
|
2363
2429
|
}
|
|
2364
2430
|
if (toolCancellation) {
|
|
@@ -2993,9 +3059,12 @@ function parseWwwAuthenticate(header: string | null): {
|
|
|
2993
3059
|
// content, erasing the failure. A JSON-RPC error survives the shim as a thrown
|
|
2994
3060
|
// McpError, which PrefixedMcpServer.callTool converts back into an MCP-shaped
|
|
2995
3061
|
// `{ isError: true }` output for the model.
|
|
2996
|
-
const
|
|
2997
|
-
|
|
2998
|
-
|
|
3062
|
+
const MCP_AUTH_NEEDED_ERROR = {
|
|
3063
|
+
// OpenGeni application-defined JSON-RPC code. Keep this positive so it cannot
|
|
3064
|
+
// collide with MCP SDK transport errors such as RequestTimeout (-32001).
|
|
3065
|
+
code: 40_101,
|
|
3066
|
+
message: "Authentication required - a connection link was posted to the session.",
|
|
3067
|
+
} as const;
|
|
2999
3068
|
|
|
3000
3069
|
function mcpToolAuthNeededResponse(id: string | number | null | undefined): Response {
|
|
3001
3070
|
return new Response(
|
|
@@ -3003,8 +3072,8 @@ function mcpToolAuthNeededResponse(id: string | number | null | undefined): Resp
|
|
|
3003
3072
|
jsonrpc: "2.0",
|
|
3004
3073
|
id: id ?? null,
|
|
3005
3074
|
error: {
|
|
3006
|
-
code:
|
|
3007
|
-
message:
|
|
3075
|
+
code: MCP_AUTH_NEEDED_ERROR.code,
|
|
3076
|
+
message: MCP_AUTH_NEEDED_ERROR.message,
|
|
3008
3077
|
},
|
|
3009
3078
|
}),
|
|
3010
3079
|
{
|
|
@@ -3019,7 +3088,11 @@ function isAuthNeededMcpError(error: unknown): boolean {
|
|
|
3019
3088
|
return false;
|
|
3020
3089
|
}
|
|
3021
3090
|
const code = (error as { code?: unknown }).code;
|
|
3022
|
-
return
|
|
3091
|
+
return (
|
|
3092
|
+
code === MCP_AUTH_NEEDED_ERROR.code &&
|
|
3093
|
+
(error.message === MCP_AUTH_NEEDED_ERROR.message ||
|
|
3094
|
+
error.message === `MCP error ${MCP_AUTH_NEEDED_ERROR.code}: ${MCP_AUTH_NEEDED_ERROR.message}`)
|
|
3095
|
+
);
|
|
3023
3096
|
}
|
|
3024
3097
|
|
|
3025
3098
|
// Model-facing text for a best-effort server whose tool call failed for a
|
|
@@ -3053,19 +3126,59 @@ function safeMcpErrorFields(error: unknown): {
|
|
|
3053
3126
|
return { errorClass, status };
|
|
3054
3127
|
}
|
|
3055
3128
|
|
|
3056
|
-
|
|
3129
|
+
type SafeMcpTransportError = Error & {
|
|
3130
|
+
status?: number;
|
|
3131
|
+
code?: number;
|
|
3132
|
+
mcpTransportFailureKind?: "request_timeout";
|
|
3133
|
+
};
|
|
3134
|
+
|
|
3135
|
+
/**
|
|
3136
|
+
* Preserve the MCP SDK's exact request-timeout meaning without retaining or
|
|
3137
|
+
* exposing a raw HTTP response body. The numeric code is not sufficient:
|
|
3138
|
+
* Streamable HTTP also uses -32001 for "Session not found" and arbitrary
|
|
3139
|
+
* AbortSignal reasons, so only the SDK's two owned timeout messages qualify.
|
|
3140
|
+
*/
|
|
3141
|
+
export function isMcpRequestTimeoutError(error: unknown, seen = new WeakSet<object>()): boolean {
|
|
3142
|
+
if (!error || typeof error !== "object") {
|
|
3143
|
+
return false;
|
|
3144
|
+
}
|
|
3145
|
+
if (seen.has(error)) {
|
|
3146
|
+
return false;
|
|
3147
|
+
}
|
|
3148
|
+
seen.add(error);
|
|
3149
|
+
const record = error as Record<string, unknown>;
|
|
3150
|
+
if (record.mcpTransportFailureKind === "request_timeout") {
|
|
3151
|
+
return true;
|
|
3152
|
+
}
|
|
3153
|
+
const code = typeof record.code === "number" ? record.code : record.status;
|
|
3154
|
+
const message = typeof record.message === "string" ? record.message : "";
|
|
3155
|
+
const sdkTimeoutMessages = new Set([
|
|
3156
|
+
"Request timed out",
|
|
3157
|
+
"MCP error -32001: Request timed out",
|
|
3158
|
+
"Maximum total timeout exceeded",
|
|
3159
|
+
"MCP error -32001: Maximum total timeout exceeded",
|
|
3160
|
+
]);
|
|
3161
|
+
if (code === -32_001 && sdkTimeoutMessages.has(message)) {
|
|
3162
|
+
return true;
|
|
3163
|
+
}
|
|
3164
|
+
return ["error", "cause", "response", "data"].some((key) =>
|
|
3165
|
+
isMcpRequestTimeoutError(record[key], seen),
|
|
3166
|
+
);
|
|
3167
|
+
}
|
|
3168
|
+
|
|
3169
|
+
export function safeMcpTransportError(error: unknown): SafeMcpTransportError {
|
|
3057
3170
|
const fields = safeMcpErrorFields(error);
|
|
3058
3171
|
const safeError = new Error(
|
|
3059
3172
|
`MCP transport operation failed (${mcpErrorReason(fields)})`,
|
|
3060
|
-
) as
|
|
3061
|
-
status?: number;
|
|
3062
|
-
code?: number;
|
|
3063
|
-
};
|
|
3173
|
+
) as SafeMcpTransportError;
|
|
3064
3174
|
safeError.name = "McpTransportError";
|
|
3065
3175
|
if (fields.status !== undefined) {
|
|
3066
3176
|
safeError.status = fields.status;
|
|
3067
3177
|
safeError.code = fields.status;
|
|
3068
3178
|
}
|
|
3179
|
+
if (isMcpRequestTimeoutError(error)) {
|
|
3180
|
+
safeError.mcpTransportFailureKind = "request_timeout";
|
|
3181
|
+
}
|
|
3069
3182
|
return safeError;
|
|
3070
3183
|
}
|
|
3071
3184
|
|
|
@@ -3162,17 +3275,33 @@ async function signFirstPartyDelegatedBearer(
|
|
|
3162
3275
|
if (!settings.delegationSecret || !options.accountId || !options.workspaceId) {
|
|
3163
3276
|
return null;
|
|
3164
3277
|
}
|
|
3278
|
+
const attemptClaims = [
|
|
3279
|
+
options.sessionId,
|
|
3280
|
+
options.turnId,
|
|
3281
|
+
options.attemptId,
|
|
3282
|
+
options.executionGeneration,
|
|
3283
|
+
];
|
|
3284
|
+
const hasAnyAttemptClaim = attemptClaims.some((claim) => claim !== undefined);
|
|
3285
|
+
const hasExactAttemptClaims = attemptClaims.every((claim) => claim !== undefined);
|
|
3286
|
+
if (hasAnyAttemptClaim && !hasExactAttemptClaims) {
|
|
3287
|
+
return null;
|
|
3288
|
+
}
|
|
3165
3289
|
return await signDelegatedAccessToken(settings.delegationSecret, {
|
|
3166
3290
|
accountId: options.accountId,
|
|
3167
3291
|
workspaceId: options.workspaceId,
|
|
3168
3292
|
subjectId: options.subjectId ?? "worker:first-party-mcp",
|
|
3169
3293
|
...(options.subjectLabel ? { subjectLabel: options.subjectLabel } : {}),
|
|
3170
3294
|
permissions: options.firstPartyPermissions ?? [...DEFAULT_FIRST_PARTY_MCP_PERMISSIONS],
|
|
3295
|
+
principalKind: hasExactAttemptClaims ? "agent_attempt" : "service",
|
|
3171
3296
|
firstPartyMcpTools: options.firstPartyTools ?? [...DEFAULT_FIRST_PARTY_MCP_TOOLS],
|
|
3172
|
-
...(
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
3297
|
+
...(hasExactAttemptClaims
|
|
3298
|
+
? {
|
|
3299
|
+
sessionId: options.sessionId!,
|
|
3300
|
+
turnId: options.turnId!,
|
|
3301
|
+
attemptId: options.attemptId!,
|
|
3302
|
+
executionGeneration: options.executionGeneration!,
|
|
3303
|
+
}
|
|
3304
|
+
: {}),
|
|
3176
3305
|
exp: Math.floor(Date.now() / 1000) + 60 * 60,
|
|
3177
3306
|
});
|
|
3178
3307
|
}
|
|
@@ -3469,7 +3598,7 @@ class PrefixedMcpServer implements MCPServer {
|
|
|
3469
3598
|
if (isAuthNeededMcpError(error)) {
|
|
3470
3599
|
return {
|
|
3471
3600
|
isError: true,
|
|
3472
|
-
content: [{ type: "text", text:
|
|
3601
|
+
content: [{ type: "text", text: MCP_AUTH_NEEDED_ERROR.message }],
|
|
3473
3602
|
};
|
|
3474
3603
|
}
|
|
3475
3604
|
// Best-effort INVOCATION isolation (sibling to the listTools guard). When
|
|
@@ -3798,18 +3927,6 @@ export const normalizeComputerCallsFilter: CallModelInputFilter = ({ modelData }
|
|
|
3798
3927
|
) as unknown as AgentInputItem[],
|
|
3799
3928
|
});
|
|
3800
3929
|
|
|
3801
|
-
/**
|
|
3802
|
-
* Per-call state compaction for local image inspection. Re-opening the same
|
|
3803
|
-
* path supersedes its prior base64 result; carrying both provides no newer
|
|
3804
|
-
* information and can otherwise balloon every following model request.
|
|
3805
|
-
*/
|
|
3806
|
-
export const elideSupersededViewImagesFilter: CallModelInputFilter = ({ modelData }) => ({
|
|
3807
|
-
...modelData,
|
|
3808
|
-
input: elideSupersededViewImagePairs(
|
|
3809
|
-
modelData.input as unknown as Array<Record<string, unknown>>,
|
|
3810
|
-
) as unknown as AgentInputItem[],
|
|
3811
|
-
});
|
|
3812
|
-
|
|
3813
3930
|
/**
|
|
3814
3931
|
* Canonical Codex-style tool-result bound at the final model-input seam. The
|
|
3815
3932
|
* identical pure normalizer also runs before conversation rows are persisted,
|
|
@@ -3936,7 +4053,6 @@ export function callModelInputFilterForSettings(
|
|
|
3936
4053
|
): CallModelInputFilter | undefined {
|
|
3937
4054
|
const filters: CallModelInputFilter[] = [
|
|
3938
4055
|
normalizeComputerCallsFilter,
|
|
3939
|
-
elideSupersededViewImagesFilter,
|
|
3940
4056
|
boundModelToolOutputsFilterForSettings(settings),
|
|
3941
4057
|
];
|
|
3942
4058
|
if (settings.openaiProviderItemIds === "strip") {
|
|
@@ -5005,6 +5121,72 @@ export function normalizeToolOutputForEvent(output: unknown): unknown {
|
|
|
5005
5121
|
return output;
|
|
5006
5122
|
}
|
|
5007
5123
|
|
|
5124
|
+
/**
|
|
5125
|
+
* Hosted web_search progresses on the raw Responses stream
|
|
5126
|
+
* (`response.output_item.added/done` with `web_search_call`) long before the SDK
|
|
5127
|
+
* materializes a `RunToolCallItem` at `response_done`. Without this mapping the
|
|
5128
|
+
* timeline only sees search cards after the whole model round finishes — or
|
|
5129
|
+
* never mid-turn — while assistant prose ("Search 1/5") streams live.
|
|
5130
|
+
*/
|
|
5131
|
+
function hostedWebSearchToolCallFromResponsesEvent(raw: unknown): NormalizedRuntimeEvent | null {
|
|
5132
|
+
if (!raw || typeof raw !== "object") {
|
|
5133
|
+
return null;
|
|
5134
|
+
}
|
|
5135
|
+
const event = raw as {
|
|
5136
|
+
type?: unknown;
|
|
5137
|
+
item?: {
|
|
5138
|
+
id?: unknown;
|
|
5139
|
+
type?: unknown;
|
|
5140
|
+
status?: unknown;
|
|
5141
|
+
action?: unknown;
|
|
5142
|
+
[key: string]: unknown;
|
|
5143
|
+
};
|
|
5144
|
+
};
|
|
5145
|
+
const eventType = typeof event.type === "string" ? event.type : "";
|
|
5146
|
+
if (
|
|
5147
|
+
!(
|
|
5148
|
+
(eventType === "response.output_item.added" || eventType === "response.output_item.done") &&
|
|
5149
|
+
event.item?.type === "web_search_call"
|
|
5150
|
+
)
|
|
5151
|
+
) {
|
|
5152
|
+
// Progress-only `response.web_search_call.*` events lack the action
|
|
5153
|
+
// payload; added/done are enough for a live, query-bearing card.
|
|
5154
|
+
return null;
|
|
5155
|
+
}
|
|
5156
|
+
const item = event.item;
|
|
5157
|
+
const itemId = typeof item.id === "string" ? item.id : null;
|
|
5158
|
+
if (!itemId) {
|
|
5159
|
+
return null;
|
|
5160
|
+
}
|
|
5161
|
+
const status =
|
|
5162
|
+
typeof item.status === "string"
|
|
5163
|
+
? item.status
|
|
5164
|
+
: eventType === "response.output_item.done"
|
|
5165
|
+
? "completed"
|
|
5166
|
+
: "in_progress";
|
|
5167
|
+
const action = item.action ?? null;
|
|
5168
|
+
// Codex frequently emits `output_item.added` for web_search_call before the
|
|
5169
|
+
// action payload exists. Persist that so the timeline can show "Searching…",
|
|
5170
|
+
// then the matching `done` (same id) fills in query/queries via merge.
|
|
5171
|
+
const { status: _status, ...providerData } = item;
|
|
5172
|
+
|
|
5173
|
+
return {
|
|
5174
|
+
type: "agent.toolCall.created",
|
|
5175
|
+
payload: {
|
|
5176
|
+
id: itemId,
|
|
5177
|
+
name: "web_search_call",
|
|
5178
|
+
arguments: action,
|
|
5179
|
+
raw: {
|
|
5180
|
+
type: "hosted_tool_call",
|
|
5181
|
+
id: itemId,
|
|
5182
|
+
name: "web_search_call",
|
|
5183
|
+
status,
|
|
5184
|
+
providerData,
|
|
5185
|
+
},
|
|
5186
|
+
},
|
|
5187
|
+
};
|
|
5188
|
+
}
|
|
5189
|
+
|
|
5008
5190
|
export function normalizeSdkEvent(event: RunStreamEvent): NormalizedRuntimeEvent[] {
|
|
5009
5191
|
const out: NormalizedRuntimeEvent[] = [];
|
|
5010
5192
|
if (event.type === "raw_model_stream_event") {
|
|
@@ -5022,6 +5204,10 @@ export function normalizeSdkEvent(event: RunStreamEvent): NormalizedRuntimeEvent
|
|
|
5022
5204
|
if (raw?.type === "response.reasoning_summary_text.delta" && typeof raw.delta === "string") {
|
|
5023
5205
|
out.push({ type: "agent.reasoning.delta", payload: { text: raw.delta } });
|
|
5024
5206
|
}
|
|
5207
|
+
const webSearch = hostedWebSearchToolCallFromResponsesEvent(raw);
|
|
5208
|
+
if (webSearch) {
|
|
5209
|
+
out.push(webSearch);
|
|
5210
|
+
}
|
|
5025
5211
|
return out;
|
|
5026
5212
|
}
|
|
5027
5213
|
if (event.type === "agent_updated_stream_event") {
|
|
@@ -5120,12 +5306,65 @@ export function modelResponseUsageFromResponse(response: unknown): ModelResponse
|
|
|
5120
5306
|
: typeof (response as { responseId?: unknown } | null)?.responseId === "string"
|
|
5121
5307
|
? (response as { responseId: string }).responseId
|
|
5122
5308
|
: undefined;
|
|
5309
|
+
const serviceTier = modelResponseServiceTierFromResponse(response);
|
|
5123
5310
|
return {
|
|
5124
5311
|
...(responseId ? { responseId } : {}),
|
|
5312
|
+
...(serviceTier ? { serviceTier } : {}),
|
|
5125
5313
|
usage,
|
|
5126
5314
|
};
|
|
5127
5315
|
}
|
|
5128
5316
|
|
|
5317
|
+
export type ModelResponseServiceTierEvent = {
|
|
5318
|
+
source: "normalized" | "provider";
|
|
5319
|
+
serviceTier: string | null;
|
|
5320
|
+
};
|
|
5321
|
+
|
|
5322
|
+
/**
|
|
5323
|
+
* Read the provider's terminal service tier without depending on usage being
|
|
5324
|
+
* present. The normalized terminal can omit provider-only fields, so callers
|
|
5325
|
+
* should treat the raw provider response as the fail-closed authority.
|
|
5326
|
+
*/
|
|
5327
|
+
export function modelResponseServiceTierFromSdkEvent(
|
|
5328
|
+
event: RunStreamEvent,
|
|
5329
|
+
): ModelResponseServiceTierEvent | null {
|
|
5330
|
+
if (event.type === "raw_model_stream_event") {
|
|
5331
|
+
const data = (event as any).data;
|
|
5332
|
+
if (data?.type === "response_done") {
|
|
5333
|
+
return {
|
|
5334
|
+
source: "normalized",
|
|
5335
|
+
serviceTier: modelResponseServiceTierFromResponse(data.response),
|
|
5336
|
+
};
|
|
5337
|
+
}
|
|
5338
|
+
}
|
|
5339
|
+
if (isOpenAIResponsesRawModelStreamEvent(event)) {
|
|
5340
|
+
const raw = (event as any).data?.event;
|
|
5341
|
+
if (raw?.type === "response.completed") {
|
|
5342
|
+
return {
|
|
5343
|
+
source: "provider",
|
|
5344
|
+
serviceTier: modelResponseServiceTierFromResponse(raw.response),
|
|
5345
|
+
};
|
|
5346
|
+
}
|
|
5347
|
+
}
|
|
5348
|
+
return null;
|
|
5349
|
+
}
|
|
5350
|
+
|
|
5351
|
+
function modelResponseServiceTierFromResponse(response: unknown): string | null {
|
|
5352
|
+
if (!response || typeof response !== "object") {
|
|
5353
|
+
return null;
|
|
5354
|
+
}
|
|
5355
|
+
const record = response as Record<string, unknown>;
|
|
5356
|
+
const direct = record.service_tier ?? record.serviceTier;
|
|
5357
|
+
if (typeof direct === "string" && direct.length > 0) {
|
|
5358
|
+
return direct;
|
|
5359
|
+
}
|
|
5360
|
+
const providerData =
|
|
5361
|
+
record.providerData && typeof record.providerData === "object"
|
|
5362
|
+
? (record.providerData as Record<string, unknown>)
|
|
5363
|
+
: null;
|
|
5364
|
+
const nested = providerData?.service_tier ?? providerData?.serviceTier;
|
|
5365
|
+
return typeof nested === "string" && nested.length > 0 ? nested : null;
|
|
5366
|
+
}
|
|
5367
|
+
|
|
5129
5368
|
function modelResponseFromSdkEvent(event: RunStreamEvent): any {
|
|
5130
5369
|
if (event.type === "raw_model_stream_event") {
|
|
5131
5370
|
const data = (event as any).data;
|