libfx 0.0.3 → 0.0.4

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 CHANGED
@@ -1,78 +1,357 @@
1
1
  # libfx
2
2
 
3
- `libfx` embeds fx agents and interactive terminals in JavaScript hosts. It exposes the same public APIs in browsers and Node.js:
3
+ `libfx` embeds fx agents and interactive terminals in JavaScript
4
+ applications. It supports Node.js hosts and browser environments with
5
+ JavaScript Promise Integration (JSPI).
4
6
 
5
- - `createFxAgent()` for the headless ACP agent
6
- - `createFxTerminal()` for the interactive terminal
7
- - `supportsJspi()` for WebAssembly capability detection
8
- - `encodeXtermKeyEvent()` and `xtermAdapter()` for terminal integration
7
+ ## Installation
9
8
 
10
- ## Browser
9
+ ```sh
10
+ npm install libfx
11
+ ```
12
+
13
+ Requirements:
14
+
15
+ - Node.js 20 or later
16
+ - Chrome or Edge 137 or later for browser WebAssembly
17
+ - JSPI when using the WebAssembly backend
18
+ - A Vercel AI Gateway credential or a host-provided authenticated `fetch`
19
+
20
+ The package includes:
21
+
22
+ - Native Node addons for Linux and macOS on x64 and arm64
23
+ - `fx-core.wasm` for headless agents
24
+ - `fx-term.wasm` for interactive terminals
25
+ - A dependency-free JavaScript host layer
11
26
 
12
- Import the browser entry point and serve the two WebAssembly artifacts beside it:
27
+ ## Exports
28
+
29
+ | Import | Environment | Description |
30
+ | --- | --- | --- |
31
+ | `libfx` | Node.js or browser | Environment-aware default |
32
+ | `libfx/node` | Node.js | Native-first Node entry point |
33
+ | `libfx/browser` | Browser | WebAssembly browser entry point |
34
+ | `libfx/wasm` | Browser or Node.js | Direct WebAssembly host layer |
35
+
36
+ Public exports:
37
+
38
+ - `createFxAgent()` creates a headless ACP agent.
39
+ - `createFxTerminal()` runs the interactive fx terminal.
40
+ - `supportsJspi()` detects WebAssembly JSPI support.
41
+ - `xtermAdapter()` connects fx to an xterm.js terminal.
42
+ - `encodeXtermKeyEvent()` translates browser keyboard events into terminal input.
43
+
44
+ ## Headless agent
45
+
46
+ The default Node entry point prefers the native addon and falls back to
47
+ WebAssembly when necessary.
13
48
 
14
49
  ```js
15
- import { createFxAgent } from "libfx/browser";
50
+ import { createFxAgent } from "libfx";
16
51
 
17
52
  const agent = await createFxAgent({
18
- env: { AI_GATEWAY_API_KEY },
53
+ env: {
54
+ AI_GATEWAY_API_KEY: process.env.AI_GATEWAY_API_KEY,
55
+ },
56
+ onEvent(event) {
57
+ console.log(event.type);
58
+ },
59
+ async onPermission(request) {
60
+ // Return one of request.options[*].optionId to approve it.
61
+ // Returning null or undefined cancels the request.
62
+ return null;
63
+ },
19
64
  });
65
+
66
+ const session = await agent.createSession();
67
+ const turn = session.prompt("Explain the files in this project.");
68
+
69
+ for await (const update of turn) {
70
+ console.log(update);
71
+ }
72
+
73
+ console.log("Stopped:", await turn.stopReason);
74
+
75
+ await session.close();
76
+ await agent.close();
20
77
  ```
21
78
 
22
- The default browser assets are `fx-core.wasm` and `fx-term.wasm` beside the JavaScript package. Pass `wasm` explicitly to use another URL, `Response`, byte buffer, or precompiled `WebAssembly.Module`.
79
+ A prompt may be a string or an array of text and resource blocks:
80
+
81
+ ```js
82
+ const turn = session.prompt([
83
+ { type: "text", text: "Summarize this file." },
84
+ {
85
+ type: "resource",
86
+ resource: {
87
+ uri: "file:///workspace/README.md",
88
+ text: readmeContents,
89
+ },
90
+ },
91
+ ]);
92
+ ```
93
+
94
+ Image prompt blocks are not currently supported.
95
+
96
+ ### Agent lifecycle
97
+
98
+ The object returned by `createFxAgent()` provides:
99
+
100
+ | Member | Description |
101
+ | --- | --- |
102
+ | `createSession()` | Creates a new active session |
103
+ | `openSession(id)` | Loads a stored session |
104
+ | `listSessions()` | Lists stored sessions |
105
+ | `close()` | Closes the active session and shuts down cleanly |
106
+ | `abort()` | Immediately aborts the runtime |
107
+ | `exited` | Promise that resolves with the process exit code |
23
108
 
24
- Browsers require JavaScript Promise Integration (JSPI), detected by `supportsJspi()`. Use Chrome or Edge 137 or later.
109
+ A session provides:
25
110
 
26
- ## Node.js
111
+ | Member | Description |
112
+ | --- | --- |
113
+ | `prompt(input, options?)` | Starts an async iterable turn |
114
+ | `setModel(model)` | Changes the active model |
115
+ | `setMode(mode)` | Changes the active mode |
116
+ | `setConfig(config)` | Applies multiple configuration values |
117
+ | `close()` | Closes the active session |
118
+ | `remove()` | Removes the stored session |
119
+ | `history` | Previously loaded session updates |
120
+ | `configOptions` | Current configurable values |
27
121
 
28
- The default `libfx` export is Node-aware:
122
+ Each session allows one active prompt at a time. Cancel a turn directly or
123
+ with an `AbortSignal`:
29
124
 
30
125
  ```js
31
- import { createFxAgent } from "libfx";
126
+ const controller = new AbortController();
127
+ const turn = session.prompt("Wait for more instructions.", {
128
+ signal: controller.signal,
129
+ });
130
+
131
+ controller.abort();
132
+ console.log(await turn.stopReason); // "cancelled"
133
+ ```
134
+
135
+ ## Browser agent
136
+
137
+ Browser hosts always use WebAssembly.
138
+
139
+ ```js
140
+ import {
141
+ createFxAgent,
142
+ supportsJspi,
143
+ } from "libfx/browser";
144
+
145
+ if (!supportsJspi()) {
146
+ throw new Error("This browser does not support WebAssembly JSPI.");
147
+ }
148
+
149
+ const agent = await createFxAgent({
150
+ env: {
151
+ AI_GATEWAY_API_KEY: "<short-lived credential>",
152
+ },
153
+ });
154
+
155
+ const session = await agent.createSession();
156
+ const turn = session.prompt("Describe this workspace.");
157
+
158
+ for await (const update of turn) {
159
+ console.log(update);
160
+ }
161
+ ```
162
+
163
+ The browser entry point resolves `fx-core.wasm` and `fx-term.wasm` relative to
164
+ the installed package. Pass `wasm` explicitly to provide a URL, `Response`,
165
+ `ArrayBuffer`, typed array, or precompiled `WebAssembly.Module`.
166
+
167
+ Do not embed a long-lived API key in public browser code. Use a short-lived
168
+ credential or an authenticated server-side proxy.
169
+
170
+ ## Interactive terminal
171
+
172
+ Install xterm.js in the host application:
173
+
174
+ ```sh
175
+ npm install @xterm/xterm @xterm/addon-fit
176
+ ```
32
177
 
178
+ Create the terminal and connect it to fx:
179
+
180
+ ```js
181
+ import { Terminal } from "@xterm/xterm";
182
+ import { FitAddon } from "@xterm/addon-fit";
183
+ import "@xterm/xterm/css/xterm.css";
184
+ import {
185
+ createFxTerminal,
186
+ supportsJspi,
187
+ xtermAdapter,
188
+ } from "libfx/browser";
189
+
190
+ if (!supportsJspi()) {
191
+ throw new Error("This browser does not support WebAssembly JSPI.");
192
+ }
193
+
194
+ const terminal = new Terminal({
195
+ cursorBlink: true,
196
+ scrollback: 10_000,
197
+ });
198
+
199
+ const fit = new FitAddon();
200
+ terminal.loadAddon(fit);
201
+ terminal.open(document.querySelector("#terminal"));
202
+ fit.fit();
203
+
204
+ const runtime = await createFxTerminal({
205
+ terminal: xtermAdapter(terminal),
206
+ env: {
207
+ AI_GATEWAY_API_KEY: "<short-lived credential>",
208
+ },
209
+ });
210
+
211
+ await runtime.interactive;
212
+
213
+ window.addEventListener("resize", () => {
214
+ fit.fit();
215
+ runtime.resize();
216
+ });
217
+ ```
218
+
219
+ The terminal runtime provides:
220
+
221
+ | Member | Description |
222
+ | --- | --- |
223
+ | `interactive` | Resolves after the terminal is ready for input |
224
+ | `exited` | Resolves with the terminal exit code |
225
+ | `write(data)` | Writes input directly to fx |
226
+ | `resize()` | Notifies fx of terminal geometry changes |
227
+ | `abort()` | Stops the terminal and releases subscriptions |
228
+
229
+ Try the hosted terminal at [fx.sh/try](https://fx.sh/try).
230
+
231
+ ## Backend selection
232
+
233
+ Node hosts may select a backend explicitly:
234
+
235
+ ```js
33
236
  const agent = await createFxAgent({
34
- env: { AI_GATEWAY_API_KEY },
237
+ backend: "native",
35
238
  });
36
239
  ```
37
240
 
38
- Node tries a compatible native addon first (`libfx.node`, then a platform-specific `libfx.<platform>-<arch>.node`). The current native addon implements `createFxAgent()` in-process through the ACP core, while Gateway requests use the host's `fetch` implementation and `AbortController`, matching the WebAssembly host boundary. Pass `fetch` to override Node's global implementation. Configure its API key, model, and Gateway URL through `env.AI_GATEWAY_API_KEY`, `env.FX_MODEL`, and `env.FX_GATEWAY_CHAT_URL`. `createFxTerminal()` falls back to WebAssembly. Missing native surfaces always fall back independently.
241
+ | Backend | Behavior |
242
+ | --- | --- |
243
+ | `auto` | Prefer a compatible native addon and fall back to WebAssembly |
244
+ | `native` | Require the native backend and fail if it cannot load |
245
+ | `wasm` | Require WebAssembly and JSPI |
246
+
247
+ The native loader checks `libfx.node` followed by the platform-specific addon:
248
+
249
+ ```text
250
+ libfx.<platform>-<arch>.node
251
+ ```
252
+
253
+ Supported packaged targets:
39
254
 
40
- `nativeAddon` and `env.FX_GATEWAY_CHAT_URL` are trusted host configuration, not request or tenant input. The native backend sends production credentials only to the canonical Vercel AI Gateway endpoint. Custom endpoints are limited to explicit loopback HTTP URLs for local development. Never pass user-controlled module paths, URLs, or environment objects into these options.
255
+ - `linux-x64`
256
+ - `linux-arm64`
257
+ - `darwin-x64`
258
+ - `darwin-arm64`
41
259
 
42
- The WebAssembly fallback requires JSPI. On Node versions where JSPI is still behind a flag, start Node with:
260
+ If no compatible native backend is available and JSPI cannot run, startup
261
+ rejects with:
262
+
263
+ ```js
264
+ error.code === "LIBFX_JSPI_REQUIRED"
265
+ ```
266
+
267
+ On Node versions where JSPI remains behind a flag, start the process with:
43
268
 
44
269
  ```sh
45
270
  node --experimental-wasm-jspi app.mjs
46
271
  ```
47
272
 
48
- If neither a compatible native addon nor JSPI is available, `libfx` rejects with `code === "LIBFX_JSPI_REQUIRED"` and an actionable message. Control backend selection with `backend: "auto" | "native" | "wasm"`; tests and custom distributions may provide `nativeAddon` as a module, path, URL, or `false`.
273
+ ## Host integrations
274
+
275
+ Hosts may provide adapters for runtime state and external effects:
276
+
277
+ | Option | Purpose |
278
+ | --- | --- |
279
+ | `fetch` | Routes Gateway requests through the host |
280
+ | `env` | Supplies runtime configuration without changing process globals |
281
+ | `onEvent` | Receives runtime, ACP, terminal, and lifecycle events |
282
+ | `onPermission` | Resolves agent permission requests |
283
+ | `configStore` | Persists accepted configuration values |
284
+ | `sessionStore` | Persists agent or terminal sessions |
285
+ | `oauthSessionStore` | Persists browser device-login sessions |
286
+ | `promptHistoryStore` | Stores terminal prompt history |
287
+ | `openUrl` | Opens authentication and verification URLs |
288
+ | `workspace` | Provides the constrained browser workspace adapter |
289
+
290
+ ## Security boundaries
291
+
292
+ `nativeAddon` and `env.FX_GATEWAY_CHAT_URL` are trusted host configuration. Do
293
+ not populate them from request, tenant, or other untrusted input.
294
+
295
+ The native backend sends production credentials only to the canonical Vercel
296
+ AI Gateway endpoint. Custom Gateway endpoints are limited to explicit loopback
297
+ HTTP URLs for local development.
298
+
299
+ The WebAssembly runtime intentionally does not provide:
300
+
301
+ - Native processes
302
+ - OS sandboxing
303
+ - Native MCP servers
304
+ - Subagents or skills
305
+ - Automatic upgrades
306
+ - Clipboard integration
307
+ - Arbitrary WASI filesystem access
308
+ - Web search
309
+
310
+ The optional browser workspace exposes foreground terminal execution through
311
+ the typed contract:
312
+
313
+ ```js
314
+ { action: "exec", command }
315
+ ```
316
+
317
+ The host remains responsible for admitting commands, enforcing limits, and
318
+ returning bounded output.
49
319
 
50
320
  ## Local development
51
321
 
52
- Build the native core addon and both WebAssembly surfaces from the repository root:
322
+ From the fx repository root, build the native addon and both WebAssembly
323
+ surfaces:
53
324
 
54
325
  ```sh
55
326
  zig build -Dnapi-surface=core -Doptimize=ReleaseSafe
56
327
  zig build -Dwasm-surface=core -Doptimize=ReleaseSmall
57
328
  zig build -Dwasm-surface=term -Doptimize=ReleaseSmall
58
- python3 -m http.server 8080
59
329
  ```
60
330
 
61
- Then open:
331
+ Run the SDK test suites:
62
332
 
63
- - [Core debugger](http://localhost:8080/sdk/index.html)
64
- - [Interactive terminal](http://localhost:8080/sdk/term-demo.html)
333
+ ```sh
334
+ npm ci --prefix sdk/node
335
+ npm run --prefix sdk test:node-napi
336
+ npm run --prefix sdk test:node-wasm
337
+ ```
65
338
 
66
- The local demos pass their development WASM URLs explicitly. Stage a publishable package after both builds with:
339
+ Serve the repository:
67
340
 
68
341
  ```sh
69
- node sdk/scripts/package-libfx.mjs /tmp/libfx-package
342
+ python3 -m http.server 8080
70
343
  ```
71
344
 
72
- The staging script includes `zig-out/lib/libfx.node` by default for local testing. When explicit addon paths are passed for publishing, it requires exactly one `ReleaseSafe` binary for each supported target: Linux x64, Linux arm64, macOS x64, and macOS arm64. Published WebAssembly artifacts use `ReleaseSmall`.
345
+ After starting the server, open these local URLs:
346
+
347
+ ```text
348
+ Core debugger: http://localhost:8080/sdk/index.html
349
+ Interactive terminal: http://localhost:8080/sdk/term-demo.html
350
+ ```
73
351
 
74
- The npm `latest` dist-tag is the stable channel. The `dev` dist-tag tracks successful builds from `main` and uses immutable prerelease versions. Publishing runs through `.github/workflows/publish-libfx.yml` with npm trusted publishing and provenance. Configure the npm trusted publisher for the `vercel-labs/fx` repository, workflow filename `publish-libfx.yml`, and GitHub environment `npm`. Because npm requires a package to exist before trusted publishing can be configured, the first `libfx` version must be published once by a maintainer before enabling that relationship.
352
+ These are local development pages and are not publicly hosted links.
75
353
 
76
- JavaScript hosts can provide configuration, prompt history, session persistence, device login, URL opening, and a foreground workspace. The optional workspace adapter exposes only `terminal` with `{ action: "exec", command }`; command execution is delegated to the host in a clean, root-fixed environment.
354
+ Maintainer references:
77
355
 
78
- WebAssembly builds do not include native processes, OS sandboxing, native MCP, subagents, skills, auto-upgrade, clipboard integration, arbitrary WASI filesystem access, or web search.
356
+ - [SDK contributor guide](https://github.com/vercel-labs/fx/blob/main/sdk/AGENTS.md)
357
+ - [Native Node-API design and security model](https://github.com/vercel-labs/fx/blob/main/sdk/NAPI.md)
package/fx-core.wasm CHANGED
Binary file
package/fx-term.wasm CHANGED
Binary file
Binary file
Binary file
Binary file
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "libfx",
3
- "version": "0.0.3",
3
+ "version": "0.0.4",
4
4
  "description": "Embed fx agents and terminals in JavaScript hosts",
5
5
  "type": "module",
6
6
  "repository": {