@earendil-works/pi-codemode 0.0.1 → 0.99.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 +184 -1
- package/dist/declarations.d.ts +53 -0
- package/dist/declarations.d.ts.map +1 -0
- package/dist/declarations.js +330 -0
- package/dist/declarations.js.map +1 -0
- package/dist/identifier.d.ts +6 -0
- package/dist/identifier.d.ts.map +1 -0
- package/dist/identifier.js +13 -0
- package/dist/identifier.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/runtime/host.d.ts +35 -0
- package/dist/runtime/host.d.ts.map +1 -0
- package/dist/runtime/host.js +314 -0
- package/dist/runtime/host.js.map +1 -0
- package/dist/runtime/prelude-source.d.ts +31 -0
- package/dist/runtime/prelude-source.d.ts.map +1 -0
- package/dist/runtime/prelude-source.js +322 -0
- package/dist/runtime/prelude-source.js.map +1 -0
- package/dist/runtime/protocol.d.ts +69 -0
- package/dist/runtime/protocol.d.ts.map +1 -0
- package/dist/runtime/protocol.js +10 -0
- package/dist/runtime/protocol.js.map +1 -0
- package/dist/runtime/worker.d.ts +2 -0
- package/dist/runtime/worker.d.ts.map +1 -0
- package/dist/runtime/worker.js +137 -0
- package/dist/runtime/worker.js.map +1 -0
- package/dist/source.d.ts +35 -0
- package/dist/source.d.ts.map +1 -0
- package/dist/source.js +92 -0
- package/dist/source.js.map +1 -0
- package/dist/types.d.ts +136 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/wasm.d.ts +12 -0
- package/dist/wasm.d.ts.map +1 -0
- package/dist/wasm.js +24 -0
- package/dist/wasm.js.map +1 -0
- package/package.json +54 -4
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JavaScript evaluated inside the QuickJS VM before the script runs.
|
|
3
|
+
*
|
|
4
|
+
* The VM is its own wasm instance, so nothing here guards a realm boundary.
|
|
5
|
+
* The prelude keeps the host bridge in a closure so the script cannot call it
|
|
6
|
+
* directly, and builds `tools`, `ALL_TOOLS`, the output helpers (`text`, `image`,
|
|
7
|
+
* `exit`, `console`), and globals on top of it. Tool arguments and results cross
|
|
8
|
+
* as JSON strings and are parsed on this side.
|
|
9
|
+
*
|
|
10
|
+
* Output helpers: `text(value)` appends a text item (non-strings are
|
|
11
|
+
* JSON-stringified), `image(urlOrItem)` appends an image from a base64 `data:` URL, an
|
|
12
|
+
* `{ image_url }` object, or an MCP `ImageContent` block, and `exit()` ends the script
|
|
13
|
+
* successfully. `console.*` appends text items like `text()`.
|
|
14
|
+
*
|
|
15
|
+
* `store(key, value)` and `load(key)` are synchronous: they work on a snapshot of
|
|
16
|
+
* JSON text passed in as `storeJson`, and the keys the script wrote are reported
|
|
17
|
+
* with a successful "done".
|
|
18
|
+
*
|
|
19
|
+
* Evaluates to a function `(bridge, toolsJson, globalsJson, storeJson) => { settle, run, stalled }`.
|
|
20
|
+
* `toolsJson` lists `{ name, jsName, description }`: `tools[jsName]` and `tools[name]` call the
|
|
21
|
+
* tool, and `ALL_TOOLS` lists `{ name: jsName, description }`.
|
|
22
|
+
* `stalled()` reports a script that has not finished while no host call is pending: with no timers
|
|
23
|
+
* or I/O in the VM, nothing can ever resume it.
|
|
24
|
+
* `globalsJson` lists `{ name, spread }`; `a.b` names are grouped into a frozen `a` object.
|
|
25
|
+
* `bridge(kind, a, b, c)` with kind "call" or "global" (id, name, argsJson),
|
|
26
|
+
* "output" ("text", text) or ("image", data, mimeType), or "done" (ok, valueJsonOrErrorJson, writesJson).
|
|
27
|
+
*/
|
|
28
|
+
export const MAX_STORE_VALUE_CHARS = 256 * 1024;
|
|
29
|
+
export const MAX_STORE_TOTAL_CHARS = 1024 * 1024;
|
|
30
|
+
const IMAGE_HELPER_EXPECTS = "image expects a non-empty image URL string, an object with image_url, or a raw MCP image block";
|
|
31
|
+
export const PRELUDE_SOURCE = `(function (bridge, toolsJson, globalsJson, storeJson) {
|
|
32
|
+
"use strict";
|
|
33
|
+
const stringify = JSON.stringify;
|
|
34
|
+
const parse = JSON.parse;
|
|
35
|
+
const promiseThen = Promise.prototype.then;
|
|
36
|
+
const ErrorCtor = Error;
|
|
37
|
+
const TypeErrorCtor = TypeError;
|
|
38
|
+
const pending = new Map();
|
|
39
|
+
let nextId = 1;
|
|
40
|
+
let finished = false;
|
|
41
|
+
// Thrown by exit() to unwind the script after it already reported success.
|
|
42
|
+
const EXIT = Object.freeze({});
|
|
43
|
+
|
|
44
|
+
function done(ok, payload, writes) {
|
|
45
|
+
if (finished) return;
|
|
46
|
+
finished = true;
|
|
47
|
+
bridge("done", ok, payload, writes);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function serialize(value) {
|
|
51
|
+
return value === undefined ? undefined : stringify(value);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// QuickJS stacks list frames only. Prefix "Name: message" like V8 so the
|
|
55
|
+
// text reads the same as a Node error, and drop this prelude's frames.
|
|
56
|
+
function errorText(error) {
|
|
57
|
+
const head = error.message ? error.name + ": " + error.message : String(error.name);
|
|
58
|
+
const frames =
|
|
59
|
+
typeof error.stack === "string"
|
|
60
|
+
? error.stack.split("\\n").filter((line) => line.trim() && !line.includes("codemode-prelude.js"))
|
|
61
|
+
: [];
|
|
62
|
+
return [head, ...frames].join("\\n");
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function format(value) {
|
|
66
|
+
if (typeof value === "string") return value;
|
|
67
|
+
if (value instanceof ErrorCtor) return errorText(value);
|
|
68
|
+
try {
|
|
69
|
+
const json = stringify(value);
|
|
70
|
+
return json === undefined ? String(value) : json;
|
|
71
|
+
} catch {
|
|
72
|
+
return String(value);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function describeError(error) {
|
|
77
|
+
if (error instanceof ErrorCtor) {
|
|
78
|
+
return stringify({ name: error.name, message: error.message, stack: errorText(error) });
|
|
79
|
+
}
|
|
80
|
+
return stringify({ message: format(error) });
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function caller(kind, name, spread) {
|
|
84
|
+
return (...args) =>
|
|
85
|
+
new Promise((resolve, reject) => {
|
|
86
|
+
let json;
|
|
87
|
+
try {
|
|
88
|
+
json = serialize(spread ? args : args[0]);
|
|
89
|
+
} catch (error) {
|
|
90
|
+
reject(error);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
const id = nextId++;
|
|
94
|
+
pending.set(id, { resolve, reject });
|
|
95
|
+
bridge(kind, id, name, json);
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const tools = Object.create(null);
|
|
100
|
+
const allTools = [];
|
|
101
|
+
for (const { name, jsName, description } of parse(toolsJson)) {
|
|
102
|
+
const fn = caller("call", name);
|
|
103
|
+
// The first tool wins when two names normalize to the same identifier.
|
|
104
|
+
if (!(jsName in tools)) {
|
|
105
|
+
tools[jsName] = fn;
|
|
106
|
+
allTools.push(Object.freeze({ name: jsName, description }));
|
|
107
|
+
}
|
|
108
|
+
if (!(name in tools)) tools[name] = fn;
|
|
109
|
+
}
|
|
110
|
+
Object.freeze(tools);
|
|
111
|
+
Object.freeze(allTools);
|
|
112
|
+
|
|
113
|
+
const namespaces = new Map();
|
|
114
|
+
for (const { name, spread } of parse(globalsJson)) {
|
|
115
|
+
const fn = caller("global", name, spread);
|
|
116
|
+
const dot = name.indexOf(".");
|
|
117
|
+
if (dot === -1) {
|
|
118
|
+
Object.defineProperty(globalThis, name, { value: fn, enumerable: true });
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
const namespace = name.slice(0, dot);
|
|
122
|
+
if (!namespaces.has(namespace)) namespaces.set(namespace, Object.create(null));
|
|
123
|
+
namespaces.get(namespace)[name.slice(dot + 1)] = fn;
|
|
124
|
+
}
|
|
125
|
+
for (const [namespace, members] of namespaces) {
|
|
126
|
+
Object.defineProperty(globalThis, namespace, { value: Object.freeze(members), enumerable: true });
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// key -> JSON text. Sizes count key and JSON characters.
|
|
130
|
+
const stored = new Map(Object.entries(parse(storeJson)));
|
|
131
|
+
const writes = new Map();
|
|
132
|
+
let storedChars = 0;
|
|
133
|
+
for (const [key, json] of stored) storedChars += key.length + json.length;
|
|
134
|
+
|
|
135
|
+
function checkKey(name, key) {
|
|
136
|
+
if (typeof key !== "string") throw new TypeError(name + "() key must be a string");
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function store(key, value) {
|
|
140
|
+
checkKey("store", key);
|
|
141
|
+
const previous = stored.has(key) ? key.length + stored.get(key).length : 0;
|
|
142
|
+
if (value === undefined) {
|
|
143
|
+
stored.delete(key);
|
|
144
|
+
storedChars -= previous;
|
|
145
|
+
writes.set(key, undefined);
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
let json;
|
|
149
|
+
try {
|
|
150
|
+
json = stringify(value);
|
|
151
|
+
} catch (error) {
|
|
152
|
+
throw new TypeError("store(" + stringify(key) + ") value is not JSON-serializable: " + format(error));
|
|
153
|
+
}
|
|
154
|
+
if (json === undefined) {
|
|
155
|
+
throw new TypeError("store(" + stringify(key) + ") value is not JSON-serializable");
|
|
156
|
+
}
|
|
157
|
+
if (json.length > ${MAX_STORE_VALUE_CHARS}) {
|
|
158
|
+
throw new RangeError("store(" + stringify(key) + ") value exceeds ${MAX_STORE_VALUE_CHARS} characters of JSON");
|
|
159
|
+
}
|
|
160
|
+
const next = storedChars - previous + key.length + json.length;
|
|
161
|
+
if (next > ${MAX_STORE_TOTAL_CHARS}) {
|
|
162
|
+
throw new RangeError("store is full: stored values would exceed ${MAX_STORE_TOTAL_CHARS} characters of JSON");
|
|
163
|
+
}
|
|
164
|
+
stored.set(key, json);
|
|
165
|
+
storedChars = next;
|
|
166
|
+
writes.set(key, json);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function load(key) {
|
|
170
|
+
checkKey("load", key);
|
|
171
|
+
const json = stored.get(key);
|
|
172
|
+
return json === undefined ? undefined : parse(json);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function serializeWrites() {
|
|
176
|
+
const entries = [];
|
|
177
|
+
for (const [key, json] of writes) entries.push(json === undefined ? [key] : [key, json]);
|
|
178
|
+
return stringify(entries);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
Object.defineProperty(globalThis, "store", { value: store, enumerable: true });
|
|
182
|
+
Object.defineProperty(globalThis, "load", { value: load, enumerable: true });
|
|
183
|
+
|
|
184
|
+
// Primitives become their string form, everything else JSON.
|
|
185
|
+
function outputText(value) {
|
|
186
|
+
if (value === undefined || value === null || typeof value !== "object" && typeof value !== "function") {
|
|
187
|
+
return String(value);
|
|
188
|
+
}
|
|
189
|
+
const json = stringify(value);
|
|
190
|
+
return json === undefined ? String(value) : json;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function text(value) {
|
|
194
|
+
let rendered;
|
|
195
|
+
try {
|
|
196
|
+
rendered = outputText(value);
|
|
197
|
+
} catch (error) {
|
|
198
|
+
throw new TypeErrorCtor(error instanceof ErrorCtor ? error.message : String(error));
|
|
199
|
+
}
|
|
200
|
+
if (!finished) bridge("output", "text", rendered);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function imageUrl(value) {
|
|
204
|
+
if (typeof value === "string") return value;
|
|
205
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
206
|
+
throw new TypeErrorCtor(${JSON.stringify(IMAGE_HELPER_EXPECTS)});
|
|
207
|
+
}
|
|
208
|
+
if (value.image_url !== undefined) {
|
|
209
|
+
if (typeof value.image_url !== "string") throw new TypeErrorCtor(${JSON.stringify(IMAGE_HELPER_EXPECTS)});
|
|
210
|
+
return value.image_url;
|
|
211
|
+
}
|
|
212
|
+
if (typeof value.type !== "string") throw new TypeErrorCtor(${JSON.stringify(IMAGE_HELPER_EXPECTS)});
|
|
213
|
+
if (value.type !== "image") {
|
|
214
|
+
throw new TypeErrorCtor('image only accepts MCP image blocks, got "' + value.type + '"');
|
|
215
|
+
}
|
|
216
|
+
if (typeof value.data !== "string" || value.data === "") throw new TypeErrorCtor("image expected MCP image data");
|
|
217
|
+
if (value.data.toLowerCase().startsWith("data:")) return value.data;
|
|
218
|
+
const mimeType = typeof value.mimeType === "string" && value.mimeType ? value.mimeType : "application/octet-stream";
|
|
219
|
+
return "data:" + mimeType + ";base64," + value.data;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function image(value) {
|
|
223
|
+
const url = imageUrl(value);
|
|
224
|
+
if (url === "") throw new TypeErrorCtor(${JSON.stringify(IMAGE_HELPER_EXPECTS)});
|
|
225
|
+
const colon = url.indexOf(":");
|
|
226
|
+
const scheme = colon === -1 ? "" : url.slice(0, colon).toLowerCase();
|
|
227
|
+
if (scheme === "http" || scheme === "https") {
|
|
228
|
+
throw new TypeErrorCtor("remote image URLs are not supported in tool outputs. Pass a base64 data URI instead");
|
|
229
|
+
}
|
|
230
|
+
const comma = url.indexOf(",");
|
|
231
|
+
const header = comma === -1 ? [] : url.slice(colon + 1, comma).split(";");
|
|
232
|
+
if (scheme !== "data" || comma === -1 || header.slice(1).every((part) => part.toLowerCase() !== "base64")) {
|
|
233
|
+
throw new TypeErrorCtor("invalid image output. Pass a base64 data URI instead");
|
|
234
|
+
}
|
|
235
|
+
if (!finished) bridge("output", "image", url.slice(comma + 1), header[0] || "application/octet-stream");
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function exit() {
|
|
239
|
+
let writesJson;
|
|
240
|
+
try {
|
|
241
|
+
writesJson = serializeWrites();
|
|
242
|
+
} catch (error) {
|
|
243
|
+
done(false, describeError(error));
|
|
244
|
+
throw EXIT;
|
|
245
|
+
}
|
|
246
|
+
done(true, undefined, writesJson);
|
|
247
|
+
throw EXIT;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const console = {};
|
|
251
|
+
for (const level of ["log", "info", "warn", "error", "debug"]) {
|
|
252
|
+
console[level] = (...args) => {
|
|
253
|
+
if (!finished) bridge("output", "text", args.map(format).join(" "));
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
Object.freeze(console);
|
|
257
|
+
|
|
258
|
+
Object.defineProperty(globalThis, "tools", { value: tools, enumerable: true });
|
|
259
|
+
Object.defineProperty(globalThis, "ALL_TOOLS", { value: allTools, enumerable: true });
|
|
260
|
+
Object.defineProperty(globalThis, "console", { value: console, enumerable: true });
|
|
261
|
+
Object.defineProperty(globalThis, "text", { value: text, enumerable: true });
|
|
262
|
+
Object.defineProperty(globalThis, "image", { value: image, enumerable: true });
|
|
263
|
+
Object.defineProperty(globalThis, "exit", { value: exit, enumerable: true });
|
|
264
|
+
|
|
265
|
+
return {
|
|
266
|
+
settle(id, ok, payload) {
|
|
267
|
+
const entry = pending.get(id);
|
|
268
|
+
if (!entry) return;
|
|
269
|
+
pending.delete(id);
|
|
270
|
+
if (!ok) {
|
|
271
|
+
entry.reject(new ErrorCtor(payload));
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
let value;
|
|
275
|
+
try {
|
|
276
|
+
value = payload === undefined ? undefined : parse(payload);
|
|
277
|
+
} catch (error) {
|
|
278
|
+
entry.reject(error);
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
entry.resolve(value);
|
|
282
|
+
},
|
|
283
|
+
run(fn) {
|
|
284
|
+
let promise;
|
|
285
|
+
try {
|
|
286
|
+
promise = fn(tools, console);
|
|
287
|
+
} catch (error) {
|
|
288
|
+
done(false, describeError(error));
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
promiseThen.call(
|
|
292
|
+
promise,
|
|
293
|
+
(value) => {
|
|
294
|
+
let json;
|
|
295
|
+
try {
|
|
296
|
+
json = serialize(value);
|
|
297
|
+
} catch (error) {
|
|
298
|
+
done(false, describeError(error));
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
done(true, json, serializeWrites());
|
|
302
|
+
},
|
|
303
|
+
(error) => {
|
|
304
|
+
done(false, describeError(error));
|
|
305
|
+
},
|
|
306
|
+
);
|
|
307
|
+
},
|
|
308
|
+
stalled() {
|
|
309
|
+
if (finished || pending.size > 0) return false;
|
|
310
|
+
done(
|
|
311
|
+
false,
|
|
312
|
+
stringify({
|
|
313
|
+
name: "Error",
|
|
314
|
+
message:
|
|
315
|
+
"The script is waiting on a promise that can never settle: no tool call is pending, and timers do not exist here.",
|
|
316
|
+
}),
|
|
317
|
+
);
|
|
318
|
+
return true;
|
|
319
|
+
},
|
|
320
|
+
};
|
|
321
|
+
})`;
|
|
322
|
+
//# sourceMappingURL=prelude-source.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prelude-source.js","sourceRoot":"","sources":["../../src/runtime/prelude-source.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,GAAG,GAAG,IAAI,CAAC;AAChD,MAAM,CAAC,MAAM,qBAAqB,GAAG,IAAI,GAAG,IAAI,CAAC;AAEjD,MAAM,oBAAoB,GACzB,gGAAgG,CAAC;AAElG,MAAM,CAAC,MAAM,cAAc,GAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;sBA8HhB,qBAAqB;uEAC4B,qBAAqB;;;eAG7E,qBAAqB;qEACiC,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;6BA4C7D,IAAI,CAAC,SAAS,CAAC,oBAAoB,CAAC;;;sEAGK,IAAI,CAAC,SAAS,CAAC,oBAAoB,CAAC;;;gEAG1C,IAAI,CAAC,SAAS,CAAC,oBAAoB,CAAC;;;;;;;;;;;;4CAYxD,IAAI,CAAC,SAAS,CAAC,oBAAoB,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiG7E,CAAC","sourcesContent":["/**\n * JavaScript evaluated inside the QuickJS VM before the script runs.\n *\n * The VM is its own wasm instance, so nothing here guards a realm boundary.\n * The prelude keeps the host bridge in a closure so the script cannot call it\n * directly, and builds `tools`, `ALL_TOOLS`, the output helpers (`text`, `image`,\n * `exit`, `console`), and globals on top of it. Tool arguments and results cross\n * as JSON strings and are parsed on this side.\n *\n * Output helpers: `text(value)` appends a text item (non-strings are\n * JSON-stringified), `image(urlOrItem)` appends an image from a base64 `data:` URL, an\n * `{ image_url }` object, or an MCP `ImageContent` block, and `exit()` ends the script\n * successfully. `console.*` appends text items like `text()`.\n *\n * `store(key, value)` and `load(key)` are synchronous: they work on a snapshot of\n * JSON text passed in as `storeJson`, and the keys the script wrote are reported\n * with a successful \"done\".\n *\n * Evaluates to a function `(bridge, toolsJson, globalsJson, storeJson) => { settle, run, stalled }`.\n * `toolsJson` lists `{ name, jsName, description }`: `tools[jsName]` and `tools[name]` call the\n * tool, and `ALL_TOOLS` lists `{ name: jsName, description }`.\n * `stalled()` reports a script that has not finished while no host call is pending: with no timers\n * or I/O in the VM, nothing can ever resume it.\n * `globalsJson` lists `{ name, spread }`; `a.b` names are grouped into a frozen `a` object.\n * `bridge(kind, a, b, c)` with kind \"call\" or \"global\" (id, name, argsJson),\n * \"output\" (\"text\", text) or (\"image\", data, mimeType), or \"done\" (ok, valueJsonOrErrorJson, writesJson).\n */\nexport const MAX_STORE_VALUE_CHARS = 256 * 1024;\nexport const MAX_STORE_TOTAL_CHARS = 1024 * 1024;\n\nconst IMAGE_HELPER_EXPECTS =\n\t\"image expects a non-empty image URL string, an object with image_url, or a raw MCP image block\";\n\nexport const PRELUDE_SOURCE: string = `(function (bridge, toolsJson, globalsJson, storeJson) {\n\t\"use strict\";\n\tconst stringify = JSON.stringify;\n\tconst parse = JSON.parse;\n\tconst promiseThen = Promise.prototype.then;\n\tconst ErrorCtor = Error;\n\tconst TypeErrorCtor = TypeError;\n\tconst pending = new Map();\n\tlet nextId = 1;\n\tlet finished = false;\n\t// Thrown by exit() to unwind the script after it already reported success.\n\tconst EXIT = Object.freeze({});\n\n\tfunction done(ok, payload, writes) {\n\t\tif (finished) return;\n\t\tfinished = true;\n\t\tbridge(\"done\", ok, payload, writes);\n\t}\n\n\tfunction serialize(value) {\n\t\treturn value === undefined ? undefined : stringify(value);\n\t}\n\n\t// QuickJS stacks list frames only. Prefix \"Name: message\" like V8 so the\n\t// text reads the same as a Node error, and drop this prelude's frames.\n\tfunction errorText(error) {\n\t\tconst head = error.message ? error.name + \": \" + error.message : String(error.name);\n\t\tconst frames =\n\t\t\ttypeof error.stack === \"string\"\n\t\t\t\t? error.stack.split(\"\\\\n\").filter((line) => line.trim() && !line.includes(\"codemode-prelude.js\"))\n\t\t\t\t: [];\n\t\treturn [head, ...frames].join(\"\\\\n\");\n\t}\n\n\tfunction format(value) {\n\t\tif (typeof value === \"string\") return value;\n\t\tif (value instanceof ErrorCtor) return errorText(value);\n\t\ttry {\n\t\t\tconst json = stringify(value);\n\t\t\treturn json === undefined ? String(value) : json;\n\t\t} catch {\n\t\t\treturn String(value);\n\t\t}\n\t}\n\n\tfunction describeError(error) {\n\t\tif (error instanceof ErrorCtor) {\n\t\t\treturn stringify({ name: error.name, message: error.message, stack: errorText(error) });\n\t\t}\n\t\treturn stringify({ message: format(error) });\n\t}\n\n\tfunction caller(kind, name, spread) {\n\t\treturn (...args) =>\n\t\t\tnew Promise((resolve, reject) => {\n\t\t\t\tlet json;\n\t\t\t\ttry {\n\t\t\t\t\tjson = serialize(spread ? args : args[0]);\n\t\t\t\t} catch (error) {\n\t\t\t\t\treject(error);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst id = nextId++;\n\t\t\t\tpending.set(id, { resolve, reject });\n\t\t\t\tbridge(kind, id, name, json);\n\t\t\t});\n\t}\n\n\tconst tools = Object.create(null);\n\tconst allTools = [];\n\tfor (const { name, jsName, description } of parse(toolsJson)) {\n\t\tconst fn = caller(\"call\", name);\n\t\t// The first tool wins when two names normalize to the same identifier.\n\t\tif (!(jsName in tools)) {\n\t\t\ttools[jsName] = fn;\n\t\t\tallTools.push(Object.freeze({ name: jsName, description }));\n\t\t}\n\t\tif (!(name in tools)) tools[name] = fn;\n\t}\n\tObject.freeze(tools);\n\tObject.freeze(allTools);\n\n\tconst namespaces = new Map();\n\tfor (const { name, spread } of parse(globalsJson)) {\n\t\tconst fn = caller(\"global\", name, spread);\n\t\tconst dot = name.indexOf(\".\");\n\t\tif (dot === -1) {\n\t\t\tObject.defineProperty(globalThis, name, { value: fn, enumerable: true });\n\t\t\tcontinue;\n\t\t}\n\t\tconst namespace = name.slice(0, dot);\n\t\tif (!namespaces.has(namespace)) namespaces.set(namespace, Object.create(null));\n\t\tnamespaces.get(namespace)[name.slice(dot + 1)] = fn;\n\t}\n\tfor (const [namespace, members] of namespaces) {\n\t\tObject.defineProperty(globalThis, namespace, { value: Object.freeze(members), enumerable: true });\n\t}\n\n\t// key -> JSON text. Sizes count key and JSON characters.\n\tconst stored = new Map(Object.entries(parse(storeJson)));\n\tconst writes = new Map();\n\tlet storedChars = 0;\n\tfor (const [key, json] of stored) storedChars += key.length + json.length;\n\n\tfunction checkKey(name, key) {\n\t\tif (typeof key !== \"string\") throw new TypeError(name + \"() key must be a string\");\n\t}\n\n\tfunction store(key, value) {\n\t\tcheckKey(\"store\", key);\n\t\tconst previous = stored.has(key) ? key.length + stored.get(key).length : 0;\n\t\tif (value === undefined) {\n\t\t\tstored.delete(key);\n\t\t\tstoredChars -= previous;\n\t\t\twrites.set(key, undefined);\n\t\t\treturn;\n\t\t}\n\t\tlet json;\n\t\ttry {\n\t\t\tjson = stringify(value);\n\t\t} catch (error) {\n\t\t\tthrow new TypeError(\"store(\" + stringify(key) + \") value is not JSON-serializable: \" + format(error));\n\t\t}\n\t\tif (json === undefined) {\n\t\t\tthrow new TypeError(\"store(\" + stringify(key) + \") value is not JSON-serializable\");\n\t\t}\n\t\tif (json.length > ${MAX_STORE_VALUE_CHARS}) {\n\t\t\tthrow new RangeError(\"store(\" + stringify(key) + \") value exceeds ${MAX_STORE_VALUE_CHARS} characters of JSON\");\n\t\t}\n\t\tconst next = storedChars - previous + key.length + json.length;\n\t\tif (next > ${MAX_STORE_TOTAL_CHARS}) {\n\t\t\tthrow new RangeError(\"store is full: stored values would exceed ${MAX_STORE_TOTAL_CHARS} characters of JSON\");\n\t\t}\n\t\tstored.set(key, json);\n\t\tstoredChars = next;\n\t\twrites.set(key, json);\n\t}\n\n\tfunction load(key) {\n\t\tcheckKey(\"load\", key);\n\t\tconst json = stored.get(key);\n\t\treturn json === undefined ? undefined : parse(json);\n\t}\n\n\tfunction serializeWrites() {\n\t\tconst entries = [];\n\t\tfor (const [key, json] of writes) entries.push(json === undefined ? [key] : [key, json]);\n\t\treturn stringify(entries);\n\t}\n\n\tObject.defineProperty(globalThis, \"store\", { value: store, enumerable: true });\n\tObject.defineProperty(globalThis, \"load\", { value: load, enumerable: true });\n\n\t// Primitives become their string form, everything else JSON.\n\tfunction outputText(value) {\n\t\tif (value === undefined || value === null || typeof value !== \"object\" && typeof value !== \"function\") {\n\t\t\treturn String(value);\n\t\t}\n\t\tconst json = stringify(value);\n\t\treturn json === undefined ? String(value) : json;\n\t}\n\n\tfunction text(value) {\n\t\tlet rendered;\n\t\ttry {\n\t\t\trendered = outputText(value);\n\t\t} catch (error) {\n\t\t\tthrow new TypeErrorCtor(error instanceof ErrorCtor ? error.message : String(error));\n\t\t}\n\t\tif (!finished) bridge(\"output\", \"text\", rendered);\n\t}\n\n\tfunction imageUrl(value) {\n\t\tif (typeof value === \"string\") return value;\n\t\tif (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n\t\t\tthrow new TypeErrorCtor(${JSON.stringify(IMAGE_HELPER_EXPECTS)});\n\t\t}\n\t\tif (value.image_url !== undefined) {\n\t\t\tif (typeof value.image_url !== \"string\") throw new TypeErrorCtor(${JSON.stringify(IMAGE_HELPER_EXPECTS)});\n\t\t\treturn value.image_url;\n\t\t}\n\t\tif (typeof value.type !== \"string\") throw new TypeErrorCtor(${JSON.stringify(IMAGE_HELPER_EXPECTS)});\n\t\tif (value.type !== \"image\") {\n\t\t\tthrow new TypeErrorCtor('image only accepts MCP image blocks, got \"' + value.type + '\"');\n\t\t}\n\t\tif (typeof value.data !== \"string\" || value.data === \"\") throw new TypeErrorCtor(\"image expected MCP image data\");\n\t\tif (value.data.toLowerCase().startsWith(\"data:\")) return value.data;\n\t\tconst mimeType = typeof value.mimeType === \"string\" && value.mimeType ? value.mimeType : \"application/octet-stream\";\n\t\treturn \"data:\" + mimeType + \";base64,\" + value.data;\n\t}\n\n\tfunction image(value) {\n\t\tconst url = imageUrl(value);\n\t\tif (url === \"\") throw new TypeErrorCtor(${JSON.stringify(IMAGE_HELPER_EXPECTS)});\n\t\tconst colon = url.indexOf(\":\");\n\t\tconst scheme = colon === -1 ? \"\" : url.slice(0, colon).toLowerCase();\n\t\tif (scheme === \"http\" || scheme === \"https\") {\n\t\t\tthrow new TypeErrorCtor(\"remote image URLs are not supported in tool outputs. Pass a base64 data URI instead\");\n\t\t}\n\t\tconst comma = url.indexOf(\",\");\n\t\tconst header = comma === -1 ? [] : url.slice(colon + 1, comma).split(\";\");\n\t\tif (scheme !== \"data\" || comma === -1 || header.slice(1).every((part) => part.toLowerCase() !== \"base64\")) {\n\t\t\tthrow new TypeErrorCtor(\"invalid image output. Pass a base64 data URI instead\");\n\t\t}\n\t\tif (!finished) bridge(\"output\", \"image\", url.slice(comma + 1), header[0] || \"application/octet-stream\");\n\t}\n\n\tfunction exit() {\n\t\tlet writesJson;\n\t\ttry {\n\t\t\twritesJson = serializeWrites();\n\t\t} catch (error) {\n\t\t\tdone(false, describeError(error));\n\t\t\tthrow EXIT;\n\t\t}\n\t\tdone(true, undefined, writesJson);\n\t\tthrow EXIT;\n\t}\n\n\tconst console = {};\n\tfor (const level of [\"log\", \"info\", \"warn\", \"error\", \"debug\"]) {\n\t\tconsole[level] = (...args) => {\n\t\t\tif (!finished) bridge(\"output\", \"text\", args.map(format).join(\" \"));\n\t\t};\n\t}\n\tObject.freeze(console);\n\n\tObject.defineProperty(globalThis, \"tools\", { value: tools, enumerable: true });\n\tObject.defineProperty(globalThis, \"ALL_TOOLS\", { value: allTools, enumerable: true });\n\tObject.defineProperty(globalThis, \"console\", { value: console, enumerable: true });\n\tObject.defineProperty(globalThis, \"text\", { value: text, enumerable: true });\n\tObject.defineProperty(globalThis, \"image\", { value: image, enumerable: true });\n\tObject.defineProperty(globalThis, \"exit\", { value: exit, enumerable: true });\n\n\treturn {\n\t\tsettle(id, ok, payload) {\n\t\t\tconst entry = pending.get(id);\n\t\t\tif (!entry) return;\n\t\t\tpending.delete(id);\n\t\t\tif (!ok) {\n\t\t\t\tentry.reject(new ErrorCtor(payload));\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tlet value;\n\t\t\ttry {\n\t\t\t\tvalue = payload === undefined ? undefined : parse(payload);\n\t\t\t} catch (error) {\n\t\t\t\tentry.reject(error);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tentry.resolve(value);\n\t\t},\n\t\trun(fn) {\n\t\t\tlet promise;\n\t\t\ttry {\n\t\t\t\tpromise = fn(tools, console);\n\t\t\t} catch (error) {\n\t\t\t\tdone(false, describeError(error));\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tpromiseThen.call(\n\t\t\t\tpromise,\n\t\t\t\t(value) => {\n\t\t\t\t\tlet json;\n\t\t\t\t\ttry {\n\t\t\t\t\t\tjson = serialize(value);\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\tdone(false, describeError(error));\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tdone(true, json, serializeWrites());\n\t\t\t\t},\n\t\t\t\t(error) => {\n\t\t\t\t\tdone(false, describeError(error));\n\t\t\t\t},\n\t\t\t);\n\t\t},\n\t\tstalled() {\n\t\t\tif (finished || pending.size > 0) return false;\n\t\t\tdone(\n\t\t\t\tfalse,\n\t\t\t\tstringify({\n\t\t\t\t\tname: \"Error\",\n\t\t\t\t\tmessage:\n\t\t\t\t\t\t\"The script is waiting on a promise that can never settle: no tool call is pending, and timers do not exist here.\",\n\t\t\t\t}),\n\t\t\t);\n\t\t\treturn true;\n\t\t},\n\t};\n})`;\n"]}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { CodemodeOutputItem } from "../types.ts";
|
|
2
|
+
import type { CodemodeWasmModule } from "../wasm.ts";
|
|
3
|
+
/**
|
|
4
|
+
* Messages between the host (main thread) and the worker. Tool arguments,
|
|
5
|
+
* results, and values cross as JSON strings: the worker passes them into and
|
|
6
|
+
* out of the QuickJS VM as strings and never builds structured values itself.
|
|
7
|
+
*/
|
|
8
|
+
export interface WorkerData {
|
|
9
|
+
code: string;
|
|
10
|
+
/** `jsName` is the identifier the script uses; `description` is listed in `ALL_TOOLS`. */
|
|
11
|
+
tools: {
|
|
12
|
+
name: string;
|
|
13
|
+
jsName: string;
|
|
14
|
+
description: string;
|
|
15
|
+
}[];
|
|
16
|
+
globals: {
|
|
17
|
+
name: string;
|
|
18
|
+
spread: boolean;
|
|
19
|
+
}[];
|
|
20
|
+
/** Compiled `quickjs-wasi` module. Structured clone shares the compiled code with the worker. */
|
|
21
|
+
wasm: CodemodeWasmModule;
|
|
22
|
+
memoryLimitBytes: number | undefined;
|
|
23
|
+
/** Snapshot for `load()`: key to JSON text. */
|
|
24
|
+
store: Record<string, string>;
|
|
25
|
+
/**
|
|
26
|
+
* One Int32 the host sets to non-zero before terminating the worker. The VM's interrupt handler
|
|
27
|
+
* polls it, because Bun's `worker.terminate()` cannot stop a thread that is spinning in wasm.
|
|
28
|
+
*/
|
|
29
|
+
interrupt: SharedArrayBuffer;
|
|
30
|
+
}
|
|
31
|
+
/** JSON-encoded `{ name?, message, stack? }` of an error thrown by the script. */
|
|
32
|
+
export type ScriptErrorJson = string;
|
|
33
|
+
export type WorkerToHostMessage = {
|
|
34
|
+
type: "call";
|
|
35
|
+
id: number;
|
|
36
|
+
target: "tool" | "global";
|
|
37
|
+
name: string;
|
|
38
|
+
args: string | undefined;
|
|
39
|
+
} | {
|
|
40
|
+
type: "output";
|
|
41
|
+
item: CodemodeOutputItem;
|
|
42
|
+
}
|
|
43
|
+
/** `writes` is a JSON array of `[key, json]` for `store()` and `[key]` for deletions. */
|
|
44
|
+
| {
|
|
45
|
+
type: "done";
|
|
46
|
+
ok: true;
|
|
47
|
+
value: string | undefined;
|
|
48
|
+
writes: string;
|
|
49
|
+
} | {
|
|
50
|
+
type: "done";
|
|
51
|
+
ok: false;
|
|
52
|
+
error: ScriptErrorJson;
|
|
53
|
+
}
|
|
54
|
+
/** The VM failed outside the script's control, for example a wasm trap. */
|
|
55
|
+
| {
|
|
56
|
+
type: "crash";
|
|
57
|
+
message: string;
|
|
58
|
+
};
|
|
59
|
+
export type HostToWorkerMessage =
|
|
60
|
+
/** `payload` is the JSON result when `ok`, otherwise the error message. */
|
|
61
|
+
{
|
|
62
|
+
type: "result";
|
|
63
|
+
id: number;
|
|
64
|
+
ok: boolean;
|
|
65
|
+
payload: string | undefined;
|
|
66
|
+
};
|
|
67
|
+
export declare function isWorkerToHostMessage(value: unknown): value is WorkerToHostMessage;
|
|
68
|
+
export declare function isHostToWorkerMessage(value: unknown): value is HostToWorkerMessage;
|
|
69
|
+
//# sourceMappingURL=protocol.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../../src/runtime/protocol.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACtD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAErD;;;;GAIG;AAEH,MAAM,WAAW,UAAU;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,0FAA0F;IAC1F,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC/D,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,OAAO,CAAA;KAAE,EAAE,CAAC;IAC7C,iGAAiG;IACjG,IAAI,EAAE,kBAAkB,CAAC;IACzB,gBAAgB,EAAE,MAAM,GAAG,SAAS,CAAC;IACrC,+CAA+C;IAC/C,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9B;;;OAGG;IACH,SAAS,EAAE,iBAAiB,CAAC;CAC7B;AAED,kFAAkF;AAClF,MAAM,MAAM,eAAe,GAAG,MAAM,CAAC;AAErC,MAAM,MAAM,mBAAmB,GAC5B;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,GAAG,QAAQ,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,GAC/F;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,IAAI,EAAE,kBAAkB,CAAA;CAAE;AAC9C,yFAAyF;GACvF;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACrE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,eAAe,CAAA;CAAE;AACrD,2EAA2E;GACzE;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAEtC,MAAM,MAAM,mBAAmB;AAC9B,2EAA2E;AAC3E;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,CAAC;AAE1E,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,mBAAmB,CAIlF;AAED,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,mBAAmB,CAElF"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export function isWorkerToHostMessage(value) {
|
|
2
|
+
if (typeof value !== "object" || value === null)
|
|
3
|
+
return false;
|
|
4
|
+
const type = value.type;
|
|
5
|
+
return type === "call" || type === "output" || type === "done" || type === "crash";
|
|
6
|
+
}
|
|
7
|
+
export function isHostToWorkerMessage(value) {
|
|
8
|
+
return typeof value === "object" && value !== null && value.type === "result";
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=protocol.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"protocol.js","sourceRoot":"","sources":["../../src/runtime/protocol.ts"],"names":[],"mappings":"AA0CA,MAAM,UAAU,qBAAqB,CAAC,KAAc;IACnD,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,KAAK,CAAC;IAC9D,MAAM,IAAI,GAAI,KAA4B,CAAC,IAAI,CAAC;IAChD,OAAO,IAAI,KAAK,MAAM,IAAI,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,MAAM,IAAI,IAAI,KAAK,OAAO,CAAC;AACpF,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,KAAc;IACnD,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAK,KAA4B,CAAC,IAAI,KAAK,QAAQ,CAAC;AACvG,CAAC","sourcesContent":["import type { CodemodeOutputItem } from \"../types.ts\";\nimport type { CodemodeWasmModule } from \"../wasm.ts\";\n\n/**\n * Messages between the host (main thread) and the worker. Tool arguments,\n * results, and values cross as JSON strings: the worker passes them into and\n * out of the QuickJS VM as strings and never builds structured values itself.\n */\n\nexport interface WorkerData {\n\tcode: string;\n\t/** `jsName` is the identifier the script uses; `description` is listed in `ALL_TOOLS`. */\n\ttools: { name: string; jsName: string; description: string }[];\n\tglobals: { name: string; spread: boolean }[];\n\t/** Compiled `quickjs-wasi` module. Structured clone shares the compiled code with the worker. */\n\twasm: CodemodeWasmModule;\n\tmemoryLimitBytes: number | undefined;\n\t/** Snapshot for `load()`: key to JSON text. */\n\tstore: Record<string, string>;\n\t/**\n\t * One Int32 the host sets to non-zero before terminating the worker. The VM's interrupt handler\n\t * polls it, because Bun's `worker.terminate()` cannot stop a thread that is spinning in wasm.\n\t */\n\tinterrupt: SharedArrayBuffer;\n}\n\n/** JSON-encoded `{ name?, message, stack? }` of an error thrown by the script. */\nexport type ScriptErrorJson = string;\n\nexport type WorkerToHostMessage =\n\t| { type: \"call\"; id: number; target: \"tool\" | \"global\"; name: string; args: string | undefined }\n\t| { type: \"output\"; item: CodemodeOutputItem }\n\t/** `writes` is a JSON array of `[key, json]` for `store()` and `[key]` for deletions. */\n\t| { type: \"done\"; ok: true; value: string | undefined; writes: string }\n\t| { type: \"done\"; ok: false; error: ScriptErrorJson }\n\t/** The VM failed outside the script's control, for example a wasm trap. */\n\t| { type: \"crash\"; message: string };\n\nexport type HostToWorkerMessage =\n\t/** `payload` is the JSON result when `ok`, otherwise the error message. */\n\t{ type: \"result\"; id: number; ok: boolean; payload: string | undefined };\n\nexport function isWorkerToHostMessage(value: unknown): value is WorkerToHostMessage {\n\tif (typeof value !== \"object\" || value === null) return false;\n\tconst type = (value as { type?: unknown }).type;\n\treturn type === \"call\" || type === \"output\" || type === \"done\" || type === \"crash\";\n}\n\nexport function isHostToWorkerMessage(value: unknown): value is HostToWorkerMessage {\n\treturn typeof value === \"object\" && value !== null && (value as { type?: unknown }).type === \"result\";\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worker.d.ts","sourceRoot":"","sources":["../../src/runtime/worker.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Worker thread entry. One worker runs one script inside a fresh QuickJS VM
|
|
3
|
+
* (a separate wasm instance), relays tool calls and output to the host, and
|
|
4
|
+
* reports the result. The host terminates the worker when the script settles,
|
|
5
|
+
* times out, or is aborted; the worker exists so that a spinning script never
|
|
6
|
+
* blocks the host thread.
|
|
7
|
+
*
|
|
8
|
+
* Importing this module starts the worker. Hosts that bundle their code (for
|
|
9
|
+
* example a Bun compiled executable) add a file that imports
|
|
10
|
+
* `@earendil-works/pi-codemode/worker` as a separate entrypoint and pass its URL
|
|
11
|
+
* as `workerUrl`.
|
|
12
|
+
*/
|
|
13
|
+
import { parentPort, workerData } from "node:worker_threads";
|
|
14
|
+
import { JSException, MAX_STACK_SIZE, QuickJS } from "quickjs-wasi";
|
|
15
|
+
import { PRELUDE_SOURCE } from "./prelude-source.js";
|
|
16
|
+
import { isHostToWorkerMessage } from "./protocol.js";
|
|
17
|
+
function post(message) {
|
|
18
|
+
parentPort?.postMessage(message);
|
|
19
|
+
}
|
|
20
|
+
function crash(error) {
|
|
21
|
+
post({ type: "crash", message: error instanceof Error ? `${error.name}: ${error.message}` : String(error) });
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* QuickJS writes engine diagnostics to fd 1 and 2, which the default shim
|
|
25
|
+
* forwards to the host's stdout and stderr. That output belongs to the host
|
|
26
|
+
* application (for example a TUI), so it is discarded. Reporting every byte as
|
|
27
|
+
* written keeps libc from retrying.
|
|
28
|
+
*/
|
|
29
|
+
function discardOutput(memory) {
|
|
30
|
+
return {
|
|
31
|
+
fd_write(_fd, iovsPtr, iovsLen, nwrittenPtr) {
|
|
32
|
+
const view = new DataView(memory.buffer);
|
|
33
|
+
let written = 0;
|
|
34
|
+
for (let i = 0; i < iovsLen; i++) {
|
|
35
|
+
written += view.getUint32(iovsPtr + i * 8 + 4, true);
|
|
36
|
+
}
|
|
37
|
+
view.setUint32(nwrittenPtr, written, true);
|
|
38
|
+
return 0;
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
function describeException(error) {
|
|
43
|
+
const head = error.message ? `${error.name}: ${error.message}` : error.name;
|
|
44
|
+
const stack = error.stack?.trimEnd();
|
|
45
|
+
return JSON.stringify({ name: error.name, message: error.message, stack: stack ? `${head}\n${stack}` : head });
|
|
46
|
+
}
|
|
47
|
+
async function main(data) {
|
|
48
|
+
const interrupt = new Int32Array(data.interrupt);
|
|
49
|
+
const vm = await QuickJS.create({
|
|
50
|
+
wasm: data.wasm,
|
|
51
|
+
memoryLimit: data.memoryLimitBytes,
|
|
52
|
+
// Without a guard, deep recursion overflows the wasm stack and traps instead of throwing a
|
|
53
|
+
// catchable RangeError.
|
|
54
|
+
maxStackSize: MAX_STACK_SIZE,
|
|
55
|
+
interruptHandler: () => Atomics.load(interrupt, 0) !== 0,
|
|
56
|
+
wasi: discardOutput,
|
|
57
|
+
});
|
|
58
|
+
// Called from the prelude with primitives only.
|
|
59
|
+
const bridge = vm.newFunction("bridge", (kind, a, b, c) => {
|
|
60
|
+
switch (kind.toString()) {
|
|
61
|
+
case "call":
|
|
62
|
+
case "global":
|
|
63
|
+
post({
|
|
64
|
+
type: "call",
|
|
65
|
+
id: a.toNumber(),
|
|
66
|
+
target: kind.toString() === "call" ? "tool" : "global",
|
|
67
|
+
name: b.toString(),
|
|
68
|
+
args: c === undefined || c.isUndefined ? undefined : c.toString(),
|
|
69
|
+
});
|
|
70
|
+
break;
|
|
71
|
+
case "output":
|
|
72
|
+
post({
|
|
73
|
+
type: "output",
|
|
74
|
+
item: a.toString() === "image"
|
|
75
|
+
? { type: "image", data: b.toString(), mimeType: c.toString() }
|
|
76
|
+
: { type: "text", text: b.toString() },
|
|
77
|
+
});
|
|
78
|
+
break;
|
|
79
|
+
case "done":
|
|
80
|
+
if (a.toBoolean()) {
|
|
81
|
+
post({
|
|
82
|
+
type: "done",
|
|
83
|
+
ok: true,
|
|
84
|
+
value: b === undefined || b.isUndefined ? undefined : b.toString(),
|
|
85
|
+
writes: c.toString(),
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
post({ type: "done", ok: false, error: b.toString() });
|
|
90
|
+
}
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
93
|
+
return vm.undefined;
|
|
94
|
+
});
|
|
95
|
+
// The VM lives until the host terminates the worker, so these handles are never disposed.
|
|
96
|
+
const api = vm.withScope((scope) => scope.escape(vm.callFunction(vm.evalCode(PRELUDE_SOURCE, "codemode-prelude.js"), vm.undefined, bridge, vm.newString(JSON.stringify(data.tools)), vm.newString(JSON.stringify(data.globals)), vm.newString(JSON.stringify(data.store)))));
|
|
97
|
+
const settle = api.getProp("settle");
|
|
98
|
+
const run = api.getProp("run");
|
|
99
|
+
const stalled = api.getProp("stalled");
|
|
100
|
+
/** Run queued jobs, then fail a script that waits on nothing that can ever resume it. */
|
|
101
|
+
const drain = () => {
|
|
102
|
+
vm.executePendingJobs();
|
|
103
|
+
vm.callFunction(stalled, api).dispose();
|
|
104
|
+
};
|
|
105
|
+
parentPort?.on("message", (message) => {
|
|
106
|
+
if (!isHostToWorkerMessage(message))
|
|
107
|
+
return;
|
|
108
|
+
try {
|
|
109
|
+
vm.withScope(() => {
|
|
110
|
+
vm.callFunction(settle, api, vm.newNumber(message.id), message.ok ? vm.true : vm.false, message.payload === undefined ? vm.undefined : vm.newString(message.payload));
|
|
111
|
+
});
|
|
112
|
+
drain();
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
crash(error);
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
// The prefix shares the first line with the script so reported line numbers
|
|
119
|
+
// match the script as written.
|
|
120
|
+
let fn;
|
|
121
|
+
try {
|
|
122
|
+
fn = vm.evalCode(`(async (tools, console) => {${data.code}\n})`, "codemode.js");
|
|
123
|
+
}
|
|
124
|
+
catch (error) {
|
|
125
|
+
if (!(error instanceof JSException))
|
|
126
|
+
throw error;
|
|
127
|
+
post({ type: "done", ok: false, error: describeException(error) });
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
vm.callFunction(run, api, fn).dispose();
|
|
131
|
+
fn.dispose();
|
|
132
|
+
drain();
|
|
133
|
+
}
|
|
134
|
+
if (parentPort) {
|
|
135
|
+
main(workerData).catch(crash);
|
|
136
|
+
}
|
|
137
|
+
//# sourceMappingURL=worker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worker.js","sourceRoot":"","sources":["../../src/runtime/worker.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Worker thread entry. One worker runs one script inside a fresh QuickJS VM\n * (a separate wasm instance), relays tool calls and output to the host, and\n * reports the result. The host terminates the worker when the script settles,\n * times out, or is aborted; the worker exists so that a spinning script never\n * blocks the host thread.\n *\n * Importing this module starts the worker. Hosts that bundle their code (for\n * example a Bun compiled executable) add a file that imports\n * `@earendil-works/pi-codemode/worker` as a separate entrypoint and pass its URL\n * as `workerUrl`.\n */\nimport { parentPort, workerData } from \"node:worker_threads\";\nimport { JSException, type JSValueHandle, MAX_STACK_SIZE, QuickJS } from \"quickjs-wasi\";\nimport { PRELUDE_SOURCE } from \"./prelude-source.ts\";\nimport { isHostToWorkerMessage, type WorkerData, type WorkerToHostMessage } from \"./protocol.ts\";\n\nfunction post(message: WorkerToHostMessage): void {\n\tparentPort?.postMessage(message);\n}\n\nfunction crash(error: unknown): void {\n\tpost({ type: \"crash\", message: error instanceof Error ? `${error.name}: ${error.message}` : String(error) });\n}\n\n/**\n * QuickJS writes engine diagnostics to fd 1 and 2, which the default shim\n * forwards to the host's stdout and stderr. That output belongs to the host\n * application (for example a TUI), so it is discarded. Reporting every byte as\n * written keeps libc from retrying.\n */\nfunction discardOutput(memory: { readonly buffer: ArrayBufferLike }) {\n\treturn {\n\t\tfd_write(_fd: number, iovsPtr: number, iovsLen: number, nwrittenPtr: number): number {\n\t\t\tconst view = new DataView(memory.buffer);\n\t\t\tlet written = 0;\n\t\t\tfor (let i = 0; i < iovsLen; i++) {\n\t\t\t\twritten += view.getUint32(iovsPtr + i * 8 + 4, true);\n\t\t\t}\n\t\t\tview.setUint32(nwrittenPtr, written, true);\n\t\t\treturn 0;\n\t\t},\n\t};\n}\n\nfunction describeException(error: JSException): string {\n\tconst head = error.message ? `${error.name}: ${error.message}` : error.name;\n\tconst stack = error.stack?.trimEnd();\n\treturn JSON.stringify({ name: error.name, message: error.message, stack: stack ? `${head}\\n${stack}` : head });\n}\n\nasync function main(data: WorkerData): Promise<void> {\n\tconst interrupt = new Int32Array(data.interrupt);\n\tconst vm = await QuickJS.create({\n\t\twasm: data.wasm,\n\t\tmemoryLimit: data.memoryLimitBytes,\n\t\t// Without a guard, deep recursion overflows the wasm stack and traps instead of throwing a\n\t\t// catchable RangeError.\n\t\tmaxStackSize: MAX_STACK_SIZE,\n\t\tinterruptHandler: () => Atomics.load(interrupt, 0) !== 0,\n\t\twasi: discardOutput,\n\t});\n\n\t// Called from the prelude with primitives only.\n\tconst bridge = vm.newFunction(\"bridge\", (kind, a, b, c) => {\n\t\tswitch (kind.toString()) {\n\t\t\tcase \"call\":\n\t\t\tcase \"global\":\n\t\t\t\tpost({\n\t\t\t\t\ttype: \"call\",\n\t\t\t\t\tid: a.toNumber(),\n\t\t\t\t\ttarget: kind.toString() === \"call\" ? \"tool\" : \"global\",\n\t\t\t\t\tname: b.toString(),\n\t\t\t\t\targs: c === undefined || c.isUndefined ? undefined : c.toString(),\n\t\t\t\t});\n\t\t\t\tbreak;\n\t\t\tcase \"output\":\n\t\t\t\tpost({\n\t\t\t\t\ttype: \"output\",\n\t\t\t\t\titem:\n\t\t\t\t\t\ta.toString() === \"image\"\n\t\t\t\t\t\t\t? { type: \"image\", data: b.toString(), mimeType: c.toString() }\n\t\t\t\t\t\t\t: { type: \"text\", text: b.toString() },\n\t\t\t\t});\n\t\t\t\tbreak;\n\t\t\tcase \"done\":\n\t\t\t\tif (a.toBoolean()) {\n\t\t\t\t\tpost({\n\t\t\t\t\t\ttype: \"done\",\n\t\t\t\t\t\tok: true,\n\t\t\t\t\t\tvalue: b === undefined || b.isUndefined ? undefined : b.toString(),\n\t\t\t\t\t\twrites: c.toString(),\n\t\t\t\t\t});\n\t\t\t\t} else {\n\t\t\t\t\tpost({ type: \"done\", ok: false, error: b.toString() });\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t}\n\t\treturn vm.undefined;\n\t});\n\n\t// The VM lives until the host terminates the worker, so these handles are never disposed.\n\tconst api = vm.withScope((scope) =>\n\t\tscope.escape(\n\t\t\tvm.callFunction(\n\t\t\t\tvm.evalCode(PRELUDE_SOURCE, \"codemode-prelude.js\"),\n\t\t\t\tvm.undefined,\n\t\t\t\tbridge,\n\t\t\t\tvm.newString(JSON.stringify(data.tools)),\n\t\t\t\tvm.newString(JSON.stringify(data.globals)),\n\t\t\t\tvm.newString(JSON.stringify(data.store)),\n\t\t\t),\n\t\t),\n\t);\n\tconst settle = api.getProp(\"settle\");\n\tconst run = api.getProp(\"run\");\n\tconst stalled = api.getProp(\"stalled\");\n\t/** Run queued jobs, then fail a script that waits on nothing that can ever resume it. */\n\tconst drain = () => {\n\t\tvm.executePendingJobs();\n\t\tvm.callFunction(stalled, api).dispose();\n\t};\n\n\tparentPort?.on(\"message\", (message: unknown) => {\n\t\tif (!isHostToWorkerMessage(message)) return;\n\t\ttry {\n\t\t\tvm.withScope(() => {\n\t\t\t\tvm.callFunction(\n\t\t\t\t\tsettle,\n\t\t\t\t\tapi,\n\t\t\t\t\tvm.newNumber(message.id),\n\t\t\t\t\tmessage.ok ? vm.true : vm.false,\n\t\t\t\t\tmessage.payload === undefined ? vm.undefined : vm.newString(message.payload),\n\t\t\t\t);\n\t\t\t});\n\t\t\tdrain();\n\t\t} catch (error) {\n\t\t\tcrash(error);\n\t\t}\n\t});\n\n\t// The prefix shares the first line with the script so reported line numbers\n\t// match the script as written.\n\tlet fn: JSValueHandle;\n\ttry {\n\t\tfn = vm.evalCode(`(async (tools, console) => {${data.code}\\n})`, \"codemode.js\");\n\t} catch (error) {\n\t\tif (!(error instanceof JSException)) throw error;\n\t\tpost({ type: \"done\", ok: false, error: describeException(error) });\n\t\treturn;\n\t}\n\tvm.callFunction(run, api, fn).dispose();\n\tfn.dispose();\n\tdrain();\n}\n\nif (parentPort) {\n\tmain(workerData as WorkerData).catch(crash);\n}\n"]}
|
package/dist/source.d.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Codemode source format: JavaScript, optionally preceded by one options line.
|
|
3
|
+
*
|
|
4
|
+
* ```js
|
|
5
|
+
* // @options: {"max_output_tokens": 2000, "timeout_ms": 30000}
|
|
6
|
+
* const text = await tools.read({ path: "package.json" });
|
|
7
|
+
* text(JSON.parse(text).name);
|
|
8
|
+
* ```
|
|
9
|
+
*/
|
|
10
|
+
export declare const CODEMODE_OPTIONS_PREFIX = "// @options:";
|
|
11
|
+
/**
|
|
12
|
+
* Lark grammar for providers with grammar-constrained tool input. It only fixes the shape of the
|
|
13
|
+
* options line; the options JSON and the code are checked by {@link parseCodemodeSource}.
|
|
14
|
+
*/
|
|
15
|
+
export declare const CODEMODE_SOURCE_GRAMMAR: string;
|
|
16
|
+
export interface CodemodeSourceOptions {
|
|
17
|
+
/** Token budget for the script's output. */
|
|
18
|
+
maxOutputTokens?: number;
|
|
19
|
+
/** Hard deadline for the whole script in milliseconds, including tool calls. */
|
|
20
|
+
timeoutMs?: number;
|
|
21
|
+
}
|
|
22
|
+
export interface ParsedCodemodeSource {
|
|
23
|
+
/** The script with the options line replaced by an empty line, so line numbers are unchanged. */
|
|
24
|
+
code: string;
|
|
25
|
+
options: CodemodeSourceOptions;
|
|
26
|
+
}
|
|
27
|
+
export declare class CodemodeSourceError extends Error {
|
|
28
|
+
constructor(message: string);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Split an optional first-line `// @options: {...}` from the script. Throws
|
|
32
|
+
* {@link CodemodeSourceError} for empty input and invalid options.
|
|
33
|
+
*/
|
|
34
|
+
export declare function parseCodemodeSource(input: string): ParsedCodemodeSource;
|
|
35
|
+
//# sourceMappingURL=source.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"source.d.ts","sourceRoot":"","sources":["../src/source.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,eAAO,MAAM,uBAAuB,iBAAiB,CAAC;AAOtD;;;GAGG;AACH,eAAO,MAAM,uBAAuB,QAQnC,CAAC;AAEF,MAAM,WAAW,qBAAqB;IACrC,4CAA4C;IAC5C,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,gFAAgF;IAChF,SAAS,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,oBAAoB;IACpC,iGAAiG;IACjG,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,qBAAqB,CAAC;CAC/B;AAED,qBAAa,mBAAoB,SAAQ,KAAK;IAC7C,YAAY,OAAO,EAAE,MAAM,EAG1B;CACD;AA8CD;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,oBAAoB,CAevE"}
|