MCPcopy
hub / github.com/coder/mux / QuickJSRuntime

Class QuickJSRuntime

src/node/services/ptc/quickjsRuntime.ts:32–576  ·  view source on GitHub ↗

Source from the content-addressed store, hash-verified

30 * Uses Asyncify build for async host function support.
31 */
32export class QuickJSRuntime implements IJSRuntime {
33 private disposed = false;
34 private eventHandler?: (event: PTCEvent) => void;
35 private abortController?: AbortController;
36 private abortRequested = false; // Track abort requests before eval() starts
37 private limits: RuntimeLimits = {};
38 private consoleSetup = false;
39
40 // Execution state (reset per eval)
41 private toolCalls: PTCToolCallRecord[] = [];
42 private consoleOutput: PTCConsoleRecord[] = [];
43
44 private constructor(private readonly ctx: QuickJSAsyncContext) {}
45
46 static async create(): Promise<QuickJSRuntime> {
47 // Create the async variant manually due to bun's package export resolution issues.
48 // The self-referential import in the variant package doesn't resolve correctly.
49 const variant = {
50 type: "async" as const,
51 importFFI: () => Promise.resolve(QuickJSAsyncFFI),
52 // eslint-disable-next-line @typescript-eslint/require-await -- sync require wrapped for interface
53 importModuleLoader: async () => {
54 // Use require() with the named export path since bun's dynamic import()
55 // doesn't resolve package exports correctly from within node_modules
56 // eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-unsafe-assignment
57 const mod = require("@jitl/quickjs-wasmfile-release-asyncify/emscripten-module");
58 // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-return
59 return mod.default ?? mod;
60 },
61 };
62
63 const QuickJS = await newQuickJSAsyncWASMModuleFromVariant(variant);
64 const ctx = QuickJS.newContext();
65 return new QuickJSRuntime(ctx);
66 }
67
68 setLimits(limits: RuntimeLimits): void {
69 this.limits = limits;
70
71 // Apply memory limit to the runtime
72 const memoryBytes = limits.memoryBytes ?? DEFAULT_MEMORY_BYTES;
73 this.ctx.runtime.setMemoryLimit(memoryBytes);
74 }
75
76 onEvent(handler: (event: PTCEvent) => void): void {
77 this.eventHandler = handler;
78 }
79
80 registerFunction(name: string, fn: (...args: unknown[]) => Promise<unknown>): void {
81 this.assertNotDisposed("registerFunction");
82
83 const handle = this.ctx.newAsyncifiedFunction(name, async (...argHandles) => {
84 if (this.abortController?.signal.aborted) {
85 throw new Error("Execution aborted");
86 }
87
88 // Convert QuickJS handles to JS values - cast to unknown at the FFI boundary
89 const args: unknown[] = argHandles.map((h) => this.ctx.dump(h) as unknown);

Callers

nothing calls this directly

Calls

no outgoing calls

Tested by

no test coverage detected