libfx 0.0.7-dev.632.g9fe8a30c19a7 → 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/README.md CHANGED
@@ -41,8 +41,9 @@ await agent.close();
41
41
  Agent configuration uses named options; `env` is reserved for
42
42
  `createFxTerminal()`.
43
43
 
44
- The host selects the model. Agent creation and prompting do not fetch the
45
- Gateway model catalog.
44
+ The host selects the model. Agent creation does not fetch the Gateway model
45
+ catalog. Prompting can resolve model capabilities and context capacity through
46
+ the supplied `fetch`; fx caches that metadata for the agent.
46
47
 
47
48
  `onEvent` receives runtime diagnostics separately from model output. Transport
48
49
  events report request start, response status and elapsed time, safe Gateway
@@ -59,6 +60,28 @@ returns an async iterable of normalized events:
59
60
  - `tool_start`
60
61
  - `tool_end`
61
62
 
63
+ Consume the turn while it runs, then await `turn.result`. Output is lossless and
64
+ backpressured: a slow reader pauses production instead of growing an unlimited
65
+ event queue. Awaiting only `turn.result` can wait for an unread stream to drain.
66
+ If you only need the result, explicitly discard events:
67
+
68
+ ```js
69
+ const turn = agent.prompt("Update the index.");
70
+ for await (const _ of turn) {}
71
+ const result = await turn.result;
72
+ ```
73
+
74
+ A turn has one event consumer. Breaking out of its iterator cancels the turn;
75
+ `turn.cancel()` and `agent.close()` also release blocked output. Transport or
76
+ message-decoding failures reject the result instead of returning success with
77
+ missing text.
78
+
79
+ Native transport buffers at most 8 MiB of output bytes. Unread SDK events apply
80
+ backpressure at 1 MiB of encoded messages or 256 events. One message can exceed
81
+ that threshold when the queue is empty; an individual encoded ACP message is
82
+ limited to 64 MiB on both backends. These are transport bounds, not a total
83
+ answer-size limit or a bound on retained conversation history.
84
+
62
85
  Only one prompt may run at a time. `checkpoint()` is idle-only and returns
63
86
  opaque, bounded, versioned bytes. Restore them only when creating a fresh
64
87
  agent:
@@ -67,6 +90,9 @@ agent:
67
90
  const restored = await createFxAgent({ apiKey, model, checkpoint });
68
91
  ```
69
92
 
93
+ An already-aborted prompt signal returns `cancelled` without a model request
94
+ or a history change. The next prompt can run normally.
95
+
70
96
  The checkpoint contains conversation history and usage only. The host owns
71
97
  durable storage and must resupply models, credentials, instructions, tools,
72
98
  MCP clients, and skill records.
@@ -112,6 +138,9 @@ const agent = await createFxAgent({
112
138
 
113
139
  The JavaScript host is the authority for tool effects. The same descriptors,
114
140
  schemas, cancellation, results, and events are used by N-API and WebAssembly.
141
+ Cancelling a prompt aborts its tools' signals and stops waiting for their
142
+ callbacks. Late results and rejections are ignored. Tools remain responsible
143
+ for stopping their own work when their signal is aborted.
115
144
  Instructions are limited to 64 KiB of UTF-8 text, including text assembled by
116
145
  the MCP and skills adapters. They are the complete host-owned system context:
117
146
  libfx adds no hidden base prompt, and omitting `instructions` sends no system
@@ -120,7 +149,19 @@ message.
120
149
  ## MCP
121
150
 
122
151
  `libfx/mcp` accepts a host-owned MCP client. Transport, authentication,
123
- elicitation, and cleanup remain outside the kernel.
152
+ elicitation, and cleanup remain outside the kernel. The client uses the MCP
153
+ TypeScript SDK v1 signature: `callTool(params, resultSchema?, options?)`, with
154
+ cancellation passed in `options`. Tool text and structured data
155
+ reach the model together. PNG, JPEG, GIF, and WebP tool images reach models that
156
+ advertise image input support; other models receive an explicit omission notice.
157
+ Images are retained in checkpoints within the existing checkpoint size limit.
158
+ Each image may contain up to 5 MiB of base64 data, with at most eight images and
159
+ an 8 MiB result frame. Ordinary host tool objects remain JSON text. Resource and
160
+ prompt options supply text instructions; non-text context has an omission notice.
161
+ Tool catalogs are paginated up to the existing 64-tool bound. Tool names are
162
+ normalized for model APIs, with collisions kept distinct and original names used
163
+ for calls to the MCP client. Each tool description and JSON schema may contain up
164
+ to 64 KiB, within the control message's 8 MiB limit.
124
165
 
125
166
  ```js
126
167
  import { createMcpAdapter } from "libfx/mcp";
@@ -165,13 +206,97 @@ await createFxAgent({ apiKey, backend: "native" }); // require N-API
165
206
  await createFxAgent({ apiKey, backend: "wasm" }); // require Wasm + JSPI
166
207
  ```
167
208
 
168
- Within one JavaScript realm, libfx compiles each stable Wasm source once and
209
+ CommonJS applications can load the same Node API with `require("libfx")`. The
210
+ package chooses its generated CommonJS entry automatically and keeps asset
211
+ paths relative to the installed package.
212
+
213
+ Use `getBackendInfo()` to inspect backend availability without creating an
214
+ Agent or terminal:
215
+
216
+ ```js
217
+ import { getBackendInfo } from "libfx";
218
+
219
+ const info = await getBackendInfo({ surface: "agent", backend: "auto" });
220
+ // {
221
+ // surface: "agent",
222
+ // backend: "native" | "wasm-jspi" | "unavailable",
223
+ // attempts: [{ backend, available, reason }]
224
+ // }
225
+ ```
226
+
227
+ `surface` is `agent` by default and may also be `terminal`. `backend` has the
228
+ same `auto`, `native`, and `wasm` selection as the factories. `nativeAddon` and
229
+ `wasm` select explicit assets with the same meanings as their factory options.
230
+ The probe loads and validates the native module or compiles the selected Wasm
231
+ asset, then stops. It does not create a core, open a runtime socket, read
232
+ credentials, start a model request, or write session state. A remote Wasm
233
+ source can perform its normal asset fetch.
234
+
235
+ Expected environmental failures resolve as structured attempts. Invalid
236
+ options reject with `TypeError`. The stable reason codes are:
237
+
238
+ | Code | Meaning |
239
+ | --- | --- |
240
+ | `LIBFX_UNSUPPORTED_PLATFORM` | No packaged native addon supports the current platform and architecture. |
241
+ | `LIBFX_NATIVE_ARTIFACT_MISSING` | The selected native addon file is absent. |
242
+ | `LIBFX_NATIVE_LOAD_FAILED` | Node could not load the selected native addon. |
243
+ | `LIBFX_NATIVE_API_MISMATCH` | The addon API version is incompatible. |
244
+ | `LIBFX_NATIVE_SURFACE_MISSING` | The addon does not implement the selected Agent or terminal surface. |
245
+ | `LIBFX_NATIVE_DISABLED` | `nativeAddon: false` disabled native loading. |
246
+ | `LIBFX_JSPI_UNAVAILABLE` | The current JavaScript runtime does not provide JSPI. |
247
+ | `LIBFX_WASM_LOAD_FAILED` | The selected Wasm asset could not be loaded or compiled. |
248
+
249
+ The optional `causeCode` field retains a Node error code such as `ENOENT` or
250
+ `ERR_DLOPEN_FAILED` when one exists. Probe success establishes backend loading
251
+ only; it does not validate credentials, a future Agent initialization, or a
252
+ model request.
253
+
254
+ Within one loaded SDK module, libfx compiles each stable Wasm source once and
169
255
  creates a separate WebAssembly instance for every Agent. Agent memory, history,
170
256
  tools, cancellation, and shutdown remain isolated. Workers and separate
171
- processes maintain their own module caches.
257
+ processes maintain their own module caches, as do the ESM and CommonJS entries.
172
258
 
173
259
  Node.js 20+ is supported. Browser WebAssembly requires a JSPI-capable browser.
260
+ The Linux x64 and arm64 native addons require glibc 2.34 or newer. Native
261
+ agents do not require JSPI or experimental Node flags.
174
262
  Some Node versions require `--experimental-wasm-jspi`.
263
+ Bun 1.4.2 is the tested recommendation for Bun's WebAssembly backend.
264
+ Bun 1.3.14 can crash when a hot WebAssembly loop resumes through JSPI during
265
+ JIT tier-up.
266
+
267
+ ### Next.js and Vercel
268
+
269
+ Create agents in a server route using the Node.js runtime. Import `libfx`
270
+ normally; the package includes its native assets and exposes both ESM and
271
+ CommonJS Node entrypoints. No `serverExternalPackages` setting or manual native
272
+ file inclusion is required.
273
+
274
+ ```js
275
+ import { createFxAgent } from "libfx";
276
+
277
+ export const runtime = "nodejs";
278
+
279
+ export async function POST(request) {
280
+ const { prompt } = await request.json();
281
+ const agent = await createFxAgent({ apiKey: process.env.AI_GATEWAY_API_KEY });
282
+ try {
283
+ let text = "";
284
+ const turn = agent.prompt(prompt, { signal: request.signal });
285
+ for await (const event of turn) {
286
+ if (event.type === "text_delta") text += event.delta;
287
+ }
288
+ await turn.result;
289
+ return Response.json({ text });
290
+ } finally {
291
+ await agent.close();
292
+ }
293
+ }
294
+ ```
295
+
296
+ Use the application's normal authentication and request limits around the
297
+ route. JavaScript tools and MCP clients remain host-owned and must be supplied
298
+ when creating an agent, including after checkpoint restoration. The native
299
+ backend does not enable the CLI's built-in shell or filesystem tools.
175
300
 
176
301
  ## Interactive terminal
177
302
 
package/core-output.js ADDED
@@ -0,0 +1,58 @@
1
+ export const maxCoreMessageBytes = 64 * 1024 * 1024;
2
+
3
+ // One ordered writer per core; a pending handler holds admission of the next message.
4
+ export class CoreOutput {
5
+ constructor(handler) {
6
+ this.handler = handler;
7
+ this.decoder = new TextDecoder("utf-8", { fatal: true });
8
+ this.buffer = new Uint8Array(0);
9
+ this.bytes = 0;
10
+ this.closed = false;
11
+ }
12
+
13
+ write(chunk) {
14
+ let offset = 0;
15
+ const consume = () => {
16
+ while (offset < chunk.length) {
17
+ if (this.closed) throw new Error("core output is closed");
18
+ const newline = chunk.indexOf(10, offset);
19
+ const end = newline < 0 ? chunk.length : newline;
20
+ const fragment = chunk.subarray(offset, end);
21
+ const length = this.bytes + fragment.length;
22
+ if (length + (newline < 0 ? 0 : 1) > maxCoreMessageBytes) throw new RangeError("core output message exceeds 64 MiB");
23
+ let data = fragment;
24
+ if (this.bytes || newline < 0) {
25
+ if (length > this.buffer.length) {
26
+ const next = new Uint8Array(Math.min(maxCoreMessageBytes, Math.max(length, this.buffer.length * 2, 4096)));
27
+ next.set(this.buffer.subarray(0, this.bytes));
28
+ this.buffer = next;
29
+ }
30
+ this.buffer.set(fragment, this.bytes);
31
+ data = this.buffer.subarray(0, length);
32
+ }
33
+ this.bytes = length;
34
+ offset = newline < 0 ? end : end + 1;
35
+ if (newline < 0) return;
36
+ const line = this.decoder.decode(data);
37
+ const size = length + 1;
38
+ this.bytes = 0;
39
+ if (this.buffer.length > 1024 * 1024) this.buffer = new Uint8Array(0);
40
+ if (line) {
41
+ const pending = this.handler(JSON.parse(line), size);
42
+ if (pending) return Promise.resolve(pending).then(consume);
43
+ }
44
+ }
45
+ };
46
+ return consume();
47
+ }
48
+
49
+ finish() {
50
+ if (this.bytes) throw new Error("core output ended within a message");
51
+ }
52
+
53
+ close() {
54
+ this.closed = true;
55
+ this.buffer = new Uint8Array(0);
56
+ this.bytes = 0;
57
+ }
58
+ }
package/fx-core.wasm CHANGED
Binary file