import { recoverExecutionBody, stripTypeScript, type CodeExecutor, type ExecuteOutputItem, type ExecuteResult, type SandboxToolInvoker,} from "@executor-js/codemode-core";import * as Data from "effect/Data";import * as Effect from "effect/Effect";import { getQuickJS, type QuickJSContext, type QuickJSDeferredPromise, type QuickJSHandle, type QuickJSRuntime, type QuickJSWASMModule,} from "quickjs-emscripten"; export type QuickJsExecutorOptions = { timeoutMs?: number; memoryLimitBytes?: number; maxStackSizeBytes?: number;}; // Allow pre-loading a QuickJS module (e.g. with custom WASM bytes for compiled binaries)let preloadedModule: QuickJSWASMModule | null = null; export const setQuickJSModule = (mod: QuickJSWASMModule) => { preloadedModule = mod;}; const resolveQuickJS = (): Promise<QuickJSWASMModule> => preloadedModule ? Promise.resolve(preloadedModule) : getQuickJS(); class QuickJsExecutionError extends Data.TaggedError("QuickJsExecutionError")<{ readonly message: string;}> {} // Large OpenAPI specs can take longer to parse inside QuickJS, so keep the// default execution budget at five minutes unless a caller opts into less.const DEFAULT_TIMEOUT_MS = 5 * 60_000;const DEFAULT_MEMORY_LIMIT_BYTES = 64 * 1024 * 1024;const DEFAULT_MAX_STACK_SIZE_BYTES = 1 * 1024 * 1024;const EXECUTION_FILENAME = "executor-quickjs-runtime.js"; const toError = (cause: unknown): Error => cause instanceof Error ? cause : new Error(String(cause)); // Defect surfaced to the sandbox when a tool dispatch reaches the// reject path. Two sources reach here:// - `ExecutionToolError` emitted by the execution package — message// is already either the canonical// `Internal tool error [<corrId>]` opaque generic (plugin defects// passed through `makeExecutorToolInvoker`'s catchCause) or an// intentional public string (validators in engine.ts:// "tools.search expects an object: ...").// - Anything else — assume infra defect and emit a bare opaque// generic.// `_tag === "ExecutionToolError"` is the in-band signal that the// message was generated by code we control and is safe to pass// through; we never read `.cause` / `.stack`.const sandboxDefectMessage = (cause: unknown): string => { if (cause !== null && typeof cause === "object") { const tagged = cause as { readonly _tag?: unknown; readonly message?: unknown }; // oxlint-disable-next-line executor/no-manual-tag-check, executor/no-unknown-error-message -- boundary: QuickJS reject path receives an already-rendered ExecutionToolError; we pass its in-band public message through and otherwise emit the opaque generic if (tagged._tag === "ExecutionToolError" && typeof tagged.message === "string") { return tagged.message; } } return "Internal tool error";}; const toErrorMessage = (cause: unknown): string => { if (typeof cause === "object" && cause !== null) { const message = "message" in cause && typeof cause.message === "string" ? cause.message : undefined; if (message) { return message; } const stack = "stack" in cause && typeof cause.stack === "string" ? cause.stack : undefined; if (stack) { return stack; } } const error = toError(cause); return error.stack ?? error.message;}; const serializeJson = (value: unknown, label: string): string | undefined => { if (typeof value === "undefined") { return undefined; } try { return JSON.stringify(value); } catch (cause) { throw new Error(`${label} is not JSON serializable: ${toError(cause).message}`); }}; const looksLikeInterruptedError = (message: string): boolean => /\binterrupted\b/i.test(message); const timeoutMessage = (timeoutMs: number): string => `QuickJS execution timed out after ${timeoutMs}ms`; type DeadlineTracker = { /** Current absolute deadline, or null while at least one dispatch is in flight. */ readonly deadlineMs: () => number | null; readonly dispatchStarted: () => void; readonly dispatchReturned: () => void;}; const makeDeadlineTracker = (timeoutMs: number): DeadlineTracker => { const start = Date.now(); let inFlight = 0; let lastReturnedAt = start; return { deadlineMs: () => (inFlight > 0 ? null : Math.max(start, lastReturnedAt) + timeoutMs), dispatchStarted: () => { inFlight += 1; }, dispatchReturned: () => { inFlight -= 1; lastReturnedAt = Date.now(); }, };}; const shouldInterruptAfterDeadline = (deadline: DeadlineTracker): (() => boolean) => () => { const deadlineMs = deadline.deadlineMs(); return deadlineMs !== null && Date.now() >= deadlineMs; }; const normalizeExecutionError = ( cause: unknown, deadline: DeadlineTracker, timeoutMs: number,): string => { const message = toErrorMessage(cause); const deadlineMs = deadline.deadlineMs(); return deadlineMs !== null && Date.now() >= deadlineMs && looksLikeInterruptedError(message) ? timeoutMessage(timeoutMs) : message;}; const buildExecutionSource = (code: string): string => { // QuickJS evaluates plain JavaScript only; strip any TS type syntax // first. A parse failure here throws a SyntaxError which the outer // `Effect.tryPromise` maps to `QuickJsExecutionError` with the // sucrase-formatted message intact. const body = stripTypeScript(recoverExecutionBody(code)); return [ '"use strict";', "const __invokeTool = __executor_invokeTool;", "const __log = __executor_log;", "try { delete globalThis.__executor_invokeTool; } catch {}", "try { delete globalThis.__executor_log; } catch {}", "const __formatLogArg = (value) => {", " if (typeof value === 'string') return value;", " try {", " return JSON.stringify(value);", " } catch {", " return String(value);", " }", "};", "const __formatLogLine = (args) => args.map(__formatLogArg).join(' ');", "const __outputs = [];", "globalThis.__executor_outputs = __outputs;", "const __formatOutputText = (value) => {", " if (typeof value === 'undefined') return 'undefined';", " if (value === null) return 'null';", " if (typeof value === 'string') return value;", " try {", " return JSON.stringify(value);", " } catch {", " return String(value);", " }", "};", "const __isToolFile = (value) => value && typeof value === 'object' && value._tag === 'ToolFile' && typeof value.mimeType === 'string' && value.encoding === 'base64' && typeof value.data === 'string' && typeof value.byteLength === 'number';", "const __isMcpTextContentBlock = (value) => value && typeof value === 'object' && value.type === 'text' && typeof value.text === 'string';", "const __isMcpImageContentBlock = (value) => value && typeof value === 'object' && value.type === 'image' && typeof value.data === 'string' && typeof value.mimeType === 'string';", "const __isMcpAudioContentBlock = (value) => value && typeof value === 'object' && value.type === 'audio' && typeof value.data === 'string' && typeof value.mimeType === 'string';", "const __isMcpResourceContentBlock = (value) => value && typeof value === 'object' && value.type === 'resource' && value.resource && typeof value.resource === 'object' && typeof value.resource.uri === 'string' && (typeof value.resource.text === 'string' || typeof value.resource.blob === 'string');", "const __isMcpResourceLinkContentBlock = (value) => value && typeof value === 'object' && value.type === 'resource_link' && typeof value.uri === 'string' && typeof value.name === 'string';", "const __isMcpContentBlock = (value) => __isMcpTextContentBlock(value) || __isMcpImageContentBlock(value) || __isMcpAudioContentBlock(value) || __isMcpResourceContentBlock(value) || __isMcpResourceLinkContentBlock(value);", "const emit = (value) => {", " if (__isToolFile(value)) {", " __outputs.push({ type: 'file', file: value });", " return;", " }", " if (__isMcpContentBlock(value)) {", " __outputs.push({ type: 'content', content: value });", " return;", " }", " __outputs.push({ type: 'content', content: { type: 'text', text: __formatOutputText(value) } });", "};", "const __toolsEnumerationError = (path) => new Error(", " (path.length === 0 ? 'tools' : 'tools.' + path.join('.')) +", ' \' is a lazy proxy and cannot be enumerated. Use tools.search({ query: "..." }) to find tools, tools.search({ namespace: "<integration>", query: "" }) to list every tool in an integration, or tools.executor.coreTools.connections.list({}) to list saved connections.\',', ");", "const __makeToolsProxy = (path = []) => new Proxy(() => undefined, {", " get(_target, prop) {", " if (prop === 'then' || typeof prop === 'symbol') {", " return undefined;", " }", " return __makeToolsProxy([...path, String(prop)]);", " },", " ownKeys() {", " throw __toolsEnumerationError(path);", " },", " getOwnPropertyDescriptor() {", " throw __toolsEnumerationError(path);", " },", " apply(_target, _thisArg, args) {", " const toolPath = path.join('.');", " if (!toolPath) {", " throw new Error('Tool path missing in invocation');", " }", " return Promise.resolve(__invokeTool(toolPath, args[0])).then((raw) => raw === undefined ? undefined : JSON.parse(raw));", " },", "});", "const tools = __makeToolsProxy();", "const console = {", " log: (...args) => __log('log', __formatLogLine(args)),", " warn: (...args) => __log('warn', __formatLogLine(args)),", " error: (...args) => __log('error', __formatLogLine(args)),", " info: (...args) => __log('info', __formatLogLine(args)),", " debug: (...args) => __log('debug', __formatLogLine(args)),", "};", "const fetch = (..._args) => {", " throw new Error('fetch is disabled in QuickJS executor');", "};", "(async () => {", body, "})()", ].join("\n");}; const readPropDump = (context: QuickJSContext, handle: QuickJSHandle, key: string): unknown => { const prop = context.getProp(handle, key); try { return context.dump(prop); } finally { prop.dispose(); }}; const readOutputItems = (context: QuickJSContext): ExecuteOutputItem[] | undefined => { const output = readPropDump(context, context.global, "__executor_outputs"); return Array.isArray(output) && output.length > 0 ? (output as ExecuteOutputItem[]) : undefined;}; const readResultState = ( context: QuickJSContext, handle: QuickJSHandle,): { settled: boolean; value: unknown; error: unknown;} => ({ settled: readPropDump(context, handle, "settled") === true, value: readPropDump(context, handle, "v"), error: readPropDump(context, handle, "e"),}); const createLogBridge = (context: QuickJSContext, logs: string[]): QuickJSHandle => context.newFunction("__executor_log", (levelHandle, lineHandle) => { const level = context.getString(levelHandle); const line = context.getString(lineHandle); logs.push(`[${level}] ${line}`); return context.undefined; }); type RunPromise = <A, E>(effect: Effect.Effect<A, E>) => Promise<A>; const createToolBridge = ( context: QuickJSContext, toolInvoker: SandboxToolInvoker, pendingDeferreds: Set<QuickJSDeferredPromise>, runPromise: RunPromise, deadline: DeadlineTracker,): QuickJSHandle => context.newFunction("__executor_invokeTool", (pathHandle, argsHandle) => { const path = context.getString(pathHandle); const args = argsHandle === undefined || context.typeof(argsHandle) === "undefined" ? undefined : context.dump(argsHandle); const deferred = context.newPromise(); pendingDeferreds.add(deferred); deferred.settled.finally(() => { pendingDeferreds.delete(deferred); }); deadline.dispatchStarted(); void runPromise(toolInvoker.invoke({ path, args })).then( (value) => { deadline.dispatchReturned(); if (!deferred.alive) { return; } const serialized = serializeJson(value, `Tool result for ${path}`); if (typeof serialized === "undefined") { deferred.resolve(); return; } const valueHandle = context.newString(serialized); deferred.resolve(valueHandle); valueHandle.dispose(); }, (cause) => { deadline.dispatchReturned(); if (!deferred.alive) { return; } // The reject path is reserved for true infra defects. The // upstream tool-invoker has already replaced the message with // an opaque generic plus a correlation id, but defensively log // the cause here and emit a stable generic if upstream changes. const message = sandboxDefectMessage(cause); try { console.error("[executor:quickjs] tool dispatch defect", { path, cause }); } catch { /* ignore logger failures */ } const errorHandle = context.newError(message); deferred.reject(errorHandle); errorHandle.dispose(); }, ); return deferred.handle; }); const drainJobs = ( context: QuickJSContext, runtime: QuickJSRuntime, deadline: DeadlineTracker, timeoutMs: number,): void => { while (runtime.hasPendingJob()) { const deadlineMs = deadline.deadlineMs(); if (deadlineMs !== null && Date.now() >= deadlineMs) { throw new Error(timeoutMessage(timeoutMs)); } const pending = runtime.executePendingJobs(); if (pending.error) { const error = context.dump(pending.error); pending.error.dispose(); throw toError(error); } }}; const waitForDeferreds = async ( pendingDeferreds: ReadonlySet<QuickJSDeferredPromise>, deadline: DeadlineTracker, timeoutMs: number,): Promise<void> => { const settled = Promise.race([...pendingDeferreds].map((deferred) => deferred.settled)); const deadlineMs = deadline.deadlineMs(); if (deadlineMs === null) { await settled; return; } const remainingMs = deadlineMs - Date.now(); if (remainingMs <= 0) { throw new Error(timeoutMessage(timeoutMs)); } let timer: ReturnType<typeof setTimeout> | undefined; try { await Promise.race([ settled, new Promise<never>((_, reject) => { timer = setTimeout(() => reject(new Error(timeoutMessage(timeoutMs))), remainingMs); }), ]); } finally { if (timer !== undefined) { clearTimeout(timer); } }}; const drainAsync = async ( context: QuickJSContext, runtime: QuickJSRuntime, pendingDeferreds: ReadonlySet<QuickJSDeferredPromise>, deadline: DeadlineTracker, timeoutMs: number,): Promise<void> => { drainJobs(context, runtime, deadline, timeoutMs); while (pendingDeferreds.size > 0) { await waitForDeferreds(pendingDeferreds, deadline, timeoutMs); drainJobs(context, runtime, deadline, timeoutMs); } drainJobs(context, runtime, deadline, timeoutMs);}; const evaluateInQuickJs = async ( options: QuickJsExecutorOptions, code: string, toolInvoker: SandboxToolInvoker, runPromise: RunPromise,): Promise<ExecuteResult> => { const timeoutMs = Math.max(100, options.timeoutMs ?? DEFAULT_TIMEOUT_MS); const deadline = makeDeadlineTracker(timeoutMs); const logs: string[] = []; const pendingDeferreds = new Set<QuickJSDeferredPromise>(); const QuickJS = await resolveQuickJS(); const runtime = QuickJS.newRuntime(); try { runtime.setMemoryLimit(options.memoryLimitBytes ?? DEFAULT_MEMORY_LIMIT_BYTES); runtime.setMaxStackSize(options.maxStackSizeBytes ?? DEFAULT_MAX_STACK_SIZE_BYTES); runtime.setInterruptHandler(shouldInterruptAfterDeadline(deadline)); const context = runtime.newContext(); try { const logBridge = createLogBridge(context, logs); context.setProp(context.global, "__executor_log", logBridge); logBridge.dispose(); const toolBridge = createToolBridge( context, toolInvoker, pendingDeferreds, runPromise, deadline, ); context.setProp(context.global, "__executor_invokeTool", toolBridge); toolBridge.dispose(); const evaluated = context.evalCode(buildExecutionSource(code), EXECUTION_FILENAME); if (evaluated.error) { const error = context.dump(evaluated.error); evaluated.error.dispose(); return { result: null, error: normalizeExecutionError(error, deadline, timeoutMs), logs, } satisfies ExecuteResult; } context.setProp(context.global, "__executor_result", evaluated.value); evaluated.value.dispose(); const stateResult = context.evalCode( "(function(p){ var s = { v: void 0, e: void 0, settled: false }; var formatError = function(e){ if (e && typeof e === 'object') { var message = typeof e.message === 'string' ? e.message : ''; var stack = typeof e.stack === 'string' ? e.stack : ''; if (message && stack) { return stack.indexOf(message) === -1 ? message + '\\n' + stack : stack; } if (message) return message; if (stack) return stack; } return String(e); }; p.then(function(v){ s.v = v; s.settled = true; }, function(e){ s.e = formatError(e); s.settled = true; }); return s; })(__executor_result)", ); if (stateResult.error) { const error = context.dump(stateResult.error); stateResult.error.dispose(); return { result: null, error: normalizeExecutionError(error, deadline, timeoutMs), logs, } satisfies ExecuteResult; } const stateHandle = stateResult.value; try { await drainAsync(context, runtime, pendingDeferreds, deadline, timeoutMs); const state = readResultState(context, stateHandle); if (!state.settled) { return { result: null, error: timeoutMessage(timeoutMs), output: readOutputItems(context), logs, } satisfies ExecuteResult; } if (typeof state.error !== "undefined") { return { result: null, error: normalizeExecutionError(state.error, deadline, timeoutMs), output: readOutputItems(context), logs, } satisfies ExecuteResult; } Lines 1–500 of 553. Load more
import { recoverExecutionBody, stripTypeScript, type CodeExecutor, type ExecuteOutputItem, type ExecuteResult, type SandboxToolInvoker, } from "@executor-js/codemode-core"; import * as Data from "effect/Data"; import * as Effect from "effect/Effect"; import { getQuickJS, type QuickJSContext, type QuickJSDeferredPromise, type QuickJSHandle, type QuickJSRuntime, type QuickJSWASMModule, } from "quickjs-emscripten"; export type QuickJsExecutorOptions = { timeoutMs?: number; memoryLimitBytes?: number; maxStackSizeBytes?: number; }; // Allow pre-loading a QuickJS module (e.g. with custom WASM bytes for compiled binaries) let preloadedModule: QuickJSWASMModule | null = null; export const setQuickJSModule = (mod: QuickJSWASMModule) => { preloadedModule = mod; }; const resolveQuickJS = (): Promise<QuickJSWASMModule> => preloadedModule ? Promise.resolve(preloadedModule) : getQuickJS(); class QuickJsExecutionError extends Data.TaggedError("QuickJsExecutionError")<{ readonly message: string; }> {} // Large OpenAPI specs can take longer to parse inside QuickJS, so keep the // default execution budget at five minutes unless a caller opts into less. const DEFAULT_TIMEOUT_MS = 5 * 60_000; const DEFAULT_MEMORY_LIMIT_BYTES = 64 * 1024 * 1024; const DEFAULT_MAX_STACK_SIZE_BYTES = 1 * 1024 * 1024; const EXECUTION_FILENAME = "executor-quickjs-runtime.js"; const toError = (cause: unknown): Error => cause instanceof Error ? cause : new Error(String(cause)); // Defect surfaced to the sandbox when a tool dispatch reaches the // reject path. Two sources reach here: // - `ExecutionToolError` emitted by the execution package — message // is already either the canonical // `Internal tool error [<corrId>]` opaque generic (plugin defects // passed through `makeExecutorToolInvoker`'s catchCause) or an // intentional public string (validators in engine.ts: // "tools.search expects an object: ..."). // - Anything else — assume infra defect and emit a bare opaque // generic. // `_tag === "ExecutionToolError"` is the in-band signal that the // message was generated by code we control and is safe to pass // through; we never read `.cause` / `.stack`. const sandboxDefectMessage = (cause: unknown): string => { if (cause !== null && typeof cause === "object") { const tagged = cause as { readonly _tag?: unknown; readonly message?: unknown }; // oxlint-disable-next-line executor/no-manual-tag-check, executor/no-unknown-error-message -- boundary: QuickJS reject path receives an already-rendered ExecutionToolError; we pass its in-band public message through and otherwise emit the opaque generic if (tagged._tag === "ExecutionToolError" && typeof tagged.message === "string") { return tagged.message; } } return "Internal tool error"; }; const toErrorMessage = (cause: unknown): string => { if (typeof cause === "object" && cause !== null) { const message = "message" in cause && typeof cause.message === "string" ? cause.message : undefined; if (message) { return message; } const stack = "stack" in cause && typeof cause.stack === "string" ? cause.stack : undefined; if (stack) { return stack; } } const error = toError(cause); return error.stack ?? error.message; }; const serializeJson = (value: unknown, label: string): string | undefined => { if (typeof value === "undefined") { return undefined; } try { return JSON.stringify(value); } catch (cause) { throw new Error(`${label} is not JSON serializable: ${toError(cause).message}`); } }; const looksLikeInterruptedError = (message: string): boolean => /\binterrupted\b/i.test(message); const timeoutMessage = (timeoutMs: number): string => `QuickJS execution timed out after ${timeoutMs}ms`; type DeadlineTracker = { /** Current absolute deadline, or null while at least one dispatch is in flight. */ readonly deadlineMs: () => number | null; readonly dispatchStarted: () => void; readonly dispatchReturned: () => void; }; const makeDeadlineTracker = (timeoutMs: number): DeadlineTracker => { const start = Date.now(); let inFlight = 0; let lastReturnedAt = start; return { deadlineMs: () => (inFlight > 0 ? null : Math.max(start, lastReturnedAt) + timeoutMs), dispatchStarted: () => { inFlight += 1; }, dispatchReturned: () => { inFlight -= 1; lastReturnedAt = Date.now(); }, }; }; const shouldInterruptAfterDeadline = (deadline: DeadlineTracker): (() => boolean) => () => { const deadlineMs = deadline.deadlineMs(); return deadlineMs !== null && Date.now() >= deadlineMs; }; const normalizeExecutionError = ( cause: unknown, deadline: DeadlineTracker, timeoutMs: number, ): string => { const message = toErrorMessage(cause); const deadlineMs = deadline.deadlineMs(); return deadlineMs !== null && Date.now() >= deadlineMs && looksLikeInterruptedError(message) ? timeoutMessage(timeoutMs) : message; }; const buildExecutionSource = (code: string): string => { // QuickJS evaluates plain JavaScript only; strip any TS type syntax // first. A parse failure here throws a SyntaxError which the outer // `Effect.tryPromise` maps to `QuickJsExecutionError` with the // sucrase-formatted message intact. const body = stripTypeScript(recoverExecutionBody(code)); return [ '"use strict";', "const __invokeTool = __executor_invokeTool;", "const __log = __executor_log;", "try { delete globalThis.__executor_invokeTool; } catch {}", "try { delete globalThis.__executor_log; } catch {}", "const __formatLogArg = (value) => {", " if (typeof value === 'string') return value;", " try {", " return JSON.stringify(value);", " } catch {", " return String(value);", " }", "};", "const __formatLogLine = (args) => args.map(__formatLogArg).join(' ');", "const __outputs = [];", "globalThis.__executor_outputs = __outputs;", "const __formatOutputText = (value) => {", " if (typeof value === 'undefined') return 'undefined';", " if (value === null) return 'null';", " if (typeof value === 'string') return value;", " try {", " return JSON.stringify(value);", " } catch {", " return String(value);", " }", "};", "const __isToolFile = (value) => value && typeof value === 'object' && value._tag === 'ToolFile' && typeof value.mimeType === 'string' && value.encoding === 'base64' && typeof value.data === 'string' && typeof value.byteLength === 'number';", "const __isMcpTextContentBlock = (value) => value && typeof value === 'object' && value.type === 'text' && typeof value.text === 'string';", "const __isMcpImageContentBlock = (value) => value && typeof value === 'object' && value.type === 'image' && typeof value.data === 'string' && typeof value.mimeType === 'string';", "const __isMcpAudioContentBlock = (value) => value && typeof value === 'object' && value.type === 'audio' && typeof value.data === 'string' && typeof value.mimeType === 'string';", "const __isMcpResourceContentBlock = (value) => value && typeof value === 'object' && value.type === 'resource' && value.resource && typeof value.resource === 'object' && typeof value.resource.uri === 'string' && (typeof value.resource.text === 'string' || typeof value.resource.blob === 'string');", "const __isMcpResourceLinkContentBlock = (value) => value && typeof value === 'object' && value.type === 'resource_link' && typeof value.uri === 'string' && typeof value.name === 'string';", "const __isMcpContentBlock = (value) => __isMcpTextContentBlock(value) || __isMcpImageContentBlock(value) || __isMcpAudioContentBlock(value) || __isMcpResourceContentBlock(value) || __isMcpResourceLinkContentBlock(value);", "const emit = (value) => {", " if (__isToolFile(value)) {", " __outputs.push({ type: 'file', file: value });", " return;", " }", " if (__isMcpContentBlock(value)) {", " __outputs.push({ type: 'content', content: value });", " return;", " }", " __outputs.push({ type: 'content', content: { type: 'text', text: __formatOutputText(value) } });", "};", "const __toolsEnumerationError = (path) => new Error(", " (path.length === 0 ? 'tools' : 'tools.' + path.join('.')) +", ' \' is a lazy proxy and cannot be enumerated. Use tools.search({ query: "..." }) to find tools, tools.search({ namespace: "<integration>", query: "" }) to list every tool in an integration, or tools.executor.coreTools.connections.list({}) to list saved connections.\',', ");", "const __makeToolsProxy = (path = []) => new Proxy(() => undefined, {", " get(_target, prop) {", " if (prop === 'then' || typeof prop === 'symbol') {", " return undefined;", " }", " return __makeToolsProxy([...path, String(prop)]);", " },", " ownKeys() {", " throw __toolsEnumerationError(path);", " },", " getOwnPropertyDescriptor() {", " throw __toolsEnumerationError(path);", " },", " apply(_target, _thisArg, args) {", " const toolPath = path.join('.');", " if (!toolPath) {", " throw new Error('Tool path missing in invocation');", " }", " return Promise.resolve(__invokeTool(toolPath, args[0])).then((raw) => raw === undefined ? undefined : JSON.parse(raw));", " },", "});", "const tools = __makeToolsProxy();", "const console = {", " log: (...args) => __log('log', __formatLogLine(args)),", " warn: (...args) => __log('warn', __formatLogLine(args)),", " error: (...args) => __log('error', __formatLogLine(args)),", " info: (...args) => __log('info', __formatLogLine(args)),", " debug: (...args) => __log('debug', __formatLogLine(args)),", "};", "const fetch = (..._args) => {", " throw new Error('fetch is disabled in QuickJS executor');", "};", "(async () => {", body, "})()", ].join("\n"); }; const readPropDump = (context: QuickJSContext, handle: QuickJSHandle, key: string): unknown => { const prop = context.getProp(handle, key); try { return context.dump(prop); } finally { prop.dispose(); } }; const readOutputItems = (context: QuickJSContext): ExecuteOutputItem[] | undefined => { const output = readPropDump(context, context.global, "__executor_outputs"); return Array.isArray(output) && output.length > 0 ? (output as ExecuteOutputItem[]) : undefined; }; const readResultState = ( context: QuickJSContext, handle: QuickJSHandle, ): { settled: boolean; value: unknown; error: unknown; } => ({ settled: readPropDump(context, handle, "settled") === true, value: readPropDump(context, handle, "v"), error: readPropDump(context, handle, "e"), }); const createLogBridge = (context: QuickJSContext, logs: string[]): QuickJSHandle => context.newFunction("__executor_log", (levelHandle, lineHandle) => { const level = context.getString(levelHandle); const line = context.getString(lineHandle); logs.push(`[${level}] ${line}`); return context.undefined; }); type RunPromise = <A, E>(effect: Effect.Effect<A, E>) => Promise<A>; const createToolBridge = ( context: QuickJSContext, toolInvoker: SandboxToolInvoker, pendingDeferreds: Set<QuickJSDeferredPromise>, runPromise: RunPromise, deadline: DeadlineTracker, ): QuickJSHandle => context.newFunction("__executor_invokeTool", (pathHandle, argsHandle) => { const path = context.getString(pathHandle); const args = argsHandle === undefined || context.typeof(argsHandle) === "undefined" ? undefined : context.dump(argsHandle); const deferred = context.newPromise(); pendingDeferreds.add(deferred); deferred.settled.finally(() => { pendingDeferreds.delete(deferred); }); deadline.dispatchStarted(); void runPromise(toolInvoker.invoke({ path, args })).then( (value) => { deadline.dispatchReturned(); if (!deferred.alive) { return; } const serialized = serializeJson(value, `Tool result for ${path}`); if (typeof serialized === "undefined") { deferred.resolve(); return; } const valueHandle = context.newString(serialized); deferred.resolve(valueHandle); valueHandle.dispose(); }, (cause) => { deadline.dispatchReturned(); if (!deferred.alive) { return; } // The reject path is reserved for true infra defects. The // upstream tool-invoker has already replaced the message with // an opaque generic plus a correlation id, but defensively log // the cause here and emit a stable generic if upstream changes. const message = sandboxDefectMessage(cause); try { console.error("[executor:quickjs] tool dispatch defect", { path, cause }); } catch { /* ignore logger failures */ } const errorHandle = context.newError(message); deferred.reject(errorHandle); errorHandle.dispose(); }, ); return deferred.handle; }); const drainJobs = ( context: QuickJSContext, runtime: QuickJSRuntime, deadline: DeadlineTracker, timeoutMs: number, ): void => { while (runtime.hasPendingJob()) { const deadlineMs = deadline.deadlineMs(); if (deadlineMs !== null && Date.now() >= deadlineMs) { throw new Error(timeoutMessage(timeoutMs)); } const pending = runtime.executePendingJobs(); if (pending.error) { const error = context.dump(pending.error); pending.error.dispose(); throw toError(error); } } }; const waitForDeferreds = async ( pendingDeferreds: ReadonlySet<QuickJSDeferredPromise>, deadline: DeadlineTracker, timeoutMs: number, ): Promise<void> => { const settled = Promise.race([...pendingDeferreds].map((deferred) => deferred.settled)); const deadlineMs = deadline.deadlineMs(); if (deadlineMs === null) { await settled; return; } const remainingMs = deadlineMs - Date.now(); if (remainingMs <= 0) { throw new Error(timeoutMessage(timeoutMs)); } let timer: ReturnType<typeof setTimeout> | undefined; try { await Promise.race([ settled, new Promise<never>((_, reject) => { timer = setTimeout(() => reject(new Error(timeoutMessage(timeoutMs))), remainingMs); }), ]); } finally { if (timer !== undefined) { clearTimeout(timer); } } }; const drainAsync = async ( context: QuickJSContext, runtime: QuickJSRuntime, pendingDeferreds: ReadonlySet<QuickJSDeferredPromise>, deadline: DeadlineTracker, timeoutMs: number, ): Promise<void> => { drainJobs(context, runtime, deadline, timeoutMs); while (pendingDeferreds.size > 0) { await waitForDeferreds(pendingDeferreds, deadline, timeoutMs); drainJobs(context, runtime, deadline, timeoutMs); } drainJobs(context, runtime, deadline, timeoutMs); }; const evaluateInQuickJs = async ( options: QuickJsExecutorOptions, code: string, toolInvoker: SandboxToolInvoker, runPromise: RunPromise, ): Promise<ExecuteResult> => { const timeoutMs = Math.max(100, options.timeoutMs ?? DEFAULT_TIMEOUT_MS); const deadline = makeDeadlineTracker(timeoutMs); const logs: string[] = []; const pendingDeferreds = new Set<QuickJSDeferredPromise>(); const QuickJS = await resolveQuickJS(); const runtime = QuickJS.newRuntime(); try { runtime.setMemoryLimit(options.memoryLimitBytes ?? DEFAULT_MEMORY_LIMIT_BYTES); runtime.setMaxStackSize(options.maxStackSizeBytes ?? DEFAULT_MAX_STACK_SIZE_BYTES); runtime.setInterruptHandler(shouldInterruptAfterDeadline(deadline)); const context = runtime.newContext(); try { const logBridge = createLogBridge(context, logs); context.setProp(context.global, "__executor_log", logBridge); logBridge.dispose(); const toolBridge = createToolBridge( context, toolInvoker, pendingDeferreds, runPromise, deadline, ); context.setProp(context.global, "__executor_invokeTool", toolBridge); toolBridge.dispose(); const evaluated = context.evalCode(buildExecutionSource(code), EXECUTION_FILENAME); if (evaluated.error) { const error = context.dump(evaluated.error); evaluated.error.dispose(); return { result: null, error: normalizeExecutionError(error, deadline, timeoutMs), logs, } satisfies ExecuteResult; } context.setProp(context.global, "__executor_result", evaluated.value); evaluated.value.dispose(); const stateResult = context.evalCode( "(function(p){ var s = { v: void 0, e: void 0, settled: false }; var formatError = function(e){ if (e && typeof e === 'object') { var message = typeof e.message === 'string' ? e.message : ''; var stack = typeof e.stack === 'string' ? e.stack : ''; if (message && stack) { return stack.indexOf(message) === -1 ? message + '\\n' + stack : stack; } if (message) return message; if (stack) return stack; } return String(e); }; p.then(function(v){ s.v = v; s.settled = true; }, function(e){ s.e = formatError(e); s.settled = true; }); return s; })(__executor_result)", ); if (stateResult.error) { const error = context.dump(stateResult.error); stateResult.error.dispose(); return { result: null, error: normalizeExecutionError(error, deadline, timeoutMs), logs, } satisfies ExecuteResult; } const stateHandle = stateResult.value; try { await drainAsync(context, runtime, pendingDeferreds, deadline, timeoutMs); const state = readResultState(context, stateHandle); if (!state.settled) { return { result: null, error: timeoutMessage(timeoutMs), output: readOutputItems(context), logs, } satisfies ExecuteResult; } if (typeof state.error !== "undefined") { return { result: null, error: normalizeExecutionError(state.error, deadline, timeoutMs), output: readOutputItems(context), logs, } satisfies ExecuteResult; }