libfx 0.0.7 → 0.0.8-dev.820.g1d9d3b63d6ea

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/node.cjs ADDED
@@ -0,0 +1,2542 @@
1
+ var __libfxModuleUrl = require("node:url").pathToFileURL(__filename).href;
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ function __accessProp(key) {
7
+ return this[key];
8
+ }
9
+ var __toCommonJS = (from) => {
10
+ var entry = (__moduleCache ??= new WeakMap).get(from), desc;
11
+ if (entry)
12
+ return entry;
13
+ entry = __defProp({}, "__esModule", { value: true });
14
+ if (from && typeof from === "object" || typeof from === "function") {
15
+ for (var key of __getOwnPropNames(from))
16
+ if (!__hasOwnProp.call(entry, key))
17
+ __defProp(entry, key, {
18
+ get: __accessProp.bind(from, key),
19
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
20
+ });
21
+ }
22
+ __moduleCache.set(from, entry);
23
+ return entry;
24
+ };
25
+ var __moduleCache;
26
+ var __returnValue = (v) => v;
27
+ function __exportSetter(name, newValue) {
28
+ this[name] = __returnValue.bind(null, newValue);
29
+ }
30
+ var __export = (target, all) => {
31
+ for (var name in all)
32
+ __defProp(target, name, {
33
+ get: all[name],
34
+ enumerable: true,
35
+ configurable: true,
36
+ set: __exportSetter.bind(all, name)
37
+ });
38
+ };
39
+
40
+ // sdk/node.js
41
+ var exports_node = {};
42
+ __export(exports_node, {
43
+ createFxAgent: () => createFxAgent2,
44
+ createFxTerminal: () => createFxTerminal2,
45
+ encodeXtermKeyEvent: () => encodeXtermKeyEvent,
46
+ fxSdkApiVersion: () => fxSdkApiVersion,
47
+ getBackendInfo: () => getBackendInfo,
48
+ libfxApiVersion: () => libfxApiVersion,
49
+ listModels: () => listModels,
50
+ supportsJspi: () => supportsJspi,
51
+ xtermAdapter: () => xtermAdapter
52
+ });
53
+ module.exports = __toCommonJS(exports_node);
54
+ var import_promises = require("node:fs/promises");
55
+ var import_node_fs = require("node:fs");
56
+ var import_node_module = require("node:module");
57
+ var import_node_net = require("node:net");
58
+ var import_node_os = require("node:os");
59
+ var import_node_path = require("node:path");
60
+ var import_node_url = require("node:url");
61
+
62
+ // sdk/core-output.js
63
+ var maxCoreMessageBytes = 64 * 1024 * 1024;
64
+
65
+ class CoreOutput {
66
+ constructor(handler) {
67
+ this.handler = handler;
68
+ this.decoder = new TextDecoder("utf-8", { fatal: true });
69
+ this.buffer = new Uint8Array(0);
70
+ this.bytes = 0;
71
+ this.closed = false;
72
+ }
73
+ write(chunk) {
74
+ let offset = 0;
75
+ const consume = () => {
76
+ while (offset < chunk.length) {
77
+ if (this.closed)
78
+ throw new Error("core output is closed");
79
+ const newline = chunk.indexOf(10, offset);
80
+ const end = newline < 0 ? chunk.length : newline;
81
+ const fragment = chunk.subarray(offset, end);
82
+ const length = this.bytes + fragment.length;
83
+ if (length + (newline < 0 ? 0 : 1) > maxCoreMessageBytes)
84
+ throw new RangeError("core output message exceeds 64 MiB");
85
+ let data = fragment;
86
+ if (this.bytes || newline < 0) {
87
+ if (length > this.buffer.length) {
88
+ const next = new Uint8Array(Math.min(maxCoreMessageBytes, Math.max(length, this.buffer.length * 2, 4096)));
89
+ next.set(this.buffer.subarray(0, this.bytes));
90
+ this.buffer = next;
91
+ }
92
+ this.buffer.set(fragment, this.bytes);
93
+ data = this.buffer.subarray(0, length);
94
+ }
95
+ this.bytes = length;
96
+ offset = newline < 0 ? end : end + 1;
97
+ if (newline < 0)
98
+ return;
99
+ const line = this.decoder.decode(data);
100
+ const size = length + 1;
101
+ this.bytes = 0;
102
+ if (this.buffer.length > 1024 * 1024)
103
+ this.buffer = new Uint8Array(0);
104
+ if (line) {
105
+ const pending = this.handler(JSON.parse(line), size);
106
+ if (pending)
107
+ return Promise.resolve(pending).then(consume);
108
+ }
109
+ }
110
+ };
111
+ return consume();
112
+ }
113
+ finish() {
114
+ if (this.bytes)
115
+ throw new Error("core output ended within a message");
116
+ }
117
+ close() {
118
+ this.closed = true;
119
+ this.buffer = new Uint8Array(0);
120
+ this.bytes = 0;
121
+ }
122
+ }
123
+
124
+ // sdk/wasm-module.js
125
+ var modulePromisesBySource = new Map;
126
+ var modulePromisesByObject = new WeakMap;
127
+ var moduleFailureSource = Symbol("libfx.moduleFailureSource");
128
+ function withModuleFailure(input, onFailure) {
129
+ return { [moduleFailureSource]: { input, onFailure } };
130
+ }
131
+ async function compileModule(input) {
132
+ const failureSource = input?.[moduleFailureSource];
133
+ if (failureSource) {
134
+ try {
135
+ return await compileModule(failureSource.input);
136
+ } catch (error) {
137
+ failureSource.onFailure();
138
+ throw error;
139
+ }
140
+ }
141
+ if (input instanceof WebAssembly.Module)
142
+ return input;
143
+ if (typeof input === "string")
144
+ input = fetch(input);
145
+ if (input instanceof Promise)
146
+ input = await input;
147
+ if (input instanceof WebAssembly.Module)
148
+ return input;
149
+ if (input instanceof Response) {
150
+ const contentType = input.headers.get("content-type")?.split(";", 1)[0].trim().toLowerCase();
151
+ if (contentType === "application/wasm" && typeof WebAssembly.compileStreaming === "function") {
152
+ return WebAssembly.compileStreaming(input);
153
+ }
154
+ const bytes = await input.arrayBuffer();
155
+ return WebAssembly.compile(bytes);
156
+ }
157
+ if (input instanceof ArrayBuffer || ArrayBuffer.isView(input)) {
158
+ return WebAssembly.compile(input);
159
+ }
160
+ throw new TypeError("wasm must be a URL, Response, ArrayBuffer, typed array, or WebAssembly.Module");
161
+ }
162
+ function loadModule(input) {
163
+ if (input instanceof WebAssembly.Module)
164
+ return Promise.resolve(input);
165
+ const isString = typeof input === "string";
166
+ if (!isString && (typeof input !== "object" || input === null))
167
+ return compileModule(input);
168
+ const cache = isString ? modulePromisesBySource : modulePromisesByObject;
169
+ const cached = cache.get(input);
170
+ if (cached)
171
+ return cached;
172
+ const pending = compileModule(input);
173
+ cache.set(input, pending);
174
+ pending.catch(() => {
175
+ if (cache.get(input) === pending)
176
+ cache.delete(input);
177
+ });
178
+ return pending;
179
+ }
180
+
181
+ // sdk/fx-sdk.js
182
+ var encoder = new TextEncoder;
183
+ var decoder = new TextDecoder;
184
+ var strictDecoder = new TextDecoder("utf-8", { fatal: true });
185
+ var workspaceInfoLimit = 4 * 1024;
186
+ var workspaceCommandLimit = 64 * 1024;
187
+ var workspaceOutputLimit = 64 * 1024;
188
+ var maxInstructionsBytes = 64 * 1024;
189
+ var maxApiKeyBytes = 64 * 1024;
190
+ var maxModelBytes = 1024;
191
+ var maxUrlBytes = 16 * 1024;
192
+ var maxModelCatalogBytes = 4 * 1024 * 1024;
193
+ var maxModelCatalogEntries = 1e4;
194
+ var streamReadsPerTaskYield = 32;
195
+ var maxUnreadEventBytes = 1024 * 1024;
196
+ var maxUnreadEvents = 256;
197
+ function boundedString(value, name, maxBytes, required) {
198
+ if (value === undefined && !required)
199
+ return;
200
+ if (typeof value !== "string" || value.length === 0) {
201
+ throw new TypeError(`${name} ${required ? "is required and " : ""}must be a non-empty string`);
202
+ }
203
+ if (encoder.encode(value).length > maxBytes) {
204
+ throw new RangeError(`${name} exceeds the ${maxBytes} byte libfx limit`);
205
+ }
206
+ return value;
207
+ }
208
+ function validateGatewayChatUrl(value) {
209
+ if (value === undefined)
210
+ return;
211
+ boundedString(value, "gatewayChatUrl", maxUrlBytes, false);
212
+ let url;
213
+ try {
214
+ url = new URL(value);
215
+ } catch {
216
+ throw new TypeError("gatewayChatUrl must be a valid URL");
217
+ }
218
+ if (url.username || url.password || url.hash) {
219
+ throw new TypeError("gatewayChatUrl must not contain credentials or a fragment");
220
+ }
221
+ if (url.href === "https://ai-gateway.vercel.sh/v3/ai/language-model")
222
+ return;
223
+ const loopback = url.hostname === "127.0.0.1" || url.hostname === "[::1]" || url.hostname === "localhost";
224
+ if (url.protocol !== "http:" || !loopback || !url.port) {
225
+ throw new TypeError("gatewayChatUrl must use the canonical Gateway or explicit loopback HTTP");
226
+ }
227
+ }
228
+ function normalizeAgentOptions(value) {
229
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
230
+ throw new TypeError("createFxAgent() options must be an object");
231
+ }
232
+ const options = { ...value };
233
+ if (Object.hasOwn(options, "env")) {
234
+ throw new TypeError("createFxAgent() does not accept env; pass apiKey and model directly");
235
+ }
236
+ options.apiKey = boundedString(options.apiKey, "apiKey", maxApiKeyBytes, true);
237
+ options.model = boundedString(options.model, "model", maxModelBytes, false);
238
+ validateGatewayChatUrl(options.gatewayChatUrl);
239
+ return options;
240
+ }
241
+ function agentEnvironment(options) {
242
+ return {
243
+ AI_GATEWAY_API_KEY: options.apiKey,
244
+ ...options.model === undefined ? {} : { FX_MODEL: options.model },
245
+ ...options.gatewayChatUrl === undefined ? {} : { FX_GATEWAY_CHAT_URL: options.gatewayChatUrl }
246
+ };
247
+ }
248
+ async function cancelResponseBody(response) {
249
+ try {
250
+ await response.body?.cancel();
251
+ } catch {}
252
+ }
253
+ async function readBoundedResponseText(response, limit) {
254
+ const declared = Number(response.headers.get("content-length"));
255
+ if (Number.isFinite(declared) && declared > limit) {
256
+ await cancelResponseBody(response);
257
+ throw new RangeError(`model catalog exceeds the ${limit} byte libfx limit`);
258
+ }
259
+ if (!response.body) {
260
+ const bytes = new Uint8Array(await response.arrayBuffer());
261
+ if (bytes.length > limit)
262
+ throw new RangeError(`model catalog exceeds the ${limit} byte libfx limit`);
263
+ return strictDecoder.decode(bytes);
264
+ }
265
+ const reader = response.body.getReader();
266
+ const chunks = [];
267
+ let total = 0;
268
+ for (;; ) {
269
+ const { done, value } = await reader.read();
270
+ if (done)
271
+ break;
272
+ if (!value?.length)
273
+ continue;
274
+ total += value.length;
275
+ if (total > limit) {
276
+ try {
277
+ await reader.cancel();
278
+ } catch {}
279
+ throw new RangeError(`model catalog exceeds the ${limit} byte libfx limit`);
280
+ }
281
+ chunks.push(value);
282
+ }
283
+ const bytes = new Uint8Array(total);
284
+ let offset = 0;
285
+ for (const chunk of chunks) {
286
+ bytes.set(chunk, offset);
287
+ offset += chunk.length;
288
+ }
289
+ return strictDecoder.decode(bytes);
290
+ }
291
+ async function listModels(options = {}) {
292
+ if (!options || typeof options !== "object" || Array.isArray(options)) {
293
+ throw new TypeError("listModels() options must be an object");
294
+ }
295
+ const apiKey = boundedString(options.apiKey, "apiKey", maxApiKeyBytes, true);
296
+ const fetchModels = options.fetch ?? globalThis.fetch?.bind(globalThis);
297
+ if (typeof fetchModels !== "function")
298
+ throw new TypeError("fetch is unavailable");
299
+ const response = await fetchModels("https://ai-gateway.vercel.sh/coding-agent/v1/models", {
300
+ method: "GET",
301
+ headers: { authorization: `Bearer ${apiKey}` }
302
+ });
303
+ if (!response.ok) {
304
+ await cancelResponseBody(response);
305
+ throw new Error(`model catalog request failed with HTTP ${response.status}`);
306
+ }
307
+ let catalog;
308
+ try {
309
+ catalog = JSON.parse(await readBoundedResponseText(response, maxModelCatalogBytes));
310
+ } catch (error) {
311
+ if (error instanceof RangeError)
312
+ throw error;
313
+ throw new TypeError("model catalog response is malformed");
314
+ }
315
+ if (!catalog || typeof catalog !== "object" || !Array.isArray(catalog.data)) {
316
+ throw new TypeError("model catalog response is malformed");
317
+ }
318
+ if (catalog.data.length > maxModelCatalogEntries) {
319
+ throw new RangeError(`model catalog exceeds the ${maxModelCatalogEntries} entry libfx limit`);
320
+ }
321
+ const ids = new Set;
322
+ for (const entry of catalog.data) {
323
+ if (!entry || typeof entry !== "object")
324
+ continue;
325
+ if (typeof entry.type === "string" && entry.type.toLowerCase() !== "language")
326
+ continue;
327
+ if (typeof entry.id !== "string" || entry.id.length === 0)
328
+ continue;
329
+ if (encoder.encode(entry.id).length > maxModelBytes)
330
+ continue;
331
+ ids.add(entry.id);
332
+ }
333
+ return [...ids].sort();
334
+ }
335
+ function validWorkspacePath(path) {
336
+ if (typeof path !== "string" || !path.startsWith("/") || path.includes("\x00"))
337
+ return false;
338
+ if (strictDecoder.decode(encoder.encode(path)) !== path)
339
+ return false;
340
+ if (path === "/")
341
+ return true;
342
+ if (path.endsWith("/"))
343
+ return false;
344
+ return path.slice(1).split("/").every((part) => part && part !== "." && part !== "..");
345
+ }
346
+ function prepareWorkspaceAdapter(workspace) {
347
+ if (workspace == null)
348
+ return { present: false, valid: false };
349
+ try {
350
+ const info = workspace.info;
351
+ const permission = workspace.permission;
352
+ if (!info || typeof workspace.exec !== "function" || info.version !== 1 || !validWorkspacePath(info.root) || !validWorkspacePath(info.cwd) || !validWorkspacePath(info.home) || info.cwd !== info.root || info.gitAvailable !== false || info.ephemeral !== true || permission !== "allow-sandboxed" && permission !== "prompt") {
353
+ return { present: true, valid: false };
354
+ }
355
+ const value = {
356
+ version: 1,
357
+ root: info.root,
358
+ cwd: info.cwd,
359
+ home: info.home,
360
+ git: false,
361
+ ephemeral: true,
362
+ permission
363
+ };
364
+ const encoded = encoder.encode(JSON.stringify(value));
365
+ if (encoded.length > workspaceInfoLimit)
366
+ return { present: true, valid: false };
367
+ return { present: true, valid: true, adapter: workspace, info: value, encoded };
368
+ } catch {
369
+ return { present: true, valid: false };
370
+ }
371
+ }
372
+ function utf8Prefix(value, limit) {
373
+ if (value.length <= limit)
374
+ return value;
375
+ let end = limit;
376
+ while (end > 0 && (value[end] & 192) === 128)
377
+ end -= 1;
378
+ return value.subarray(0, end);
379
+ }
380
+ var fxSdkApiVersion = 2;
381
+ function supportsJspi() {
382
+ return typeof WebAssembly.Suspending === "function" && typeof WebAssembly.promising === "function";
383
+ }
384
+ function encodeXtermKeyEvent(event) {
385
+ if (event.type !== "keydown" || event.altKey || event.ctrlKey)
386
+ return null;
387
+ if (event.key === "Enter" && event.shiftKey && !event.metaKey)
388
+ return "\x1B[13;2u";
389
+ if (event.metaKey) {
390
+ const modifiers = 8 | (event.shiftKey ? 1 : 0);
391
+ if (event.key === "Backspace")
392
+ return `\x1B[127;${modifiers + 1}u`;
393
+ const arrow = { ArrowUp: "A", ArrowDown: "B", ArrowRight: "C", ArrowLeft: "D" }[event.key];
394
+ if (arrow)
395
+ return `\x1B[1;${modifiers + 1}${arrow}`;
396
+ }
397
+ return null;
398
+ }
399
+ function xtermAdapter(term) {
400
+ let keyDataHandler = null;
401
+ if (typeof term.attachCustomKeyEventHandler === "function") {
402
+ term.attachCustomKeyEventHandler((event) => {
403
+ const data = encodeXtermKeyEvent(event);
404
+ if (data === null || keyDataHandler === null)
405
+ return true;
406
+ keyDataHandler(data);
407
+ return false;
408
+ });
409
+ }
410
+ return {
411
+ write(bytes) {
412
+ term.write(typeof bytes === "string" ? bytes : decoder.decode(bytes));
413
+ },
414
+ onData(callback) {
415
+ const disposable = term.onData(callback);
416
+ return () => disposable.dispose();
417
+ },
418
+ onKeyData(callback) {
419
+ keyDataHandler = callback;
420
+ return () => {
421
+ if (keyDataHandler === callback)
422
+ keyDataHandler = null;
423
+ };
424
+ },
425
+ get cols() {
426
+ return term.cols;
427
+ },
428
+ get rows() {
429
+ return term.rows;
430
+ },
431
+ onResize(callback) {
432
+ const disposable = term.onResize(callback);
433
+ return () => disposable.dispose();
434
+ }
435
+ };
436
+ }
437
+
438
+ class ByteQueue {
439
+ chunks = [];
440
+ waiters = [];
441
+ closed = false;
442
+ push(bytes) {
443
+ if (this.closed)
444
+ throw new Error("fx runtime stdin is closed");
445
+ if (!bytes.length)
446
+ return;
447
+ this.chunks.push(bytes);
448
+ this.wake();
449
+ }
450
+ read(max) {
451
+ if (!this.chunks.length)
452
+ return null;
453
+ const chunk = this.chunks[0];
454
+ const value = chunk.subarray(0, max);
455
+ if (value.length === chunk.length)
456
+ this.chunks.shift();
457
+ else
458
+ this.chunks[0] = chunk.subarray(value.length);
459
+ return value;
460
+ }
461
+ wait(timeoutMs) {
462
+ if (this.closed)
463
+ return Promise.resolve(true);
464
+ return new Promise((resolve) => {
465
+ let settled = false;
466
+ let timer;
467
+ const waiter = () => {
468
+ if (settled)
469
+ return;
470
+ settled = true;
471
+ if (timer !== undefined)
472
+ clearTimeout(timer);
473
+ resolve(true);
474
+ };
475
+ this.waiters.push(waiter);
476
+ if (timeoutMs !== undefined) {
477
+ timer = setTimeout(() => {
478
+ if (settled)
479
+ return;
480
+ settled = true;
481
+ const index = this.waiters.indexOf(waiter);
482
+ if (index >= 0)
483
+ this.waiters.splice(index, 1);
484
+ resolve(false);
485
+ }, timeoutMs);
486
+ }
487
+ });
488
+ }
489
+ close() {
490
+ this.closed = true;
491
+ this.wake();
492
+ }
493
+ wake() {
494
+ this.waiters.splice(0).forEach((resolve) => resolve());
495
+ }
496
+ }
497
+ function raceWithTimeout(promise, timeoutMs, timeoutValue) {
498
+ let timer;
499
+ return new Promise((resolve, reject) => {
500
+ timer = setTimeout(() => resolve(timeoutValue), timeoutMs);
501
+ promise.then((value) => {
502
+ clearTimeout(timer);
503
+ resolve(value);
504
+ }, (error) => {
505
+ clearTimeout(timer);
506
+ reject(error);
507
+ });
508
+ });
509
+ }
510
+ function yieldToHostTask() {
511
+ if (typeof globalThis.setImmediate === "function") {
512
+ return new Promise((resolve) => globalThis.setImmediate(resolve));
513
+ }
514
+ return new Promise((resolve) => setTimeout(resolve, 0));
515
+ }
516
+ function createRuntime(options) {
517
+ const abortReason = new DOMException("This operation was aborted", "AbortError");
518
+ const stdin = new ByteQueue;
519
+ const streams = new Map;
520
+ const httpRequests = new Set;
521
+ const workspaceExecs = new Set;
522
+ const workspace = prepareWorkspaceAdapter(options.workspace);
523
+ const args = ["fx", ...options.args || []];
524
+ const env = Object.entries(options.env || {}).map(([key, value]) => `${key}=${value}`);
525
+ let instance;
526
+ let nextHandle = 1;
527
+ let exitedResolve;
528
+ let exitCode = null;
529
+ let aborted = false;
530
+ let coreOutput;
531
+ let outputError;
532
+ const exited = new Promise((resolve) => {
533
+ exitedResolve = resolve;
534
+ });
535
+ const markExited = (code) => {
536
+ if (exitCode !== null)
537
+ return;
538
+ exitCode = code;
539
+ exitedResolve(code);
540
+ };
541
+ const memory = () => instance.exports.memory;
542
+ const bytes = (ptr, len) => new Uint8Array(memory().buffer, ptr, len);
543
+ const text = (ptr, len) => decoder.decode(bytes(ptr, len));
544
+ const writeU32 = (ptr, value) => new DataView(memory().buffer).setUint32(ptr, value, true);
545
+ const writeU64 = (ptr, value) => new DataView(memory().buffer).setBigUint64(ptr, BigInt(value), true);
546
+ function checkedBytes(ptr, len) {
547
+ if (!Number.isInteger(ptr) || !Number.isInteger(len) || ptr < 0 || len < 0 || ptr > memory().buffer.byteLength || len > memory().buffer.byteLength - ptr)
548
+ return null;
549
+ return bytes(ptr, len);
550
+ }
551
+ function writeVector(values, ptrs, data) {
552
+ let cursor = data;
553
+ values.forEach((value, index) => {
554
+ const encoded = encoder.encode(`${value}\x00`);
555
+ writeU32(ptrs + index * 4, cursor);
556
+ bytes(cursor, encoded.length).set(encoded);
557
+ cursor += encoded.length;
558
+ });
559
+ }
560
+ function emitStdout(chunk) {
561
+ if (options.stdout)
562
+ return options.stdout(chunk);
563
+ }
564
+ function fdWrite(fd, iovs, count, nwritten) {
565
+ if (options.traceWasi)
566
+ console.error("wasi fd_write", { fd, count });
567
+ const view = new DataView(memory().buffer);
568
+ let total = 0;
569
+ for (let index = 0;index < count; index++) {
570
+ total += view.getUint32(iovs + index * 8 + 4, true);
571
+ }
572
+ if (coreOutput && total > maxCoreMessageBytes)
573
+ throw new RangeError("core output message exceeds 64 MiB");
574
+ if (fd === 1 || fd === 2) {
575
+ const chunk = new Uint8Array(total);
576
+ let offset = 0;
577
+ for (let index = 0;index < count; index++) {
578
+ const ptr = view.getUint32(iovs + index * 8, true);
579
+ const len = view.getUint32(iovs + index * 8 + 4, true);
580
+ chunk.set(bytes(ptr, len), offset);
581
+ offset += len;
582
+ }
583
+ if (fd === 1) {
584
+ const pending = emitStdout(chunk);
585
+ if (coreOutput && pending)
586
+ return Promise.resolve(pending).then(() => {
587
+ writeU32(nwritten, total);
588
+ return 0;
589
+ });
590
+ } else if (typeof options.stderr === "function")
591
+ options.stderr(chunk);
592
+ else
593
+ console.warn(decoder.decode(chunk));
594
+ }
595
+ writeU32(nwritten, total);
596
+ return 0;
597
+ }
598
+ function fdRead(fd, iovs, count, nread) {
599
+ if (fd !== 0)
600
+ return 8;
601
+ const attempt = () => {
602
+ const view = new DataView(memory().buffer);
603
+ let total = 0;
604
+ for (let index = 0;index < count; index++) {
605
+ const ptr = view.getUint32(iovs + index * 8, true);
606
+ const len = view.getUint32(iovs + index * 8 + 4, true);
607
+ const chunk = stdin.read(len);
608
+ if (!chunk)
609
+ break;
610
+ bytes(ptr, chunk.length).set(chunk);
611
+ total += chunk.length;
612
+ if (chunk.length < len)
613
+ break;
614
+ }
615
+ if (total) {
616
+ writeU32(nread, total);
617
+ return 0;
618
+ }
619
+ return null;
620
+ };
621
+ const immediate = attempt();
622
+ if (immediate !== null)
623
+ return immediate;
624
+ if (stdin.closed) {
625
+ writeU32(nread, 0);
626
+ return 0;
627
+ }
628
+ return stdin.wait().then(() => {
629
+ const result = attempt();
630
+ if (result !== null)
631
+ return result;
632
+ writeU32(nread, 0);
633
+ return 0;
634
+ });
635
+ }
636
+ function pollOneoff(subscriptions, events, count, nevents) {
637
+ const view = new DataView(memory().buffer);
638
+ for (let index = 0;index < count; index++) {
639
+ const base = subscriptions + index * 48;
640
+ const type = view.getUint8(base + 8);
641
+ if (type === 1 && stdin.chunks.length) {
642
+ bytes(events, 32).fill(0);
643
+ bytes(events, 8).set(bytes(base, 8));
644
+ view.setUint8(events + 10, 1);
645
+ writeU32(nevents, 1);
646
+ return 0;
647
+ }
648
+ }
649
+ let timeout = null;
650
+ for (let index = 0;index < count; index++) {
651
+ const base = subscriptions + index * 48;
652
+ if (view.getUint8(base + 8) === 0)
653
+ timeout = Number(view.getBigUint64(base + 24, true) / 1000000n);
654
+ }
655
+ return stdin.wait(timeout === null ? undefined : timeout).then(() => {
656
+ bytes(events, 32).fill(0);
657
+ bytes(events, 8).set(bytes(subscriptions, 8));
658
+ writeU32(nevents, 1);
659
+ return 0;
660
+ });
661
+ }
662
+ function termPollInput(timeoutMs) {
663
+ options.onTerminalPoll?.();
664
+ if (stdin.chunks.length)
665
+ return 1;
666
+ if (stdin.closed)
667
+ return -1;
668
+ if (timeoutMs === 0)
669
+ return 0;
670
+ return stdin.wait(timeoutMs >= 0 ? timeoutMs : undefined).then(() => stdin.chunks.length ? 1 : stdin.closed ? -1 : 0);
671
+ }
672
+ function headersFromJson(ptr, len) {
673
+ const headers = new Headers;
674
+ for (const { name, value } of JSON.parse(text(ptr, len) || "[]"))
675
+ headers.append(name, value);
676
+ return headers;
677
+ }
678
+ function streamOpen(methodPtr, methodLen, urlPtr, urlLen, headersPtr, headersLen, bodyPtr, bodyLen) {
679
+ const controller = new AbortController;
680
+ const handle = nextHandle++;
681
+ const state = {
682
+ controller,
683
+ reader: null,
684
+ leftover: new Uint8Array,
685
+ response: null,
686
+ responseError: null,
687
+ responseSettled: null,
688
+ pendingRead: null,
689
+ readResult: null,
690
+ readError: null,
691
+ readsSinceTaskYield: 0
692
+ };
693
+ streams.set(handle, state);
694
+ state.responseSettled = Promise.resolve().then(() => options.fetch(text(urlPtr, urlLen), {
695
+ method: text(methodPtr, methodLen),
696
+ headers: headersFromJson(headersPtr, headersLen),
697
+ body: bodyLen ? bytes(bodyPtr, bodyLen).slice() : undefined,
698
+ signal: controller.signal
699
+ })).then((response) => {
700
+ state.response = response;
701
+ state.reader = response.body?.getReader() || null;
702
+ }).catch((error) => {
703
+ state.responseError = error;
704
+ });
705
+ return handle;
706
+ }
707
+ function streamStatus(handle, statusOut) {
708
+ const state = streams.get(handle);
709
+ if (!state)
710
+ return -1;
711
+ const settled = () => {
712
+ if (state.responseError)
713
+ return state.responseError?.name === "AbortError" ? -2 : -1;
714
+ if (!state.response)
715
+ return 0;
716
+ new DataView(memory().buffer).setUint16(statusOut, state.response.status, true);
717
+ return 1;
718
+ };
719
+ const immediate = settled();
720
+ if (immediate !== 0)
721
+ return immediate;
722
+ return raceWithTimeout(state.responseSettled.then(() => true), 50, false).then((ready) => ready ? settled() : 0);
723
+ }
724
+ function streamNext(handle, outPtr, outCap) {
725
+ const state = streams.get(handle);
726
+ if (!state)
727
+ return -1;
728
+ const yieldAfterReadyResult = (result) => {
729
+ if (result <= 0)
730
+ return result;
731
+ state.readsSinceTaskYield += 1;
732
+ if (state.readsSinceTaskYield < streamReadsPerTaskYield)
733
+ return result;
734
+ state.readsSinceTaskYield = 0;
735
+ return yieldToHostTask().then(() => result);
736
+ };
737
+ const copy = (chunk) => {
738
+ const written = chunk.subarray(0, outCap);
739
+ bytes(outPtr, written.length).set(written);
740
+ state.leftover = chunk.subarray(written.length);
741
+ return written.length;
742
+ };
743
+ const consume = () => {
744
+ if (state.leftover.length)
745
+ return copy(state.leftover);
746
+ if (state.readError)
747
+ return state.readError?.name === "AbortError" ? -2 : -1;
748
+ if (!state.readResult)
749
+ return null;
750
+ const { done, value } = state.readResult;
751
+ state.readResult = null;
752
+ if (done)
753
+ return 0;
754
+ if (!value?.length)
755
+ return null;
756
+ return copy(value);
757
+ };
758
+ const immediate = consume();
759
+ if (immediate !== null)
760
+ return yieldAfterReadyResult(immediate);
761
+ if (!state.reader)
762
+ return 0;
763
+ if (!state.pendingRead) {
764
+ state.pendingRead = state.reader.read().then((result) => {
765
+ state.readResult = result;
766
+ state.pendingRead = null;
767
+ }).catch((error) => {
768
+ state.readError = error;
769
+ state.pendingRead = null;
770
+ });
771
+ }
772
+ return raceWithTimeout(state.pendingRead.then(() => true), 50, false).then((ready) => {
773
+ if (!ready)
774
+ return -3;
775
+ const result = consume();
776
+ return result === null ? -3 : yieldAfterReadyResult(result);
777
+ });
778
+ }
779
+ function httpRequest(methodPtr, methodLen, urlPtr, urlLen, headersPtr, headersLen, bodyPtr, bodyLen, statusOut, responsePtr, responseCap) {
780
+ const controller = new AbortController;
781
+ httpRequests.add(controller);
782
+ let onAbort;
783
+ const cancelled = new Promise((resolve) => {
784
+ onAbort = () => resolve(-1);
785
+ controller.signal.addEventListener("abort", onAbort, { once: true });
786
+ });
787
+ const request = (async () => {
788
+ const response = await options.fetch(text(urlPtr, urlLen), {
789
+ method: text(methodPtr, methodLen),
790
+ headers: headersFromJson(headersPtr, headersLen),
791
+ body: bodyLen ? bytes(bodyPtr, bodyLen).slice() : undefined,
792
+ signal: controller.signal
793
+ });
794
+ if (controller.signal.aborted) {
795
+ cancelResponseBody(response);
796
+ return -1;
797
+ }
798
+ const body = new Uint8Array(await response.arrayBuffer());
799
+ if (controller.signal.aborted)
800
+ return -1;
801
+ new DataView(memory().buffer).setUint16(statusOut, response.status, true);
802
+ if (body.length > responseCap)
803
+ return -2;
804
+ bytes(responsePtr, body.length).set(body);
805
+ return body.length;
806
+ })().catch(() => -1);
807
+ return Promise.race([request, cancelled]).finally(() => {
808
+ controller.signal.removeEventListener("abort", onAbort);
809
+ httpRequests.delete(controller);
810
+ });
811
+ }
812
+ let pendingHostToolResult = null;
813
+ function hostToolCall(namePtr, nameLen, argumentsPtr, argumentsLen, outputPtr, outputCap, statusPtr) {
814
+ pendingHostToolResult = null;
815
+ if (typeof options.hostToolExecutor !== "function")
816
+ return -1;
817
+ if (options.traceWasi)
818
+ console.error("fx host tool call start");
819
+ let input;
820
+ try {
821
+ input = JSON.parse(text(argumentsPtr, argumentsLen));
822
+ } catch {
823
+ return -1;
824
+ }
825
+ return Promise.resolve(options.hostToolExecutor(text(namePtr, nameLen), input)).then((result) => {
826
+ if (options.traceWasi)
827
+ console.error("fx host tool call settled", result.cancelled, result.isError);
828
+ if (result.cancelled)
829
+ return -2;
830
+ const output = encoder.encode(result.content);
831
+ bytes(statusPtr, 1)[0] = (result.isError ? 1 : 0) + (result.rich ? 2 : 0);
832
+ if (output.length > outputCap) {
833
+ if (!result.rich || output.length > 8 * 1024 * 1024)
834
+ return -3;
835
+ pendingHostToolResult = output;
836
+ return output.length;
837
+ }
838
+ bytes(outputPtr, output.length).set(output);
839
+ return output.length;
840
+ }).catch(() => -1);
841
+ }
842
+ function openUrl(urlPtr, urlLen) {
843
+ if (typeof options.openUrl !== "function")
844
+ return 0;
845
+ return Promise.resolve().then(() => options.openUrl(text(urlPtr, urlLen))).then((accepted) => accepted === false ? 0 : 1).catch(() => 0);
846
+ }
847
+ function oauthSessionLoad(outPtr, outCap, revisionPtr, revisionCap, revisionLenOut) {
848
+ if (!options.oauthSessionStore?.load)
849
+ return -1;
850
+ return Promise.resolve().then(() => options.oauthSessionStore.load()).then((record) => {
851
+ if (!record)
852
+ return -2;
853
+ const value = record.bytes instanceof Uint8Array ? record.bytes : new Uint8Array(record.bytes);
854
+ if (typeof record.revision !== "string")
855
+ return -1;
856
+ const revision = encoder.encode(record.revision);
857
+ if (value.length > outCap || revision.length > revisionCap)
858
+ return -3;
859
+ bytes(outPtr, value.length).set(value);
860
+ bytes(revisionPtr, revision.length).set(revision);
861
+ writeU32(revisionLenOut, revision.length);
862
+ return value.length;
863
+ }).catch(() => -1);
864
+ }
865
+ function oauthSessionCommit(valuePtr, valueLen, expectedPtr, expectedLen, revisionPtr, revisionCap, revisionLenOut) {
866
+ if (!options.oauthSessionStore?.commit)
867
+ return -1;
868
+ const expectedRevision = expectedLen ? text(expectedPtr, expectedLen) : undefined;
869
+ const value = bytes(valuePtr, valueLen).slice();
870
+ return Promise.resolve().then(() => options.oauthSessionStore.commit(value, expectedRevision)).then((result) => {
871
+ if (typeof result?.revision !== "string")
872
+ return -1;
873
+ const revision = encoder.encode(result.revision);
874
+ if (revision.length > revisionCap)
875
+ return -1;
876
+ bytes(revisionPtr, revision.length).set(revision);
877
+ writeU32(revisionLenOut, revision.length);
878
+ return 0;
879
+ }).catch((error) => error?.code === "FX_OAUTH_SESSION_REVISION_CONFLICT" ? -2 : -1);
880
+ }
881
+ function oauthSessionRemove(expectedPtr, expectedLen) {
882
+ if (!options.oauthSessionStore?.remove)
883
+ return -1;
884
+ const expectedRevision = expectedLen ? text(expectedPtr, expectedLen) : undefined;
885
+ return Promise.resolve().then(() => options.oauthSessionStore.remove(expectedRevision)).then((result) => result === false || result === "missing" ? 1 : 0).catch((error) => error?.code === "FX_OAUTH_SESSION_REVISION_CONFLICT" ? -2 : -1);
886
+ }
887
+ function configGet(idPtr, idLen, outPtr, outCap) {
888
+ if (!options.configStore?.get)
889
+ return -2;
890
+ const configId = text(idPtr, idLen);
891
+ return Promise.resolve().then(() => options.configStore.get(configId)).then((value) => {
892
+ if (value === null || value === undefined)
893
+ return -2;
894
+ if (typeof value !== "string")
895
+ throw new TypeError("configStore.get() must return a string or null");
896
+ const encoded = encoder.encode(value);
897
+ if (encoded.length > outCap)
898
+ return -3;
899
+ bytes(outPtr, encoded.length).set(encoded);
900
+ options.emit?.("config.restore", { configId, value });
901
+ return encoded.length;
902
+ }).catch((error) => {
903
+ options.emit?.("config.restore_error", { configId, error });
904
+ return -1;
905
+ });
906
+ }
907
+ function configSet(idPtr, idLen, valuePtr, valueLen) {
908
+ if (!options.configStore?.set)
909
+ return 0;
910
+ const configId = text(idPtr, idLen);
911
+ const value = text(valuePtr, valueLen);
912
+ return Promise.resolve().then(() => options.configStore.set(configId, value)).then(() => {
913
+ options.emit?.("config.changed", { configId, value, source: "terminal" });
914
+ return 0;
915
+ }).catch((error) => {
916
+ options.emit?.("config.persist_error", { configId, error });
917
+ return -1;
918
+ });
919
+ }
920
+ function promptHistoryLoad(workspacePtr, workspaceLen, limit, outPtr, outCap) {
921
+ if (!options.promptHistoryStore?.load)
922
+ return -1;
923
+ const workspaceRoot = text(workspacePtr, workspaceLen);
924
+ return Promise.resolve().then(() => options.promptHistoryStore.load(workspaceRoot, limit)).then((entries) => {
925
+ if (!Array.isArray(entries) || entries.some((entry) => typeof entry !== "string")) {
926
+ throw new TypeError("promptHistoryStore.load() must return an array of strings");
927
+ }
928
+ const value = encoder.encode(JSON.stringify(entries));
929
+ if (value.length > outCap)
930
+ return -2;
931
+ bytes(outPtr, value.length).set(value);
932
+ options.emit?.("history.restore", { workspaceRoot, count: entries.length });
933
+ return value.length;
934
+ }).catch((error) => {
935
+ options.emit?.("history.restore_error", { workspaceRoot, error });
936
+ return -1;
937
+ });
938
+ }
939
+ function promptHistoryAppend(timestampMs, workspacePtr, workspaceLen, valuePtr, valueLen) {
940
+ if (!options.promptHistoryStore?.append)
941
+ return -1;
942
+ const workspaceRoot = text(workspacePtr, workspaceLen);
943
+ const value = text(valuePtr, valueLen);
944
+ return Promise.resolve().then(() => options.promptHistoryStore.append(workspaceRoot, value, Number(timestampMs))).then((result) => {
945
+ options.emit?.("history.append", { workspaceRoot });
946
+ if (result === "duplicate")
947
+ return 1;
948
+ if (result === "record_too_large")
949
+ return 2;
950
+ return 0;
951
+ }).catch((error) => {
952
+ options.emit?.("history.append_error", { workspaceRoot, error });
953
+ return -1;
954
+ });
955
+ }
956
+ function promptHistoryClear(workspacePtr, workspaceLen) {
957
+ if (!options.promptHistoryStore?.clear)
958
+ return -1;
959
+ const workspaceRoot = text(workspacePtr, workspaceLen);
960
+ return Promise.resolve().then(() => options.promptHistoryStore.clear(workspaceRoot)).then(() => {
961
+ options.emit?.("history.clear", { workspaceRoot });
962
+ return 0;
963
+ }).catch((error) => {
964
+ options.emit?.("history.clear_error", { workspaceRoot, error });
965
+ return -1;
966
+ });
967
+ }
968
+ function sessionLoad(idPtr, idLen, outPtr, outCap, revisionPtr, revisionCap, revisionLenOut) {
969
+ if (!options.sessionStore)
970
+ return -1;
971
+ return Promise.resolve().then(() => options.sessionStore.load(text(idPtr, idLen))).then((record) => {
972
+ if (!record)
973
+ return -2;
974
+ const value = record.bytes instanceof Uint8Array ? record.bytes : new Uint8Array(record.bytes);
975
+ const revision = encoder.encode(record.revision);
976
+ if (value.length > outCap || revision.length > revisionCap)
977
+ return -3;
978
+ bytes(outPtr, value.length).set(value);
979
+ bytes(revisionPtr, revision.length).set(revision);
980
+ writeU32(revisionLenOut, revision.length);
981
+ return value.length;
982
+ }).catch(() => -1);
983
+ }
984
+ function sessionCommit(idPtr, idLen, valuePtr, valueLen, expectedPtr, expectedLen, revisionPtr, revisionCap, revisionLenOut) {
985
+ if (!options.sessionStore)
986
+ return -1;
987
+ const id = text(idPtr, idLen);
988
+ const expectedRevision = expectedLen ? text(expectedPtr, expectedLen) : undefined;
989
+ return Promise.resolve().then(() => options.sessionStore.commit(id, bytes(valuePtr, valueLen).slice(), expectedRevision)).then((result) => {
990
+ const revision = encoder.encode(result.revision);
991
+ if (revision.length > revisionCap)
992
+ return -1;
993
+ bytes(revisionPtr, revision.length).set(revision);
994
+ writeU32(revisionLenOut, revision.length);
995
+ return 0;
996
+ }).catch((error) => error?.code === "FX_SESSION_REVISION_CONFLICT" ? -2 : -1);
997
+ }
998
+ function sessionList(outPtr, outCap) {
999
+ if (!options.sessionStore)
1000
+ return -1;
1001
+ return Promise.resolve().then(() => options.sessionStore.list()).then((records) => {
1002
+ const value = encoder.encode(JSON.stringify(records));
1003
+ if (value.length > outCap)
1004
+ return -2;
1005
+ bytes(outPtr, value.length).set(value);
1006
+ return value.length;
1007
+ }).catch(() => -1);
1008
+ }
1009
+ function sessionRemove(idPtr, idLen) {
1010
+ if (!options.sessionStore)
1011
+ return -1;
1012
+ return Promise.resolve().then(() => options.sessionStore.remove(text(idPtr, idLen))).then(() => 0).catch(() => -1);
1013
+ }
1014
+ function workspaceInfo(outPtr, outCap) {
1015
+ if (!workspace.present)
1016
+ return -2;
1017
+ if (!workspace.valid)
1018
+ return -4;
1019
+ const output = checkedBytes(outPtr, outCap);
1020
+ if (!output)
1021
+ return -4;
1022
+ if (workspace.encoded.length > outCap)
1023
+ return -3;
1024
+ output.set(workspace.encoded);
1025
+ return workspace.encoded.length;
1026
+ }
1027
+ function workspaceExec(commandPtr, commandLen, timeoutMs, outputPtr, outputCap, resultPtr) {
1028
+ if (!workspace.present)
1029
+ return Promise.resolve(-2);
1030
+ if (!workspace.valid)
1031
+ return Promise.resolve(-4);
1032
+ const commandBytes = checkedBytes(commandPtr, commandLen);
1033
+ const output = checkedBytes(outputPtr, outputCap);
1034
+ const resultBytes = checkedBytes(resultPtr, 32);
1035
+ if (!commandBytes || !output || !resultBytes || commandLen > workspaceCommandLimit || outputCap > workspaceOutputLimit || !Number.isInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > 30000)
1036
+ return Promise.resolve(-4);
1037
+ let command;
1038
+ try {
1039
+ command = strictDecoder.decode(commandBytes);
1040
+ } catch {
1041
+ return Promise.resolve(-4);
1042
+ }
1043
+ if (command.includes("\x00"))
1044
+ return Promise.resolve(-4);
1045
+ const controller = new AbortController;
1046
+ let resolveAbort;
1047
+ const aborted = new Promise((resolve) => {
1048
+ resolveAbort = resolve;
1049
+ });
1050
+ const state = {
1051
+ controller,
1052
+ status: null,
1053
+ abort(status) {
1054
+ if (this.status !== null)
1055
+ return;
1056
+ this.status = status;
1057
+ resolveAbort(status);
1058
+ controller.abort(new DOMException(status === -5 ? "workspace command timed out" : "workspace command aborted", status === -5 ? "TimeoutError" : "AbortError"));
1059
+ }
1060
+ };
1061
+ workspaceExecs.add(state);
1062
+ const timer = setTimeout(() => state.abort(-5), timeoutMs);
1063
+ const execution = Promise.resolve().then(() => workspace.adapter.exec({
1064
+ command,
1065
+ cwd: workspace.info.cwd,
1066
+ signal: controller.signal,
1067
+ timeoutMs,
1068
+ outputLimitBytes: workspaceOutputLimit
1069
+ })).then((value) => {
1070
+ if (state.status !== null)
1071
+ return state.status;
1072
+ if (!value || !Number.isInteger(value.exitCode) || value.exitCode < -2147483648 || value.exitCode > 2147483647 || typeof value.stdout !== "string" || typeof value.stderr !== "string")
1073
+ return -1;
1074
+ const stdout = encoder.encode(value.stdout);
1075
+ const stderr = encoder.encode(value.stderr);
1076
+ if (stdout.length > 4294967295 || stderr.length > 4294967295)
1077
+ return -1;
1078
+ let stdoutCap = stdout.length;
1079
+ let stderrCap = stderr.length;
1080
+ if (stdout.length + stderr.length > outputCap) {
1081
+ stdoutCap = Math.min(stdout.length, Math.ceil(outputCap / 2));
1082
+ stderrCap = Math.min(stderr.length, Math.floor(outputCap / 2));
1083
+ let remaining = outputCap - stdoutCap - stderrCap;
1084
+ const stdoutExtra = Math.min(remaining, stdout.length - stdoutCap);
1085
+ stdoutCap += stdoutExtra;
1086
+ remaining -= stdoutExtra;
1087
+ stderrCap += Math.min(remaining, stderr.length - stderrCap);
1088
+ }
1089
+ const stdoutPreview = utf8Prefix(stdout, stdoutCap);
1090
+ const stderrPreview = utf8Prefix(stderr, stderrCap);
1091
+ output.set(stdoutPreview, 0);
1092
+ output.set(stderrPreview, stdoutPreview.length);
1093
+ const copied = stdoutPreview.length + stderrPreview.length;
1094
+ const view = new DataView(memory().buffer, resultPtr, 32);
1095
+ view.setInt32(0, value.exitCode, true);
1096
+ view.setUint32(4, 0, true);
1097
+ view.setUint32(8, stdoutPreview.length, true);
1098
+ view.setUint32(12, stdout.length, true);
1099
+ view.setUint32(16, stdoutPreview.length, true);
1100
+ view.setUint32(20, stderrPreview.length, true);
1101
+ view.setUint32(24, stderr.length, true);
1102
+ view.setUint32(28, copied < stdout.length + stderr.length ? 1 : 0, true);
1103
+ return 0;
1104
+ }).catch((error) => {
1105
+ if (state.status !== null)
1106
+ return state.status;
1107
+ if (error?.name === "TimeoutError")
1108
+ return -5;
1109
+ if (error?.name === "AbortError")
1110
+ return -3;
1111
+ return -1;
1112
+ });
1113
+ return Promise.race([execution, aborted]).finally(() => {
1114
+ clearTimeout(timer);
1115
+ workspaceExecs.delete(state);
1116
+ });
1117
+ }
1118
+ function abortHostEffects() {
1119
+ pendingHostToolResult = null;
1120
+ streams.forEach((state) => state.controller.abort(abortReason));
1121
+ httpRequests.forEach((controller) => controller.abort(abortReason));
1122
+ workspaceExecs.forEach((state) => state.abort(-3));
1123
+ }
1124
+ const unavailable = () => 52;
1125
+ const wasi = {
1126
+ args_sizes_get(count, size) {
1127
+ if (options.traceWasi)
1128
+ console.error("wasi args_sizes_get");
1129
+ writeU32(count, args.length);
1130
+ writeU32(size, args.reduce((n, v) => n + encoder.encode(v).length + 1, 0));
1131
+ return 0;
1132
+ },
1133
+ args_get(ptrs, data) {
1134
+ if (options.traceWasi)
1135
+ console.error("wasi args_get");
1136
+ writeVector(args, ptrs, data);
1137
+ return 0;
1138
+ },
1139
+ environ_sizes_get(count, size) {
1140
+ if (options.traceWasi)
1141
+ console.error("wasi environ_sizes_get");
1142
+ writeU32(count, env.length);
1143
+ writeU32(size, env.reduce((n, v) => n + encoder.encode(v).length + 1, 0));
1144
+ return 0;
1145
+ },
1146
+ environ_get(ptrs, data) {
1147
+ if (options.traceWasi)
1148
+ console.error("wasi environ_get");
1149
+ writeVector(env, ptrs, data);
1150
+ return 0;
1151
+ },
1152
+ fd_write: options.args?.[0] === "acp" ? new WebAssembly.Suspending(fdWrite) : fdWrite,
1153
+ fd_read: new WebAssembly.Suspending(fdRead),
1154
+ fd_close() {
1155
+ return 0;
1156
+ },
1157
+ fd_fdstat_get(fd, out) {
1158
+ if (options.traceWasi)
1159
+ console.error("wasi fd_fdstat_get", fd);
1160
+ bytes(out, 24).fill(0);
1161
+ const view = new DataView(memory().buffer);
1162
+ view.setUint8(out, fd <= 2 ? 2 : 0);
1163
+ view.setBigUint64(out + 8, 0xffffffffffffffffn, true);
1164
+ view.setBigUint64(out + 16, 0xffffffffffffffffn, true);
1165
+ return 0;
1166
+ },
1167
+ fd_filestat_get: unavailable,
1168
+ fd_filestat_set_size: unavailable,
1169
+ fd_filestat_set_times: unavailable,
1170
+ fd_pread: unavailable,
1171
+ fd_prestat_get() {
1172
+ return 8;
1173
+ },
1174
+ fd_prestat_dir_name: unavailable,
1175
+ fd_pwrite: unavailable,
1176
+ fd_readdir: unavailable,
1177
+ fd_seek() {
1178
+ return 29;
1179
+ },
1180
+ fd_sync() {
1181
+ return 0;
1182
+ },
1183
+ clock_res_get(_id, out) {
1184
+ if (options.traceWasi)
1185
+ console.error("wasi clock_res_get");
1186
+ writeU64(out, 1000000n);
1187
+ return 0;
1188
+ },
1189
+ clock_time_get(_id, _precision, out) {
1190
+ if (options.traceWasi)
1191
+ console.error("wasi clock_time_get");
1192
+ writeU64(out, BigInt(Date.now()) * 1000000n);
1193
+ return 0;
1194
+ },
1195
+ path_create_directory: unavailable,
1196
+ path_filestat_get: unavailable,
1197
+ path_filestat_set_times: unavailable,
1198
+ path_link: unavailable,
1199
+ path_open: unavailable,
1200
+ path_readlink: unavailable,
1201
+ path_remove_directory: unavailable,
1202
+ path_rename: unavailable,
1203
+ path_symlink: unavailable,
1204
+ path_unlink_file: unavailable,
1205
+ random_get(ptr, len) {
1206
+ crypto.getRandomValues(bytes(ptr, len));
1207
+ return 0;
1208
+ },
1209
+ poll_oneoff: new WebAssembly.Suspending(pollOneoff),
1210
+ proc_exit(code) {
1211
+ if (options.traceWasi)
1212
+ console.error("wasi proc_exit", code);
1213
+ markExited(code);
1214
+ throw new WebAssembly.RuntimeError(`proc_exit(${code})`);
1215
+ }
1216
+ };
1217
+ const fx = {
1218
+ fx_term_poll_input: new WebAssembly.Suspending(termPollInput),
1219
+ fx_prompt_history_available() {
1220
+ return options.promptHistoryStore ? 1 : 0;
1221
+ },
1222
+ fx_workspace_available() {
1223
+ return workspace.present ? 1 : 0;
1224
+ },
1225
+ fx_workspace_info: workspaceInfo,
1226
+ fx_workspace_exec: new WebAssembly.Suspending(workspaceExec),
1227
+ fx_http_stream_open: streamOpen,
1228
+ fx_http_stream_status: new WebAssembly.Suspending(streamStatus),
1229
+ fx_http_stream_next: new WebAssembly.Suspending(streamNext),
1230
+ fx_http_stream_close(handle) {
1231
+ const state = streams.get(handle);
1232
+ state?.controller.abort(abortReason);
1233
+ streams.delete(handle);
1234
+ },
1235
+ fx_http_request: new WebAssembly.Suspending(httpRequest),
1236
+ fx_host_tool_call: new WebAssembly.Suspending(hostToolCall),
1237
+ fx_host_tool_result_read(offset, ptr, cap) {
1238
+ if (!pendingHostToolResult || offset < 0 || offset > pendingHostToolResult.length)
1239
+ return -1;
1240
+ const chunk = pendingHostToolResult.subarray(offset, offset + cap);
1241
+ bytes(ptr, chunk.length).set(chunk);
1242
+ return chunk.length;
1243
+ },
1244
+ fx_host_tool_result_release() {
1245
+ pendingHostToolResult = null;
1246
+ },
1247
+ fx_open_url: new WebAssembly.Suspending(openUrl),
1248
+ fx_oauth_session_load: new WebAssembly.Suspending(oauthSessionLoad),
1249
+ fx_oauth_session_commit: new WebAssembly.Suspending(oauthSessionCommit),
1250
+ fx_oauth_session_remove: new WebAssembly.Suspending(oauthSessionRemove),
1251
+ fx_config_get: new WebAssembly.Suspending(configGet),
1252
+ fx_config_set: new WebAssembly.Suspending(configSet),
1253
+ fx_prompt_history_load: new WebAssembly.Suspending(promptHistoryLoad),
1254
+ fx_prompt_history_append: new WebAssembly.Suspending(promptHistoryAppend),
1255
+ fx_prompt_history_clear: new WebAssembly.Suspending(promptHistoryClear),
1256
+ fx_session_load: new WebAssembly.Suspending(sessionLoad),
1257
+ fx_session_commit: new WebAssembly.Suspending(sessionCommit),
1258
+ fx_session_list: new WebAssembly.Suspending(sessionList),
1259
+ fx_session_remove: new WebAssembly.Suspending(sessionRemove),
1260
+ fx_term_size(cols, rows) {
1261
+ const width = options.terminal?.cols || 80;
1262
+ const height = options.terminal?.rows || 24;
1263
+ new DataView(memory().buffer).setUint16(cols, width, true);
1264
+ new DataView(memory().buffer).setUint16(rows, height, true);
1265
+ options.emit?.("terminal.size", { cols: width, rows: height });
1266
+ }
1267
+ };
1268
+ return {
1269
+ imports: { wasi_snapshot_preview1: wasi, fx },
1270
+ exited,
1271
+ setInstance(value) {
1272
+ instance = value;
1273
+ },
1274
+ write(data) {
1275
+ stdin.push(typeof data === "string" ? encoder.encode(data) : data);
1276
+ },
1277
+ wake() {
1278
+ stdin.wake();
1279
+ },
1280
+ closeStdin() {
1281
+ stdin.close();
1282
+ },
1283
+ abortHostEffects,
1284
+ abort(error) {
1285
+ aborted = true;
1286
+ outputError = error;
1287
+ coreOutput?.close();
1288
+ abortHostEffects();
1289
+ stdin.close();
1290
+ markExited(130);
1291
+ },
1292
+ markExited,
1293
+ get aborted() {
1294
+ return aborted;
1295
+ },
1296
+ get exitCode() {
1297
+ return exitCode;
1298
+ },
1299
+ get error() {
1300
+ return outputError;
1301
+ },
1302
+ setLineHandler(handler) {
1303
+ coreOutput = new CoreOutput(handler);
1304
+ options.stdout = (chunk) => coreOutput.write(chunk);
1305
+ },
1306
+ finishOutput() {
1307
+ coreOutput?.finish();
1308
+ }
1309
+ };
1310
+ }
1311
+ async function instantiate(options) {
1312
+ if (!supportsJspi())
1313
+ throw new Error("fx WebAssembly requires JSPI (Chrome or Edge 137+)");
1314
+ const runtime = createRuntime({ fetch: globalThis.fetch.bind(globalThis), ...options });
1315
+ const module2 = await loadModule(options.wasm);
1316
+ const instance = await WebAssembly.instantiate(module2, runtime.imports);
1317
+ runtime.setInstance(instance);
1318
+ const start = WebAssembly.promising(instance.exports._start);
1319
+ start().then(() => {
1320
+ runtime.setInstance(null);
1321
+ try {
1322
+ runtime.finishOutput();
1323
+ runtime.markExited(0);
1324
+ } catch (error) {
1325
+ runtime.abort(error);
1326
+ }
1327
+ }, (error) => {
1328
+ runtime.setInstance(null);
1329
+ if (options.args?.[0] === "acp" && !String(error).includes("proc_exit"))
1330
+ runtime.abort(error);
1331
+ else {
1332
+ if (!String(error).includes("proc_exit"))
1333
+ console.error(error);
1334
+ runtime.markExited(runtime.aborted ? 130 : 1);
1335
+ }
1336
+ });
1337
+ return runtime;
1338
+ }
1339
+ async function createFxTerminal(options) {
1340
+ if (!options?.terminal)
1341
+ throw new TypeError("terminal is required");
1342
+ const emit = (type, detail = {}) => {
1343
+ try {
1344
+ options.onEvent?.({ type, timestamp: performance.now(), ...detail });
1345
+ } catch {}
1346
+ };
1347
+ let resolveInteractive;
1348
+ let rejectInteractive;
1349
+ let interactiveScheduled = false;
1350
+ const interactive = new Promise((resolve, reject) => {
1351
+ resolveInteractive = resolve;
1352
+ rejectInteractive = reject;
1353
+ });
1354
+ const stdout = (bytes) => options.terminal.write(bytes);
1355
+ const onTerminalPoll = () => {
1356
+ if (interactiveScheduled)
1357
+ return;
1358
+ interactiveScheduled = true;
1359
+ queueMicrotask(async () => {
1360
+ try {
1361
+ await options.terminal.drain?.();
1362
+ resolveInteractive();
1363
+ } catch (error) {
1364
+ rejectInteractive(error);
1365
+ }
1366
+ });
1367
+ };
1368
+ emit("runtime.start", { surface: "terminal" });
1369
+ const runtime = await instantiate({ ...options, emit, stdout, onTerminalPoll });
1370
+ runtime.exited.then((code) => {
1371
+ if (!interactiveScheduled)
1372
+ rejectInteractive(new Error(`fx terminal exited with code ${code} before becoming interactive`));
1373
+ });
1374
+ emit("runtime.ready", { surface: "terminal" });
1375
+ const interruptKey = options.interruptKey ?? "\x03";
1376
+ const forwardData = (data) => {
1377
+ if (interruptKey && data.includes(interruptKey))
1378
+ runtime.abortHostEffects();
1379
+ runtime.write(data);
1380
+ };
1381
+ const unsubscribeData = options.terminal.onData(forwardData);
1382
+ const unsubscribeKeyData = options.terminal.onKeyData?.(forwardData) ?? (() => {});
1383
+ const signalResize = () => {
1384
+ emit("terminal.resize", { cols: options.terminal.cols, rows: options.terminal.rows });
1385
+ runtime.wake();
1386
+ };
1387
+ const unsubscribeResize = options.terminal.onResize(signalResize);
1388
+ let subscriptionsReleased = false;
1389
+ const releaseSubscriptions = () => {
1390
+ if (subscriptionsReleased)
1391
+ return;
1392
+ subscriptionsReleased = true;
1393
+ try {
1394
+ unsubscribeData?.();
1395
+ } catch (error) {
1396
+ emit("terminal.cleanup_error", { source: "data", error });
1397
+ }
1398
+ try {
1399
+ unsubscribeKeyData?.();
1400
+ } catch (error) {
1401
+ emit("terminal.cleanup_error", { source: "key_data", error });
1402
+ }
1403
+ try {
1404
+ unsubscribeResize?.();
1405
+ } catch (error) {
1406
+ emit("terminal.cleanup_error", { source: "resize", error });
1407
+ }
1408
+ };
1409
+ runtime.exited.then((code) => {
1410
+ releaseSubscriptions();
1411
+ emit("runtime.exit", { surface: "terminal", code });
1412
+ });
1413
+ return {
1414
+ interactive,
1415
+ exited: runtime.exited,
1416
+ write(data) {
1417
+ if (interruptKey && typeof data === "string" && data.includes(interruptKey))
1418
+ runtime.abortHostEffects();
1419
+ runtime.write(data);
1420
+ },
1421
+ resize: signalResize,
1422
+ abort() {
1423
+ releaseSubscriptions();
1424
+ runtime.abort();
1425
+ }
1426
+ };
1427
+ }
1428
+ function normalizePromptInput(input) {
1429
+ if (typeof input === "string")
1430
+ return [{ type: "text", text: input }];
1431
+ if (!Array.isArray(input))
1432
+ throw new TypeError("prompt input must be a string or an array of prompt blocks");
1433
+ return input.map((block, index) => {
1434
+ if (!block || typeof block !== "object")
1435
+ throw new TypeError(`prompt block ${index} must be an object`);
1436
+ if (block.type === "image")
1437
+ throw new TypeError("image prompt blocks are unsupported");
1438
+ if (block.type === "text") {
1439
+ if (typeof block.text !== "string")
1440
+ throw new TypeError(`text prompt block ${index} requires text`);
1441
+ return { type: "text", text: block.text };
1442
+ }
1443
+ if (block.type === "resource") {
1444
+ const resource = block.resource || block;
1445
+ if (typeof resource.uri !== "string")
1446
+ throw new TypeError(`resource prompt block ${index} requires uri`);
1447
+ if (resource.text !== undefined && typeof resource.text !== "string")
1448
+ throw new TypeError(`resource prompt block ${index} text must be a string`);
1449
+ return { type: "resource", resource: { uri: resource.uri, ...resource.text === undefined ? {} : { text: resource.text } } };
1450
+ }
1451
+ throw new TypeError(`unsupported prompt block type: ${String(block.type)}`);
1452
+ });
1453
+ }
1454
+ function normalizeHostTools(value) {
1455
+ if (value === undefined)
1456
+ return { descriptors: [], executors: new Map };
1457
+ if (!Array.isArray(value))
1458
+ throw new TypeError("tools must be an array");
1459
+ if (value.length > 64)
1460
+ throw new RangeError("tools cannot contain more than 64 entries");
1461
+ const descriptors = [];
1462
+ const executors = new Map;
1463
+ for (const [index, tool] of value.entries()) {
1464
+ if (!tool || typeof tool !== "object")
1465
+ throw new TypeError(`tool ${index} must be an object`);
1466
+ const { name, description, inputSchema, execute } = tool;
1467
+ if (typeof name !== "string" || !/^[A-Za-z0-9_-]{1,64}$/.test(name)) {
1468
+ throw new TypeError(`tool ${index} has an invalid name`);
1469
+ }
1470
+ if (executors.has(name))
1471
+ throw new TypeError(`duplicate tool name: ${name}`);
1472
+ if (typeof description !== "string")
1473
+ throw new TypeError(`tool ${name} requires a description`);
1474
+ if (typeof execute !== "function")
1475
+ throw new TypeError(`tool ${name} requires execute()`);
1476
+ if (!inputSchema || typeof inputSchema !== "object" || Array.isArray(inputSchema)) {
1477
+ throw new TypeError(`tool ${name} requires an object inputSchema`);
1478
+ }
1479
+ let schema;
1480
+ try {
1481
+ schema = JSON.parse(JSON.stringify(inputSchema));
1482
+ } catch {
1483
+ throw new TypeError(`tool ${name} inputSchema must be JSON-serializable`);
1484
+ }
1485
+ descriptors.push({ name, description, inputSchema: schema });
1486
+ executors.set(name, execute);
1487
+ }
1488
+ return { descriptors, executors };
1489
+ }
1490
+ function normalizeInstructions(value) {
1491
+ let instructions;
1492
+ if (value === undefined)
1493
+ instructions = "";
1494
+ else if (typeof value === "string")
1495
+ instructions = value;
1496
+ if (Array.isArray(value) && value.every((entry) => typeof entry === "string")) {
1497
+ instructions = value.filter(Boolean).join(`
1498
+
1499
+ `);
1500
+ }
1501
+ if (instructions === undefined) {
1502
+ throw new TypeError("instructions must be a string or an array of strings");
1503
+ }
1504
+ if (encoder.encode(instructions).length > maxInstructionsBytes) {
1505
+ throw new RangeError(`instructions exceed the ${maxInstructionsBytes} byte libfx limit`);
1506
+ }
1507
+ return instructions;
1508
+ }
1509
+ function hostToolContent(value) {
1510
+ if (value?.type === "libfx.tool-result") {
1511
+ if (typeof value.text !== "string" || !Array.isArray(value.images) || value.images.length > 8) {
1512
+ throw new TypeError("invalid typed tool result");
1513
+ }
1514
+ let imageBytes = 0;
1515
+ const images = value.images.map((image) => {
1516
+ if (image?.type !== "image" || typeof image.data !== "string" || typeof image.mimeType !== "string" || image.mimeType.length > 128 || image.data.length > 5 * 1024 * 1024) {
1517
+ throw new TypeError("invalid tool image");
1518
+ }
1519
+ imageBytes += image.data.length;
1520
+ if (imageBytes > 8 * 1024 * 1024)
1521
+ throw new RangeError("tool images exceed the result limit");
1522
+ return { type: "image", data: image.data, mimeType: image.mimeType };
1523
+ });
1524
+ const content = JSON.stringify({ text: value.text, images });
1525
+ if (new TextEncoder().encode(content).length > 8 * 1024 * 1024)
1526
+ throw new RangeError("typed tool result exceeds the result limit");
1527
+ return { content, rich: true, isError: value.isError === true };
1528
+ }
1529
+ if (typeof value === "string")
1530
+ return { content: value, rich: false };
1531
+ if (value === undefined)
1532
+ return { content: "null", rich: false };
1533
+ const encoded = JSON.stringify(value);
1534
+ return { content: encoded === undefined ? "null" : encoded, rich: false };
1535
+ }
1536
+ function checkpointBytes(value) {
1537
+ if (value === undefined)
1538
+ return null;
1539
+ if (value instanceof Uint8Array)
1540
+ return value.slice();
1541
+ if (value instanceof ArrayBuffer)
1542
+ return new Uint8Array(value.slice(0));
1543
+ if (ArrayBuffer.isView(value)) {
1544
+ return new Uint8Array(value.buffer.slice(value.byteOffset, value.byteOffset + value.byteLength));
1545
+ }
1546
+ throw new TypeError("checkpoint must be an ArrayBuffer or typed array");
1547
+ }
1548
+ function bytesToBase64(value) {
1549
+ let binary = "";
1550
+ for (let offset = 0;offset < value.length; offset += 32768) {
1551
+ binary += String.fromCharCode(...value.subarray(offset, offset + 32768));
1552
+ }
1553
+ return btoa(binary);
1554
+ }
1555
+ function base64ToBytes(value) {
1556
+ const binary = atob(value);
1557
+ const bytes = new Uint8Array(binary.length);
1558
+ for (let index = 0;index < binary.length; index++)
1559
+ bytes[index] = binary.charCodeAt(index);
1560
+ return bytes;
1561
+ }
1562
+ async function createFxAgent(options = {}) {
1563
+ options = normalizeAgentOptions(options);
1564
+ const hostTools = normalizeHostTools(options.tools);
1565
+ const instructions = normalizeInstructions(options.instructions);
1566
+ const initialCheckpoint = checkpointBytes(options.checkpoint);
1567
+ const pending = new Map;
1568
+ let nextId = 1;
1569
+ let sessionId = null;
1570
+ let activeTurn = null;
1571
+ let closing = false;
1572
+ const isCurrentTurn = (turn) => turn && activeTurn === turn && !turn.cancelled && !closing;
1573
+ const emit = (type, detail = {}) => {
1574
+ try {
1575
+ options.onEvent?.({ type, timestamp: performance.now(), ...detail });
1576
+ } catch {}
1577
+ };
1578
+ const hostFetch = options.fetch ?? globalThis.fetch?.bind(globalThis);
1579
+ const transportFetch = async (input, init = {}) => {
1580
+ const method = String(init.method ?? input?.method ?? "GET").toUpperCase();
1581
+ let endpoint = String(input?.url ?? input);
1582
+ try {
1583
+ const url = new URL(endpoint);
1584
+ endpoint = `${url.origin}${url.pathname}`;
1585
+ } catch {}
1586
+ for (let attemptIndex = 0;attemptIndex < 2; attemptIndex++) {
1587
+ const startedAt = performance.now();
1588
+ const attempt = activeTurn ? ++activeTurn.transportAttempts : attemptIndex + 1;
1589
+ emit("transport.start", { attempt, method, endpoint, model: options.model });
1590
+ try {
1591
+ if (activeTurn?.cancelled) {
1592
+ runtime.abortHostEffects();
1593
+ throw new DOMException("Aborted", "AbortError");
1594
+ }
1595
+ if (!hostFetch)
1596
+ throw new TypeError("fetch is unavailable");
1597
+ const response = await hostFetch(input, init);
1598
+ const headers = response.headers;
1599
+ emit("transport.response", {
1600
+ attempt,
1601
+ status: response.status,
1602
+ elapsedMs: performance.now() - startedAt,
1603
+ requestId: headers.get("x-vercel-id"),
1604
+ generationId: headers.get("x-generation-id"),
1605
+ model: headers.get("x-model-id") ?? options.model,
1606
+ provider: headers.get("x-vercel-ai-gateway-provider") ?? headers.get("x-ai-gateway-provider")
1607
+ });
1608
+ return response;
1609
+ } catch (error) {
1610
+ const errorName = error instanceof Error ? error.name : "Error";
1611
+ const elapsedMs = performance.now() - startedAt;
1612
+ emit("transport.error", { attempt, elapsedMs, error: errorName });
1613
+ if (init.signal?.aborted)
1614
+ throw new DOMException("Aborted", "AbortError");
1615
+ if (attemptIndex === 1)
1616
+ throw error;
1617
+ emit("transport.retry", {
1618
+ attempt,
1619
+ nextAttempt: attempt + 1,
1620
+ elapsedMs,
1621
+ error: errorName
1622
+ });
1623
+ if (init.signal?.aborted)
1624
+ throw new DOMException("Aborted", "AbortError");
1625
+ }
1626
+ }
1627
+ throw new Error("transport retry exhausted");
1628
+ };
1629
+ const executeHostTool = async (name, input, requestedSessionId) => {
1630
+ const execute = hostTools.executors.get(name);
1631
+ const turn = requestedSessionId === undefined || requestedSessionId === sessionId ? activeTurn : null;
1632
+ if (!isCurrentTurn(turn))
1633
+ return { content: "", isError: true, cancelled: true };
1634
+ const controller = new AbortController;
1635
+ turn.toolControllers.add(controller);
1636
+ let onAbort;
1637
+ const aborted = new Promise((resolve) => {
1638
+ onAbort = () => resolve();
1639
+ });
1640
+ controller.signal.addEventListener("abort", onAbort, { once: true });
1641
+ let content = "";
1642
+ let rich = false;
1643
+ let isError = false;
1644
+ try {
1645
+ if (!execute)
1646
+ throw new Error(`unknown host tool: ${String(name)}`);
1647
+ const execution = Promise.resolve().then(() => {
1648
+ if (controller.signal.aborted || !isCurrentTurn(turn))
1649
+ return;
1650
+ return execute(input, { signal: controller.signal });
1651
+ });
1652
+ const value = await Promise.race([execution, aborted]);
1653
+ if (!controller.signal.aborted) {
1654
+ const normalized = hostToolContent(value);
1655
+ content = normalized.content;
1656
+ rich = normalized.rich;
1657
+ isError = normalized.isError === true;
1658
+ }
1659
+ } catch (error) {
1660
+ isError = true;
1661
+ if (error?.toolResult?.type === "libfx.tool-result") {
1662
+ try {
1663
+ const normalized = hostToolContent(error.toolResult);
1664
+ content = normalized.content;
1665
+ rich = normalized.rich;
1666
+ } catch {
1667
+ content = error instanceof Error ? error.message : String(error);
1668
+ }
1669
+ } else {
1670
+ content = error instanceof Error ? error.message : String(error);
1671
+ }
1672
+ } finally {
1673
+ controller.signal.removeEventListener("abort", onAbort);
1674
+ turn.toolControllers.delete(controller);
1675
+ }
1676
+ return { content, isError, rich, cancelled: controller.signal.aborted || !isCurrentTurn(turn) };
1677
+ };
1678
+ emit("runtime.start");
1679
+ const runtimeOptions = {
1680
+ ...options,
1681
+ fetch: transportFetch,
1682
+ args: ["acp"],
1683
+ env: agentEnvironment(options),
1684
+ hostToolExecutor: executeHostTool
1685
+ };
1686
+ const runtime = options.runtimeFactory ? await options.runtimeFactory(runtimeOptions) : await instantiate(runtimeOptions);
1687
+ emit("runtime.ready");
1688
+ const send = (message) => {
1689
+ if (closing)
1690
+ throw new Error("fx agent is closing");
1691
+ emit("acp.send", { message });
1692
+ runtime.write(`${JSON.stringify(message)}
1693
+ `);
1694
+ };
1695
+ const request = (method, params = {}) => new Promise((resolve, reject) => {
1696
+ const id = nextId++;
1697
+ pending.set(id, { resolve, reject });
1698
+ try {
1699
+ send({ jsonrpc: "2.0", id, method, params });
1700
+ } catch (error) {
1701
+ pending.delete(id);
1702
+ reject(error);
1703
+ }
1704
+ });
1705
+ runtime.exited.then((code) => {
1706
+ emit("runtime.exit", { code });
1707
+ closing = true;
1708
+ const error = runtime.error ?? new Error(`fx-core exited with code ${code} before completing the ACP request`);
1709
+ for (const waiter of pending.values())
1710
+ waiter.reject(error);
1711
+ pending.clear();
1712
+ });
1713
+ runtime.setLineHandler((message, size) => {
1714
+ emit("acp.receive", { message });
1715
+ if (message.method === "session/update") {
1716
+ if (message.params.sessionId === sessionId)
1717
+ return activeTurn?.push(message.params.update, size);
1718
+ return;
1719
+ }
1720
+ handleControlMessage(message).catch((error) => runtime.abort(error));
1721
+ });
1722
+ async function handleControlMessage(message) {
1723
+ if (message.method === "session/request_permission") {
1724
+ const turn = activeTurn;
1725
+ if (!isCurrentTurn(turn))
1726
+ return;
1727
+ emit("permission.request", { request: message.params });
1728
+ if (!isCurrentTurn(turn))
1729
+ return;
1730
+ let optionId = null;
1731
+ try {
1732
+ optionId = await options.onPermission?.(message.params);
1733
+ } catch {}
1734
+ if (!isCurrentTurn(turn))
1735
+ return;
1736
+ emit("permission.resolve", { optionId });
1737
+ if (!isCurrentTurn(turn))
1738
+ return;
1739
+ send({ jsonrpc: "2.0", id: message.id, result: optionId ? { outcome: { outcome: "selected", optionId } } : { outcome: { outcome: "cancelled" } } });
1740
+ return;
1741
+ }
1742
+ if (message.method === "libfx/tool_call") {
1743
+ const { content, isError, rich, cancelled } = await executeHostTool(message.params?.name, message.params?.input, message.params?.sessionId);
1744
+ if (cancelled || closing)
1745
+ return;
1746
+ const response = { jsonrpc: "2.0", id: message.id, result: { content, isError, ...rich ? { contentType: "rich" } : {} } };
1747
+ if (encoder.encode(JSON.stringify(response)).length + 1 > 8 * 1024 * 1024) {
1748
+ response.result = { content: "Host tool result exceeded the response frame limit", isError: true };
1749
+ }
1750
+ send(response);
1751
+ return;
1752
+ }
1753
+ const waiter = pending.get(message.id);
1754
+ if (!waiter)
1755
+ return;
1756
+ pending.delete(message.id);
1757
+ if (message.error)
1758
+ waiter.reject(new Error(message.error.message));
1759
+ else
1760
+ waiter.resolve(message.result);
1761
+ }
1762
+ try {
1763
+ await request("initialize", {
1764
+ protocolVersion: 1,
1765
+ clientCapabilities: {
1766
+ ...hostTools.descriptors.length || instructions ? { libfx: { tools: hostTools.descriptors, instructions } } : {}
1767
+ }
1768
+ });
1769
+ const sessionResult = await request("libfx/new");
1770
+ sessionId = sessionResult.sessionId;
1771
+ if (initialCheckpoint) {
1772
+ await request("libfx/restore", {
1773
+ sessionId,
1774
+ checkpoint: bytesToBase64(initialCheckpoint)
1775
+ });
1776
+ }
1777
+ } catch (error) {
1778
+ closing = true;
1779
+ try {
1780
+ runtime.abortHostEffects();
1781
+ } catch {}
1782
+ try {
1783
+ runtime.closeStdin();
1784
+ } catch {}
1785
+ try {
1786
+ await runtime.exited;
1787
+ } catch {}
1788
+ throw error;
1789
+ }
1790
+ const agent = {
1791
+ prompt(input, promptOptions = {}) {
1792
+ if (closing)
1793
+ throw new Error("fx agent is closed");
1794
+ if (activeTurn)
1795
+ throw new Error("a prompt is already in progress for this session");
1796
+ return normalizeTurn(startTurn(input, promptOptions));
1797
+ },
1798
+ async checkpoint() {
1799
+ if (closing)
1800
+ throw new Error("fx agent is closed");
1801
+ if (activeTurn)
1802
+ throw new Error("cannot checkpoint while a prompt is active");
1803
+ const response = await request("libfx/checkpoint", { sessionId });
1804
+ if (typeof response?.checkpoint !== "string")
1805
+ throw new Error("fx returned an invalid checkpoint");
1806
+ return base64ToBytes(response.checkpoint);
1807
+ },
1808
+ async close() {
1809
+ if (closing) {
1810
+ await runtime.exited;
1811
+ return;
1812
+ }
1813
+ const turn = activeTurn;
1814
+ turn?.cancel();
1815
+ if (turn)
1816
+ await turn.result.catch(() => {});
1817
+ closing = true;
1818
+ runtime.closeStdin();
1819
+ await runtime.exited;
1820
+ }
1821
+ };
1822
+ return agent;
1823
+ function normalizeTurn(rawTurn) {
1824
+ const toolNames = new Map;
1825
+ const started = new Set;
1826
+ const eventFor = (update) => {
1827
+ if (update.sessionUpdate === "agent_message_chunk") {
1828
+ const delta = update.content?.text;
1829
+ if (!delta || delta.startsWith("[context]"))
1830
+ return null;
1831
+ return { type: "text_delta", delta };
1832
+ }
1833
+ if (update.sessionUpdate === "agent_thought_chunk") {
1834
+ const delta = update.content?.text;
1835
+ return delta ? { type: "reasoning_delta", delta } : null;
1836
+ }
1837
+ if (update.sessionUpdate === "tool_call") {
1838
+ toolNames.set(update.toolCallId, update.name || update.toolName || update.title || "tool");
1839
+ if (started.has(update.toolCallId))
1840
+ return null;
1841
+ started.add(update.toolCallId);
1842
+ return {
1843
+ type: "tool_start",
1844
+ id: update.toolCallId,
1845
+ name: toolNames.get(update.toolCallId)
1846
+ };
1847
+ }
1848
+ if (update.sessionUpdate === "tool_call_update" && (update.status === "completed" || update.status === "failed")) {
1849
+ const content = update.content?.find((entry) => entry.content?.type === "text")?.content?.text;
1850
+ return {
1851
+ type: "tool_end",
1852
+ id: update.toolCallId,
1853
+ name: toolNames.get(update.toolCallId) || "tool",
1854
+ ...content === undefined ? {} : { content },
1855
+ isError: update.status === "failed"
1856
+ };
1857
+ }
1858
+ return null;
1859
+ };
1860
+ const result = rawTurn.result.then((value) => ({
1861
+ stopReason: value.stopReason,
1862
+ usage: normalizeTurnUsage(value.usage)
1863
+ }));
1864
+ result.catch(() => {});
1865
+ return {
1866
+ cancel() {
1867
+ rawTurn.cancel();
1868
+ },
1869
+ [Symbol.asyncIterator]() {
1870
+ const iterator = async function* () {
1871
+ for await (const update of rawTurn) {
1872
+ const event = eventFor(update);
1873
+ if (event)
1874
+ yield event;
1875
+ }
1876
+ }();
1877
+ return {
1878
+ next(value) {
1879
+ return iterator.next(value);
1880
+ },
1881
+ return(value) {
1882
+ rawTurn.cancel();
1883
+ return iterator.return(value);
1884
+ },
1885
+ throw(error) {
1886
+ rawTurn.cancel();
1887
+ return iterator.throw(error);
1888
+ },
1889
+ [Symbol.asyncIterator]() {
1890
+ return this;
1891
+ }
1892
+ };
1893
+ },
1894
+ result
1895
+ };
1896
+ }
1897
+ function normalizeTurnUsage(usage) {
1898
+ const result = {};
1899
+ if (Number.isSafeInteger(usage?.inputTokens))
1900
+ result.inputTokens = usage.inputTokens;
1901
+ if (Number.isSafeInteger(usage?.outputTokens))
1902
+ result.outputTokens = usage.outputTokens;
1903
+ if (Number.isSafeInteger(usage?.cacheReadTokens))
1904
+ result.cacheReadTokens = usage.cacheReadTokens;
1905
+ if (Number.isSafeInteger(usage?.cacheWriteTokens))
1906
+ result.cacheWriteTokens = usage.cacheWriteTokens;
1907
+ if (Number.isSafeInteger(usage?.reasoningTokens))
1908
+ result.reasoningTokens = usage.reasoningTokens;
1909
+ return result;
1910
+ }
1911
+ function startTurn(input, promptOptions) {
1912
+ const prompt = normalizePromptInput(input);
1913
+ const signal = promptOptions.signal;
1914
+ if (signal !== undefined && (typeof signal?.addEventListener !== "function" || typeof signal?.removeEventListener !== "function"))
1915
+ throw new TypeError("prompt signal must be an AbortSignal");
1916
+ const queue = [];
1917
+ const waiters = [];
1918
+ let queuedBytes = 0;
1919
+ let resumeOutput;
1920
+ let iteratorTaken = false;
1921
+ let terminalError;
1922
+ let reportedPressure = false;
1923
+ let discardedBytes = 0;
1924
+ const toolControllers = new Set;
1925
+ let finished = false;
1926
+ let cancelled = false;
1927
+ const turn = {
1928
+ push(update, size = encoder.encode(JSON.stringify(update)).length) {
1929
+ if (cancelled || finished) {
1930
+ discardedBytes += size;
1931
+ return;
1932
+ }
1933
+ if (size > maxCoreMessageBytes)
1934
+ throw new RangeError("core output message exceeds 64 MiB");
1935
+ if (queue.length && (queue.length >= maxUnreadEvents || size > maxUnreadEventBytes - queuedBytes)) {
1936
+ const capacity = new Promise((resolveCapacity) => {
1937
+ resumeOutput = resolveCapacity;
1938
+ });
1939
+ if (!reportedPressure) {
1940
+ reportedPressure = true;
1941
+ emit("output.backpressure", { bufferedBytes: queuedBytes, bufferedEvents: queue.length });
1942
+ }
1943
+ return capacity.then(() => turn.push(update, size));
1944
+ }
1945
+ const waiter = waiters.shift();
1946
+ if (waiter)
1947
+ waiter.resolve({ value: update, done: false });
1948
+ else {
1949
+ queue.push({ update, size });
1950
+ queuedBytes += size;
1951
+ }
1952
+ },
1953
+ toolControllers,
1954
+ transportAttempts: 0,
1955
+ get cancelled() {
1956
+ return cancelled;
1957
+ },
1958
+ cancel() {
1959
+ if (finished || cancelled)
1960
+ return;
1961
+ cancelled = true;
1962
+ resumeOutput?.();
1963
+ resumeOutput = null;
1964
+ send({ jsonrpc: "2.0", method: "session/cancel", params: { sessionId } });
1965
+ for (const controller of toolControllers)
1966
+ controller.abort();
1967
+ runtime.abortHostEffects();
1968
+ },
1969
+ [Symbol.asyncIterator]() {
1970
+ if (iteratorTaken)
1971
+ throw new Error("a turn has only one event consumer");
1972
+ iteratorTaken = true;
1973
+ return {
1974
+ next() {
1975
+ if (queue.length) {
1976
+ const { update, size } = queue.shift();
1977
+ queuedBytes -= size;
1978
+ resumeOutput?.();
1979
+ resumeOutput = null;
1980
+ return Promise.resolve({ value: update, done: false });
1981
+ }
1982
+ if (terminalError)
1983
+ return Promise.reject(terminalError);
1984
+ if (finished)
1985
+ return Promise.resolve({ done: true });
1986
+ return new Promise((resolve, reject) => waiters.push({ resolve, reject }));
1987
+ },
1988
+ return() {
1989
+ turn.cancel();
1990
+ return Promise.resolve({ done: true });
1991
+ }
1992
+ };
1993
+ }
1994
+ };
1995
+ if (signal?.aborted) {
1996
+ finished = true;
1997
+ turn.result = Promise.resolve({ stopReason: "cancelled" });
1998
+ return turn;
1999
+ }
2000
+ activeTurn = turn;
2001
+ const abort = () => turn.cancel();
2002
+ signal?.addEventListener("abort", abort, { once: true });
2003
+ turn.result = request("session/prompt", { sessionId, prompt }).then((response) => ({ stopReason: cancelled ? "cancelled" : response.stopReason, usage: response.usage })).catch((error) => {
2004
+ if (error.message === "Cancelled")
2005
+ return { stopReason: "cancelled" };
2006
+ terminalError = error;
2007
+ throw error;
2008
+ }).finally(() => {
2009
+ finished = true;
2010
+ resumeOutput?.();
2011
+ resumeOutput = null;
2012
+ signal?.removeEventListener("abort", abort);
2013
+ if (activeTurn === turn)
2014
+ activeTurn = null;
2015
+ toolControllers.clear();
2016
+ if (discardedBytes)
2017
+ emit("output.discarded", { reason: "cancelled", bytes: discardedBytes });
2018
+ for (const waiter of waiters.splice(0)) {
2019
+ if (terminalError)
2020
+ waiter.reject(terminalError);
2021
+ else
2022
+ waiter.resolve({ done: true });
2023
+ }
2024
+ });
2025
+ if (signal?.aborted)
2026
+ turn.cancel();
2027
+ turn.result.catch(() => {});
2028
+ return turn;
2029
+ }
2030
+ }
2031
+
2032
+ // sdk/node.js
2033
+ var libfxApiVersion = 2;
2034
+ var nativeCoreApiVersion = 3;
2035
+ var fetchOperationStale = 0;
2036
+ var fetchOperationApplied = 1;
2037
+ var fetchOperationBackpressure = 2;
2038
+ var nodeRequire = import_node_module.createRequire(__libfxModuleUrl);
2039
+ var defaultCoreWasm = new URL("./fx-core.wasm", __libfxModuleUrl);
2040
+ var defaultTermWasm = new URL("./fx-term.wasm", __libfxModuleUrl);
2041
+ var nativeBackendPromise;
2042
+ var wasmFilePromises = new Map;
2043
+ var backendReasonCodes = {
2044
+ unsupportedPlatform: "LIBFX_UNSUPPORTED_PLATFORM",
2045
+ missingArtifact: "LIBFX_NATIVE_ARTIFACT_MISSING",
2046
+ nativeLoad: "LIBFX_NATIVE_LOAD_FAILED",
2047
+ nativeApi: "LIBFX_NATIVE_API_MISMATCH",
2048
+ missingSurface: "LIBFX_NATIVE_SURFACE_MISSING",
2049
+ disabledNative: "LIBFX_NATIVE_DISABLED",
2050
+ jspiUnavailable: "LIBFX_JSPI_UNAVAILABLE",
2051
+ wasmLoad: "LIBFX_WASM_LOAD_FAILED"
2052
+ };
2053
+ function jspiFallbackError(surface, nativeError) {
2054
+ const nativeDetail = nativeError ? ` Native loading failed: ${nativeError.message}.` : " No compatible native addon was found.";
2055
+ const error = new Error(`libfx could not start the ${surface} backend.${nativeDetail} ` + "The WebAssembly fallback requires JavaScript Promise Integration (JSPI). " + "Run Node with --experimental-wasm-jspi or install a libfx package containing a compatible native addon.");
2056
+ error.code = "LIBFX_JSPI_REQUIRED";
2057
+ error.cause = nativeError;
2058
+ return error;
2059
+ }
2060
+ async function loadNativeCandidate(candidate) {
2061
+ if (candidate == null)
2062
+ return null;
2063
+ if (candidate instanceof URL) {
2064
+ if (candidate.protocol === "file:" && candidate.pathname.endsWith(".node")) {
2065
+ return Reflect.apply(nodeRequire, undefined, [import_node_url.fileURLToPath(candidate)]);
2066
+ }
2067
+ const imported = await import(candidate.href);
2068
+ return imported.default ?? imported;
2069
+ }
2070
+ if (typeof candidate === "object")
2071
+ return candidate.default ?? candidate;
2072
+ if (typeof candidate !== "string") {
2073
+ throw new TypeError("nativeAddon must be a module, path, URL, false, or undefined");
2074
+ }
2075
+ if (candidate.endsWith(".node")) {
2076
+ return Reflect.apply(nodeRequire, undefined, [import_node_path.isAbsolute(candidate) ? candidate : import_node_path.resolve(candidate)]);
2077
+ }
2078
+ const imported = await (candidate.startsWith("file:") ? import(candidate) : import(import_node_url.pathToFileURL(candidate).href));
2079
+ return imported.default ?? imported;
2080
+ }
2081
+ function defaultNativeCandidate() {
2082
+ if (process.platform === "linux" && process.arch === "x64") {
2083
+ return new URL("./libfx.linux-x64.node", __libfxModuleUrl);
2084
+ }
2085
+ if (process.platform === "linux" && process.arch === "arm64") {
2086
+ return new URL("./libfx.linux-arm64.node", __libfxModuleUrl);
2087
+ }
2088
+ if (process.platform === "darwin" && process.arch === "x64") {
2089
+ return new URL("./libfx.darwin-x64.node", __libfxModuleUrl);
2090
+ }
2091
+ if (process.platform === "darwin" && process.arch === "arm64") {
2092
+ return new URL("./libfx.darwin-arm64.node", __libfxModuleUrl);
2093
+ }
2094
+ return null;
2095
+ }
2096
+ function validateNativeBackend(backend) {
2097
+ if (!backend)
2098
+ return null;
2099
+ const hasLowLevelCore = typeof backend.createCore === "function";
2100
+ const expectedVersion = hasLowLevelCore ? nativeCoreApiVersion : libfxApiVersion;
2101
+ if ((hasLowLevelCore || backend.libfxApiVersion !== undefined) && backend.libfxApiVersion !== expectedVersion) {
2102
+ const actualVersion = backend.libfxApiVersion ?? "missing";
2103
+ throw new Error(`native addon API version ${actualVersion} is incompatible with expected API version ${expectedVersion}`);
2104
+ }
2105
+ if (typeof backend.createCore !== "function" && typeof backend.createFxTerminal !== "function") {
2106
+ throw new Error("native addon must export createCore() or createFxTerminal()");
2107
+ }
2108
+ return backend;
2109
+ }
2110
+ function missingArtifact(error) {
2111
+ return error?.code === "ENOENT" || error?.code === "MODULE_NOT_FOUND" || error?.code === "ERR_MODULE_NOT_FOUND";
2112
+ }
2113
+ function nativeCandidateFilePath(candidate) {
2114
+ if (candidate instanceof URL)
2115
+ return candidate.protocol === "file:" ? import_node_url.fileURLToPath(candidate) : null;
2116
+ if (typeof candidate !== "string")
2117
+ return null;
2118
+ if (candidate.startsWith("file:"))
2119
+ return import_node_url.fileURLToPath(new URL(candidate));
2120
+ return URL.canParse(candidate) ? null : import_node_path.resolve(candidate);
2121
+ }
2122
+ async function nativeArtifactMissing(candidate) {
2123
+ try {
2124
+ const path = nativeCandidateFilePath(candidate);
2125
+ if (path === null)
2126
+ return false;
2127
+ await import_promises.access(path);
2128
+ return false;
2129
+ } catch (error) {
2130
+ return missingArtifact(error);
2131
+ }
2132
+ }
2133
+ function validationFailure(error) {
2134
+ return error?.message?.startsWith("native addon API version ") ? "api" : "surface";
2135
+ }
2136
+ async function loadAndValidateNativeCandidate(candidate, artifactMissing = false) {
2137
+ let backend;
2138
+ try {
2139
+ backend = await loadNativeCandidate(candidate);
2140
+ } catch (error) {
2141
+ return { backend: null, error, failure: artifactMissing ? "missing" : "load" };
2142
+ }
2143
+ try {
2144
+ return { backend: validateNativeBackend(backend), error: null, failure: null };
2145
+ } catch (error) {
2146
+ return { backend: null, error, failure: validationFailure(error) };
2147
+ }
2148
+ }
2149
+ async function discoverNativeBackend() {
2150
+ const candidate = defaultNativeCandidate();
2151
+ if (!candidate) {
2152
+ return { backend: null, error: null, failure: "unsupported" };
2153
+ }
2154
+ try {
2155
+ await import_promises.access(import_node_url.fileURLToPath(candidate));
2156
+ } catch (error) {
2157
+ if (missingArtifact(error)) {
2158
+ return { backend: null, error: null, probeError: error, failure: "missing" };
2159
+ }
2160
+ return { backend: null, error, failure: "load" };
2161
+ }
2162
+ return loadAndValidateNativeCandidate(candidate);
2163
+ }
2164
+ async function resolveNativeBackend(nativeAddon) {
2165
+ if (nativeAddon === false)
2166
+ return { backend: null, error: null, failure: "disabled" };
2167
+ if (nativeAddon !== undefined) {
2168
+ return loadAndValidateNativeCandidate(nativeAddon, await nativeArtifactMissing(nativeAddon));
2169
+ }
2170
+ nativeBackendPromise ??= discoverNativeBackend();
2171
+ return nativeBackendPromise;
2172
+ }
2173
+ function wasmInput(input) {
2174
+ const path = wasmFilePath(input);
2175
+ if (path === null) {
2176
+ if (input instanceof URL)
2177
+ return input.href;
2178
+ return input;
2179
+ }
2180
+ const cached = wasmFilePromises.get(path);
2181
+ if (cached)
2182
+ return cached;
2183
+ const pendingRead = import_promises.readFile(path);
2184
+ let pending;
2185
+ pending = withModuleFailure(pendingRead, () => {
2186
+ if (wasmFilePromises.get(path) === pending)
2187
+ wasmFilePromises.delete(path);
2188
+ });
2189
+ wasmFilePromises.set(path, pending);
2190
+ pendingRead.catch(() => {
2191
+ if (wasmFilePromises.get(path) === pending)
2192
+ wasmFilePromises.delete(path);
2193
+ });
2194
+ return pending;
2195
+ }
2196
+ function wasmFilePath(input) {
2197
+ if (input instanceof URL && input.protocol === "file:")
2198
+ return import_node_url.fileURLToPath(input);
2199
+ if (typeof input === "string" && !URL.canParse(input))
2200
+ return import_node_path.resolve(input);
2201
+ return null;
2202
+ }
2203
+ function reason(code, message, error) {
2204
+ const causeCode = error?.code;
2205
+ return {
2206
+ code,
2207
+ message,
2208
+ ...typeof causeCode === "string" || typeof causeCode === "number" ? { causeCode } : {}
2209
+ };
2210
+ }
2211
+ function nativeFailureReason(result) {
2212
+ const detailError = result.probeError ?? result.error;
2213
+ switch (result.failure) {
2214
+ case "unsupported":
2215
+ return reason(backendReasonCodes.unsupportedPlatform, `native addon is not available for ${process.platform}-${process.arch}`);
2216
+ case "missing":
2217
+ return reason(backendReasonCodes.missingArtifact, `native addon artifact was not found${detailError?.message ? `: ${detailError.message}` : ""}`, detailError);
2218
+ case "load":
2219
+ return reason(backendReasonCodes.nativeLoad, `native addon failed to load${result.error?.message ? `: ${result.error.message}` : ""}`, result.error);
2220
+ case "api":
2221
+ return reason(backendReasonCodes.nativeApi, result.error.message, result.error);
2222
+ case "surface":
2223
+ return reason(backendReasonCodes.missingSurface, result.error.message, result.error);
2224
+ case "disabled":
2225
+ return reason(backendReasonCodes.disabledNative, "native addon loading is disabled");
2226
+ default:
2227
+ return reason(backendReasonCodes.nativeLoad, "native addon is unavailable", result.error);
2228
+ }
2229
+ }
2230
+ function validateBackendInfoOptions(value) {
2231
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
2232
+ throw new TypeError("getBackendInfo() options must be an object");
2233
+ }
2234
+ const options = { ...value };
2235
+ for (const key of Object.keys(options)) {
2236
+ if (!new Set(["surface", "backend", "nativeAddon", "wasm"]).has(key)) {
2237
+ throw new TypeError(`getBackendInfo() does not accept ${key}`);
2238
+ }
2239
+ }
2240
+ const surface = options.surface ?? "agent";
2241
+ if (!new Set(["agent", "terminal"]).has(surface)) {
2242
+ throw new TypeError('surface must be "agent" or "terminal"');
2243
+ }
2244
+ const backend = options.backend ?? "auto";
2245
+ if (!new Set(["auto", "native", "wasm"]).has(backend)) {
2246
+ throw new TypeError('backend must be "auto", "native", or "wasm"');
2247
+ }
2248
+ if (Object.hasOwn(options, "nativeAddon") && options.nativeAddon !== undefined && options.nativeAddon !== false && typeof options.nativeAddon !== "string" && !(options.nativeAddon instanceof URL) && (typeof options.nativeAddon !== "object" || options.nativeAddon === null)) {
2249
+ throw new TypeError("nativeAddon must be a module, path, URL, false, or undefined");
2250
+ }
2251
+ const validWasm = options.wasm === undefined || typeof options.wasm === "string" || options.wasm instanceof URL || options.wasm instanceof Promise || options.wasm instanceof WebAssembly.Module || options.wasm instanceof Response || options.wasm instanceof ArrayBuffer || ArrayBuffer.isView(options.wasm);
2252
+ if (Object.hasOwn(options, "wasm") && !validWasm) {
2253
+ throw new TypeError("wasm must be a URL, Response, ArrayBuffer, typed array, or WebAssembly.Module");
2254
+ }
2255
+ return { ...options, surface, backend };
2256
+ }
2257
+ async function getBackendInfo(value = {}) {
2258
+ const { surface, backend, nativeAddon, wasm } = validateBackendInfoOptions(value);
2259
+ const attempts = [];
2260
+ if (backend !== "wasm") {
2261
+ const native = await resolveNativeBackend(nativeAddon);
2262
+ const nativeMethod = surface === "agent" ? "createCore" : "createFxTerminal";
2263
+ if (typeof native.backend?.[nativeMethod] === "function") {
2264
+ attempts.push({ backend: "native", available: true, reason: null });
2265
+ return { surface, backend: "native", attempts };
2266
+ }
2267
+ const failureReason = native.backend ? reason(backendReasonCodes.missingSurface, `native addon does not provide ${nativeMethod}()`) : nativeFailureReason(native);
2268
+ attempts.push({ backend: "native", available: false, reason: failureReason });
2269
+ if (backend === "native")
2270
+ return { surface, backend: "unavailable", attempts };
2271
+ }
2272
+ if (!supportsJspi()) {
2273
+ attempts.push({
2274
+ backend: "wasm-jspi",
2275
+ available: false,
2276
+ reason: reason(backendReasonCodes.jspiUnavailable, "WebAssembly backend requires JavaScript Promise Integration (JSPI)")
2277
+ });
2278
+ return { surface, backend: "unavailable", attempts };
2279
+ }
2280
+ const defaultWasm = surface === "agent" ? defaultCoreWasm : defaultTermWasm;
2281
+ const wasmSource = wasm ?? defaultWasm;
2282
+ try {
2283
+ await loadModule(wasmInput(wasmSource));
2284
+ attempts.push({ backend: "wasm-jspi", available: true, reason: null });
2285
+ return { surface, backend: "wasm-jspi", attempts };
2286
+ } catch (error) {
2287
+ attempts.push({
2288
+ backend: "wasm-jspi",
2289
+ available: false,
2290
+ reason: reason(backendReasonCodes.wasmLoad, `WebAssembly asset failed to load or compile: ${error?.message ?? String(error)}`, error)
2291
+ });
2292
+ return { surface, backend: "unavailable", attempts };
2293
+ }
2294
+ }
2295
+ function createNativeCoreRuntime(addon, options) {
2296
+ const { apiKey, model, gatewayChatUrl } = options;
2297
+ const core = addon.createCore({
2298
+ apiKey,
2299
+ home: options.home ?? import_node_os.homedir(),
2300
+ workspaceRoot: options.workspaceRoot ?? process.cwd(),
2301
+ ...model === undefined ? {} : { model },
2302
+ ...gatewayChatUrl === undefined ? {} : { gatewayChatUrl }
2303
+ });
2304
+ let readyFd;
2305
+ let readySocket;
2306
+ try {
2307
+ readyFd = addon.takeCoreReadyFd(core);
2308
+ readySocket = new import_node_net.Socket({ fd: readyFd, readable: true, writable: false });
2309
+ } catch (error) {
2310
+ if (readyFd !== undefined) {
2311
+ try {
2312
+ import_node_fs.closeSync(readyFd);
2313
+ } catch {}
2314
+ }
2315
+ addon.destroyCore(core);
2316
+ throw error;
2317
+ }
2318
+ const readyClosed = new Promise((resolve) => readySocket.once("close", resolve));
2319
+ let exitedResolve;
2320
+ let lineHandler = null;
2321
+ const output = new CoreOutput((message, size) => lineHandler(message, size));
2322
+ let draining = false;
2323
+ let outputError;
2324
+ let settled = false;
2325
+ let fetchState = null;
2326
+ const exited = new Promise((resolve) => {
2327
+ exitedResolve = resolve;
2328
+ });
2329
+ const abortHostEffects = () => {
2330
+ fetchState?.controller.abort();
2331
+ try {
2332
+ addon.abortCoreFetch(core);
2333
+ } catch {}
2334
+ };
2335
+ const finish = (code, error) => {
2336
+ if (settled)
2337
+ return;
2338
+ settled = true;
2339
+ outputError = error;
2340
+ output.close();
2341
+ abortHostEffects();
2342
+ try {
2343
+ addon.destroyCore(core);
2344
+ } catch {}
2345
+ readySocket.destroy();
2346
+ readyClosed.then(() => exitedResolve(code));
2347
+ };
2348
+ const pumpFetch = async (request) => {
2349
+ const controller = new AbortController;
2350
+ const state = { handle: request.handle, controller };
2351
+ fetchState = state;
2352
+ try {
2353
+ const response = await (options.fetch ?? globalThis.fetch)(request.url, {
2354
+ method: request.method,
2355
+ headers: new Headers(JSON.parse(request.headers).map(({ name, value }) => [name, value])),
2356
+ body: request.body?.length ? Buffer.from(request.body, "base64") : undefined,
2357
+ signal: controller.signal
2358
+ });
2359
+ const started = addon.startCoreFetchResponse(core, state.handle, response.status);
2360
+ if (started === fetchOperationStale)
2361
+ return;
2362
+ if (started !== fetchOperationApplied)
2363
+ throw new Error(`invalid native fetch start result ${started}`);
2364
+ if (response.body) {
2365
+ for await (const chunk of response.body) {
2366
+ const buffer = Buffer.from(chunk);
2367
+ let offset = 0;
2368
+ while (offset < buffer.length) {
2369
+ const end = Math.min(offset + 65536, buffer.length);
2370
+ const pushed = addon.pushCoreFetchResponse(core, state.handle, buffer.subarray(offset, end));
2371
+ if (pushed === fetchOperationApplied) {
2372
+ offset = end;
2373
+ continue;
2374
+ }
2375
+ if (pushed === fetchOperationStale)
2376
+ return;
2377
+ if (pushed !== fetchOperationBackpressure)
2378
+ throw new Error(`invalid native fetch push result ${pushed}`);
2379
+ await new Promise((resolve) => setTimeout(resolve, 2));
2380
+ }
2381
+ }
2382
+ }
2383
+ const finished = addon.finishCoreFetch(core, state.handle);
2384
+ if (finished !== fetchOperationApplied && finished !== fetchOperationStale) {
2385
+ throw new Error(`invalid native fetch finish result ${finished}`);
2386
+ }
2387
+ } catch (error) {
2388
+ if (error?.name !== "AbortError" || !controller.signal.aborted) {
2389
+ try {
2390
+ if (addon.coreFetchActive(core, state.handle))
2391
+ addon.failCoreFetch(core, state.handle);
2392
+ } catch {}
2393
+ }
2394
+ } finally {
2395
+ if (fetchState === state) {
2396
+ fetchState = null;
2397
+ queueMicrotask(drainReady);
2398
+ }
2399
+ }
2400
+ };
2401
+ function drainReady() {
2402
+ if (settled)
2403
+ return;
2404
+ try {
2405
+ if (fetchState) {
2406
+ if (!fetchState.controller.signal.aborted && !addon.coreFetchActive(core, fetchState.handle)) {
2407
+ fetchState.controller.abort();
2408
+ }
2409
+ } else {
2410
+ const fetchRequest = addon.takeCoreFetch(core);
2411
+ if (fetchRequest)
2412
+ pumpFetch(JSON.parse(fetchRequest.toString("utf8")));
2413
+ }
2414
+ if (addon.coreExitCode(core) !== 0) {
2415
+ finish(1, new Error("native output delivery failed"));
2416
+ return;
2417
+ }
2418
+ drainOutput();
2419
+ } catch (error) {
2420
+ finish(1, error);
2421
+ }
2422
+ }
2423
+ async function drainOutput() {
2424
+ if (draining || settled)
2425
+ return;
2426
+ draining = true;
2427
+ try {
2428
+ while (!settled) {
2429
+ const chunk = addon.drainCore(core);
2430
+ if (!chunk.length)
2431
+ break;
2432
+ const pending = output.write(chunk);
2433
+ if (pending)
2434
+ await pending;
2435
+ }
2436
+ if (!settled && addon.coreExited(core)) {
2437
+ output.finish();
2438
+ finish(addon.coreExitCode(core));
2439
+ }
2440
+ } catch (error) {
2441
+ finish(1, error);
2442
+ } finally {
2443
+ draining = false;
2444
+ }
2445
+ }
2446
+ readySocket.on("data", drainReady);
2447
+ readySocket.on("end", () => {
2448
+ drainReady();
2449
+ if (!settled)
2450
+ finish(1);
2451
+ });
2452
+ readySocket.on("error", () => finish(1));
2453
+ readySocket.on("close", () => {
2454
+ if (!settled)
2455
+ finish(1);
2456
+ });
2457
+ if (readySocket.pending) {
2458
+ try {
2459
+ readySocket.connect({ fd: readyFd });
2460
+ } catch (error) {
2461
+ finish(1);
2462
+ throw error;
2463
+ }
2464
+ }
2465
+ return {
2466
+ exited,
2467
+ get error() {
2468
+ return outputError;
2469
+ },
2470
+ write(data) {
2471
+ addon.writeCore(core, Buffer.from(data));
2472
+ },
2473
+ closeStdin() {
2474
+ addon.closeCore(core);
2475
+ },
2476
+ abortHostEffects,
2477
+ abort(error) {
2478
+ if (error)
2479
+ finish(1, error);
2480
+ else {
2481
+ abortHostEffects();
2482
+ addon.closeCore(core);
2483
+ }
2484
+ },
2485
+ setLineHandler(handler) {
2486
+ lineHandler = handler;
2487
+ }
2488
+ };
2489
+ }
2490
+ function createNativeAgent(addon, options) {
2491
+ return createFxAgent({
2492
+ ...options,
2493
+ runtimeFactory(runtimeOptions) {
2494
+ return createNativeCoreRuntime(addon, runtimeOptions);
2495
+ }
2496
+ });
2497
+ }
2498
+ async function createWithFallback(surface, nativeMethod, wasmFactory, defaultWasm, options) {
2499
+ const { nativeAddon, backend = "auto", ...runtimeOptions } = options ?? {};
2500
+ if (!new Set(["auto", "native", "wasm"]).has(backend)) {
2501
+ throw new TypeError('backend must be "auto", "native", or "wasm"');
2502
+ }
2503
+ let nativeError;
2504
+ let nativeAttempted = false;
2505
+ if (backend !== "wasm") {
2506
+ const native = await resolveNativeBackend(nativeAddon);
2507
+ nativeError = native.error;
2508
+ if (typeof native.backend?.[nativeMethod] === "function") {
2509
+ nativeAttempted = true;
2510
+ try {
2511
+ if (surface === "agent")
2512
+ return await createNativeAgent(native.backend, runtimeOptions);
2513
+ return await native.backend[nativeMethod](runtimeOptions);
2514
+ } catch (error) {
2515
+ nativeError = error;
2516
+ if (backend === "native")
2517
+ throw error;
2518
+ }
2519
+ }
2520
+ if (backend === "native") {
2521
+ const error = nativeError ?? new Error(`native addon does not provide ${nativeMethod}()`);
2522
+ error.code ??= "LIBFX_NATIVE_UNAVAILABLE";
2523
+ throw error;
2524
+ }
2525
+ }
2526
+ if (!supportsJspi()) {
2527
+ if (nativeAttempted)
2528
+ throw nativeError;
2529
+ throw jspiFallbackError(surface, nativeError);
2530
+ }
2531
+ const wasmSource = runtimeOptions.wasm ?? defaultWasm;
2532
+ return wasmFactory({ ...runtimeOptions, wasm: wasmInput(wasmSource) });
2533
+ }
2534
+ async function createFxAgent2(options = {}) {
2535
+ if (options != null && Object.hasOwn(Object(options), "env")) {
2536
+ throw new TypeError("createFxAgent() does not accept env; pass apiKey and model directly");
2537
+ }
2538
+ return createWithFallback("agent", "createCore", createFxAgent, defaultCoreWasm, options);
2539
+ }
2540
+ function createFxTerminal2(options = {}) {
2541
+ return createWithFallback("terminal", "createFxTerminal", createFxTerminal, defaultTermWasm, options);
2542
+ }