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.
- package/LICENSE +21 -0
- package/README.md +348 -0
- package/dist/client.d.ts +74 -0
- package/dist/client.js +216 -0
- package/dist/client.js.map +1 -0
- package/dist/errors.d.ts +111 -0
- package/dist/errors.js +152 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/node/index.d.ts +28 -0
- package/dist/node/index.js +177 -0
- package/dist/node/index.js.map +1 -0
- package/dist/rpc.d.ts +37 -0
- package/dist/rpc.js +172 -0
- package/dist/rpc.js.map +1 -0
- package/dist/schema.d.ts +53 -0
- package/dist/schema.js +149 -0
- package/dist/schema.js.map +1 -0
- package/dist/session.d.ts +63 -0
- package/dist/session.js +202 -0
- package/dist/session.js.map +1 -0
- package/dist/tauri/index.d.ts +29 -0
- package/dist/tauri/index.js +80 -0
- package/dist/tauri/index.js.map +1 -0
- package/dist/tool.d.ts +39 -0
- package/dist/tool.js +48 -0
- package/dist/tool.js.map +1 -0
- package/dist/transport.d.ts +37 -0
- package/dist/transport.js +24 -0
- package/dist/transport.js.map +1 -0
- package/dist/types.d.ts +152 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/zod/index.d.ts +21 -0
- package/dist/zod/index.js +44 -0
- package/dist/zod/index.js.map +1 -0
- package/package.json +79 -0
- package/src/client.ts +312 -0
- package/src/errors.ts +170 -0
- package/src/index.ts +22 -0
- package/src/node/index.ts +203 -0
- package/src/rpc.ts +204 -0
- package/src/schema.ts +206 -0
- package/src/session.ts +290 -0
- package/src/tauri/index.ts +100 -0
- package/src/tool.ts +80 -0
- package/src/transport.ts +60 -0
- package/src/types.ts +142 -0
- 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
|
package/dist/client.d.ts
ADDED
|
@@ -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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"}
|