@ai-sdk/code-mode 1.0.21 → 1.0.22
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/README.md +1 -1
- package/dist/continuation-capability.js +5 -0
- package/dist/continuation-capability.js.map +1 -1
- package/dist/errors.js +1 -136
- package/dist/errors.js.map +1 -1
- package/dist/host-interrupt.js +2 -14
- package/dist/host-interrupt.js.map +1 -1
- package/dist/index.d.ts +14 -44
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/interrupt-continuation.js +3 -8
- package/dist/interrupt-continuation.js.map +1 -1
- package/dist/run-code-mode.js +543 -2
- package/dist/run-code-mode.js.map +1 -1
- package/dist/tool-invocation.js +9 -23
- package/dist/tool-invocation.js.map +1 -1
- package/dist/utils/serialization.js +4 -0
- package/dist/utils/serialization.js.map +1 -1
- package/package.json +8 -7
- package/src/continuation-capability.ts +18 -0
- package/src/errors.ts +0 -190
- package/src/host-interrupt.ts +2 -31
- package/src/index.ts +1 -1
- package/src/interrupt-continuation.ts +13 -15
- package/src/run-code-mode.ts +706 -3
- package/src/tool-invocation.ts +9 -23
- package/src/types.ts +38 -68
- package/src/utils/serialization.ts +4 -0
- package/dist/runtime/guest-sources.js +0 -350
- package/dist/runtime/guest-sources.js.map +0 -1
- package/dist/runtime/manager.js +0 -794
- package/dist/runtime/manager.js.map +0 -1
- package/dist/runtime/max-workers.js +0 -40
- package/dist/runtime/max-workers.js.map +0 -1
- package/dist/runtime/protocol.js +0 -1
- package/dist/runtime/protocol.js.map +0 -1
- package/dist/runtime/worker-source.d.ts +0 -11
- package/dist/runtime/worker-source.js +0 -2
- package/dist/utils/options.js +0 -76
- package/dist/utils/options.js.map +0 -1
- package/dist/utils/source-cache.js +0 -77
- package/dist/utils/source-cache.js.map +0 -1
- package/src/runtime/guest-sources.ts +0 -351
- package/src/runtime/manager.ts +0 -963
- package/src/runtime/max-workers.ts +0 -65
- package/src/runtime/protocol.ts +0 -74
- package/src/runtime/worker-source.ts +0 -10
- package/src/runtime/worker.ts +0 -607
- package/src/utils/options.ts +0 -83
- package/src/utils/source-cache.ts +0 -91
package/src/run-code-mode.ts
CHANGED
|
@@ -1,5 +1,63 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
1
|
+
import { Buffer } from 'node:buffer';
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
3
|
+
import {
|
|
4
|
+
createRunner,
|
|
5
|
+
createSignedContinuationCodec,
|
|
6
|
+
getHostFunctionContext,
|
|
7
|
+
RunError,
|
|
8
|
+
type HostFunctions,
|
|
9
|
+
type RunInterruption,
|
|
10
|
+
type RunLimits,
|
|
11
|
+
type RunResolution,
|
|
12
|
+
} from 'run';
|
|
13
|
+
import {
|
|
14
|
+
isCodeModeApprovalInterruptPayload,
|
|
15
|
+
normalizeApprovalResolution,
|
|
16
|
+
} from './approval.js';
|
|
17
|
+
import {
|
|
18
|
+
resolveCodeModeContinuationSecurity,
|
|
19
|
+
signCodeModeContinuation,
|
|
20
|
+
verifyCodeModeContinuation,
|
|
21
|
+
} from './continuation-capability.js';
|
|
22
|
+
import {
|
|
23
|
+
CodeModeAbortedError,
|
|
24
|
+
CodeModeBridgeLimitError,
|
|
25
|
+
CodeModeConcurrencyError,
|
|
26
|
+
CodeModeDetachedBridgeRequestError,
|
|
27
|
+
CodeModeError,
|
|
28
|
+
CodeModeProtocolError,
|
|
29
|
+
CodeModeSourceTooLargeError,
|
|
30
|
+
CodeModeTimeoutError,
|
|
31
|
+
CodeModeToolApprovalDeniedError,
|
|
32
|
+
} from './errors.js';
|
|
33
|
+
import { invokeHostTool } from './tool-invocation.js';
|
|
34
|
+
import type {
|
|
35
|
+
CodeModeContinuation,
|
|
36
|
+
CodeModeExecutionPolicy,
|
|
37
|
+
CodeModeInterrupt,
|
|
38
|
+
CodeModeInterruptExecutionContext,
|
|
39
|
+
CodeModeInterruptPayload,
|
|
40
|
+
CodeModePendingInterruption,
|
|
41
|
+
CodeModePendingResolution,
|
|
42
|
+
CodeModeToolExecutionOptions,
|
|
43
|
+
RunCodeModeInput,
|
|
44
|
+
} from './types.js';
|
|
45
|
+
import { fromJsonPayload, toJsonPayload } from './utils/serialization.js';
|
|
46
|
+
|
|
47
|
+
const SOURCE_LINE_OFFSET = 1;
|
|
48
|
+
const MAX_RUN_LIMIT = 2_147_483_647;
|
|
49
|
+
const DEFAULT_TIMEOUT_MS = 30_000;
|
|
50
|
+
const DEFAULT_MEMORY_LIMIT_BYTES = 64 * 1024 * 1024;
|
|
51
|
+
const DEFAULT_STACK_LIMIT_BYTES = 2 * 1024 * 1024;
|
|
52
|
+
const DEFAULT_MAX_RESULT_BYTES = 1024 * 1024;
|
|
53
|
+
const DEFAULT_MAX_CONSOLE_OUTPUT_BYTES = 64 * 1024;
|
|
54
|
+
const DEFAULT_MAX_SOURCE_BYTES = 256 * 1024;
|
|
55
|
+
const DEFAULT_MAX_TOOL_INPUT_BYTES = 1024 * 1024;
|
|
56
|
+
const DEFAULT_MAX_TOOL_OUTPUT_BYTES = 4 * 1024 * 1024;
|
|
57
|
+
const DEFAULT_MAX_BRIDGE_REQUESTS = 256;
|
|
58
|
+
const DEFAULT_MAX_IN_FLIGHT_BRIDGE_REQUESTS = 32;
|
|
59
|
+
|
|
60
|
+
let invocationCounter = 0;
|
|
3
61
|
|
|
4
62
|
/**
|
|
5
63
|
* Runs one code-mode invocation directly without wrapping it as an AI SDK tool.
|
|
@@ -11,5 +69,650 @@ import type { RunCodeModeInput } from './types.js';
|
|
|
11
69
|
* @returns The JSON-serializable value returned by the sandboxed program.
|
|
12
70
|
*/
|
|
13
71
|
export async function runCodeMode(input: RunCodeModeInput): Promise<unknown> {
|
|
14
|
-
|
|
72
|
+
const policy = resolveExecutionPolicy(input.options?.executionPolicy);
|
|
73
|
+
assertSourceSize(input.js, policy.maxSourceBytes);
|
|
74
|
+
|
|
75
|
+
const toolNames = Object.keys(input.tools).sort();
|
|
76
|
+
const continuationState = prepareContinuation(
|
|
77
|
+
input,
|
|
78
|
+
toolNames,
|
|
79
|
+
policy.maxToolOutputBytes,
|
|
80
|
+
);
|
|
81
|
+
if (continuationState.nextInterrupt !== undefined) {
|
|
82
|
+
return continuationState.nextInterrupt;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const outerToolCallId =
|
|
86
|
+
input.toolExecutionOptions?.toolCallId ??
|
|
87
|
+
input.continuation?.outerToolCallId ??
|
|
88
|
+
`code-mode-${++invocationCounter}`;
|
|
89
|
+
const source = createCodeModeSource(input.js, toolNames);
|
|
90
|
+
const codeModeErrors: CodeModeError[] = [];
|
|
91
|
+
const hostFunctions = createHostFunctions({
|
|
92
|
+
codeModeErrors,
|
|
93
|
+
input,
|
|
94
|
+
outerToolCallId,
|
|
95
|
+
policy,
|
|
96
|
+
toolNames,
|
|
97
|
+
});
|
|
98
|
+
const continuationSecurity = resolveCodeModeContinuationSecurity(
|
|
99
|
+
input.options?.continuationSecurity,
|
|
100
|
+
);
|
|
101
|
+
const runner = createRunner({
|
|
102
|
+
continuationAudience: '@ai-sdk/code-mode/v2',
|
|
103
|
+
continuationCodec: createSignedContinuationCodec({
|
|
104
|
+
maxAgeMs: continuationSecurity.maxAgeMs,
|
|
105
|
+
secret: createHash('sha256')
|
|
106
|
+
.update(continuationSecurity.signingKey)
|
|
107
|
+
.digest(),
|
|
108
|
+
}),
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
try {
|
|
112
|
+
const result = await runner.run({
|
|
113
|
+
source,
|
|
114
|
+
hostFunctions,
|
|
115
|
+
limits: toRunLimits(policy, source, input.js),
|
|
116
|
+
continuationContext: { outerToolCallId, toolNames },
|
|
117
|
+
...(input.toolExecutionOptions?.abortSignal === undefined
|
|
118
|
+
? {}
|
|
119
|
+
: { abortSignal: input.toolExecutionOptions.abortSignal }),
|
|
120
|
+
...(continuationState.token === undefined
|
|
121
|
+
? {}
|
|
122
|
+
: { continuation: continuationState.token }),
|
|
123
|
+
...(continuationState.resolutions === undefined
|
|
124
|
+
? {}
|
|
125
|
+
: { resolutions: continuationState.resolutions }),
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
if (result.status === 'completed') {
|
|
129
|
+
return fromJsonPayload(
|
|
130
|
+
toJsonPayload(result.value, policy.maxResultBytes, 'Code mode result'),
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const pendingInterruptions = toPendingInterruptions({
|
|
135
|
+
interruptions: result.interruptions,
|
|
136
|
+
outerToolCallId,
|
|
137
|
+
toolNames,
|
|
138
|
+
});
|
|
139
|
+
const continuation = signCodeModeContinuation(
|
|
140
|
+
{
|
|
141
|
+
version: 2,
|
|
142
|
+
js: input.js,
|
|
143
|
+
outerToolCallId,
|
|
144
|
+
toolNames,
|
|
145
|
+
token: result.continuation,
|
|
146
|
+
pendingInterruptions,
|
|
147
|
+
resolutions: [],
|
|
148
|
+
},
|
|
149
|
+
continuationSecurity,
|
|
150
|
+
);
|
|
151
|
+
return toCodeModeInterrupt(continuation, 0);
|
|
152
|
+
} catch (error) {
|
|
153
|
+
const preserved = findPreservedCodeModeError(error, codeModeErrors);
|
|
154
|
+
if (preserved !== undefined) {
|
|
155
|
+
throw translateSourceStack(preserved);
|
|
156
|
+
}
|
|
157
|
+
throw translateSourceStack(toCodeModeRuntimeError(error));
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
interface ResolvedExecutionPolicy {
|
|
162
|
+
timeoutMs: number;
|
|
163
|
+
memoryLimitBytes: number;
|
|
164
|
+
maxStackSizeBytes: number;
|
|
165
|
+
maxResultBytes: number;
|
|
166
|
+
maxConsoleOutputBytes: number;
|
|
167
|
+
maxSourceBytes: number;
|
|
168
|
+
maxToolInputBytes: number;
|
|
169
|
+
maxToolOutputBytes: number;
|
|
170
|
+
maxBridgeRequests: number;
|
|
171
|
+
maxInFlightBridgeRequests: number;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function resolveExecutionPolicy(
|
|
175
|
+
policy: CodeModeExecutionPolicy = {},
|
|
176
|
+
): ResolvedExecutionPolicy {
|
|
177
|
+
return {
|
|
178
|
+
timeoutMs: policy.timeoutMs ?? DEFAULT_TIMEOUT_MS,
|
|
179
|
+
memoryLimitBytes: policy.memoryLimitBytes ?? DEFAULT_MEMORY_LIMIT_BYTES,
|
|
180
|
+
maxStackSizeBytes: policy.maxStackSizeBytes ?? DEFAULT_STACK_LIMIT_BYTES,
|
|
181
|
+
maxResultBytes: policy.maxResultBytes ?? DEFAULT_MAX_RESULT_BYTES,
|
|
182
|
+
maxConsoleOutputBytes:
|
|
183
|
+
policy.maxConsoleOutputBytes ?? DEFAULT_MAX_CONSOLE_OUTPUT_BYTES,
|
|
184
|
+
maxSourceBytes: policy.maxSourceBytes ?? DEFAULT_MAX_SOURCE_BYTES,
|
|
185
|
+
maxToolInputBytes: policy.maxToolInputBytes ?? DEFAULT_MAX_TOOL_INPUT_BYTES,
|
|
186
|
+
maxToolOutputBytes:
|
|
187
|
+
policy.maxToolOutputBytes ?? DEFAULT_MAX_TOOL_OUTPUT_BYTES,
|
|
188
|
+
maxBridgeRequests: policy.maxBridgeRequests ?? DEFAULT_MAX_BRIDGE_REQUESTS,
|
|
189
|
+
maxInFlightBridgeRequests:
|
|
190
|
+
policy.maxInFlightBridgeRequests ?? DEFAULT_MAX_IN_FLIGHT_BRIDGE_REQUESTS,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function assertSourceSize(source: string, maxBytes: number): void {
|
|
195
|
+
if (!isValidRunLimit(maxBytes)) {
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
const bytes = Buffer.byteLength(source);
|
|
199
|
+
if (bytes > maxBytes) {
|
|
200
|
+
throw new CodeModeSourceTooLargeError(bytes, maxBytes);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function createCodeModeSource(js: string, toolNames: string[]): string {
|
|
205
|
+
const bindings = Object.fromEntries(
|
|
206
|
+
toolNames.map((toolName, index) => [toolName, `__codeMode.tool${index}`]),
|
|
207
|
+
);
|
|
208
|
+
const bindingSource = Object.entries(bindings)
|
|
209
|
+
.map(([name, reference]) => `${JSON.stringify(name)}:${reference}`)
|
|
210
|
+
.join(',');
|
|
211
|
+
return `const __codeModeBindings={${bindingSource}};const tools=new Proxy(Object.create(null),{get(_target,name){const binding=__codeModeBindings[name];return typeof binding==="function"?(input)=>binding(input):(input)=>__codeMode.missing(String(name),input);}});const __codeModeResult=await(async()=>{\n${js}\n})();if(__codeModeResult===undefined)return undefined;return JSON.parse(JSON.stringify(__codeModeResult));`;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function createHostFunctions({
|
|
215
|
+
codeModeErrors,
|
|
216
|
+
input,
|
|
217
|
+
outerToolCallId,
|
|
218
|
+
policy,
|
|
219
|
+
toolNames,
|
|
220
|
+
}: {
|
|
221
|
+
codeModeErrors: CodeModeError[];
|
|
222
|
+
input: RunCodeModeInput;
|
|
223
|
+
outerToolCallId: string;
|
|
224
|
+
policy: ResolvedExecutionPolicy;
|
|
225
|
+
toolNames: string[];
|
|
226
|
+
}): HostFunctions {
|
|
227
|
+
const group: Record<string, (...args: unknown[]) => Promise<unknown>> = {};
|
|
228
|
+
for (const [index, toolName] of toolNames.entries()) {
|
|
229
|
+
group[`tool${index}`] = async (toolInput: unknown) =>
|
|
230
|
+
await invokeCodeModeTool({
|
|
231
|
+
codeModeErrors,
|
|
232
|
+
input,
|
|
233
|
+
outerToolCallId,
|
|
234
|
+
policy,
|
|
235
|
+
toolInput,
|
|
236
|
+
toolName,
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
group.missing = async (toolName: unknown, toolInput: unknown) =>
|
|
240
|
+
await invokeCodeModeTool({
|
|
241
|
+
codeModeErrors,
|
|
242
|
+
input,
|
|
243
|
+
outerToolCallId,
|
|
244
|
+
policy,
|
|
245
|
+
toolInput,
|
|
246
|
+
toolName: String(toolName),
|
|
247
|
+
});
|
|
248
|
+
return { __codeMode: group } as HostFunctions;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
async function invokeCodeModeTool({
|
|
252
|
+
codeModeErrors,
|
|
253
|
+
input,
|
|
254
|
+
outerToolCallId,
|
|
255
|
+
policy,
|
|
256
|
+
toolInput,
|
|
257
|
+
toolName,
|
|
258
|
+
}: {
|
|
259
|
+
codeModeErrors: CodeModeError[];
|
|
260
|
+
input: RunCodeModeInput;
|
|
261
|
+
outerToolCallId: string;
|
|
262
|
+
policy: ResolvedExecutionPolicy;
|
|
263
|
+
toolInput: unknown;
|
|
264
|
+
toolName: string;
|
|
265
|
+
}): Promise<unknown> {
|
|
266
|
+
const context = getHostFunctionContext();
|
|
267
|
+
const toolCallId = `${outerToolCallId}:tool-${context.requestIndex}`;
|
|
268
|
+
const forwardedContext =
|
|
269
|
+
input.toolExecutionOptions?.context ??
|
|
270
|
+
input.toolExecutionOptions?.experimental_context;
|
|
271
|
+
const forwardedExperimentalContext =
|
|
272
|
+
input.toolExecutionOptions?.experimental_context ??
|
|
273
|
+
input.toolExecutionOptions?.context;
|
|
274
|
+
const baseExecutionOptions: CodeModeToolExecutionOptions = {
|
|
275
|
+
toolCallId: outerToolCallId,
|
|
276
|
+
messages: input.toolExecutionOptions?.messages ?? [],
|
|
277
|
+
abortSignal: context.abortSignal,
|
|
278
|
+
...(forwardedContext === undefined ? {} : { context: forwardedContext }),
|
|
279
|
+
...(forwardedExperimentalContext === undefined
|
|
280
|
+
? {}
|
|
281
|
+
: { experimental_context: forwardedExperimentalContext }),
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
let codeModeInterrupt: CodeModeInterruptExecutionContext | undefined;
|
|
285
|
+
let skipApproval = false;
|
|
286
|
+
if (context.resume !== undefined) {
|
|
287
|
+
const payload = assertInterruptPayload(context.resume.payload);
|
|
288
|
+
if (isCodeModeApprovalInterruptPayload(payload)) {
|
|
289
|
+
const decision = normalizeApprovalResolution(context.resume.resolution);
|
|
290
|
+
if (!decision.approved) {
|
|
291
|
+
throw new CodeModeToolApprovalDeniedError(
|
|
292
|
+
toolName,
|
|
293
|
+
toolInput,
|
|
294
|
+
toolCallId,
|
|
295
|
+
decision.reason,
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
skipApproval = true;
|
|
299
|
+
} else {
|
|
300
|
+
codeModeInterrupt = {
|
|
301
|
+
interruptId: `${toolCallId}:interrupt`,
|
|
302
|
+
payload,
|
|
303
|
+
resolution: context.resume.resolution,
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
try {
|
|
309
|
+
const inputJson = toJsonPayload(
|
|
310
|
+
toolInput,
|
|
311
|
+
policy.maxToolInputBytes,
|
|
312
|
+
`Tool "${toolName}" input`,
|
|
313
|
+
);
|
|
314
|
+
const outcome = await invokeHostTool({
|
|
315
|
+
toolName,
|
|
316
|
+
inputJson,
|
|
317
|
+
tools: input.tools,
|
|
318
|
+
baseExecutionOptions,
|
|
319
|
+
codeModeOptions: input.options ?? {},
|
|
320
|
+
maxToolInputBytes: policy.maxToolInputBytes,
|
|
321
|
+
maxToolOutputBytes: policy.maxToolOutputBytes,
|
|
322
|
+
toolCallId,
|
|
323
|
+
...(codeModeInterrupt === undefined ? {} : { codeModeInterrupt }),
|
|
324
|
+
skipApproval,
|
|
325
|
+
});
|
|
326
|
+
if (outcome.type === 'interrupted') {
|
|
327
|
+
return context.interrupt(outcome.payload);
|
|
328
|
+
}
|
|
329
|
+
return fromJsonPayload(outcome.valueJson);
|
|
330
|
+
} catch (error) {
|
|
331
|
+
if (error instanceof CodeModeError) {
|
|
332
|
+
codeModeErrors.push(error);
|
|
333
|
+
throw new RunError(error.message, error.code, error.details);
|
|
334
|
+
}
|
|
335
|
+
if (
|
|
336
|
+
RunError.isInstance(error) ||
|
|
337
|
+
(error instanceof Error && error.name === 'HostFunctionInterruptSignal')
|
|
338
|
+
) {
|
|
339
|
+
throw error;
|
|
340
|
+
}
|
|
341
|
+
throw new RunError('Host tool failed.', 'CODE_MODE_HOST_TOOL_ERROR');
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function assertInterruptPayload(value: unknown): CodeModeInterruptPayload {
|
|
346
|
+
if (
|
|
347
|
+
typeof value !== 'object' ||
|
|
348
|
+
value === null ||
|
|
349
|
+
Array.isArray(value) ||
|
|
350
|
+
typeof (value as { kind?: unknown }).kind !== 'string'
|
|
351
|
+
) {
|
|
352
|
+
throw new CodeModeProtocolError(
|
|
353
|
+
'Code mode interruption payload is malformed.',
|
|
354
|
+
);
|
|
355
|
+
}
|
|
356
|
+
return value as CodeModeInterruptPayload;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
function toRunLimits(
|
|
360
|
+
policy: ResolvedExecutionPolicy,
|
|
361
|
+
source: string,
|
|
362
|
+
userSource: string,
|
|
363
|
+
): RunLimits {
|
|
364
|
+
return {
|
|
365
|
+
timeoutMs: policy.timeoutMs,
|
|
366
|
+
memoryLimitBytes: policy.memoryLimitBytes,
|
|
367
|
+
maxStackSizeBytes: policy.maxStackSizeBytes,
|
|
368
|
+
maxResultBytes: expandedSerializationLimit(policy.maxResultBytes),
|
|
369
|
+
maxConsoleOutputBytes: policy.maxConsoleOutputBytes,
|
|
370
|
+
maxSourceBytes: withSerializationOverhead(
|
|
371
|
+
policy.maxSourceBytes,
|
|
372
|
+
Buffer.byteLength(source) - Buffer.byteLength(userSource),
|
|
373
|
+
),
|
|
374
|
+
maxHostFunctionArgumentsBytes: expandedSerializationLimit(
|
|
375
|
+
policy.maxToolInputBytes,
|
|
376
|
+
),
|
|
377
|
+
maxHostFunctionOutputBytes: expandedSerializationLimit(
|
|
378
|
+
policy.maxToolOutputBytes,
|
|
379
|
+
),
|
|
380
|
+
maxBridgeRequests: policy.maxBridgeRequests,
|
|
381
|
+
maxInFlightBridgeRequests: policy.maxInFlightBridgeRequests,
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
function expandedSerializationLimit(value: number): number {
|
|
386
|
+
if (!isValidRunLimit(value)) {
|
|
387
|
+
return value;
|
|
388
|
+
}
|
|
389
|
+
return Math.min(MAX_RUN_LIMIT, Math.max(4096, value * 2 + 1024));
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
function withSerializationOverhead(value: number, overhead: number): number {
|
|
393
|
+
return isValidRunLimit(value)
|
|
394
|
+
? Math.min(MAX_RUN_LIMIT, value + overhead)
|
|
395
|
+
: value;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
function isValidRunLimit(value: number): boolean {
|
|
399
|
+
return Number.isInteger(value) && value > 0 && value <= MAX_RUN_LIMIT;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
function prepareContinuation(
|
|
403
|
+
input: RunCodeModeInput,
|
|
404
|
+
toolNames: string[],
|
|
405
|
+
maxToolOutputBytes: number,
|
|
406
|
+
): {
|
|
407
|
+
token?: string;
|
|
408
|
+
resolutions?: RunResolution[];
|
|
409
|
+
nextInterrupt?: CodeModeInterrupt;
|
|
410
|
+
} {
|
|
411
|
+
if (input.continuation === undefined) {
|
|
412
|
+
if (input.interruptResolution !== undefined) {
|
|
413
|
+
throw new CodeModeProtocolError(
|
|
414
|
+
'A code-mode interrupt resolution was provided without continuation state.',
|
|
415
|
+
);
|
|
416
|
+
}
|
|
417
|
+
return {};
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
verifyCodeModeContinuation(
|
|
421
|
+
input.continuation,
|
|
422
|
+
input.options?.continuationSecurity,
|
|
423
|
+
);
|
|
424
|
+
if (input.continuation.js !== input.js) {
|
|
425
|
+
throw new CodeModeProtocolError(
|
|
426
|
+
'Code mode continuation source does not match the resumed source.',
|
|
427
|
+
);
|
|
428
|
+
}
|
|
429
|
+
if (!stringArraysEqual(input.continuation.toolNames, toolNames)) {
|
|
430
|
+
throw new CodeModeProtocolError(
|
|
431
|
+
'Code mode continuation tool names do not match the resumed tools.',
|
|
432
|
+
);
|
|
433
|
+
}
|
|
434
|
+
if (input.interruptResolution === undefined) {
|
|
435
|
+
throw new CodeModeProtocolError(
|
|
436
|
+
'A code-mode continuation requires an interrupt resolution.',
|
|
437
|
+
);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
const resolutionIndex = input.continuation.resolutions.length;
|
|
441
|
+
const pending = input.continuation.pendingInterruptions[resolutionIndex];
|
|
442
|
+
if (
|
|
443
|
+
pending === undefined ||
|
|
444
|
+
pending.interruptId !== input.interruptResolution.interruptId
|
|
445
|
+
) {
|
|
446
|
+
throw new CodeModeProtocolError(
|
|
447
|
+
'Interrupt resolution does not match the next pending code-mode interruption.',
|
|
448
|
+
{ interruptId: input.interruptResolution.interruptId },
|
|
449
|
+
);
|
|
450
|
+
}
|
|
451
|
+
const resolutionValue = normalizeResolutionForPending(
|
|
452
|
+
pending,
|
|
453
|
+
input.interruptResolution.resolution,
|
|
454
|
+
maxToolOutputBytes,
|
|
455
|
+
);
|
|
456
|
+
const resolutions: CodeModePendingResolution[] = [
|
|
457
|
+
...input.continuation.resolutions,
|
|
458
|
+
{
|
|
459
|
+
runInterruptionId: pending.runInterruptionId,
|
|
460
|
+
value: resolutionValue,
|
|
461
|
+
},
|
|
462
|
+
];
|
|
463
|
+
|
|
464
|
+
if (resolutions.length < input.continuation.pendingInterruptions.length) {
|
|
465
|
+
const continuation = signCodeModeContinuation(
|
|
466
|
+
{
|
|
467
|
+
...withoutAuth(input.continuation),
|
|
468
|
+
resolutions,
|
|
469
|
+
},
|
|
470
|
+
resolveCodeModeContinuationSecurity(input.options?.continuationSecurity),
|
|
471
|
+
);
|
|
472
|
+
return {
|
|
473
|
+
nextInterrupt: toCodeModeInterrupt(continuation, resolutions.length),
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
assertNoDeniedApproval(input.continuation.pendingInterruptions, resolutions);
|
|
478
|
+
return {
|
|
479
|
+
token: input.continuation.token,
|
|
480
|
+
resolutions: resolutions.map(resolution => ({
|
|
481
|
+
interruptionId: resolution.runInterruptionId,
|
|
482
|
+
value: resolution.value,
|
|
483
|
+
})),
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
function normalizeResolutionForPending(
|
|
488
|
+
pending: CodeModePendingInterruption,
|
|
489
|
+
resolution: unknown,
|
|
490
|
+
maxToolOutputBytes: number,
|
|
491
|
+
): unknown {
|
|
492
|
+
return isCodeModeApprovalInterruptPayload(pending.payload)
|
|
493
|
+
? normalizeApprovalResolution(resolution)
|
|
494
|
+
: fromJsonPayload(
|
|
495
|
+
toJsonPayload(
|
|
496
|
+
resolution,
|
|
497
|
+
maxToolOutputBytes,
|
|
498
|
+
`Resolution "${pending.interruptId}"`,
|
|
499
|
+
),
|
|
500
|
+
);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
function assertNoDeniedApproval(
|
|
504
|
+
pendingInterruptions: CodeModePendingInterruption[],
|
|
505
|
+
resolutions: CodeModePendingResolution[],
|
|
506
|
+
): void {
|
|
507
|
+
for (const [index, pending] of pendingInterruptions.entries()) {
|
|
508
|
+
if (!isCodeModeApprovalInterruptPayload(pending.payload)) {
|
|
509
|
+
continue;
|
|
510
|
+
}
|
|
511
|
+
const decision = normalizeApprovalResolution(resolutions[index]?.value);
|
|
512
|
+
if (!decision.approved) {
|
|
513
|
+
throw new CodeModeToolApprovalDeniedError(
|
|
514
|
+
pending.toolName,
|
|
515
|
+
pending.input,
|
|
516
|
+
pending.toolCallId,
|
|
517
|
+
decision.reason,
|
|
518
|
+
);
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
function withoutAuth(
|
|
524
|
+
continuation: CodeModeContinuation,
|
|
525
|
+
): Omit<CodeModeContinuation, 'auth'> {
|
|
526
|
+
const { auth: _auth, ...unsigned } = continuation;
|
|
527
|
+
return unsigned;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
function toPendingInterruptions({
|
|
531
|
+
interruptions,
|
|
532
|
+
outerToolCallId,
|
|
533
|
+
toolNames,
|
|
534
|
+
}: {
|
|
535
|
+
interruptions: RunInterruption[];
|
|
536
|
+
outerToolCallId: string;
|
|
537
|
+
toolNames: string[];
|
|
538
|
+
}): CodeModePendingInterruption[] {
|
|
539
|
+
return interruptions.map(interruption => {
|
|
540
|
+
const requestIndex = interruptionIndex(interruption.id);
|
|
541
|
+
const toolName = toolNameForInterruption(interruption, toolNames);
|
|
542
|
+
const toolCallId = `${outerToolCallId}:tool-${requestIndex}`;
|
|
543
|
+
return {
|
|
544
|
+
runInterruptionId: interruption.id,
|
|
545
|
+
interruptId: `${toolCallId}:interrupt`,
|
|
546
|
+
toolName,
|
|
547
|
+
toolCallId,
|
|
548
|
+
input:
|
|
549
|
+
interruption.hostFunctionName === '__codeMode.missing'
|
|
550
|
+
? interruption.arguments[1]
|
|
551
|
+
: interruption.arguments[0],
|
|
552
|
+
payload: assertInterruptPayload(interruption.payload),
|
|
553
|
+
};
|
|
554
|
+
});
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
function interruptionIndex(interruptionId: string): number {
|
|
558
|
+
const match = /^interrupt-(\d+)$/u.exec(interruptionId);
|
|
559
|
+
if (match === null) {
|
|
560
|
+
throw new CodeModeProtocolError(
|
|
561
|
+
`Run returned malformed interruption id "${interruptionId}".`,
|
|
562
|
+
);
|
|
563
|
+
}
|
|
564
|
+
return Number(match[1]);
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
function toolNameForInterruption(
|
|
568
|
+
interruption: RunInterruption,
|
|
569
|
+
toolNames: string[],
|
|
570
|
+
): string {
|
|
571
|
+
if (interruption.hostFunctionName === '__codeMode.missing') {
|
|
572
|
+
return String(interruption.arguments[0]);
|
|
573
|
+
}
|
|
574
|
+
const match = /^__codeMode\.tool(\d+)$/u.exec(interruption.hostFunctionName);
|
|
575
|
+
const toolName = match === null ? undefined : toolNames[Number(match[1])];
|
|
576
|
+
if (toolName === undefined) {
|
|
577
|
+
throw new CodeModeProtocolError(
|
|
578
|
+
`Run returned an unknown code-mode host function "${interruption.hostFunctionName}".`,
|
|
579
|
+
);
|
|
580
|
+
}
|
|
581
|
+
return toolName;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
function toCodeModeInterrupt(
|
|
585
|
+
continuation: CodeModeContinuation,
|
|
586
|
+
index: number,
|
|
587
|
+
): CodeModeInterrupt {
|
|
588
|
+
const pending = continuation.pendingInterruptions[index];
|
|
589
|
+
if (pending === undefined) {
|
|
590
|
+
throw new CodeModeProtocolError(
|
|
591
|
+
'Code mode continuation has no pending interruption at the requested index.',
|
|
592
|
+
);
|
|
593
|
+
}
|
|
594
|
+
return {
|
|
595
|
+
type: 'code-mode-interrupt',
|
|
596
|
+
interruptId: pending.interruptId,
|
|
597
|
+
toolName: pending.toolName,
|
|
598
|
+
toolCallId: pending.toolCallId,
|
|
599
|
+
outerToolCallId: continuation.outerToolCallId,
|
|
600
|
+
input: structuredClone(pending.input),
|
|
601
|
+
payload: structuredClone(pending.payload),
|
|
602
|
+
continuation,
|
|
603
|
+
};
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
function stringArraysEqual(left: string[], right: string[]): boolean {
|
|
607
|
+
return (
|
|
608
|
+
left.length === right.length &&
|
|
609
|
+
left.every((value, index) => value === right[index])
|
|
610
|
+
);
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
function findPreservedCodeModeError(
|
|
614
|
+
error: unknown,
|
|
615
|
+
errors: CodeModeError[],
|
|
616
|
+
): CodeModeError | undefined {
|
|
617
|
+
if (!RunError.isInstance(error)) {
|
|
618
|
+
return undefined;
|
|
619
|
+
}
|
|
620
|
+
return errors.find(candidate => candidate.code === error.code);
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
function toCodeModeRuntimeError(error: unknown): unknown {
|
|
624
|
+
if (error instanceof TypeError) {
|
|
625
|
+
error.message = translateLimitPath(error.message);
|
|
626
|
+
}
|
|
627
|
+
if (
|
|
628
|
+
error instanceof Error &&
|
|
629
|
+
error.name === 'SyntaxError' &&
|
|
630
|
+
!/syntax|unexpected|expression expected/iu.test(error.message)
|
|
631
|
+
) {
|
|
632
|
+
error.message = `Syntax error: ${error.message}`;
|
|
633
|
+
}
|
|
634
|
+
if (!RunError.isInstance(error)) {
|
|
635
|
+
return error;
|
|
636
|
+
}
|
|
637
|
+
const details = error.details as Record<string, unknown> | undefined;
|
|
638
|
+
switch (error.code) {
|
|
639
|
+
case 'RUN_ABORTED':
|
|
640
|
+
return copyStack(new CodeModeAbortedError(), error);
|
|
641
|
+
case 'RUN_TIMEOUT':
|
|
642
|
+
return copyStack(
|
|
643
|
+
new CodeModeTimeoutError(Number(details?.timeoutMs ?? 0)),
|
|
644
|
+
error,
|
|
645
|
+
);
|
|
646
|
+
case 'RUN_CONCURRENCY_LIMIT':
|
|
647
|
+
return copyStack(
|
|
648
|
+
new CodeModeConcurrencyError(Number(details?.maxWorkers ?? 0)),
|
|
649
|
+
error,
|
|
650
|
+
);
|
|
651
|
+
case 'RUN_SOURCE_TOO_LARGE':
|
|
652
|
+
return copyStack(
|
|
653
|
+
new CodeModeSourceTooLargeError(
|
|
654
|
+
Number(details?.bytes ?? 0),
|
|
655
|
+
Number(details?.maxBytes ?? 0),
|
|
656
|
+
),
|
|
657
|
+
error,
|
|
658
|
+
);
|
|
659
|
+
case 'RUN_BRIDGE_LIMIT':
|
|
660
|
+
return copyStack(
|
|
661
|
+
new CodeModeBridgeLimitError(error.message, error.details),
|
|
662
|
+
error,
|
|
663
|
+
);
|
|
664
|
+
case 'RUN_DETACHED_BRIDGE_REQUEST':
|
|
665
|
+
return copyStack(
|
|
666
|
+
new CodeModeDetachedBridgeRequestError(error.message, error.details),
|
|
667
|
+
error,
|
|
668
|
+
);
|
|
669
|
+
case 'RUN_PROTOCOL_ERROR':
|
|
670
|
+
return copyStack(
|
|
671
|
+
new CodeModeProtocolError(error.message, error.details),
|
|
672
|
+
error,
|
|
673
|
+
);
|
|
674
|
+
default:
|
|
675
|
+
return error;
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
function translateLimitPath(message: string): string {
|
|
680
|
+
const paths: Record<string, string> = {
|
|
681
|
+
'limits.timeoutMs': 'executionPolicy.timeoutMs',
|
|
682
|
+
'limits.memoryLimitBytes': 'executionPolicy.memoryLimitBytes',
|
|
683
|
+
'limits.maxStackSizeBytes': 'executionPolicy.maxStackSizeBytes',
|
|
684
|
+
'limits.maxResultBytes': 'executionPolicy.maxResultBytes',
|
|
685
|
+
'limits.maxConsoleOutputBytes': 'executionPolicy.maxConsoleOutputBytes',
|
|
686
|
+
'limits.maxSourceBytes': 'executionPolicy.maxSourceBytes',
|
|
687
|
+
'limits.maxHostFunctionArgumentsBytes': 'executionPolicy.maxToolInputBytes',
|
|
688
|
+
'limits.maxHostFunctionOutputBytes': 'executionPolicy.maxToolOutputBytes',
|
|
689
|
+
'limits.maxBridgeRequests': 'executionPolicy.maxBridgeRequests',
|
|
690
|
+
'limits.maxInFlightBridgeRequests':
|
|
691
|
+
'executionPolicy.maxInFlightBridgeRequests',
|
|
692
|
+
};
|
|
693
|
+
for (const [runPath, codeModePath] of Object.entries(paths)) {
|
|
694
|
+
if (message.includes(runPath)) {
|
|
695
|
+
return message.replace(runPath, codeModePath);
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
return message;
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
function copyStack<T extends Error>(target: T, source: Error): T {
|
|
702
|
+
if (source.stack !== undefined) {
|
|
703
|
+
target.stack = source.stack;
|
|
704
|
+
}
|
|
705
|
+
return target;
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
function translateSourceStack<T>(error: T): T {
|
|
709
|
+
if (!(error instanceof Error) || error.stack === undefined) {
|
|
710
|
+
return error;
|
|
711
|
+
}
|
|
712
|
+
error.stack = error.stack.replaceAll(
|
|
713
|
+
/run\.js:(\d+):(\d+)/gu,
|
|
714
|
+
(_match, line: string, column: string) =>
|
|
715
|
+
`code-mode.js:${Math.max(1, Number(line) - SOURCE_LINE_OFFSET)}:${column}`,
|
|
716
|
+
);
|
|
717
|
+
return error;
|
|
15
718
|
}
|