blob: 34c9e3834118b0521e96da75c58c7c1edfd5f703 [file]
import {
CodeModeError,
CodeModeToolError,
experimental_runCodeMode as runCodeMode,
} from '@ai-sdk/code-mode';
import { jsonSchema, tool, type ToolSet } from 'ai';
import type {
CodeModeDiagnostic,
CodeModeExecutionResult,
CodeModeToolCall,
ExecuteCodeCellInput,
} from './index.js';
import { DEFAULT_CODE_MODE_LIMITS } from './index.js';
export async function executeCodeCellImpl(
input: ExecuteCodeCellInput,
): Promise<CodeModeExecutionResult> {
const limits = {
maxSourceBytes: input.limits?.maxSourceBytes ?? DEFAULT_CODE_MODE_LIMITS.maxSourceBytes,
maxSandboxTimeMs: input.limits?.maxSandboxTimeMs ?? DEFAULT_CODE_MODE_LIMITS.maxSandboxTimeMs,
maxMemoryBytes: input.limits?.maxMemoryBytes ?? DEFAULT_CODE_MODE_LIMITS.maxMemoryBytes,
maxStackBytes: input.limits?.maxStackBytes ?? DEFAULT_CODE_MODE_LIMITS.maxStackBytes,
maxToolCalls: input.limits?.maxToolCalls ?? DEFAULT_CODE_MODE_LIMITS.maxToolCalls,
maxToolConcurrency:
input.limits?.maxToolConcurrency ?? DEFAULT_CODE_MODE_LIMITS.maxToolConcurrency,
maxToolInputBytes:
input.limits?.maxToolInputBytes ?? DEFAULT_CODE_MODE_LIMITS.maxToolInputBytes,
maxToolOutputBytes:
input.limits?.maxToolOutputBytes ?? DEFAULT_CODE_MODE_LIMITS.maxToolOutputBytes,
maxOutputBytes: input.limits?.maxOutputBytes ?? DEFAULT_CODE_MODE_LIMITS.maxOutputBytes,
};
const toolCalls: CodeModeToolCall[] = [];
const hostToolOperations = new Set<Promise<unknown>>();
const fatalAbortController = new AbortController();
const invocationSignal = input.signal
? AbortSignal.any([input.signal, fatalAbortController.signal])
: fatalAbortController.signal;
let fatalToolFailure: { reason: unknown } | undefined;
const tools = Object.create(null) as ToolSet;
for (const { name } of input.tools) {
tools[name] = tool({
inputSchema: jsonSchema({}),
execute: async (toolInput, options) => {
if (fatalToolFailure) throw fatalToolFailure.reason;
toolCalls.push({ index: toolCalls.length + 1, name });
const operation = Promise.resolve().then(() =>
input.callTool(name, toolInput, options.abortSignal ?? invocationSignal),
);
hostToolOperations.add(operation);
return operation.then(
(value) => {
hostToolOperations.delete(operation);
return value;
},
(error) => {
hostToolOperations.delete(operation);
if (input.isFatalToolError?.(error)) {
if (!fatalToolFailure) {
fatalToolFailure = { reason: error };
fatalAbortController.abort(error);
}
throw error;
}
throw new CodeModeToolError(error instanceof Error ? error.message : String(error), {
toolName: name,
});
},
);
},
});
}
try {
const value = await runCodeMode({
js: input.code,
tools,
toolExecutionOptions: { abortSignal: invocationSignal },
options: {
executionPolicy: {
timeoutMs: limits.maxSandboxTimeMs,
memoryLimitBytes: limits.maxMemoryBytes,
maxStackSizeBytes: limits.maxStackBytes,
maxResultBytes: limits.maxOutputBytes,
maxConsoleOutputBytes: 1,
maxSourceBytes: limits.maxSourceBytes,
maxToolInputBytes: limits.maxToolInputBytes,
maxToolOutputBytes: limits.maxToolOutputBytes,
maxBridgeRequests: limits.maxToolCalls,
maxInFlightBridgeRequests: limits.maxToolConcurrency,
},
},
});
await drainHostToolOperations(hostToolOperations);
if (fatalToolFailure) throw fatalToolFailure.reason;
return { ok: true, value: value ?? null, toolCalls };
} catch (error) {
await drainHostToolOperations(hostToolOperations);
if (fatalToolFailure) throw fatalToolFailure.reason;
if (input.signal?.aborted) throw input.signal.reason ?? error;
return {
ok: false,
error: normalizeQuickJsError(error),
toolCalls,
};
}
}
async function drainHostToolOperations(operations: ReadonlySet<Promise<unknown>>): Promise<void> {
while (operations.size > 0) await Promise.allSettled([...operations]);
}
function normalizeQuickJsError(error: unknown): CodeModeDiagnostic {
const message = error instanceof Error ? error.message : String(error);
if (error instanceof SyntaxError) {
return { kind: 'parse_error', message };
}
if (error instanceof CodeModeError) {
if (
error.code === 'CODE_MODE_TIMEOUT' ||
error.code === 'CODE_MODE_CONCURRENCY_LIMIT' ||
error.code === 'CODE_MODE_SOURCE_TOO_LARGE' ||
error.code === 'CODE_MODE_BRIDGE_LIMIT'
) {
return { kind: 'limit_exceeded', message };
}
if (error.code === 'CODE_MODE_SERIALIZATION_ERROR') {
return {
kind: /exceeds? the \d+ byte size limit/i.test(message) ? 'limit_exceeded' : 'tool_failure',
message,
};
}
if (error.code === 'CODE_MODE_TOOL_ERROR' && /^Unknown tool:/i.test(message)) {
return { kind: 'unknown_tool', message };
}
if (error.code === 'CODE_MODE_TOOL_ERROR') return { kind: 'tool_failure', message };
if (
error.name === 'InternalError' &&
(/^interrupted$/i.test(message) || /out of memory|stack (?:size|overflow)/i.test(message))
) {
return { kind: 'limit_exceeded', message };
}
}
return { kind: 'execution_error', message };
}