@ai-sdk/code-mode 0.0.1 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/LICENSE +13 -0
  2. package/README.md +99 -0
  3. package/dist/code-mode-tool.js +40 -0
  4. package/dist/code-mode-tool.js.map +1 -0
  5. package/dist/errors.js +192 -0
  6. package/dist/errors.js.map +1 -0
  7. package/dist/index.d.ts +179 -2
  8. package/dist/index.js +32 -1
  9. package/dist/index.js.map +1 -1
  10. package/dist/run-code-mode.js +8 -0
  11. package/dist/run-code-mode.js.map +1 -0
  12. package/dist/runtime/guest-sources.js +260 -0
  13. package/dist/runtime/guest-sources.js.map +1 -0
  14. package/dist/runtime/manager.js +414 -0
  15. package/dist/runtime/manager.js.map +1 -0
  16. package/dist/runtime/max-workers.js +40 -0
  17. package/dist/runtime/max-workers.js.map +1 -0
  18. package/dist/runtime/protocol.js +1 -0
  19. package/dist/runtime/protocol.js.map +1 -0
  20. package/dist/runtime/worker-source.d.ts +11 -0
  21. package/dist/runtime/worker-source.js +2 -0
  22. package/dist/tool-invocation.js +129 -0
  23. package/dist/tool-invocation.js.map +1 -0
  24. package/dist/tool-prompt.js +440 -0
  25. package/dist/tool-prompt.js.map +1 -0
  26. package/dist/types.js +1 -0
  27. package/dist/types.js.map +1 -0
  28. package/dist/utils/options.js +76 -0
  29. package/dist/utils/options.js.map +1 -0
  30. package/dist/utils/serialization.js +49 -0
  31. package/dist/utils/serialization.js.map +1 -0
  32. package/dist/utils/source-cache.js +77 -0
  33. package/dist/utils/source-cache.js.map +1 -0
  34. package/package.json +57 -16
  35. package/src/code-mode-tool.ts +60 -0
  36. package/src/errors.ts +267 -0
  37. package/src/index.ts +26 -0
  38. package/src/run-code-mode.ts +15 -0
  39. package/src/runtime/guest-sources.ts +260 -0
  40. package/src/runtime/manager.ts +498 -0
  41. package/src/runtime/max-workers.ts +65 -0
  42. package/src/runtime/protocol.ts +52 -0
  43. package/src/runtime/worker-source.ts +10 -0
  44. package/src/runtime/worker.ts +497 -0
  45. package/src/tool-invocation.ts +181 -0
  46. package/src/tool-prompt.ts +585 -0
  47. package/src/tsconfig.json +11 -0
  48. package/src/types.ts +111 -0
  49. package/src/utils/options.ts +83 -0
  50. package/src/utils/serialization.ts +66 -0
  51. package/src/utils/source-cache.ts +91 -0
  52. package/dist/index.d.ts.map +0 -1
@@ -0,0 +1,497 @@
1
+ import { writeSync } from 'node:fs';
2
+ import { formatWithOptions } from 'node:util';
3
+ import { parentPort } from 'node:worker_threads';
4
+ import {
5
+ newQuickJSAsyncWASMModule,
6
+ newVariant,
7
+ type QuickJSAsyncContext,
8
+ type QuickJSDeferredPromise,
9
+ type QuickJSHandle,
10
+ RELEASE_ASYNC,
11
+ } from 'quickjs-emscripten';
12
+ import { CodeModeProtocolError, serializeError } from '../errors.js';
13
+ import { assertJsonPayloadSize } from '../utils/serialization.js';
14
+ import { buildGuestRuntimeSetupSource, wrapUserCode } from './guest-sources.js';
15
+ import type {
16
+ MainToWorkerMessage,
17
+ WorkerBridgeResponse,
18
+ WorkerRunMessage,
19
+ } from './protocol.js';
20
+
21
+ if (!parentPort) {
22
+ throw new Error('code-mode worker must run inside a worker thread');
23
+ }
24
+
25
+ const pendingBridgeRequests = new Map<
26
+ string,
27
+ {
28
+ context: QuickJSAsyncContext;
29
+ deferred: QuickJSDeferredPromise;
30
+ invocationId: string;
31
+ }
32
+ >();
33
+ let activeInvocationId: string | undefined;
34
+ let bridgeRequestCounter = 0;
35
+ let embeddedQuickJsWasmModulePromise: Promise<WebAssembly.Module> | undefined;
36
+
37
+ parentPort.on('message', (message: MainToWorkerMessage) => {
38
+ if (message.type === 'bridge-response') {
39
+ const pending = pendingBridgeRequests.get(message.requestId);
40
+ if (!pending) {
41
+ throw new CodeModeProtocolError(
42
+ `Unexpected bridge response requestId: ${message.requestId}.`,
43
+ {
44
+ invocationId: message.invocationId,
45
+ requestId: message.requestId,
46
+ },
47
+ );
48
+ }
49
+
50
+ if (pending.invocationId !== message.invocationId) {
51
+ throw new CodeModeProtocolError(
52
+ `Bridge response invocationId mismatch for request ${message.requestId}: expected ${pending.invocationId}, received ${message.invocationId}.`,
53
+ {
54
+ expectedInvocationId: pending.invocationId,
55
+ receivedInvocationId: message.invocationId,
56
+ requestId: message.requestId,
57
+ },
58
+ );
59
+ }
60
+
61
+ pendingBridgeRequests.delete(message.requestId);
62
+ resolveBridgeResponse(pending.context, pending.deferred, message);
63
+ return;
64
+ }
65
+
66
+ if (message.type === 'run') {
67
+ if (activeInvocationId !== undefined) {
68
+ throw new CodeModeProtocolError(
69
+ `Worker received run ${message.invocationId} while ${activeInvocationId} is still active.`,
70
+ {
71
+ activeInvocationId,
72
+ receivedInvocationId: message.invocationId,
73
+ },
74
+ );
75
+ }
76
+
77
+ activeInvocationId = message.invocationId;
78
+ void run(message).finally(() => {
79
+ activeInvocationId = undefined;
80
+ });
81
+ }
82
+ });
83
+
84
+ async function run(message: WorkerRunMessage): Promise<void> {
85
+ try {
86
+ const valueJson = await execute(message);
87
+ parentPort?.postMessage({
88
+ type: 'result',
89
+ invocationId: message.invocationId,
90
+ success: true,
91
+ valueJson,
92
+ });
93
+ } catch (error) {
94
+ parentPort?.postMessage({
95
+ type: 'result',
96
+ invocationId: message.invocationId,
97
+ success: false,
98
+ error: serializeError(error),
99
+ });
100
+ } finally {
101
+ parentPort?.postMessage({
102
+ type: 'ready',
103
+ invocationId: message.invocationId,
104
+ });
105
+ }
106
+ }
107
+
108
+ async function execute(message: WorkerRunMessage): Promise<string> {
109
+ const context = await createQuickJSContext();
110
+ const runtime = context.runtime;
111
+ const deadline = Date.now() + message.options.timeoutMs;
112
+ let interruptChecks = 0;
113
+ let bridgeFunctions: { invokeTool: QuickJSHandle } | undefined;
114
+
115
+ runtime.setMemoryLimit(message.options.memoryLimitBytes);
116
+ runtime.setMaxStackSize(message.options.maxStackSizeBytes);
117
+ runtime.setInterruptHandler(() => {
118
+ interruptChecks++;
119
+ return interruptChecks > 10_000 || Date.now() > deadline;
120
+ });
121
+
122
+ try {
123
+ installConsole(context, message.options.maxConsoleOutputBytes);
124
+ bridgeFunctions = createBridgeFunctions(context, message);
125
+ const setupSource = buildGuestRuntimeSetupSource();
126
+ const setupEvalResult = await context.evalCodeAsync(
127
+ setupSource,
128
+ 'code-mode-setup.js',
129
+ );
130
+ if (setupEvalResult.error) {
131
+ const error = context.dump(setupEvalResult.error);
132
+ if (setupEvalResult.error.alive) {
133
+ setupEvalResult.error.dispose();
134
+ }
135
+ throw toError(error);
136
+ }
137
+ try {
138
+ const setupCallResult = context.callFunction(
139
+ setupEvalResult.value,
140
+ context.undefined,
141
+ bridgeFunctions.invokeTool,
142
+ );
143
+ if (setupCallResult.error) {
144
+ const error = context.dump(setupCallResult.error);
145
+ if (setupCallResult.error.alive) {
146
+ setupCallResult.error.dispose();
147
+ }
148
+ throw toError(error);
149
+ }
150
+ if (setupCallResult.value.alive) {
151
+ setupCallResult.value.dispose();
152
+ }
153
+ } finally {
154
+ if (setupEvalResult.value.alive) {
155
+ setupEvalResult.value.dispose();
156
+ }
157
+ }
158
+
159
+ const wrapped = wrapUserCode(message.js);
160
+ const evalResult = await context.evalCodeAsync(wrapped, 'code-mode.js');
161
+
162
+ if (evalResult.error) {
163
+ const error = context.dump(evalResult.error);
164
+ if (evalResult.error.alive) {
165
+ evalResult.error.dispose();
166
+ }
167
+ throw toError(error);
168
+ }
169
+
170
+ if (evalResult.value.alive) {
171
+ evalResult.value.dispose();
172
+ }
173
+
174
+ const promiseHandle = context.getProp(context.global, '__codeModeResult');
175
+ const resolvedResult = await resolveQuickJSPromise(context, promiseHandle);
176
+ if (promiseHandle.alive) {
177
+ promiseHandle.dispose();
178
+ }
179
+
180
+ if (resolvedResult.error) {
181
+ const error = context.dump(resolvedResult.error);
182
+ if (resolvedResult.error.alive) {
183
+ resolvedResult.error.dispose();
184
+ }
185
+ throw toError(error);
186
+ }
187
+
188
+ const valueJson = serializeQuickJSJsonPayload(
189
+ context,
190
+ resolvedResult.value,
191
+ );
192
+ if (resolvedResult.value.alive) {
193
+ resolvedResult.value.dispose();
194
+ }
195
+ assertJsonPayloadSize(
196
+ valueJson,
197
+ message.options.maxResultBytes,
198
+ 'Code mode result',
199
+ );
200
+ return valueJson;
201
+ } finally {
202
+ for (const pending of pendingBridgeRequests.values()) {
203
+ const error = pending.context.newError(
204
+ 'Worker execution finished before bridge response',
205
+ );
206
+ pending.deferred.reject(error);
207
+ error.dispose();
208
+ }
209
+ pendingBridgeRequests.clear();
210
+ if (bridgeFunctions?.invokeTool.alive) {
211
+ bridgeFunctions.invokeTool.dispose();
212
+ }
213
+ context.dispose();
214
+ }
215
+ }
216
+
217
+ async function createQuickJSContext(): Promise<QuickJSAsyncContext> {
218
+ const embeddedWasmBase64 = getEmbeddedQuickJsWasmBase64();
219
+ if (embeddedWasmBase64 !== undefined) {
220
+ const variant = newVariant(RELEASE_ASYNC, {
221
+ wasmModule: () => getEmbeddedQuickJsWasmModule(embeddedWasmBase64),
222
+ });
223
+ return (await newQuickJSAsyncWASMModule(variant)).newContext();
224
+ }
225
+
226
+ return (await newQuickJSAsyncWASMModule(RELEASE_ASYNC)).newContext();
227
+ }
228
+
229
+ function getEmbeddedQuickJsWasmBase64(): string | undefined {
230
+ return (
231
+ globalThis as typeof globalThis & {
232
+ __CODE_MODE_QUICKJS_WASM_BASE64__?: unknown;
233
+ }
234
+ ).__CODE_MODE_QUICKJS_WASM_BASE64__ as string | undefined;
235
+ }
236
+
237
+ function getEmbeddedQuickJsWasmModule(
238
+ wasmBase64: string,
239
+ ): Promise<WebAssembly.Module> {
240
+ embeddedQuickJsWasmModulePromise ??= WebAssembly.compile(
241
+ decodeBase64ArrayBuffer(wasmBase64),
242
+ );
243
+ return embeddedQuickJsWasmModulePromise;
244
+ }
245
+
246
+ function decodeBase64ArrayBuffer(value: string): ArrayBuffer {
247
+ const bytes = Buffer.from(value, 'base64');
248
+ const copy = new Uint8Array(bytes.byteLength);
249
+ copy.set(bytes);
250
+ return copy.buffer as ArrayBuffer;
251
+ }
252
+
253
+ function serializeQuickJSJsonPayload(
254
+ context: QuickJSAsyncContext,
255
+ value: QuickJSHandle,
256
+ ): string {
257
+ const serialize = context.getProp(
258
+ context.global,
259
+ '__codeModeSerializeJsonPayload',
260
+ );
261
+ try {
262
+ const result = context.callFunction(serialize, context.undefined, value);
263
+ if (result.error) {
264
+ const error = context.dump(result.error);
265
+ if (result.error.alive) {
266
+ result.error.dispose();
267
+ }
268
+ throw toError(error);
269
+ }
270
+ const valueJson = context.getString(result.value);
271
+ if (result.value.alive) {
272
+ result.value.dispose();
273
+ }
274
+ return valueJson;
275
+ } finally {
276
+ if (serialize.alive) {
277
+ serialize.dispose();
278
+ }
279
+ }
280
+ }
281
+
282
+ function installConsole(
283
+ context: QuickJSAsyncContext,
284
+ maxOutputBytes: number,
285
+ ): void {
286
+ const outputBudget = { remainingBytes: maxOutputBytes };
287
+ const consoleHandle = context.newObject();
288
+ const handles = [
289
+ ['log', createConsoleFunction(context, 'stdout', outputBudget)],
290
+ ['info', createConsoleFunction(context, 'stdout', outputBudget)],
291
+ ['debug', createConsoleFunction(context, 'stdout', outputBudget)],
292
+ ['error', createConsoleFunction(context, 'stderr', outputBudget)],
293
+ ] as const;
294
+
295
+ for (const [name, handle] of handles) {
296
+ context.setProp(consoleHandle, name, handle);
297
+ }
298
+ context.setProp(context.global, 'console', consoleHandle);
299
+
300
+ for (const [, handle] of handles) {
301
+ handle.dispose();
302
+ }
303
+ consoleHandle.dispose();
304
+ }
305
+
306
+ function createConsoleFunction(
307
+ context: QuickJSAsyncContext,
308
+ stream: 'stdout' | 'stderr',
309
+ outputBudget: { remainingBytes: number },
310
+ ): QuickJSHandle {
311
+ return context.newFunction('console', (...args: QuickJSHandle[]) => {
312
+ if (outputBudget.remainingBytes === 0) {
313
+ return;
314
+ }
315
+ const values = args.map(arg => dumpConsoleArg(context, arg));
316
+ const line = formatWithOptions({ colors: false, depth: 4 }, ...values);
317
+ const output = `${line}\n`;
318
+ const outputBytes = Buffer.byteLength(output);
319
+ if (outputBytes > outputBudget.remainingBytes) {
320
+ outputBudget.remainingBytes = 0;
321
+ return;
322
+ }
323
+ outputBudget.remainingBytes -= outputBytes;
324
+ writeSync(stream === 'stderr' ? 2 : 1, output);
325
+ });
326
+ }
327
+
328
+ function dumpConsoleArg(
329
+ context: QuickJSAsyncContext,
330
+ arg: QuickJSHandle,
331
+ ): unknown {
332
+ try {
333
+ return context.dump(arg);
334
+ } catch {
335
+ return '[Unprintable QuickJS value]';
336
+ }
337
+ }
338
+
339
+ function createBridgeFunctions(
340
+ context: QuickJSAsyncContext,
341
+ message: WorkerRunMessage,
342
+ ): { invokeTool: QuickJSHandle } {
343
+ const invokeTool = context.newFunction(
344
+ '__codeModeInvokeTool',
345
+ (toolNameHandle: QuickJSHandle, inputJsonHandle: QuickJSHandle) => {
346
+ const toolName = context.getString(toolNameHandle);
347
+ const inputJson = context.getString(inputJsonHandle);
348
+ return requestHost(context, message.invocationId, {
349
+ toolName,
350
+ inputJson,
351
+ });
352
+ },
353
+ );
354
+
355
+ return { invokeTool };
356
+ }
357
+
358
+ function requestHost(
359
+ context: QuickJSAsyncContext,
360
+ invocationId: string,
361
+ payload: Record<string, unknown>,
362
+ ): QuickJSHandle {
363
+ const requestId = `${invocationId}:bridge-${++bridgeRequestCounter}`;
364
+ const deferred = context.newPromise();
365
+ pendingBridgeRequests.set(requestId, {
366
+ context,
367
+ deferred,
368
+ invocationId,
369
+ });
370
+ deferred.settled.then(() => {
371
+ context.runtime.executePendingJobs();
372
+ deferred.dispose();
373
+ });
374
+ parentPort?.postMessage({
375
+ type: 'tool-request',
376
+ invocationId,
377
+ requestId,
378
+ ...payload,
379
+ });
380
+ return deferred.handle;
381
+ }
382
+
383
+ function resolveBridgeResponse(
384
+ context: QuickJSAsyncContext,
385
+ deferred: QuickJSDeferredPromise,
386
+ message: WorkerBridgeResponse,
387
+ ): void {
388
+ if (message.success) {
389
+ const value = context.newString(message.valueJson ?? '');
390
+ deferred.resolve(value);
391
+ value.dispose();
392
+ return;
393
+ }
394
+ const error = createBridgeErrorHandle(context, message.error);
395
+ deferred.reject(error);
396
+ error.dispose();
397
+ }
398
+
399
+ function drainPendingJobs(context: QuickJSAsyncContext): void {
400
+ while (context.runtime.hasPendingJob()) {
401
+ const pending = context.runtime.executePendingJobs();
402
+ if ('error' in pending && pending.error) {
403
+ const contextForError =
404
+ 'context' in pending.error ? pending.error.context : context;
405
+ const error = contextForError.dump(pending.error);
406
+ if (pending.error.alive) {
407
+ pending.error.dispose();
408
+ }
409
+ throw toError(error);
410
+ }
411
+ }
412
+ }
413
+
414
+ async function resolveQuickJSPromise(
415
+ context: QuickJSAsyncContext,
416
+ promiseHandle: QuickJSHandle,
417
+ ) {
418
+ const resolved = context.resolvePromise(promiseHandle);
419
+ for (;;) {
420
+ drainPendingJobs(context);
421
+ const result = await Promise.race([
422
+ resolved.then(value => ({ settled: true as const, value })),
423
+ new Promise<{ settled: false }>(resolve =>
424
+ setTimeout(() => resolve({ settled: false }), 0),
425
+ ),
426
+ ]);
427
+ if (result.settled) {
428
+ drainPendingJobs(context);
429
+ return result.value;
430
+ }
431
+ }
432
+ }
433
+
434
+ function toError(value: unknown): Error {
435
+ if (
436
+ typeof value === 'object' &&
437
+ value !== null &&
438
+ 'message' in value &&
439
+ typeof (value as { message?: unknown }).message === 'string'
440
+ ) {
441
+ const errorValue = value as {
442
+ message: string;
443
+ name?: string;
444
+ stack?: string;
445
+ code?: string;
446
+ details?: unknown;
447
+ };
448
+ const error = new Error(errorValue.message);
449
+ if (
450
+ 'name' in value &&
451
+ typeof (value as { name?: unknown }).name === 'string'
452
+ ) {
453
+ error.name = errorValue.name!;
454
+ }
455
+ if (
456
+ 'stack' in value &&
457
+ typeof (value as { stack?: unknown }).stack === 'string'
458
+ ) {
459
+ error.stack = (value as { stack: string }).stack;
460
+ }
461
+ if (errorValue.code !== undefined) {
462
+ Object.defineProperty(error, 'code', {
463
+ value: errorValue.code,
464
+ enumerable: true,
465
+ });
466
+ }
467
+ if (errorValue.details !== undefined) {
468
+ Object.defineProperty(error, 'details', {
469
+ value: errorValue.details,
470
+ enumerable: true,
471
+ });
472
+ }
473
+ return error;
474
+ }
475
+ return new Error(String(value));
476
+ }
477
+
478
+ function createBridgeErrorHandle(
479
+ context: QuickJSAsyncContext,
480
+ error: WorkerBridgeResponse['error'],
481
+ ): QuickJSHandle {
482
+ const handle = context.newError(
483
+ error?.message ?? 'Host bridge request failed.',
484
+ );
485
+ if (!error) {
486
+ return handle;
487
+ }
488
+ const name = context.newString(error.name);
489
+ context.setProp(handle, 'name', name);
490
+ name.dispose();
491
+ if (error.code !== undefined) {
492
+ const code = context.newString(error.code);
493
+ context.setProp(handle, 'code', code);
494
+ code.dispose();
495
+ }
496
+ return handle;
497
+ }
@@ -0,0 +1,181 @@
1
+ import { asSchema } from 'ai';
2
+ import { CodeModeToolError } from './errors.js';
3
+ import type { CodeModeToolExecutionOptions, CodeModeToolSet } from './types.js';
4
+ import {
5
+ assertJsonSerializable,
6
+ toJsonPayload,
7
+ } from './utils/serialization.js';
8
+
9
+ export async function invokeHostTool({
10
+ toolName,
11
+ inputJson,
12
+ tools,
13
+ baseExecutionOptions,
14
+ maxToolInputBytes,
15
+ maxToolOutputBytes,
16
+ toolCallId,
17
+ }: {
18
+ toolName: string;
19
+ inputJson: string;
20
+ tools: CodeModeToolSet;
21
+ baseExecutionOptions: CodeModeToolExecutionOptions;
22
+ maxToolInputBytes: number;
23
+ maxToolOutputBytes: number;
24
+ toolCallId: string;
25
+ }): Promise<string> {
26
+ throwIfAborted(baseExecutionOptions.abortSignal);
27
+
28
+ const hostTool = tools[toolName];
29
+ if (!hostTool) {
30
+ throw new CodeModeToolError(`Unknown tool: ${toolName}`, {
31
+ toolName,
32
+ availableTools: Object.keys(tools),
33
+ });
34
+ }
35
+ if (hostTool.execute == null) {
36
+ throw new CodeModeToolError(`Tool "${toolName}" does not have execute().`, {
37
+ toolName,
38
+ });
39
+ }
40
+
41
+ const input = inputJson === '' ? undefined : JSON.parse(inputJson);
42
+ assertJsonSerializable(input, maxToolInputBytes, `Tool "${toolName}" input`);
43
+
44
+ const validation = await raceAgainstAbort(
45
+ validateToolInput(hostTool.inputSchema, input),
46
+ baseExecutionOptions.abortSignal,
47
+ );
48
+ if (!validation.success) {
49
+ throw new CodeModeToolError(
50
+ `Invalid input for tool "${toolName}": ${validation.error.message}`,
51
+ { toolName, input, cause: validation.error.message },
52
+ );
53
+ }
54
+
55
+ const executionOptions: CodeModeToolExecutionOptions = {
56
+ ...baseExecutionOptions,
57
+ toolCallId,
58
+ };
59
+
60
+ if (
61
+ await raceAgainstAbort(
62
+ requiresApproval(hostTool, validation.value, executionOptions),
63
+ executionOptions.abortSignal,
64
+ )
65
+ ) {
66
+ throw new CodeModeToolError(
67
+ `Tool "${toolName}" requires approval, which code mode does not support yet.`,
68
+ { toolName, input: validation.value, toolCallId },
69
+ );
70
+ }
71
+
72
+ const output = await raceAgainstAbort(
73
+ executeHostTool(hostTool.execute.bind(hostTool), {
74
+ input: validation.value,
75
+ options: executionOptions,
76
+ }),
77
+ executionOptions.abortSignal,
78
+ );
79
+ return toJsonPayload(output, maxToolOutputBytes, `Tool "${toolName}" output`);
80
+ }
81
+
82
+ async function requiresApproval(
83
+ hostTool: CodeModeToolSet[string],
84
+ input: unknown,
85
+ options: CodeModeToolExecutionOptions,
86
+ ): Promise<boolean> {
87
+ if (hostTool.needsApproval == null) {
88
+ return false;
89
+ }
90
+ if (typeof hostTool.needsApproval === 'boolean') {
91
+ return hostTool.needsApproval;
92
+ }
93
+ return await hostTool.needsApproval(input as never, options as never);
94
+ }
95
+
96
+ async function validateToolInput(
97
+ schema: CodeModeToolSet[string]['inputSchema'],
98
+ input: unknown,
99
+ ): Promise<
100
+ { success: true; value: unknown } | { success: false; error: Error }
101
+ > {
102
+ const normalizedSchema = asSchema(schema);
103
+ if (normalizedSchema.validate === undefined) {
104
+ return { success: true, value: input };
105
+ }
106
+ return await normalizedSchema.validate(input);
107
+ }
108
+
109
+ async function executeHostTool(
110
+ execute: NonNullable<CodeModeToolSet[string]['execute']>,
111
+ {
112
+ input,
113
+ options,
114
+ }: {
115
+ input: unknown;
116
+ options: CodeModeToolExecutionOptions;
117
+ },
118
+ ): Promise<unknown> {
119
+ const output = execute(input as never, options as never);
120
+ if (isAsyncIterable(output)) {
121
+ let finalOutput: unknown;
122
+ for await (const part of output) {
123
+ finalOutput = part;
124
+ }
125
+ return finalOutput;
126
+ }
127
+ return await output;
128
+ }
129
+
130
+ function isAsyncIterable(value: unknown): value is AsyncIterable<unknown> {
131
+ return (
132
+ typeof value === 'object' &&
133
+ value !== null &&
134
+ Symbol.asyncIterator in value &&
135
+ typeof (value as { [Symbol.asyncIterator]?: unknown })[
136
+ Symbol.asyncIterator
137
+ ] === 'function'
138
+ );
139
+ }
140
+
141
+ async function raceAgainstAbort<T>(
142
+ operation: Promise<T>,
143
+ abortSignal: AbortSignal | undefined,
144
+ ): Promise<T> {
145
+ if (abortSignal === undefined) {
146
+ return await operation;
147
+ }
148
+ throwIfAborted(abortSignal);
149
+
150
+ let rejectOnAbort!: (reason?: unknown) => void;
151
+ const aborted = new Promise<never>((_resolve, reject) => {
152
+ rejectOnAbort = reject;
153
+ });
154
+ const onAbort = () => {
155
+ rejectOnAbort(abortReason(abortSignal));
156
+ };
157
+
158
+ abortSignal.addEventListener('abort', onAbort, { once: true });
159
+ if (abortSignal.aborted) {
160
+ onAbort();
161
+ }
162
+
163
+ try {
164
+ return await Promise.race([operation, aborted]);
165
+ } finally {
166
+ abortSignal.removeEventListener('abort', onAbort);
167
+ }
168
+ }
169
+
170
+ function throwIfAborted(abortSignal: AbortSignal | undefined): void {
171
+ if (abortSignal?.aborted) {
172
+ throw abortReason(abortSignal);
173
+ }
174
+ }
175
+
176
+ function abortReason(abortSignal: AbortSignal): unknown {
177
+ return (
178
+ abortSignal.reason ??
179
+ new DOMException('The operation was aborted.', 'AbortError')
180
+ );
181
+ }