libfx 0.0.15 → 0.0.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -2
- package/browser-agent.js +21 -0
- package/browser-terminal.js +7 -0
- package/browser.js +12 -37
- package/bundled-asset.js +12 -0
- package/durable/local.mjs +3 -0
- package/durable/local.mjs.map +1 -0
- package/durable/vercel.mjs +3 -0
- package/durable/vercel.mjs.map +1 -0
- package/durable/world.mjs +3 -0
- package/durable/world.mjs.map +1 -0
- package/engine.js +2498 -0
- package/fx-core.wasm +0 -0
- package/fx-harness.js +1 -1
- package/fx-sdk.js +27 -3694
- package/fx-term.wasm +0 -0
- package/node-wasm.js +63 -0
- package/node.cjs +8 -8095
- package/node.js +94 -103
- package/package.json +10 -3
- package/types/node.d.ts +1 -1
- package/wasm-runtime.js +1049 -0
- package/wasm-terminal.js +80 -0
- package/xterm.js +112 -0
- package/libfx.darwin-arm64.node +0 -0
- package/libfx.darwin-x64.node +0 -0
- package/libfx.linux-arm64.node +0 -0
- package/libfx.linux-x64.node +0 -0
package/node.js
CHANGED
|
@@ -1,28 +1,26 @@
|
|
|
1
|
-
import { access
|
|
1
|
+
import { access } from "node:fs/promises";
|
|
2
2
|
import { closeSync } from "node:fs";
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
4
|
import { Socket } from "node:net";
|
|
5
5
|
import { homedir, tmpdir } from "node:os";
|
|
6
6
|
import { isAbsolute, resolve } from "node:path";
|
|
7
7
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
8
|
+
import { bundledAssetUrl } from "./bundled-asset.js";
|
|
8
9
|
import { CoreOutput } from "./core-output.js";
|
|
9
|
-
import { loadModule, withModuleFailure } from "./wasm-module.js";
|
|
10
10
|
import { fetchCleanupAction, cleanupTimeoutMs, cleanupByteLimit } from "./fetch-cleanup.js";
|
|
11
11
|
import { createDurableAgentFactory, memory, wakeHandler } from "./durable.js";
|
|
12
12
|
import { fxHarness } from "./fx-harness.js";
|
|
13
13
|
import {
|
|
14
|
-
createFxEngine as
|
|
15
|
-
createFxTerminal as createWasmTerminal,
|
|
14
|
+
createFxEngine as createEngine,
|
|
16
15
|
createMemoryPersistence,
|
|
17
16
|
FxFencedError,
|
|
18
17
|
FxJournalVersionError,
|
|
19
|
-
encodeXtermKeyEvent,
|
|
20
18
|
fxSdkApiVersion,
|
|
21
19
|
listModels,
|
|
22
20
|
normalizeAgentOptions,
|
|
23
21
|
supportsJspi,
|
|
24
|
-
|
|
25
|
-
} from "./
|
|
22
|
+
} from "./engine.js";
|
|
23
|
+
import { encodeXtermKeyEvent, xtermAdapter } from "./xterm.js";
|
|
26
24
|
|
|
27
25
|
export { createMemoryPersistence, encodeXtermKeyEvent, FxFencedError, FxJournalVersionError, fxSdkApiVersion, listModels, memory, supportsJspi, wakeHandler, xtermAdapter };
|
|
28
26
|
export const libfxApiVersion = 3;
|
|
@@ -33,10 +31,7 @@ const fetchOperationApplied = 1;
|
|
|
33
31
|
const fetchOperationBackpressure = 2;
|
|
34
32
|
|
|
35
33
|
const nodeRequire = createRequire(import.meta.url);
|
|
36
|
-
const defaultCoreWasm = new URL("./fx-core.wasm", import.meta.url);
|
|
37
|
-
const defaultTermWasm = new URL("./fx-term.wasm", import.meta.url);
|
|
38
34
|
let nativeBackendPromise;
|
|
39
|
-
const wasmFilePromises = new Map();
|
|
40
35
|
|
|
41
36
|
const backendReasonCodes = {
|
|
42
37
|
unsupportedPlatform: "LIBFX_UNSUPPORTED_PLATFORM",
|
|
@@ -49,25 +44,24 @@ const backendReasonCodes = {
|
|
|
49
44
|
wasmLoad: "LIBFX_WASM_LOAD_FAILED",
|
|
50
45
|
};
|
|
51
46
|
|
|
52
|
-
function
|
|
53
|
-
|
|
54
|
-
typeof __webpack_base_uri__ === "undefined" || typeof __webpack_public_path__ !== "string" ||
|
|
55
|
-
!asset.href.startsWith(__webpack_public_path__)) {
|
|
56
|
-
throw new TypeError("Bundled asset URL has no filesystem mapping");
|
|
57
|
-
}
|
|
58
|
-
// Webpack's relative URL is a public asset address, not a Node URL instance.
|
|
59
|
-
return new URL(asset.href.slice(__webpack_public_path__.length), __webpack_base_uri__);
|
|
47
|
+
function sentence(message) {
|
|
48
|
+
return `${message[0].toUpperCase()}${message.slice(1)}.`;
|
|
60
49
|
}
|
|
61
50
|
|
|
62
|
-
function jspiFallbackError(surface, nativeError) {
|
|
63
|
-
const
|
|
51
|
+
function jspiFallbackError(surface, native, nativeError) {
|
|
52
|
+
const missingPackage = !nativeError && native?.failure === "missing" ? native.nativePackage : undefined;
|
|
53
|
+
const nativeDetail = nativeError ? ` Native loading failed: ${nativeError.message}.`
|
|
54
|
+
: missingPackage ? ` ${sentence(nativeFailureReason(native).message)}`
|
|
55
|
+
: " No compatible native addon was found.";
|
|
56
|
+
const remedy = missingPackage
|
|
57
|
+
? `Install ${missingPackage}, which libfx lists as an optional dependency, or run Node with --experimental-wasm-jspi.`
|
|
58
|
+
: "Run Node with --experimental-wasm-jspi or install a libfx package containing a compatible native addon.";
|
|
64
59
|
const error = new Error(
|
|
65
60
|
`libfx could not start the ${surface} backend.${nativeDetail} ` +
|
|
66
|
-
|
|
67
|
-
"Run Node with --experimental-wasm-jspi or install a libfx package containing a compatible native addon.",
|
|
61
|
+
`The WebAssembly fallback requires JavaScript Promise Integration (JSPI). ${remedy}`,
|
|
68
62
|
);
|
|
69
63
|
error.code = "LIBFX_JSPI_REQUIRED";
|
|
70
|
-
error.cause = nativeError;
|
|
64
|
+
error.cause = nativeError ?? native?.probeError;
|
|
71
65
|
return error;
|
|
72
66
|
}
|
|
73
67
|
|
|
@@ -79,7 +73,8 @@ async function loadNativeCandidate(candidate) {
|
|
|
79
73
|
// Bundlers trace the asset URL; Node must load the native file at runtime.
|
|
80
74
|
return Reflect.apply(nodeRequire, undefined, [fileURLToPath(candidate)]);
|
|
81
75
|
}
|
|
82
|
-
|
|
76
|
+
// A module the caller names is its own file, which Node loads as is.
|
|
77
|
+
const imported = await import(/* webpackIgnore: true */ /* turbopackIgnore: true */ candidate.href);
|
|
83
78
|
return imported.default ?? imported;
|
|
84
79
|
}
|
|
85
80
|
if (typeof candidate === "object") return candidate.default ?? candidate;
|
|
@@ -89,37 +84,54 @@ async function loadNativeCandidate(candidate) {
|
|
|
89
84
|
if (candidate.endsWith(".node")) {
|
|
90
85
|
return Reflect.apply(nodeRequire, undefined, [isAbsolute(candidate) ? candidate : resolve(candidate)]);
|
|
91
86
|
}
|
|
92
|
-
const imported = await import(candidate.startsWith("file:") ? candidate : pathToFileURL(candidate).href);
|
|
87
|
+
const imported = await import(/* webpackIgnore: true */ /* turbopackIgnore: true */ candidate.startsWith("file:") ? candidate : pathToFileURL(candidate).href);
|
|
93
88
|
return imported.default ?? imported;
|
|
94
89
|
}
|
|
95
90
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
91
|
+
// Each platform's addon ships in a package of its own, which libfx lists as
|
|
92
|
+
// an optional dependency, so a package manager installs only the one for its
|
|
93
|
+
// machine. The package's index.js names the addon with a literal
|
|
94
|
+
// `new URL("./libfx.node", import.meta.url)`, so a bundler that bundles
|
|
95
|
+
// libfx copies that one addon and traces it.
|
|
96
|
+
const nativePackages = {
|
|
97
|
+
"darwin-arm64": "@libfx/libfx-darwin-arm64",
|
|
98
|
+
"darwin-x64": "@libfx/libfx-darwin-x64",
|
|
99
|
+
"linux-arm64": "@libfx/libfx-linux-arm64-gnu",
|
|
100
|
+
"linux-x64": "@libfx/libfx-linux-x64-gnu",
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
// Each specifier is a literal, so Node, Turbopack, and file tracers find the
|
|
104
|
+
// installed package. A missing one fails here at run time, and inside `try`
|
|
105
|
+
// it does not fail a Turbopack build. webpack folds the `typeof` test and
|
|
106
|
+
// sees only a context over the platform packages that are installed, so it
|
|
107
|
+
// does not warn about the ones that are missing.
|
|
108
|
+
async function importNativePackage(name) {
|
|
109
|
+
try {
|
|
110
|
+
if (typeof __webpack_require__ === "function") {
|
|
111
|
+
return await import(
|
|
112
|
+
/* webpackInclude: /[\\/]libfx-(darwin|linux)-[a-z0-9-]+[\\/]index\.js$/ */
|
|
113
|
+
`@libfx/libfx-${name.slice("@libfx/libfx-".length)}`
|
|
114
|
+
);
|
|
115
|
+
} else {
|
|
116
|
+
if (name === "@libfx/libfx-darwin-arm64") return await import("@libfx/libfx-darwin-arm64");
|
|
117
|
+
if (name === "@libfx/libfx-darwin-x64") return await import("@libfx/libfx-darwin-x64");
|
|
118
|
+
if (name === "@libfx/libfx-linux-arm64-gnu") return await import("@libfx/libfx-linux-arm64-gnu");
|
|
119
|
+
if (name === "@libfx/libfx-linux-x64-gnu") return await import("@libfx/libfx-linux-x64-gnu");
|
|
120
|
+
}
|
|
121
|
+
} catch (error) {
|
|
122
|
+
return { error };
|
|
123
|
+
}
|
|
124
|
+
return { error: new Error(`${name} is not a libfx native addon package`) };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// The path of the addon the platform package names, or the error that kept
|
|
128
|
+
// the package from loading.
|
|
129
|
+
async function defaultNativeCandidate(name) {
|
|
130
|
+
const imported = await importNativePackage(name);
|
|
131
|
+
if (imported.error) return imported;
|
|
132
|
+
const asset = imported.default;
|
|
133
|
+
if (!(asset instanceof URL)) return { error: new TypeError(`${name} does not export its addon's URL`) };
|
|
134
|
+
return { candidate: fileURLToPath(asset.protocol === "" ? bundledAssetUrl(asset) : asset) };
|
|
123
135
|
}
|
|
124
136
|
|
|
125
137
|
function validateNativeBackend(backend) {
|
|
@@ -180,10 +192,17 @@ async function loadAndValidateNativeCandidate(candidate, artifactMissing = false
|
|
|
180
192
|
}
|
|
181
193
|
|
|
182
194
|
async function discoverNativeBackend() {
|
|
183
|
-
const
|
|
184
|
-
if (!
|
|
195
|
+
const nativePackage = nativePackages[`${process.platform}-${process.arch}`];
|
|
196
|
+
if (!nativePackage) {
|
|
185
197
|
return { backend: null, error: null, failure: "unsupported" };
|
|
186
198
|
}
|
|
199
|
+
// The package's index.js only names its addon, so a package that cannot be
|
|
200
|
+
// found is not installed or not part of the bundle.
|
|
201
|
+
const { candidate, error: packageError } = await defaultNativeCandidate(nativePackage);
|
|
202
|
+
if (packageError) {
|
|
203
|
+
if (missingArtifact(packageError)) return { backend: null, error: null, probeError: packageError, failure: "missing", nativePackage };
|
|
204
|
+
return { backend: null, error: packageError, failure: "load" };
|
|
205
|
+
}
|
|
187
206
|
try {
|
|
188
207
|
await access(candidate);
|
|
189
208
|
} catch (error) {
|
|
@@ -204,35 +223,6 @@ async function resolveNativeBackend(nativeAddon) {
|
|
|
204
223
|
return nativeBackendPromise;
|
|
205
224
|
}
|
|
206
225
|
|
|
207
|
-
function wasmInput(input) {
|
|
208
|
-
const path = wasmFilePath(input);
|
|
209
|
-
if (path === null) {
|
|
210
|
-
if (input instanceof URL) return input.href;
|
|
211
|
-
return input;
|
|
212
|
-
}
|
|
213
|
-
const cached = wasmFilePromises.get(path);
|
|
214
|
-
if (cached) return cached;
|
|
215
|
-
const pendingRead = readFile(path);
|
|
216
|
-
let pending;
|
|
217
|
-
pending = withModuleFailure(pendingRead, () => {
|
|
218
|
-
if (wasmFilePromises.get(path) === pending) wasmFilePromises.delete(path);
|
|
219
|
-
});
|
|
220
|
-
wasmFilePromises.set(path, pending);
|
|
221
|
-
pendingRead.catch(() => {
|
|
222
|
-
if (wasmFilePromises.get(path) === pending) wasmFilePromises.delete(path);
|
|
223
|
-
});
|
|
224
|
-
return pending;
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
function wasmFilePath(input) {
|
|
228
|
-
if (input instanceof URL) {
|
|
229
|
-
if (input.protocol === "") input = bundledAssetUrl(input);
|
|
230
|
-
if (input.protocol === "file:") return fileURLToPath(input);
|
|
231
|
-
}
|
|
232
|
-
if (typeof input === "string" && !URL.canParse(input)) return resolve(input);
|
|
233
|
-
return null;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
226
|
function reason(code, message, error) {
|
|
237
227
|
const causeCode = error?.code;
|
|
238
228
|
return {
|
|
@@ -253,7 +243,9 @@ function nativeFailureReason(result) {
|
|
|
253
243
|
case "missing":
|
|
254
244
|
return reason(
|
|
255
245
|
backendReasonCodes.missingArtifact,
|
|
256
|
-
|
|
246
|
+
result.nativePackage
|
|
247
|
+
? `the native addon package ${result.nativePackage} for ${process.platform}-${process.arch} is not installed`
|
|
248
|
+
: `native addon artifact was not found${detailError?.message ? `: ${detailError.message}` : ""}`,
|
|
257
249
|
detailError,
|
|
258
250
|
);
|
|
259
251
|
case "load":
|
|
@@ -326,15 +318,14 @@ export async function getBackendInfo(value = {}) {
|
|
|
326
318
|
available: false,
|
|
327
319
|
reason: reason(
|
|
328
320
|
backendReasonCodes.jspiUnavailable,
|
|
329
|
-
"WebAssembly backend requires JavaScript Promise Integration (JSPI)",
|
|
321
|
+
"WebAssembly backend requires JavaScript Promise Integration (JSPI); run Node with --experimental-wasm-jspi",
|
|
330
322
|
),
|
|
331
323
|
});
|
|
332
324
|
return { surface, backend: "unavailable", attempts };
|
|
333
325
|
}
|
|
334
|
-
const defaultWasm = surface === "agent" ? defaultCoreWasm : defaultTermWasm;
|
|
335
|
-
const wasmSource = wasm ?? defaultWasm;
|
|
336
326
|
try {
|
|
337
|
-
|
|
327
|
+
const { probeWasm } = await import("./node-wasm.js");
|
|
328
|
+
await probeWasm(surface, wasm);
|
|
338
329
|
attempts.push({ backend: "wasm-jspi", available: true, reason: null });
|
|
339
330
|
return { surface, backend: "wasm-jspi", attempts };
|
|
340
331
|
} catch (error) {
|
|
@@ -610,7 +601,7 @@ function createNativeCoreRuntime(addon, options) {
|
|
|
610
601
|
}
|
|
611
602
|
|
|
612
603
|
function createNativeAgent(addon, options) {
|
|
613
|
-
return
|
|
604
|
+
return createEngine({
|
|
614
605
|
...options,
|
|
615
606
|
runtimeFactory(runtimeOptions) {
|
|
616
607
|
return createNativeCoreRuntime(addon, runtimeOptions);
|
|
@@ -618,17 +609,18 @@ function createNativeAgent(addon, options) {
|
|
|
618
609
|
});
|
|
619
610
|
}
|
|
620
611
|
|
|
621
|
-
async function createWithFallback(surface, nativeMethod,
|
|
612
|
+
async function createWithFallback(surface, nativeMethod, options) {
|
|
622
613
|
const { nativeAddon, backend = "auto", ...unvalidatedOptions } = options ?? {};
|
|
623
614
|
if (!new Set(["auto", "native", "wasm"]).has(backend)) {
|
|
624
615
|
throw new TypeError('backend must be "auto", "native", or "wasm"');
|
|
625
616
|
}
|
|
626
617
|
const runtimeOptions = surface === "agent" ? normalizeAgentOptions(unvalidatedOptions) : unvalidatedOptions;
|
|
627
618
|
|
|
619
|
+
let native = null;
|
|
628
620
|
let nativeError;
|
|
629
621
|
let nativeAttempted = false;
|
|
630
622
|
if (backend !== "wasm") {
|
|
631
|
-
|
|
623
|
+
native = await resolveNativeBackend(nativeAddon);
|
|
632
624
|
nativeError = native.error;
|
|
633
625
|
if (typeof native.backend?.[nativeMethod] === "function") {
|
|
634
626
|
nativeAttempted = true;
|
|
@@ -643,7 +635,12 @@ async function createWithFallback(surface, nativeMethod, wasmFactory, defaultWas
|
|
|
643
635
|
}
|
|
644
636
|
}
|
|
645
637
|
if (backend === "native") {
|
|
646
|
-
const
|
|
638
|
+
const missingPackage = native.failure === "missing" ? native.nativePackage : undefined;
|
|
639
|
+
const error = nativeError ?? new Error(native.backend
|
|
640
|
+
? `native addon does not provide ${nativeMethod}()`
|
|
641
|
+
: missingPackage
|
|
642
|
+
? `${sentence(nativeFailureReason(native).message)} Install ${missingPackage}, which libfx lists as an optional dependency.`
|
|
643
|
+
: nativeFailureReason(native).message, native.probeError ? { cause: native.probeError } : undefined);
|
|
647
644
|
error.code ??= "LIBFX_NATIVE_UNAVAILABLE";
|
|
648
645
|
throw error;
|
|
649
646
|
}
|
|
@@ -651,10 +648,10 @@ async function createWithFallback(surface, nativeMethod, wasmFactory, defaultWas
|
|
|
651
648
|
|
|
652
649
|
if (!supportsJspi()) {
|
|
653
650
|
if (nativeAttempted) throw nativeError;
|
|
654
|
-
throw jspiFallbackError(surface, nativeError);
|
|
651
|
+
throw jspiFallbackError(surface, native, nativeError);
|
|
655
652
|
}
|
|
656
|
-
const
|
|
657
|
-
return
|
|
653
|
+
const wasmBackend = await import("./node-wasm.js");
|
|
654
|
+
return surface === "agent" ? wasmBackend.createWasmEngine(runtimeOptions) : wasmBackend.createWasmTerminal(runtimeOptions);
|
|
658
655
|
}
|
|
659
656
|
|
|
660
657
|
/**
|
|
@@ -663,13 +660,7 @@ async function createWithFallback(surface, nativeMethod, wasmFactory, defaultWas
|
|
|
663
660
|
* runs durable sessions on these.
|
|
664
661
|
*/
|
|
665
662
|
export async function createFxEngine(options = {}) {
|
|
666
|
-
return createWithFallback(
|
|
667
|
-
"agent",
|
|
668
|
-
"createCore",
|
|
669
|
-
createWasmAgent,
|
|
670
|
-
defaultCoreWasm,
|
|
671
|
-
options,
|
|
672
|
-
);
|
|
663
|
+
return createWithFallback("agent", "createCore", options);
|
|
673
664
|
}
|
|
674
665
|
|
|
675
666
|
// The durability a session gets when the caller names none: Vercel's World
|
|
@@ -723,5 +714,5 @@ export const createFxAgent = createDurableAgentFactory({
|
|
|
723
714
|
});
|
|
724
715
|
|
|
725
716
|
export function createFxTerminal(options = {}) {
|
|
726
|
-
return createWithFallback("terminal", "createFxTerminal",
|
|
717
|
+
return createWithFallback("terminal", "createFxTerminal", options);
|
|
727
718
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "libfx",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.16",
|
|
4
4
|
"description": "Embed fx agents and terminals in JavaScript hosts",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
@@ -87,12 +87,19 @@
|
|
|
87
87
|
"default": "./skills-node.js"
|
|
88
88
|
}
|
|
89
89
|
},
|
|
90
|
+
"sideEffects": false,
|
|
90
91
|
"engines": {
|
|
91
|
-
"node": ">=
|
|
92
|
+
"node": "^20.19.0 || >=22.12.0"
|
|
92
93
|
},
|
|
93
94
|
"publishConfig": {
|
|
94
95
|
"access": "public",
|
|
95
96
|
"registry": "https://registry.npmjs.org/"
|
|
96
97
|
},
|
|
97
|
-
"license": "Apache-2.0"
|
|
98
|
+
"license": "Apache-2.0",
|
|
99
|
+
"optionalDependencies": {
|
|
100
|
+
"@libfx/libfx-linux-x64-gnu": "0.0.16",
|
|
101
|
+
"@libfx/libfx-linux-arm64-gnu": "0.0.16",
|
|
102
|
+
"@libfx/libfx-darwin-x64": "0.0.16",
|
|
103
|
+
"@libfx/libfx-darwin-arm64": "0.0.16"
|
|
104
|
+
}
|
|
98
105
|
}
|
package/types/node.d.ts
CHANGED