@execbox/quickjs 0.2.0 → 0.3.0
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 +21 -5
- package/dist/index.cjs +610 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +19 -4
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +19 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +607 -4
- package/dist/index.js.map +1 -1
- package/dist/processEntry.cjs +17 -0
- package/dist/processEntry.cjs.map +1 -0
- package/dist/processEntry.d.cts +5 -0
- package/dist/processEntry.d.ts +5 -0
- package/dist/processEntry.js +18 -0
- package/dist/processEntry.js.map +1 -0
- package/dist/protocolEndpoint-A1JBvHG_.cjs +147 -0
- package/dist/protocolEndpoint-A1JBvHG_.cjs.map +1 -0
- package/dist/protocolEndpoint-CDGgtfFu.js +142 -0
- package/dist/protocolEndpoint-CDGgtfFu.js.map +1 -0
- package/dist/runner/index.cjs +1 -1
- package/dist/runner/index.d.cts +16 -78
- package/dist/runner/index.d.cts.map +1 -1
- package/dist/runner/index.d.ts +16 -78
- package/dist/runner/index.d.ts.map +1 -1
- package/dist/runner/index.js +1 -1
- package/dist/runner/protocolEndpoint.cjs +3 -137
- package/dist/runner/protocolEndpoint.d.cts +4 -0
- package/dist/runner/protocolEndpoint.d.cts.map +1 -1
- package/dist/runner/protocolEndpoint.d.ts +4 -0
- package/dist/runner/protocolEndpoint.d.ts.map +1 -1
- package/dist/runner/protocolEndpoint.js +3 -137
- package/dist/{runner-B4UG88h1.js → runner-CteKTaPD.js} +93 -10
- package/dist/runner-CteKTaPD.js.map +1 -0
- package/dist/{runner-DhOZH9xz.cjs → runner-DRt0kpEk.cjs} +140 -9
- package/dist/{runner-B4UG88h1.js.map → runner-DRt0kpEk.cjs.map} +1 -1
- package/dist/types-BeVqrcj8.d.ts +71 -0
- package/dist/types-BeVqrcj8.d.ts.map +1 -0
- package/dist/types-CnNmLawC.d.cts +71 -0
- package/dist/types-CnNmLawC.d.cts.map +1 -0
- package/dist/workerEntry.cjs +19 -0
- package/dist/workerEntry.cjs.map +1 -0
- package/dist/workerEntry.d.cts +5 -0
- package/dist/workerEntry.d.ts +5 -0
- package/dist/workerEntry.js +20 -0
- package/dist/workerEntry.js.map +1 -0
- package/package.json +16 -7
- package/dist/runner/protocolEndpoint.cjs.map +0 -1
- package/dist/runner/protocolEndpoint.js.map +0 -1
- package/dist/runner-DhOZH9xz.cjs.map +0 -1
- package/dist/types-BB8mb_-T.d.cts +0 -14
- package/dist/types-BB8mb_-T.d.cts.map +0 -1
- package/dist/types-D7uau0GM.d.ts +0 -14
- package/dist/types-D7uau0GM.d.ts.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,22 +1,625 @@
|
|
|
1
|
-
import { t as runQuickJsSession } from "./runner-
|
|
2
|
-
import {
|
|
1
|
+
import { a as isJsonSerializable, c as getExecutionTimeoutMessage, i as isExecuteFailure, l as normalizeThrownMessage, n as resolveExecutorRuntimeOptions, o as createExecutionContext, r as ExecuteFailure, s as createTimeoutExecuteResult, t as runQuickJsSession } from "./runner-CteKTaPD.js";
|
|
2
|
+
import { fork } from "node:child_process";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { createResourcePool, getNodeTransportExecArgv, runHostTransportSession } from "@execbox/protocol";
|
|
5
|
+
import { randomUUID } from "node:crypto";
|
|
6
|
+
import { availableParallelism } from "node:os";
|
|
7
|
+
import { Worker } from "node:worker_threads";
|
|
3
8
|
|
|
9
|
+
//#region ../core/src/runner.ts
|
|
10
|
+
function toTrustedExecuteError(error) {
|
|
11
|
+
if (isExecuteFailure(error)) return {
|
|
12
|
+
code: error.code,
|
|
13
|
+
message: error.message
|
|
14
|
+
};
|
|
15
|
+
return {
|
|
16
|
+
code: "tool_error",
|
|
17
|
+
message: normalizeThrownMessage(error)
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Converts resolved providers into manifest metadata that reveals only namespace details.
|
|
22
|
+
*/
|
|
23
|
+
function extractProviderManifests(providers) {
|
|
24
|
+
return providers.map((provider) => ({
|
|
25
|
+
name: provider.name,
|
|
26
|
+
tools: Object.fromEntries(Object.entries(provider.tools).map(([safeToolName, descriptor]) => [safeToolName, {
|
|
27
|
+
description: descriptor.description,
|
|
28
|
+
originalName: descriptor.originalName,
|
|
29
|
+
safeName: descriptor.safeName
|
|
30
|
+
}])),
|
|
31
|
+
types: provider.types
|
|
32
|
+
}));
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Creates a host-side dispatcher for runner-emitted tool calls.
|
|
36
|
+
*/
|
|
37
|
+
function createToolCallDispatcher(providers, signal) {
|
|
38
|
+
const providerMap = new Map(providers.map((provider) => [provider.name, provider]));
|
|
39
|
+
return async (call) => {
|
|
40
|
+
const provider = providerMap.get(call.providerName);
|
|
41
|
+
const descriptor = provider?.tools[call.safeToolName];
|
|
42
|
+
if (!provider || !descriptor) return {
|
|
43
|
+
error: {
|
|
44
|
+
code: "internal_error",
|
|
45
|
+
message: `Unknown tool ${call.providerName}.${call.safeToolName}`
|
|
46
|
+
},
|
|
47
|
+
ok: false
|
|
48
|
+
};
|
|
49
|
+
try {
|
|
50
|
+
if (signal.aborted) return {
|
|
51
|
+
error: {
|
|
52
|
+
code: "timeout",
|
|
53
|
+
message: getExecutionTimeoutMessage()
|
|
54
|
+
},
|
|
55
|
+
ok: false
|
|
56
|
+
};
|
|
57
|
+
const result = await descriptor.execute(call.input, createExecutionContext(signal, provider.name, descriptor.safeName, descriptor.originalName));
|
|
58
|
+
if (result !== void 0 && !isJsonSerializable(result)) throw new ExecuteFailure("serialization_error", "Host value is not JSON-serializable");
|
|
59
|
+
return {
|
|
60
|
+
ok: true,
|
|
61
|
+
result
|
|
62
|
+
};
|
|
63
|
+
} catch (error) {
|
|
64
|
+
return {
|
|
65
|
+
error: toTrustedExecuteError(error),
|
|
66
|
+
ok: false
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
//#endregion
|
|
73
|
+
//#region src/hosted/shared.ts
|
|
74
|
+
/**
|
|
75
|
+
* Default grace period before a hosted shell is forcefully terminated.
|
|
76
|
+
*/
|
|
77
|
+
const DEFAULT_CANCEL_GRACE_MS = 25;
|
|
78
|
+
/**
|
|
79
|
+
* Default pooling limits shared by the hosted QuickJS executors.
|
|
80
|
+
*/
|
|
81
|
+
const DEFAULT_POOL_OPTIONS = {
|
|
82
|
+
idleTimeoutMs: 3e4,
|
|
83
|
+
maxSize: 1,
|
|
84
|
+
minSize: 0,
|
|
85
|
+
prewarm: false
|
|
86
|
+
};
|
|
87
|
+
/**
|
|
88
|
+
* Minimal code used to warm a hosted shell without touching user providers.
|
|
89
|
+
*/
|
|
90
|
+
const DEFAULT_PREWARM_CODE = "undefined";
|
|
91
|
+
/**
|
|
92
|
+
* Wraps a transport so warmup and pooled execution can borrow it without
|
|
93
|
+
* taking ownership of its lifecycle.
|
|
94
|
+
*/
|
|
95
|
+
function createBorrowedTransport(transport) {
|
|
96
|
+
return {
|
|
97
|
+
dispose() {},
|
|
98
|
+
onClose: transport.onClose,
|
|
99
|
+
onError: transport.onError,
|
|
100
|
+
onMessage: transport.onMessage,
|
|
101
|
+
send: transport.send,
|
|
102
|
+
terminate: transport.terminate
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Resolves how many pooled shells should be prewarmed from the pool settings.
|
|
107
|
+
*/
|
|
108
|
+
function getPrewarmCount(pool) {
|
|
109
|
+
if (!pool?.prewarm) return 0;
|
|
110
|
+
if (typeof pool.prewarm === "number") return Math.max(0, Math.min(pool.prewarm, pool.maxSize));
|
|
111
|
+
return Math.max(1, Math.min(pool.minSize ?? 1, pool.maxSize));
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Caps an explicit warmup request to the configured pool boundaries.
|
|
115
|
+
*/
|
|
116
|
+
function getWarmupTarget(count, poolOptions) {
|
|
117
|
+
return Math.max(0, Math.min(count ?? poolOptions.minSize ?? 0, poolOptions.maxSize));
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Returns whether a hosted execution result is safe to return to a pool.
|
|
121
|
+
*/
|
|
122
|
+
function isReusableResult(result) {
|
|
123
|
+
return result.ok || !["internal_error", "timeout"].includes(result.error.code);
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Normalizes a failed warmup result into an actionable host-side error.
|
|
127
|
+
*/
|
|
128
|
+
function toWarmupError(label, result) {
|
|
129
|
+
if (result.ok) return /* @__PURE__ */ new Error(`Failed to prewarm pooled ${label}`);
|
|
130
|
+
return /* @__PURE__ */ new Error(`Failed to prewarm pooled ${label}: ${result.error.message}`);
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Runs one transport-backed execution session with resolved runtime limits and
|
|
134
|
+
* a fresh execution identifier.
|
|
135
|
+
*/
|
|
136
|
+
async function runHostedTransportSession(options) {
|
|
137
|
+
return await runHostTransportSession({
|
|
138
|
+
cancelGraceMs: options.cancelGraceMs,
|
|
139
|
+
code: options.code,
|
|
140
|
+
executionId: randomUUID(),
|
|
141
|
+
onSettled: options.onSettled,
|
|
142
|
+
providers: options.providers,
|
|
143
|
+
runtimeOptions: resolveExecutorRuntimeOptions(options.executorOptions, options.requestOptions),
|
|
144
|
+
signal: options.requestOptions?.signal,
|
|
145
|
+
transport: options.transport
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Exercises a set of leased shells with a warmup run and releases each lease
|
|
150
|
+
* according to the warmup result.
|
|
151
|
+
*/
|
|
152
|
+
async function warmHostedPool(options) {
|
|
153
|
+
const rejected = (await Promise.allSettled(options.shells.map(async (shell) => {
|
|
154
|
+
let reusable = false;
|
|
155
|
+
try {
|
|
156
|
+
const result = await options.runSession(createBorrowedTransport(options.getTransport(shell)), DEFAULT_PREWARM_CODE, []);
|
|
157
|
+
reusable = result.ok;
|
|
158
|
+
if (!result.ok) throw toWarmupError(options.label, result);
|
|
159
|
+
} finally {
|
|
160
|
+
await options.onRelease(shell, reusable);
|
|
161
|
+
}
|
|
162
|
+
}))).find((result) => result.status === "rejected");
|
|
163
|
+
if (rejected?.status === "rejected") throw rejected.reason;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
//#endregion
|
|
167
|
+
//#region src/hosted/processHostedExecutor.ts
|
|
168
|
+
function resolveProcessEntryPath() {
|
|
169
|
+
const extension = import.meta.url.endsWith(".ts") ? ".ts" : ".js";
|
|
170
|
+
return fileURLToPath(new URL(`../processEntry${extension}`, import.meta.url));
|
|
171
|
+
}
|
|
172
|
+
function createUnexpectedExitMessage(code, signal) {
|
|
173
|
+
if (code !== null) return `Child process exited unexpectedly with code ${code}`;
|
|
174
|
+
if (signal) return `Child process exited unexpectedly with signal ${signal}`;
|
|
175
|
+
return "Child process exited unexpectedly";
|
|
176
|
+
}
|
|
177
|
+
function createChildProcess() {
|
|
178
|
+
return fork(resolveProcessEntryPath(), [], {
|
|
179
|
+
execArgv: getNodeTransportExecArgv(import.meta.url),
|
|
180
|
+
stdio: [
|
|
181
|
+
"ignore",
|
|
182
|
+
"ignore",
|
|
183
|
+
"ignore",
|
|
184
|
+
"ipc"
|
|
185
|
+
]
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
function createProcessTransport(child) {
|
|
189
|
+
let terminated = false;
|
|
190
|
+
let closeReason;
|
|
191
|
+
const closeHandlers = /* @__PURE__ */ new Set();
|
|
192
|
+
const errorHandlers = /* @__PURE__ */ new Set();
|
|
193
|
+
const messageHandlers = /* @__PURE__ */ new Set();
|
|
194
|
+
const terminateChild = () => {
|
|
195
|
+
if (terminated) return;
|
|
196
|
+
terminated = true;
|
|
197
|
+
child.kill("SIGKILL");
|
|
198
|
+
};
|
|
199
|
+
const notifyClose = (reason) => {
|
|
200
|
+
if (closeReason) return;
|
|
201
|
+
closeReason = reason;
|
|
202
|
+
for (const handler of closeHandlers) handler(reason);
|
|
203
|
+
};
|
|
204
|
+
const onDisconnect = () => {
|
|
205
|
+
notifyClose({ message: "Child process disconnected unexpectedly" });
|
|
206
|
+
};
|
|
207
|
+
const onExit = (code, signal) => {
|
|
208
|
+
notifyClose({
|
|
209
|
+
code,
|
|
210
|
+
message: createUnexpectedExitMessage(code, signal),
|
|
211
|
+
signal
|
|
212
|
+
});
|
|
213
|
+
};
|
|
214
|
+
const onError = (error) => {
|
|
215
|
+
for (const handler of errorHandlers) handler(error);
|
|
216
|
+
};
|
|
217
|
+
const onMessage = (message) => {
|
|
218
|
+
for (const handler of messageHandlers) handler(message);
|
|
219
|
+
};
|
|
220
|
+
child.on("disconnect", onDisconnect);
|
|
221
|
+
child.on("exit", onExit);
|
|
222
|
+
child.on("error", onError);
|
|
223
|
+
child.on("message", onMessage);
|
|
224
|
+
return {
|
|
225
|
+
dispose: () => {
|
|
226
|
+
child.off("disconnect", onDisconnect);
|
|
227
|
+
child.off("exit", onExit);
|
|
228
|
+
child.off("error", onError);
|
|
229
|
+
child.off("message", onMessage);
|
|
230
|
+
terminateChild();
|
|
231
|
+
},
|
|
232
|
+
onClose: (handler) => {
|
|
233
|
+
closeHandlers.add(handler);
|
|
234
|
+
if (closeReason) queueMicrotask(() => {
|
|
235
|
+
if (closeHandlers.has(handler)) handler(closeReason);
|
|
236
|
+
});
|
|
237
|
+
return () => {
|
|
238
|
+
closeHandlers.delete(handler);
|
|
239
|
+
};
|
|
240
|
+
},
|
|
241
|
+
onError: (handler) => {
|
|
242
|
+
errorHandlers.add(handler);
|
|
243
|
+
return () => errorHandlers.delete(handler);
|
|
244
|
+
},
|
|
245
|
+
onMessage: (handler) => {
|
|
246
|
+
messageHandlers.add(handler);
|
|
247
|
+
return () => messageHandlers.delete(handler);
|
|
248
|
+
},
|
|
249
|
+
send: (message) => new Promise((resolve, reject) => {
|
|
250
|
+
if (!child.connected || typeof child.send !== "function") {
|
|
251
|
+
reject(/* @__PURE__ */ new Error("Child process disconnected unexpectedly"));
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
child.send(message, (error) => {
|
|
255
|
+
if (error) {
|
|
256
|
+
reject(error);
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
resolve();
|
|
260
|
+
});
|
|
261
|
+
}),
|
|
262
|
+
terminate: () => {
|
|
263
|
+
terminateChild();
|
|
264
|
+
}
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
function createProcessShell() {
|
|
268
|
+
const child = createChildProcess();
|
|
269
|
+
return {
|
|
270
|
+
child,
|
|
271
|
+
transport: createProcessTransport(child)
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
function resolvePoolOptions$1(options) {
|
|
275
|
+
if (options.mode === "ephemeral") return;
|
|
276
|
+
return {
|
|
277
|
+
...DEFAULT_POOL_OPTIONS,
|
|
278
|
+
...options.pool
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Child-process executor that runs guest code inside a dedicated QuickJS runtime per call.
|
|
283
|
+
*/
|
|
284
|
+
var ProcessHostedQuickJsExecutor = class {
|
|
285
|
+
cancelGraceMs;
|
|
286
|
+
options;
|
|
287
|
+
pool;
|
|
288
|
+
poolOptions;
|
|
289
|
+
warmup;
|
|
290
|
+
/**
|
|
291
|
+
* Creates a hosted QuickJS executor that launches child-process shells on demand.
|
|
292
|
+
*/
|
|
293
|
+
constructor(options) {
|
|
294
|
+
this.cancelGraceMs = options.cancelGraceMs ?? DEFAULT_CANCEL_GRACE_MS;
|
|
295
|
+
this.options = options;
|
|
296
|
+
const poolOptions = resolvePoolOptions$1(options);
|
|
297
|
+
this.poolOptions = poolOptions;
|
|
298
|
+
if (poolOptions) {
|
|
299
|
+
this.pool = createResourcePool({
|
|
300
|
+
create: async () => createProcessShell(),
|
|
301
|
+
destroy: async (shell) => {
|
|
302
|
+
await shell.transport.dispose();
|
|
303
|
+
},
|
|
304
|
+
idleTimeoutMs: poolOptions.idleTimeoutMs,
|
|
305
|
+
maxSize: poolOptions.maxSize,
|
|
306
|
+
minSize: poolOptions.minSize
|
|
307
|
+
});
|
|
308
|
+
const prewarmCount = getPrewarmCount(poolOptions);
|
|
309
|
+
if (prewarmCount > 0) this.warmup = this.warmPool(prewarmCount);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Disposes any pooled child-process shells owned by this executor.
|
|
314
|
+
*/
|
|
315
|
+
async dispose() {
|
|
316
|
+
await this.pool?.dispose();
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* Prewarms pooled child-process shells up to the requested count.
|
|
320
|
+
*/
|
|
321
|
+
async prewarm(count) {
|
|
322
|
+
if (!this.pool || !this.poolOptions) return;
|
|
323
|
+
const target = getWarmupTarget(count, this.poolOptions);
|
|
324
|
+
if (target <= 0) return;
|
|
325
|
+
await this.warmPool(target);
|
|
326
|
+
}
|
|
327
|
+
async runTransportSession(transport, code, providers, options = {}, onSettled) {
|
|
328
|
+
return await runHostedTransportSession({
|
|
329
|
+
cancelGraceMs: this.cancelGraceMs,
|
|
330
|
+
code,
|
|
331
|
+
executorOptions: this.options,
|
|
332
|
+
onSettled,
|
|
333
|
+
providers,
|
|
334
|
+
requestOptions: options,
|
|
335
|
+
transport
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
async warmPool(count) {
|
|
339
|
+
if (!this.pool) return;
|
|
340
|
+
await this.pool.prewarm(count);
|
|
341
|
+
const leases = [];
|
|
342
|
+
try {
|
|
343
|
+
for (let index = 0; index < count; index += 1) leases.push(await this.pool.acquire());
|
|
344
|
+
} catch (error) {
|
|
345
|
+
await Promise.allSettled(leases.map(async (lease) => await lease.release(false)));
|
|
346
|
+
throw error;
|
|
347
|
+
}
|
|
348
|
+
await warmHostedPool({
|
|
349
|
+
count,
|
|
350
|
+
getTransport: (lease) => lease.value.transport,
|
|
351
|
+
label: "child process",
|
|
352
|
+
onRelease: async (lease, reusable) => await lease.release(reusable),
|
|
353
|
+
runSession: async (transport, code, providers) => await this.runTransportSession(transport, code, providers),
|
|
354
|
+
shells: leases
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* Executes guest code in a child-process-hosted QuickJS shell.
|
|
359
|
+
*/
|
|
360
|
+
async execute(code, providers, options = {}) {
|
|
361
|
+
if (options.signal?.aborted) return createTimeoutExecuteResult();
|
|
362
|
+
await this.warmup;
|
|
363
|
+
if (this.pool) {
|
|
364
|
+
const lease = await this.pool.acquire();
|
|
365
|
+
return await this.runTransportSession(createBorrowedTransport(lease.value.transport), code, providers, options, async (result) => {
|
|
366
|
+
await lease.release(isReusableResult(result));
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
let child;
|
|
370
|
+
try {
|
|
371
|
+
child = createChildProcess();
|
|
372
|
+
} catch (error) {
|
|
373
|
+
return {
|
|
374
|
+
durationMs: 0,
|
|
375
|
+
error: {
|
|
376
|
+
code: "internal_error",
|
|
377
|
+
message: error instanceof Error ? error.message : String(error)
|
|
378
|
+
},
|
|
379
|
+
logs: [],
|
|
380
|
+
ok: false
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
return await this.runTransportSession(createProcessTransport(child), code, providers, options);
|
|
384
|
+
}
|
|
385
|
+
};
|
|
386
|
+
|
|
387
|
+
//#endregion
|
|
388
|
+
//#region src/hosted/workerHostedExecutor.ts
|
|
389
|
+
const DEFAULT_POOLED_WORKER_MAX_SIZE = 4;
|
|
390
|
+
function resolveWorkerEntryUrl() {
|
|
391
|
+
const extension = import.meta.url.endsWith(".ts") ? ".ts" : ".js";
|
|
392
|
+
return new URL(`../workerEntry${extension}`, import.meta.url);
|
|
393
|
+
}
|
|
394
|
+
function createWorkerTransport(worker) {
|
|
395
|
+
let terminated = false;
|
|
396
|
+
let closeReason;
|
|
397
|
+
const closeHandlers = /* @__PURE__ */ new Set();
|
|
398
|
+
const errorHandlers = /* @__PURE__ */ new Set();
|
|
399
|
+
const messageHandlers = /* @__PURE__ */ new Set();
|
|
400
|
+
const terminateWorker = async () => {
|
|
401
|
+
if (terminated) return;
|
|
402
|
+
terminated = true;
|
|
403
|
+
await worker.terminate().catch(() => {});
|
|
404
|
+
};
|
|
405
|
+
const notifyClose = (reason) => {
|
|
406
|
+
if (closeReason) return;
|
|
407
|
+
closeReason = reason;
|
|
408
|
+
for (const handler of closeHandlers) handler(reason);
|
|
409
|
+
};
|
|
410
|
+
const onExit = (code) => {
|
|
411
|
+
notifyClose({
|
|
412
|
+
code,
|
|
413
|
+
message: `Worker exited unexpectedly with code ${code}`
|
|
414
|
+
});
|
|
415
|
+
};
|
|
416
|
+
const onError = (error) => {
|
|
417
|
+
for (const handler of errorHandlers) handler(error);
|
|
418
|
+
};
|
|
419
|
+
const onMessage = (message) => {
|
|
420
|
+
for (const handler of messageHandlers) handler(message);
|
|
421
|
+
};
|
|
422
|
+
worker.on("exit", onExit);
|
|
423
|
+
worker.on("error", onError);
|
|
424
|
+
worker.on("message", onMessage);
|
|
425
|
+
return {
|
|
426
|
+
dispose: async () => {
|
|
427
|
+
worker.off("exit", onExit);
|
|
428
|
+
worker.off("error", onError);
|
|
429
|
+
worker.off("message", onMessage);
|
|
430
|
+
await terminateWorker();
|
|
431
|
+
},
|
|
432
|
+
onClose: (handler) => {
|
|
433
|
+
closeHandlers.add(handler);
|
|
434
|
+
if (closeReason) queueMicrotask(() => {
|
|
435
|
+
if (closeHandlers.has(handler)) handler(closeReason);
|
|
436
|
+
});
|
|
437
|
+
return () => closeHandlers.delete(handler);
|
|
438
|
+
},
|
|
439
|
+
onError: (handler) => {
|
|
440
|
+
errorHandlers.add(handler);
|
|
441
|
+
return () => errorHandlers.delete(handler);
|
|
442
|
+
},
|
|
443
|
+
onMessage: (handler) => {
|
|
444
|
+
messageHandlers.add(handler);
|
|
445
|
+
return () => messageHandlers.delete(handler);
|
|
446
|
+
},
|
|
447
|
+
send: (message) => {
|
|
448
|
+
worker.postMessage(message);
|
|
449
|
+
},
|
|
450
|
+
terminate: async () => {
|
|
451
|
+
await terminateWorker();
|
|
452
|
+
}
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
function createWorkerShell(options) {
|
|
456
|
+
const worker = new Worker(resolveWorkerEntryUrl(), {
|
|
457
|
+
execArgv: getNodeTransportExecArgv(import.meta.url),
|
|
458
|
+
resourceLimits: options.workerResourceLimits
|
|
459
|
+
});
|
|
460
|
+
return {
|
|
461
|
+
transport: createWorkerTransport(worker),
|
|
462
|
+
worker
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
function getDefaultPoolMaxSize() {
|
|
466
|
+
try {
|
|
467
|
+
return Math.max(1, Math.min(availableParallelism(), DEFAULT_POOLED_WORKER_MAX_SIZE));
|
|
468
|
+
} catch {
|
|
469
|
+
return 1;
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
function resolvePoolOptions(options) {
|
|
473
|
+
if (options.mode === "ephemeral") return;
|
|
474
|
+
return {
|
|
475
|
+
...DEFAULT_POOL_OPTIONS,
|
|
476
|
+
maxSize: getDefaultPoolMaxSize(),
|
|
477
|
+
...options.pool
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
/**
|
|
481
|
+
* Worker-thread executor that runs guest code inside a dedicated QuickJS runtime per call.
|
|
482
|
+
*/
|
|
483
|
+
var WorkerHostedQuickJsExecutor = class {
|
|
484
|
+
cancelGraceMs;
|
|
485
|
+
options;
|
|
486
|
+
pool;
|
|
487
|
+
poolOptions;
|
|
488
|
+
warmup;
|
|
489
|
+
/**
|
|
490
|
+
* Creates a hosted QuickJS executor that launches worker-thread shells on demand.
|
|
491
|
+
*/
|
|
492
|
+
constructor(options) {
|
|
493
|
+
this.cancelGraceMs = options.cancelGraceMs ?? DEFAULT_CANCEL_GRACE_MS;
|
|
494
|
+
this.options = options;
|
|
495
|
+
const poolOptions = resolvePoolOptions(options);
|
|
496
|
+
this.poolOptions = poolOptions;
|
|
497
|
+
if (poolOptions) {
|
|
498
|
+
this.pool = createResourcePool({
|
|
499
|
+
create: async () => createWorkerShell(options),
|
|
500
|
+
destroy: async (shell) => {
|
|
501
|
+
await shell.transport.dispose();
|
|
502
|
+
},
|
|
503
|
+
idleTimeoutMs: poolOptions.idleTimeoutMs,
|
|
504
|
+
maxSize: poolOptions.maxSize,
|
|
505
|
+
minSize: poolOptions.minSize
|
|
506
|
+
});
|
|
507
|
+
const prewarmCount = getPrewarmCount(poolOptions);
|
|
508
|
+
if (prewarmCount > 0) this.warmup = this.warmPool(prewarmCount);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
/**
|
|
512
|
+
* Disposes any pooled worker-thread shells owned by this executor.
|
|
513
|
+
*/
|
|
514
|
+
async dispose() {
|
|
515
|
+
await this.pool?.dispose();
|
|
516
|
+
}
|
|
517
|
+
/**
|
|
518
|
+
* Prewarms pooled worker-thread shells up to the requested count.
|
|
519
|
+
*/
|
|
520
|
+
async prewarm(count) {
|
|
521
|
+
if (!this.pool || !this.poolOptions) return;
|
|
522
|
+
const target = getWarmupTarget(count, this.poolOptions);
|
|
523
|
+
if (target <= 0) return;
|
|
524
|
+
await this.warmPool(target);
|
|
525
|
+
}
|
|
526
|
+
async runTransportSession(transport, code, providers, options = {}, onSettled) {
|
|
527
|
+
return await runHostedTransportSession({
|
|
528
|
+
cancelGraceMs: this.cancelGraceMs,
|
|
529
|
+
code,
|
|
530
|
+
executorOptions: this.options,
|
|
531
|
+
onSettled,
|
|
532
|
+
providers,
|
|
533
|
+
requestOptions: options,
|
|
534
|
+
transport
|
|
535
|
+
});
|
|
536
|
+
}
|
|
537
|
+
async warmPool(count) {
|
|
538
|
+
if (!this.pool) return;
|
|
539
|
+
await this.pool.prewarm(count);
|
|
540
|
+
const leases = [];
|
|
541
|
+
try {
|
|
542
|
+
for (let index = 0; index < count; index += 1) leases.push(await this.pool.acquire());
|
|
543
|
+
} catch (error) {
|
|
544
|
+
await Promise.allSettled(leases.map(async (lease) => await lease.release(false)));
|
|
545
|
+
throw error;
|
|
546
|
+
}
|
|
547
|
+
await warmHostedPool({
|
|
548
|
+
count,
|
|
549
|
+
getTransport: (lease) => lease.value.transport,
|
|
550
|
+
label: "worker shell",
|
|
551
|
+
onRelease: async (lease, reusable) => await lease.release(reusable),
|
|
552
|
+
runSession: async (transport, code, providers) => await this.runTransportSession(transport, code, providers),
|
|
553
|
+
shells: leases
|
|
554
|
+
});
|
|
555
|
+
}
|
|
556
|
+
/**
|
|
557
|
+
* Executes guest code in a worker-thread-hosted QuickJS shell.
|
|
558
|
+
*/
|
|
559
|
+
async execute(code, providers, options = {}) {
|
|
560
|
+
if (options.signal?.aborted) return createTimeoutExecuteResult();
|
|
561
|
+
await this.warmup;
|
|
562
|
+
if (this.pool) {
|
|
563
|
+
const lease = await this.pool.acquire();
|
|
564
|
+
return await this.runTransportSession(createBorrowedTransport(lease.value.transport), code, providers, options, async (result) => {
|
|
565
|
+
await lease.release(isReusableResult(result));
|
|
566
|
+
});
|
|
567
|
+
}
|
|
568
|
+
const worker = new Worker(resolveWorkerEntryUrl(), {
|
|
569
|
+
execArgv: getNodeTransportExecArgv(import.meta.url),
|
|
570
|
+
resourceLimits: this.options.workerResourceLimits
|
|
571
|
+
});
|
|
572
|
+
return await this.runTransportSession(createWorkerTransport(worker), code, providers, options);
|
|
573
|
+
}
|
|
574
|
+
};
|
|
575
|
+
|
|
576
|
+
//#endregion
|
|
4
577
|
//#region src/quickjsExecutor.ts
|
|
578
|
+
function isWorkerOptions(options) {
|
|
579
|
+
return options.host === "worker";
|
|
580
|
+
}
|
|
581
|
+
function isProcessOptions(options) {
|
|
582
|
+
return options.host === "process";
|
|
583
|
+
}
|
|
5
584
|
/**
|
|
6
|
-
* QuickJS-backed executor for
|
|
585
|
+
* QuickJS-backed executor for inline, worker-backed, or process-backed JavaScript runs.
|
|
7
586
|
*/
|
|
8
587
|
var QuickJsExecutor = class {
|
|
588
|
+
hostedExecutor;
|
|
9
589
|
options;
|
|
10
590
|
/**
|
|
11
|
-
* Creates a QuickJS executor with
|
|
591
|
+
* Creates a QuickJS executor with inline QuickJS by default, or a hosted
|
|
592
|
+
* worker/process shell when `host` is explicitly set.
|
|
12
593
|
*/
|
|
13
594
|
constructor(options = {}) {
|
|
595
|
+
if (isWorkerOptions(options)) {
|
|
596
|
+
this.hostedExecutor = new WorkerHostedQuickJsExecutor(options);
|
|
597
|
+
return;
|
|
598
|
+
}
|
|
599
|
+
if (isProcessOptions(options)) {
|
|
600
|
+
this.hostedExecutor = new ProcessHostedQuickJsExecutor(options);
|
|
601
|
+
return;
|
|
602
|
+
}
|
|
14
603
|
this.options = options;
|
|
15
604
|
}
|
|
16
605
|
/**
|
|
606
|
+
* Disposes any pooled hosted shells owned by this executor.
|
|
607
|
+
*/
|
|
608
|
+
async dispose() {
|
|
609
|
+
await this.hostedExecutor?.dispose?.();
|
|
610
|
+
}
|
|
611
|
+
/**
|
|
612
|
+
* Prewarms pooled hosted shells when the executor is running in worker or
|
|
613
|
+
* process mode. Inline mode treats this as a no-op.
|
|
614
|
+
*/
|
|
615
|
+
async prewarm(count) {
|
|
616
|
+
await this.hostedExecutor?.prewarm?.(count);
|
|
617
|
+
}
|
|
618
|
+
/**
|
|
17
619
|
* Executes JavaScript against the provided tool namespaces in a fresh QuickJS runtime.
|
|
18
620
|
*/
|
|
19
621
|
async execute(code, providers, options = {}) {
|
|
622
|
+
if (this.hostedExecutor) return await this.hostedExecutor.execute(code, providers, options);
|
|
20
623
|
if (options.signal?.aborted) return createTimeoutExecuteResult();
|
|
21
624
|
const abortController = new AbortController();
|
|
22
625
|
const onToolCall = createToolCallDispatcher(providers, abortController.signal);
|