afm-bridge 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +348 -0
  3. package/dist/client.d.ts +74 -0
  4. package/dist/client.js +216 -0
  5. package/dist/client.js.map +1 -0
  6. package/dist/errors.d.ts +111 -0
  7. package/dist/errors.js +152 -0
  8. package/dist/errors.js.map +1 -0
  9. package/dist/index.d.ts +8 -0
  10. package/dist/index.js +8 -0
  11. package/dist/index.js.map +1 -0
  12. package/dist/node/index.d.ts +28 -0
  13. package/dist/node/index.js +177 -0
  14. package/dist/node/index.js.map +1 -0
  15. package/dist/rpc.d.ts +37 -0
  16. package/dist/rpc.js +172 -0
  17. package/dist/rpc.js.map +1 -0
  18. package/dist/schema.d.ts +53 -0
  19. package/dist/schema.js +149 -0
  20. package/dist/schema.js.map +1 -0
  21. package/dist/session.d.ts +63 -0
  22. package/dist/session.js +202 -0
  23. package/dist/session.js.map +1 -0
  24. package/dist/tauri/index.d.ts +29 -0
  25. package/dist/tauri/index.js +80 -0
  26. package/dist/tauri/index.js.map +1 -0
  27. package/dist/tool.d.ts +39 -0
  28. package/dist/tool.js +48 -0
  29. package/dist/tool.js.map +1 -0
  30. package/dist/transport.d.ts +37 -0
  31. package/dist/transport.js +24 -0
  32. package/dist/transport.js.map +1 -0
  33. package/dist/types.d.ts +152 -0
  34. package/dist/types.js +2 -0
  35. package/dist/types.js.map +1 -0
  36. package/dist/zod/index.d.ts +21 -0
  37. package/dist/zod/index.js +44 -0
  38. package/dist/zod/index.js.map +1 -0
  39. package/package.json +79 -0
  40. package/src/client.ts +312 -0
  41. package/src/errors.ts +170 -0
  42. package/src/index.ts +22 -0
  43. package/src/node/index.ts +203 -0
  44. package/src/rpc.ts +204 -0
  45. package/src/schema.ts +206 -0
  46. package/src/session.ts +290 -0
  47. package/src/tauri/index.ts +100 -0
  48. package/src/tool.ts +80 -0
  49. package/src/transport.ts +60 -0
  50. package/src/types.ts +142 -0
  51. package/src/zod/index.ts +53 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Miroslav Švarc
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,348 @@
1
+ # afm-bridge
2
+
3
+ Apple's on-device language model ([Foundation Models](https://developer.apple.com/documentation/foundationmodels))
4
+ for JavaScript and TypeScript apps: **Electron**, **Tauri 2** and plain **Node.js**.
5
+
6
+ - Streaming responses, multi-turn sessions, cancellation with `AbortSignal`
7
+ - Structured output from JSON Schema or Zod, validated and typed
8
+ - Tool calling: the model calls your JavaScript functions
9
+ - Image input (macOS 27)
10
+ - Typed errors with a reason, for example when Apple Intelligence is turned off
11
+ - No native Node addon: a small Swift sidecar talks JSON-RPC over stdin/stdout, and the
12
+ client is plain TypeScript with zero runtime dependencies
13
+
14
+ ```ts
15
+ import { AppleFoundationModels } from 'afm-bridge'
16
+ import { NodeStdioTransport } from 'afm-bridge/node'
17
+
18
+ const afm = new AppleFoundationModels({ transport: new NodeStdioTransport() })
19
+ const session = await afm.createSession({ instructions: 'Answer briefly.' })
20
+ for await (const chunk of session.stream('Write a haiku about the sea')) process.stdout.write(chunk.delta)
21
+ await afm.close()
22
+ ```
23
+
24
+ ## Requirements and limits
25
+
26
+ | | |
27
+ | --- | --- |
28
+ | Platform | macOS 26 or newer on Apple silicon. Intel Macs, Windows and Linux are not supported. |
29
+ | Apple Intelligence | Must be turned on in System Settings, and the model must be downloaded. |
30
+ | Context window | 8192 tokens on macOS 27, less on macOS 26 (4096 on 26.0 to 26.3), shared by instructions, history, prompt and answer. `availability()` reports the actual size. |
31
+ | Languages | 24 languages (English, German, French, Spanish, Italian, Portuguese, Japanese, Chinese, Korean, ...). Check `supportedLanguages`. **Czech, for example, is not supported.** |
32
+ | Model size | A small model: good at summarizing, extracting, classifying, rewriting and short generation. It is not a source of facts or world knowledge. |
33
+ | Guardrails | Apple's safety filters can reject prompts or answers (`GuardrailViolationError`). |
34
+ | Newer features | Image input, token usage, `toolCalling` and `reasoningLevel` options need macOS 27. |
35
+ | Private Cloud Compute | Not supported (needs a restricted Apple entitlement). `private-cloud` is always reported as unavailable. |
36
+
37
+ ## Install
38
+
39
+ ```bash
40
+ npm install afm-bridge
41
+ ```
42
+
43
+ The prebuilt sidecar comes in `afm-bridge-darwin-arm64`, an optional dependency that npm
44
+ installs only on macOS arm64. With pnpm, add `afm-bridge-darwin-arm64` as a direct
45
+ dependency if you need its path at build time (Electron, Tauri). For structured output with
46
+ Zod, also install `zod` (version 4).
47
+
48
+ Entry points:
49
+
50
+ | Import | Use |
51
+ | --- | --- |
52
+ | `afm-bridge` | Client, sessions, errors, schema and tool helpers. No Node APIs, works in a webview. |
53
+ | `afm-bridge/node` | `NodeStdioTransport`, `resolveSidecarPath()` (Node, Electron main process) |
54
+ | `afm-bridge/tauri` | `TauriSidecarTransport` (Tauri webview, needs `@tauri-apps/plugin-shell`) |
55
+ | `afm-bridge/zod` | `zodSchema()` (needs `zod`) |
56
+
57
+ ## Quick start: Electron
58
+
59
+ afm-bridge runs in the **main process**, where it can start the sidecar. The renderer talks
60
+ to it over IPC.
61
+
62
+ ```js
63
+ // main.js
64
+ import { AppleFoundationModels } from 'afm-bridge'
65
+ import { NodeStdioTransport } from 'afm-bridge/node'
66
+ import { app, ipcMain } from 'electron'
67
+
68
+ const afm = new AppleFoundationModels({ transport: new NodeStdioTransport() })
69
+ const session = afm.createSession({ instructions: 'Answer briefly.' })
70
+
71
+ ipcMain.handle('ask', async (event, prompt) => {
72
+ let text = ''
73
+ for await (const chunk of (await session).stream(prompt)) {
74
+ if (!chunk.done) event.sender.send('chunk', chunk.delta)
75
+ text = chunk.text
76
+ }
77
+ return text // the final text travels in the reply: IPC does not order `send` against `invoke`
78
+ })
79
+
80
+ app.on('will-quit', () => afm.close())
81
+ ```
82
+
83
+ **Packaging:** the sidecar must be outside `app.asar`. With electron-builder:
84
+
85
+ ```yaml
86
+ files:
87
+ - "!node_modules/afm-bridge-darwin-arm64/**"
88
+ extraResources:
89
+ - from: node_modules/afm-bridge-darwin-arm64/bin/afm-bridge-server
90
+ to: afm-bridge-server
91
+ mac:
92
+ hardenedRuntime: true
93
+ ```
94
+
95
+ `NodeStdioTransport` finds it at `Contents/Resources/afm-bridge-server` automatically.
96
+ Electron Forge (`extraResource`) and `asarUnpack` work too. The full example with preload,
97
+ renderer, both packagers and signing is in [examples/electron](examples/electron).
98
+
99
+ ## Quick start: Tauri 2
100
+
101
+ afm-bridge runs **in the webview** and starts the sidecar through the shell plugin.
102
+
103
+ 1. `npm install afm-bridge @tauri-apps/plugin-shell` and in `src-tauri`:
104
+ `cargo add tauri-plugin-shell`, then register `.plugin(tauri_plugin_shell::init())`.
105
+ 2. Copy `node_modules/afm-bridge-darwin-arm64/bin/afm-bridge-server` to
106
+ `src-tauri/binaries/afm-bridge-server-aarch64-apple-darwin` (Tauri needs the target triple
107
+ suffix; run the copy from `beforeDevCommand`/`beforeBuildCommand`) and declare it:
108
+ ```json
109
+ "bundle": {
110
+ "externalBin": ["binaries/afm-bridge-server"],
111
+ "macOS": { "minimumSystemVersion": "26.0", "hardenedRuntime": true }
112
+ }
113
+ ```
114
+ 3. Allow exactly this sidecar in `src-tauri/capabilities/default.json`:
115
+ ```json
116
+ "permissions": [
117
+ "core:default",
118
+ { "identifier": "shell:allow-spawn",
119
+ "allow": [{ "name": "binaries/afm-bridge-server", "sidecar": true, "args": false }] },
120
+ "shell:allow-stdin-write",
121
+ "shell:allow-kill"
122
+ ]
123
+ ```
124
+ 4. Use it in the frontend:
125
+ ```js
126
+ import { AppleFoundationModels } from 'afm-bridge'
127
+ import { TauriSidecarTransport } from 'afm-bridge/tauri'
128
+
129
+ const afm = new AppleFoundationModels({ transport: new TauriSidecarTransport() })
130
+ const session = await afm.createSession()
131
+ for await (const chunk of session.stream('Write a haiku')) output.textContent = chunk.text
132
+ ```
133
+
134
+ The full example, including a fix for `tauri build` on Xcode 27, is in
135
+ [examples/tauri](examples/tauri).
136
+
137
+ ## Usage
138
+
139
+ ### Availability
140
+
141
+ ```ts
142
+ const status = await afm.availability()
143
+ if (!status.available) {
144
+ // reason: 'appleIntelligenceNotEnabled' | 'modelNotReady' | 'deviceNotEligible'
145
+ // | 'unsupportedOS' | 'unsupportedPlatform' | ...
146
+ console.log(status.reason, status.message)
147
+ } else {
148
+ console.log(status.contextSize, status.supportedLanguages)
149
+ }
150
+ ```
151
+
152
+ `availability()` never throws because the model is unavailable. Other calls throw
153
+ `ModelUnavailableError` with the same `reason`.
154
+
155
+ ### Sessions
156
+
157
+ A session is one conversation; the sidecar keeps its history. Only one `respond` or
158
+ `stream` can run in a session at a time (a second one fails with `SessionBusyError`), but
159
+ many sessions can generate in parallel.
160
+
161
+ ```ts
162
+ const session = await afm.createSession({
163
+ instructions: 'You are a helpful assistant.',
164
+ guardrails: 'default', // or 'permissiveContentTransformations'
165
+ })
166
+
167
+ const text = await session.respond('Summarize: ...', {
168
+ temperature: 0.3,
169
+ maximumResponseTokens: 200,
170
+ sampling: { mode: 'topP', threshold: 0.9, seed: 42 }, // or { mode: 'greedy' } / { mode: 'topK', k: 40 }
171
+ })
172
+
173
+ const { text: answer, usage } = await session.generate('...') // usage: token counts (macOS 27)
174
+ const info = await session.info() // { contextSize, historyTokenCount, usage, isResponding }
175
+ await session.prewarm() // load the model ahead of the first prompt
176
+ await session.dispose()
177
+ ```
178
+
179
+ ### Streaming and cancellation
180
+
181
+ ```ts
182
+ const controller = new AbortController()
183
+ for await (const chunk of session.stream('Tell me a story', { signal: controller.signal })) {
184
+ if (chunk.done) console.log('\n', chunk.usage)
185
+ else process.stdout.write(chunk.delta) // chunk.text holds the full text so far
186
+ }
187
+ ```
188
+
189
+ Aborting rejects with `AbortError` and stops the generation in the sidecar; the session stays
190
+ usable. Breaking out of the loop cancels too. `timeoutMs` sets a client-side timeout for `respond`, `generate`, `stream` and the other calls that take options. In rare cases
191
+ the model rewrites earlier output: the chunk then has `reset: true` and `text` replaces what
192
+ you showed.
193
+
194
+ ### Structured output
195
+
196
+ Pass a JSON Schema or a Zod schema. The model is constrained to the schema, and the result is
197
+ validated before it is returned.
198
+
199
+ ```ts
200
+ import { zodSchema } from 'afm-bridge/zod'
201
+ import { z } from 'zod'
202
+
203
+ const Book = z.object({
204
+ title: z.string(),
205
+ year: z.number().int().min(1900).max(2030),
206
+ genre: z.enum(['fantasy', 'mystery', 'other']),
207
+ subtitle: z.string().nullable(),
208
+ })
209
+
210
+ const book = await session.respond('Invent a mystery novel', { schema: zodSchema(Book) }) // typed
211
+
212
+ for await (const chunk of session.stream('Invent a novel', { schema: zodSchema(Book) })) {
213
+ if (!chunk.done) render(chunk.partial) // partial object while it streams
214
+ }
215
+ ```
216
+
217
+ Plain JSON Schema works the same (`{ schema: { type: 'object', properties: ... } }`, or
218
+ `jsonSchema<T>(schema)` for a typed result). Supported: objects (`properties`, `required`,
219
+ `additionalProperties: false`), strings with `enum`, `const` and simple `pattern`, `integer`
220
+ and `number` with `minimum`/`maximum`, `boolean`, arrays with `items`/`minItems`/`maxItems`,
221
+ `anyOf`/`oneOf`, nullable properties, `$defs`/`$ref` including recursion. Anything else
222
+ (`maxLength`, `exclusiveMinimum`, ...) fails early with `UnsupportedSchemaError` and a JSON
223
+ pointer. The on-device model accepts only simple regex patterns (`\d{3}` works, `[a-z]+` does
224
+ not); `zodSchema()` therefore leaves out the regex Zod adds for `.email()` or `.uuid()` and lets
225
+ Zod check the format after generation.
226
+
227
+ ### Tools
228
+
229
+ ```ts
230
+ import { tool } from 'afm-bridge'
231
+
232
+ const getWeather = tool({
233
+ name: 'getWeather',
234
+ description: 'Get the current temperature for a city',
235
+ parameters: zodSchema(z.object({ city: z.string() })),
236
+ execute: async ({ city }, { signal }) => fetchWeather(city, { signal }), // any JSON value or string
237
+ })
238
+
239
+ const session = await afm.createSession({ tools: [getWeather] })
240
+ await session.respond('Is it warmer in Prague or in Oslo?') // may call getWeather twice
241
+ ```
242
+
243
+ Arguments are validated before `execute` runs. A tool that throws or times out (`timeoutMs`,
244
+ default 60 s) fails the generation with `ToolExecutionError`. Cancelling the generation aborts
245
+ the tool's `signal`.
246
+
247
+ ### Images (macOS 27)
248
+
249
+ ```ts
250
+ await session.respond([
251
+ { type: 'text', text: 'What is in this picture?' },
252
+ { type: 'image', path: '/Users/me/photo.jpg' }, // or { type: 'image', data: base64 }
253
+ ])
254
+ ```
255
+
256
+ ### Errors
257
+
258
+ Every error extends `AfmError` with a stable `type`, and `data` with details.
259
+
260
+ | Class | When |
261
+ | --- | --- |
262
+ | `ModelUnavailableError` | Model cannot be used; see `reason` |
263
+ | `ContextWindowExceededError` | Instructions, history and prompt do not fit (`contextSize`) |
264
+ | `GuardrailViolationError`, `RefusalError` | Safety filters rejected the prompt or answer |
265
+ | `UnsupportedLanguageError` | The prompt's language is not supported |
266
+ | `UnsupportedSchemaError` | The schema uses something outside the supported subset |
267
+ | `DecodingError` | The generated value did not validate (`data.path`) |
268
+ | `ToolExecutionError` | A tool threw, timed out or got invalid arguments |
269
+ | `SessionBusyError` | Another response is running in this session |
270
+ | `AbortError`, `RequestTimeoutError` | Cancelled by `signal` / `timeoutMs` |
271
+ | `UnsupportedOSError` | A feature needs a newer macOS |
272
+ | `SidecarCrashedError` | The sidecar stopped and could not be restarted |
273
+ | `SessionLostError` | The session belonged to a sidecar process that crashed; create a new session |
274
+ | `TransportError` | The sidecar binary was not found or could not start |
275
+
276
+ ### Process lifecycle
277
+
278
+ The sidecar starts on the first call. If it crashes, the next call starts a new process
279
+ (at most 3 restarts per minute, configurable with `restart`); sessions of the crashed process
280
+ reject with `SessionLostError`. `close()` stops it. If your app exits or is killed without
281
+ `close()`, the sidecar notices that its stdin closed and exits as well. In Node, an idle sidecar
282
+ does not keep the process alive.
283
+
284
+ ```ts
285
+ new AppleFoundationModels({
286
+ transport: new NodeStdioTransport({ binaryPath, logLevel: 'debug' }),
287
+ restart: { maxRestarts: 3, windowMs: 60_000 },
288
+ requestTimeoutMs: 30_000,
289
+ onLog: (line) => console.error(line), // sidecar logs (stderr)
290
+ })
291
+ ```
292
+
293
+ `resolveSidecarPath()` looks for the binary in `AFM_BRIDGE_BINARY`, then
294
+ `process.resourcesPath` (packaged Electron app), then the `afm-bridge-darwin-arm64` package.
295
+
296
+ ### Custom transports
297
+
298
+ `Transport` is a public interface: start the sidecar anywhere that can pass lines of text,
299
+ for example behind your own IPC layer. See `NodeStdioTransport` for a reference implementation.
300
+
301
+ ## Signing and notarization
302
+
303
+ afm-bridge is a library; the host app signs the sidecar together with everything else:
304
+
305
+ - Sign the nested `afm-bridge-server` with your identity and **hardened runtime**. electron-builder,
306
+ Electron Forge and `tauri build` do this when signing is configured.
307
+ - The sidecar needs **no entitlements**. Inheriting the app's entitlements is harmless.
308
+ - Notarize the app as usual (Developer ID). Details and verification commands are in the
309
+ example READMEs.
310
+
311
+ ## Build from source
312
+
313
+ Requires Xcode 27 (the sidecar uses the macOS 27 SDK, and runs on macOS 26+).
314
+
315
+ ```bash
316
+ pnpm install
317
+ scripts/build-sidecar.sh # release build to packages/darwin-arm64/bin, signed ad hoc
318
+ pnpm build # TypeScript client
319
+ # or just the sidecar:
320
+ swift build -c release --package-path swift
321
+ ```
322
+
323
+ Point `NodeStdioTransport({ binaryPath })` or `AFM_BRIDGE_BINARY` at your build.
324
+
325
+ ## Development
326
+
327
+ ```
328
+ swift/ Swift package: afm-bridge-server + AFMBridgeCore (protocol, schema conversion)
329
+ packages/client/ npm package afm-bridge
330
+ packages/darwin-arm64/ npm package with the prebuilt sidecar
331
+ examples/node/ chat, structured output and tool calling from the terminal
332
+ examples/electron/ Electron app (installed with npm from packed tarballs, see its README)
333
+ examples/tauri/ Tauri 2 app
334
+ ```
335
+
336
+ ```bash
337
+ pnpm lint # Biome + swift format
338
+ pnpm test # TypeScript unit tests (mock sidecar)
339
+ pnpm test:swift # Swift unit tests
340
+ AFM_INTEGRATION=1 pnpm test # also run tests against the real model
341
+ ```
342
+
343
+ Release note: publish `afm-bridge-darwin-arm64` with `npm publish` (pnpm 8 drops the
344
+ executable bit of the binary) and `afm-bridge` with `pnpm publish` (rewrites `workspace:*`).
345
+
346
+ ## License
347
+
348
+ MIT
@@ -0,0 +1,74 @@
1
+ import { Session } from './session.js';
2
+ import type { Transport } from './transport.js';
3
+ import type { CallOptions, ModelId, ModelStatus, ServerInfo, SessionOptions } from './types.js';
4
+ /** Protocol version this client speaks. See PROTOCOL.md section 3. */
5
+ export declare const PROTOCOL_VERSION = 1;
6
+ export declare const CLIENT_VERSION = "0.1.0";
7
+ export interface AppleFoundationModelsOptions {
8
+ /** How to reach the sidecar, e.g. `new NodeStdioTransport()` from `afm-bridge/node`. */
9
+ transport: Transport;
10
+ /** Reported to the sidecar in `initialize` (shows up in its debug log). */
11
+ clientInfo?: {
12
+ name: string;
13
+ version: string;
14
+ };
15
+ /**
16
+ * Automatic restart after the sidecar crashes. The process is restarted on the next
17
+ * call; after `maxRestarts` crashes within `windowMs` calls fail with `SidecarCrashedError`
18
+ * until the window passes. Default: 3 restarts per 60 s.
19
+ */
20
+ restart?: {
21
+ maxRestarts?: number;
22
+ windowMs?: number;
23
+ };
24
+ /** Timeout for starting the sidecar and the `initialize` handshake. Default 10 s. */
25
+ initializeTimeoutMs?: number;
26
+ /** Default client-side timeout for every call. Default: none (use `AbortSignal`). */
27
+ requestTimeoutMs?: number;
28
+ /** Receives every stderr line of the sidecar. */
29
+ onLog?: (line: string) => void;
30
+ }
31
+ /**
32
+ * Entry point of afm-bridge. The sidecar starts lazily on the first call.
33
+ *
34
+ * ```ts
35
+ * const afm = new AppleFoundationModels({ transport: new NodeStdioTransport() })
36
+ * const session = await afm.createSession({ instructions: 'Answer briefly.' })
37
+ * console.log(await session.respond('Hello'))
38
+ * await afm.close()
39
+ * ```
40
+ */
41
+ export declare class AppleFoundationModels {
42
+ private readonly transport;
43
+ private readonly options;
44
+ private live;
45
+ private starting;
46
+ private epoch;
47
+ private crashes;
48
+ private closed;
49
+ private readonly logTail;
50
+ private readonly host;
51
+ constructor(options: AppleFoundationModelsOptions);
52
+ /** Handshake result: server version, macOS version and status of every model. Starts the sidecar. */
53
+ serverInfo(): Promise<ServerInfo>;
54
+ /**
55
+ * Fresh availability of a model. Never throws because the model is unavailable:
56
+ * check `available` and `reason` instead.
57
+ */
58
+ availability(model?: ModelId, options?: CallOptions): Promise<ModelStatus>;
59
+ /** Number of tokens `text` occupies in the model's context (macOS 26.4+). */
60
+ tokenCount(text: string, options?: CallOptions & {
61
+ model?: ModelId;
62
+ }): Promise<number>;
63
+ /** Creates a conversation. Throws `ModelUnavailableError` (with `reason`) when the model cannot be used. */
64
+ createSession(options?: SessionOptions): Promise<Session>;
65
+ /** Stops the sidecar. Pending calls reject with `ClientClosedError`. The client cannot be reused. */
66
+ close(): Promise<void>;
67
+ private request;
68
+ private requestInEpoch;
69
+ private connect;
70
+ private start;
71
+ private handleClose;
72
+ private crashError;
73
+ private formatLogTail;
74
+ }
package/dist/client.js ADDED
@@ -0,0 +1,216 @@
1
+ import { AfmError, ClientClosedError, ModelUnavailableError, ProtocolError, RequestTimeoutError, SessionLostError, SidecarCrashedError, } from './errors.js';
2
+ import { RpcPeer } from './rpc.js';
3
+ import { Session } from './session.js';
4
+ import { runTool, toolDefinition } from './tool.js';
5
+ /** Protocol version this client speaks. See PROTOCOL.md section 3. */
6
+ export const PROTOCOL_VERSION = 1;
7
+ export const CLIENT_VERSION = '0.1.0';
8
+ const LOG_TAIL = 20;
9
+ /**
10
+ * Entry point of afm-bridge. The sidecar starts lazily on the first call.
11
+ *
12
+ * ```ts
13
+ * const afm = new AppleFoundationModels({ transport: new NodeStdioTransport() })
14
+ * const session = await afm.createSession({ instructions: 'Answer briefly.' })
15
+ * console.log(await session.respond('Hello'))
16
+ * await afm.close()
17
+ * ```
18
+ */
19
+ export class AppleFoundationModels {
20
+ transport;
21
+ options;
22
+ live;
23
+ starting;
24
+ epoch = 0;
25
+ crashes = [];
26
+ closed = false;
27
+ logTail = [];
28
+ host;
29
+ constructor(options) {
30
+ this.transport = options.transport;
31
+ this.options = options;
32
+ this.host = {
33
+ request: (epoch, method, params, opts) => this.requestInEpoch(epoch, method, params, opts),
34
+ listen: (epoch, requestId, listener) => {
35
+ const slot = this.live?.epoch === epoch ? this.live.slot : undefined;
36
+ slot?.listeners.set(requestId, listener);
37
+ return () => slot?.listeners.delete(requestId);
38
+ },
39
+ disposed: (epoch, sessionId) => {
40
+ if (this.live?.epoch === epoch)
41
+ this.live.slot.tools.delete(sessionId);
42
+ },
43
+ };
44
+ }
45
+ /** Handshake result: server version, macOS version and status of every model. Starts the sidecar. */
46
+ async serverInfo() {
47
+ return (await this.connect()).info;
48
+ }
49
+ /**
50
+ * Fresh availability of a model. Never throws because the model is unavailable:
51
+ * check `available` and `reason` instead.
52
+ */
53
+ async availability(model = 'on-device', options = {}) {
54
+ try {
55
+ return await this.request('model/status', { model }, options);
56
+ }
57
+ catch (error) {
58
+ if (error instanceof ModelUnavailableError) {
59
+ return { available: false, reason: error.reason, message: error.message };
60
+ }
61
+ throw error;
62
+ }
63
+ }
64
+ /** Number of tokens `text` occupies in the model's context (macOS 26.4+). */
65
+ async tokenCount(text, options = {}) {
66
+ const { model, ...call } = options;
67
+ const result = await this.request('model/tokenCount', { text, ...(model ? { model } : {}) }, call);
68
+ return result.tokenCount;
69
+ }
70
+ /** Creates a conversation. Throws `ModelUnavailableError` (with `reason`) when the model cannot be used. */
71
+ async createSession(options = {}) {
72
+ const live = await this.connect();
73
+ const { signal, tools, ...params } = options;
74
+ const result = await this.requestInEpoch(live.epoch, 'session/create', tools?.length ? { ...params, tools: tools.map(toolDefinition) } : params, { signal });
75
+ if (tools?.length)
76
+ live.slot.tools.set(result.sessionId, new Map(tools.map((t) => [t.name, t])));
77
+ return new Session(this.host, live.epoch, result.sessionId, result.contextSize, options.model ?? 'on-device');
78
+ }
79
+ /** Stops the sidecar. Pending calls reject with `ClientClosedError`. The client cannot be reused. */
80
+ async close() {
81
+ if (this.closed)
82
+ return;
83
+ this.closed = true;
84
+ const live = this.live ?? (await this.starting?.catch(() => undefined));
85
+ this.live = undefined;
86
+ if (!live)
87
+ return;
88
+ live.slot.intentional = true;
89
+ try {
90
+ await live.peer.request('shutdown', {}, { timeoutMs: 2000, cancelGraceMs: 0 });
91
+ }
92
+ catch {
93
+ // The process is killed below anyway.
94
+ }
95
+ live.peer.close(new ClientClosedError('The afm-bridge client was closed'));
96
+ await live.connection.close();
97
+ }
98
+ // MARK: - Internals
99
+ async request(method, params, options = {}) {
100
+ const live = await this.connect();
101
+ return this.requestInEpoch(live.epoch, method, params, options);
102
+ }
103
+ requestInEpoch(epoch, method, params, options = {}) {
104
+ if (this.closed)
105
+ return Promise.reject(new ClientClosedError('The afm-bridge client was closed'));
106
+ const live = this.live;
107
+ if (!live || live.epoch !== epoch) {
108
+ return Promise.reject(new SessionLostError('This session belonged to an afm-bridge-server process that is no longer running (it crashed or was restarted). Create a new session.', { type: 'sessionLost' }));
109
+ }
110
+ return live.peer.request(method, params, {
111
+ ...options,
112
+ timeoutMs: options.timeoutMs ?? this.options.requestTimeoutMs,
113
+ });
114
+ }
115
+ connect() {
116
+ if (this.closed)
117
+ return Promise.reject(new ClientClosedError('The afm-bridge client was closed'));
118
+ if (this.live)
119
+ return Promise.resolve(this.live);
120
+ if (!this.starting) {
121
+ this.starting = this.start().finally(() => {
122
+ this.starting = undefined;
123
+ });
124
+ }
125
+ return this.starting;
126
+ }
127
+ async start() {
128
+ const { maxRestarts = 3, windowMs = 60_000 } = this.options.restart ?? {};
129
+ const now = Date.now();
130
+ this.crashes = this.crashes.filter((t) => now - t < windowMs);
131
+ if (this.crashes.length > maxRestarts) {
132
+ throw new SidecarCrashedError(`afm-bridge-server crashed ${this.crashes.length} times within ${Math.round(windowMs / 1000)} s; not restarting it again yet.${this.formatLogTail()}`, { type: 'sidecarCrashed', data: { crashes: this.crashes.length } });
133
+ }
134
+ const epoch = ++this.epoch;
135
+ const slot = { intentional: false, listeners: new Map(), tools: new Map() };
136
+ const connection = await this.transport.connect({
137
+ onMessage: (line) => slot.peer?.handleLine(line),
138
+ onLog: (line) => {
139
+ this.logTail.push(line);
140
+ if (this.logTail.length > LOG_TAIL)
141
+ this.logTail.shift();
142
+ this.options.onLog?.(line);
143
+ },
144
+ onClose: (info) => this.handleClose(epoch, slot, info),
145
+ });
146
+ const peer = new RpcPeer(connection, {
147
+ onNotification: (method, params) => {
148
+ if (method === 'session/delta') {
149
+ const p = params;
150
+ slot.listeners.get(p.requestId)?.(p);
151
+ }
152
+ },
153
+ onRequest: async (method, params, signal) => {
154
+ if (method !== 'tool/call') {
155
+ throw new AfmError(`Method not found: ${method}`, { type: 'methodNotFound', code: -32601 });
156
+ }
157
+ const { sessionId, name, arguments: args, } = params;
158
+ const found = slot.tools.get(sessionId)?.get(name);
159
+ if (!found)
160
+ throw new AfmError(`Unknown tool '${name}'`, { type: 'toolNotFound', code: -32000 });
161
+ return runTool(found, args, { signal, sessionId });
162
+ },
163
+ onPendingChange: (count) => connection.setActive?.(count > 0),
164
+ onProtocolError: (error) => this.options.onLog?.(`[afm-bridge client] ${error.message}`),
165
+ });
166
+ slot.peer = peer;
167
+ if (slot.exited)
168
+ peer.close(this.crashError(slot.exited));
169
+ try {
170
+ const info = await peer.request('initialize', {
171
+ protocolVersion: PROTOCOL_VERSION,
172
+ client: this.options.clientInfo ?? { name: 'afm-bridge', version: CLIENT_VERSION },
173
+ }, { timeoutMs: this.options.initializeTimeoutMs ?? 10_000, cancelGraceMs: 0 });
174
+ if (info.protocolVersion !== PROTOCOL_VERSION) {
175
+ throw new ProtocolError(`afm-bridge-server ${info.server.version} speaks protocol version ${info.protocolVersion}, this client needs ${PROTOCOL_VERSION}. Install matching versions of afm-bridge and afm-bridge-server.`, { type: 'protocolVersionMismatch' });
176
+ }
177
+ if (this.closed)
178
+ throw new ClientClosedError('The afm-bridge client was closed');
179
+ const live = { epoch, peer, connection, info, slot };
180
+ this.live = live;
181
+ return live;
182
+ }
183
+ catch (error) {
184
+ slot.intentional = true;
185
+ peer.close(error instanceof Error ? error : new Error(String(error)));
186
+ await connection.close().catch(() => { });
187
+ if (error instanceof RequestTimeoutError) {
188
+ throw new SidecarCrashedError(`afm-bridge-server did not answer the initialize handshake within ${this.options.initializeTimeoutMs ?? 10_000} ms.${this.formatLogTail()}`, { type: 'sidecarCrashed', cause: error });
189
+ }
190
+ throw error;
191
+ }
192
+ }
193
+ handleClose(epoch, slot, info) {
194
+ slot.exited = info;
195
+ if (this.live?.epoch === epoch)
196
+ this.live = undefined;
197
+ if (slot.intentional || this.closed) {
198
+ slot.peer?.close(new ClientClosedError('The afm-bridge client was closed'));
199
+ return;
200
+ }
201
+ this.crashes.push(Date.now());
202
+ slot.peer?.close(this.crashError(info));
203
+ }
204
+ crashError(info) {
205
+ const how = info.signal ? `signal ${info.signal}` : `exit code ${info.code}`;
206
+ return new SidecarCrashedError(`afm-bridge-server stopped unexpectedly (${how}).${this.formatLogTail()}`, {
207
+ type: 'sidecarCrashed',
208
+ data: { exitCode: info.code, signal: info.signal },
209
+ cause: info.error,
210
+ });
211
+ }
212
+ formatLogTail() {
213
+ return this.logTail.length > 0 ? `\nLast log lines:\n${this.logTail.join('\n')}` : '';
214
+ }
215
+ }
216
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}