apple-llm 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 +176 -0
- package/dist/chunk-FQTRQ3KP.js +1590 -0
- package/dist/cli.cjs +2027 -0
- package/dist/cli.js +456 -0
- package/dist/index.cjs +1667 -0
- package/dist/index.d.cts +690 -0
- package/dist/index.d.ts +690 -0
- package/dist/index.js +80 -0
- package/package.json +61 -0
- package/swift/helper.swift +900 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,690 @@
|
|
|
1
|
+
interface Progress {
|
|
2
|
+
status: string;
|
|
3
|
+
percent?: number;
|
|
4
|
+
}
|
|
5
|
+
type OnProgress = (p: Progress) => void;
|
|
6
|
+
/**
|
|
7
|
+
* Where the compiled helper is cached.
|
|
8
|
+
*
|
|
9
|
+
* Deliberately not namespaced per language binding: the npm and the pip package
|
|
10
|
+
* embed byte-identical Swift, so they derive the same fingerprint and share one
|
|
11
|
+
* compiled binary. Installing both costs one compile, not two.
|
|
12
|
+
*/
|
|
13
|
+
declare function cacheDir(): string;
|
|
14
|
+
/**
|
|
15
|
+
* The cache key: sha256 over the Swift source, a newline, and the target triple,
|
|
16
|
+
* truncated to 12 hex characters.
|
|
17
|
+
*
|
|
18
|
+
* The exact recipe is load-bearing — the Python package computes the same string
|
|
19
|
+
* and must agree byte for byte, or the two would each compile their own copy.
|
|
20
|
+
* A helper edit or an OS upgrade changes it, so both rebuild automatically.
|
|
21
|
+
*/
|
|
22
|
+
declare function fingerprint(source: string, triple: string): string;
|
|
23
|
+
/** The embedded Swift source, shipped as package data beside the built output. */
|
|
24
|
+
declare function helperSource(): Promise<string>;
|
|
25
|
+
/**
|
|
26
|
+
* Compile the helper on first use and cache it. Never called at install time —
|
|
27
|
+
* importing this package on Linux, an Intel Mac or macOS 25 must not fail.
|
|
28
|
+
*
|
|
29
|
+
* `force` bypasses the in-process memo, so a caller can rebuild after the cached
|
|
30
|
+
* binary is removed or found corrupt without restarting the program.
|
|
31
|
+
*
|
|
32
|
+
* The memo is fingerprint-aware: a helper edit or OS upgrade changes the
|
|
33
|
+
* fingerprint, so a stale in-process memo is discarded rather than reused.
|
|
34
|
+
*/
|
|
35
|
+
declare function ensureBinary(onProgress?: OnProgress, options?: {
|
|
36
|
+
force?: boolean;
|
|
37
|
+
}): Promise<string>;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Apple's `GenerationSchema` is `Decodable` from JSON Schema, but only accepts a
|
|
41
|
+
* restricted dialect. These rules were established empirically in api-scribe, by
|
|
42
|
+
* decoding a real schema against macOS 26 and 27 until it was accepted:
|
|
43
|
+
*
|
|
44
|
+
* 1. Union types are rejected — `type: ['string','null']` fails with
|
|
45
|
+
* "Expected value of type String". Nullability is expressed by leaving the
|
|
46
|
+
* property out of `required` instead.
|
|
47
|
+
* 2. Every object schema requires an `x-order` array naming its properties in
|
|
48
|
+
* generation order. Omitting it fails with "Key 'x-order' not found".
|
|
49
|
+
* 3. `enum` is not recognized. A node may only carry `type`, `const`, `$ref` or
|
|
50
|
+
* `anyOf`, so enums become `anyOf` of `const` branches.
|
|
51
|
+
* 4. Every object schema *and* every `anyOf` schema requires a `title`, and
|
|
52
|
+
* `$ref` resolves by that title rather than by JSON pointer — so a
|
|
53
|
+
* definition's title must equal the key it is referenced by.
|
|
54
|
+
* 5. Every object schema must state `additionalProperties`; omitting it fails
|
|
55
|
+
* with "Key 'additionalProperties' not found". macOS 26's decoder tolerated
|
|
56
|
+
* its absence, so this one only surfaced on macOS 27.
|
|
57
|
+
*
|
|
58
|
+
* 6. `const` is decoded as a String, always. A numeric member (`const: 200`)
|
|
59
|
+
* fails with "Expected value of type String". Stringifying it is accepted
|
|
60
|
+
* but changes the output type — the model then emits `"200"` rather than
|
|
61
|
+
* `200` — so a numeric enum is converted to its underlying type instead and
|
|
62
|
+
* the literal constraint is dropped. api-scribe reached the same conclusion
|
|
63
|
+
* the hard way: it settled on a plain `integer` for HTTP status after a
|
|
64
|
+
* string-typed union produced junk like `": 201"`.
|
|
65
|
+
* 7. An object with no properties still needs an explicit `properties: {}`.
|
|
66
|
+
* Without it Apple reads `additionalProperties` in its other JSON Schema
|
|
67
|
+
* sense — a schema for the values — and fails with "Expected value of type
|
|
68
|
+
* Dictionary<String, Any>". Supplying a dictionary there is accepted but
|
|
69
|
+
* turns the field into a free-form map the model fills with invented keys.
|
|
70
|
+
*
|
|
71
|
+
* 8. A `title` on a `type: "string"` node is rejected: Apple reads it as a
|
|
72
|
+
* "named string type" and fails with "Named string types must have a
|
|
73
|
+
* non-empty enum field". Other primitives (integer, number, boolean) and
|
|
74
|
+
* arrays accept a title happily. Titles are therefore stripped from plain
|
|
75
|
+
* strings. This matters most for pydantic, whose `model_json_schema()`
|
|
76
|
+
* titles every single property.
|
|
77
|
+
*
|
|
78
|
+
* Rules 6, 7 and 8 were found by decoding this package's own fixture corpus
|
|
79
|
+
* against macOS 27. api-scribe hit none of them: it had only string enums, no
|
|
80
|
+
* empty objects, and hand-written schemas that never titled a string.
|
|
81
|
+
*
|
|
82
|
+
* `$ref` / `$defs` are otherwise supported and pass through untouched.
|
|
83
|
+
*
|
|
84
|
+
* Why this matters more than it looks: constrained decoding makes a schema
|
|
85
|
+
* mistake invisible but total. Collapsing a `["number","string"]` union to
|
|
86
|
+
* `"string"` made it physically impossible for the model to emit a status code —
|
|
87
|
+
* it wrote `": 201"` and the literal `"default"` instead. Never collapse a
|
|
88
|
+
* multi-type union to one branch; convert it to `anyOf`.
|
|
89
|
+
*/
|
|
90
|
+
type JsonSchema = Record<string, unknown>;
|
|
91
|
+
/**
|
|
92
|
+
* Translate a standard JSON Schema into the dialect Apple's `GenerationSchema`
|
|
93
|
+
* accepts. Constrained decoding then makes the shape of the reply a guarantee
|
|
94
|
+
* rather than a request.
|
|
95
|
+
*/
|
|
96
|
+
declare function toAppleSchema(schema: JsonSchema, rootName?: string): JsonSchema;
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Sampling temperature. Not zero on purpose.
|
|
100
|
+
*
|
|
101
|
+
* Constrained decoding already guarantees the schema, so greedy decoding buys
|
|
102
|
+
* nothing and reliably degenerates: at 0 the model padded an unbounded array
|
|
103
|
+
* forever, then ran away *inside a single string*, emitting 2.7KB of
|
|
104
|
+
* "tasks-tasks-tasks-…". A 2s call became 20s. Apple honours `maxItems` but
|
|
105
|
+
* ignores `maxLength`, so bounding strings is not available as a fix — a little
|
|
106
|
+
* sampling is. Nothing about this is a quality/creativity tradeoff.
|
|
107
|
+
*/
|
|
108
|
+
declare const DEFAULT_TEMPERATURE = 0.4;
|
|
109
|
+
/**
|
|
110
|
+
* Cap on generated tokens. Left uncapped, the model occasionally runs away and
|
|
111
|
+
* only stops when it exhausts the context window — one observed run burned
|
|
112
|
+
* ~206s before failing. This bounds that to well under a minute while leaving
|
|
113
|
+
* room for a few paragraphs of prose.
|
|
114
|
+
*/
|
|
115
|
+
declare const DEFAULT_MAX_TOKENS = 2048;
|
|
116
|
+
/** What the model can actually do, as reported by the framework (macOS 27+). */
|
|
117
|
+
interface ModelCapabilities {
|
|
118
|
+
vision: boolean;
|
|
119
|
+
guidedGeneration: boolean;
|
|
120
|
+
reasoning: boolean;
|
|
121
|
+
toolCalling: boolean;
|
|
122
|
+
}
|
|
123
|
+
/** Siri-parity feature flags reported by --probe (absent on older helpers). */
|
|
124
|
+
interface HelperFeatures {
|
|
125
|
+
streaming: boolean;
|
|
126
|
+
sessions: boolean;
|
|
127
|
+
history: boolean;
|
|
128
|
+
labelledAttachments: boolean;
|
|
129
|
+
builtInTools: string[];
|
|
130
|
+
}
|
|
131
|
+
/** An image attachment: a bare path, or a path with a Siri-style label. */
|
|
132
|
+
type ImageAttachment = string | {
|
|
133
|
+
path: string;
|
|
134
|
+
label?: string;
|
|
135
|
+
};
|
|
136
|
+
/** Apple's built-in on-device tools (all macOS 27+, all local). */
|
|
137
|
+
type BuiltInTool = 'ocr' | 'barcode' | 'spotlight';
|
|
138
|
+
/** A history turn mirrored by the helper for a named session. */
|
|
139
|
+
interface HistoryTurn {
|
|
140
|
+
role: string;
|
|
141
|
+
content: string;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Private Cloud Compute quota, read from the framework without calling it.
|
|
145
|
+
*
|
|
146
|
+
* PCC *inference* needs an entitlement no installable package can ship, which is
|
|
147
|
+
* why the cloud tier goes through Shortcuts — but `quotaUsage` is readable from
|
|
148
|
+
* an unentitled process, so this is real first-party quota state rather than a
|
|
149
|
+
* guess parsed out of an error message.
|
|
150
|
+
*/
|
|
151
|
+
interface CloudQuota {
|
|
152
|
+
isAvailable: boolean;
|
|
153
|
+
status: 'belowLimit' | 'limitReached' | 'unknown';
|
|
154
|
+
approachingLimit?: boolean;
|
|
155
|
+
resetDate?: string;
|
|
156
|
+
}
|
|
157
|
+
interface DeviceProbe {
|
|
158
|
+
available: boolean;
|
|
159
|
+
reason?: string;
|
|
160
|
+
contextSize?: number;
|
|
161
|
+
variant?: string;
|
|
162
|
+
/** macOS 27+ only; absent on macOS 26. */
|
|
163
|
+
capabilities?: ModelCapabilities;
|
|
164
|
+
useCases?: string[];
|
|
165
|
+
/** Helper feature flags; absent when the cached binary predates them. */
|
|
166
|
+
features?: HelperFeatures;
|
|
167
|
+
/** PCC quota, surfaced here because the on-device helper is what can read it. */
|
|
168
|
+
cloud?: CloudQuota;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* How tokens are chosen.
|
|
172
|
+
*
|
|
173
|
+
* `greedy` is deterministic but degenerates under guided generation — it is the
|
|
174
|
+
* `temperature: 0` trap by another name. The seeded modes give the same
|
|
175
|
+
* reproducibility *without* that failure: `{ mode: 'topK', k: 50, seed: 42 }`
|
|
176
|
+
* returned byte-identical output across three runs here. Determinism relies on
|
|
177
|
+
* a fresh session per request, which is the default.
|
|
178
|
+
*/
|
|
179
|
+
type SamplingMode = {
|
|
180
|
+
mode: 'greedy';
|
|
181
|
+
} | {
|
|
182
|
+
mode: 'topK';
|
|
183
|
+
k?: number;
|
|
184
|
+
seed?: number;
|
|
185
|
+
} | {
|
|
186
|
+
mode: 'threshold';
|
|
187
|
+
p?: number;
|
|
188
|
+
seed?: number;
|
|
189
|
+
};
|
|
190
|
+
/** Apple ships a use case specialised for tagging and topic extraction. */
|
|
191
|
+
type UseCase = 'general' | 'contentTagging';
|
|
192
|
+
/**
|
|
193
|
+
* `permissive` selects `permissiveContentTransformations`, which relaxes the
|
|
194
|
+
* guardrails for content *transformation* — rewriting or summarising text the
|
|
195
|
+
* default guardrails would refuse to touch.
|
|
196
|
+
*/
|
|
197
|
+
type Guardrails = 'default' | 'permissive';
|
|
198
|
+
interface DeviceRequest {
|
|
199
|
+
system?: string;
|
|
200
|
+
prompt: string;
|
|
201
|
+
schema?: JsonSchema | null;
|
|
202
|
+
temperature?: number;
|
|
203
|
+
maxTokens?: number;
|
|
204
|
+
/** Image attachments. Entries may carry a label for follow-up turns. */
|
|
205
|
+
images?: ImageAttachment[];
|
|
206
|
+
/** Text documents inlined into the prompt client-side (see inlineDocuments). */
|
|
207
|
+
documents?: string[];
|
|
208
|
+
/** Named multi-turn conversation; the helper keeps the native transcript. */
|
|
209
|
+
sessionId?: string;
|
|
210
|
+
/** Built-in Apple tools: on-device OCR, barcode reading, Spotlight RAG. */
|
|
211
|
+
tools?: BuiltInTool[];
|
|
212
|
+
useCase?: UseCase;
|
|
213
|
+
guardrails?: Guardrails;
|
|
214
|
+
sampling?: SamplingMode;
|
|
215
|
+
/** Send the schema in the prompt as well as to the decoder. See helper.swift. */
|
|
216
|
+
includeSchemaInPrompt?: boolean;
|
|
217
|
+
/**
|
|
218
|
+
* Reuse one session across calls, as api-scribe did. Off by default: it makes
|
|
219
|
+
* unrelated calls share a transcript. See the note in helper.swift.
|
|
220
|
+
* Prefer sessionId for conversations; this flag is the legacy single slot.
|
|
221
|
+
*/
|
|
222
|
+
reuseSession?: boolean;
|
|
223
|
+
}
|
|
224
|
+
/** Assert every requested tool is one the helper knows, before spending a call. */
|
|
225
|
+
declare function assertTools(tools: BuiltInTool[] | undefined): void;
|
|
226
|
+
/** Probe the on-device model without constructing a client. */
|
|
227
|
+
declare function probeDevice(onProgress?: OnProgress): Promise<DeviceProbe>;
|
|
228
|
+
/**
|
|
229
|
+
* The on-device tier: Apple's `FoundationModels` framework, reached through a
|
|
230
|
+
* self-compiled Swift helper. Nothing leaves the machine.
|
|
231
|
+
*/
|
|
232
|
+
interface DeviceClientOptions {
|
|
233
|
+
useCase?: UseCase;
|
|
234
|
+
guardrails?: Guardrails;
|
|
235
|
+
}
|
|
236
|
+
declare class DeviceClient {
|
|
237
|
+
private readonly options;
|
|
238
|
+
private binary?;
|
|
239
|
+
private probeResult?;
|
|
240
|
+
private server?;
|
|
241
|
+
constructor(options?: DeviceClientOptions);
|
|
242
|
+
get label(): string;
|
|
243
|
+
get contextSize(): number | undefined;
|
|
244
|
+
ensureReady(onProgress?: OnProgress): Promise<void>;
|
|
245
|
+
getProbe(): DeviceProbe | undefined;
|
|
246
|
+
/** Send one envelope and return the parsed reply, raising a typed error on failure. */
|
|
247
|
+
private exchange;
|
|
248
|
+
/** One request. Returns the helper's `content` string, unparsed. */
|
|
249
|
+
complete(request: DeviceRequest): Promise<string>;
|
|
250
|
+
/**
|
|
251
|
+
* Streaming text. Deltas arrive via onDelta as the model generates; the
|
|
252
|
+
* promise resolves with the full text. Text only: the helper rejects
|
|
253
|
+
* schema+stream, because partial JSON is not a usable delta.
|
|
254
|
+
*/
|
|
255
|
+
stream(prompt: string, options?: Omit<DeviceRequest, 'prompt' | 'schema'> & {
|
|
256
|
+
onDelta?: (delta: string) => void;
|
|
257
|
+
}): Promise<string>;
|
|
258
|
+
/**
|
|
259
|
+
* Conversation history for a named session: the mirrored turns the helper
|
|
260
|
+
* persisted, oldest first. Survives helper restarts; the native transcript
|
|
261
|
+
* does not, so treat a restart as a context break, not a loss.
|
|
262
|
+
*/
|
|
263
|
+
history(sessionId: string): Promise<{
|
|
264
|
+
instructions: string;
|
|
265
|
+
history: HistoryTurn[];
|
|
266
|
+
}>;
|
|
267
|
+
/** Drop a named session (and its persisted history), or all of them. */
|
|
268
|
+
resetSession(sessionId?: string): Promise<void>;
|
|
269
|
+
/**
|
|
270
|
+
* How many tokens a prompt costs, before sending it.
|
|
271
|
+
*
|
|
272
|
+
* The point is to turn a ContextLengthError into an arithmetic check: compare
|
|
273
|
+
* against `contextSize` and trim, rather than discovering the ceiling by
|
|
274
|
+
* hitting it. Counts the instructions too, since they share the window.
|
|
275
|
+
*/
|
|
276
|
+
countTokens(prompt: string, options?: {
|
|
277
|
+
system?: string;
|
|
278
|
+
images?: ImageAttachment[];
|
|
279
|
+
tools?: BuiltInTool[];
|
|
280
|
+
}): Promise<{
|
|
281
|
+
tokens: number;
|
|
282
|
+
contextSize: number;
|
|
283
|
+
}>;
|
|
284
|
+
/**
|
|
285
|
+
* Load the model assets now so the first real call does not pay for it.
|
|
286
|
+
*
|
|
287
|
+
* Cheap and idempotent, but do not expect much on a warm machine: with the
|
|
288
|
+
* assets already resident this measured 0.31s against 0.36s for an
|
|
289
|
+
* unprewarmed first call — inside the noise. The win is on a genuinely cold
|
|
290
|
+
* system, where the very first call to the framework here took 7.8s. Worth
|
|
291
|
+
* calling at startup when you know a request is coming; not worth building
|
|
292
|
+
* around.
|
|
293
|
+
*/
|
|
294
|
+
prewarm(system?: string): Promise<void>;
|
|
295
|
+
text(prompt: string, options?: Omit<DeviceRequest, 'prompt' | 'schema'>): Promise<string>;
|
|
296
|
+
json(prompt: string, options: Omit<DeviceRequest, 'prompt'> & {
|
|
297
|
+
schema: JsonSchema;
|
|
298
|
+
}): Promise<unknown>;
|
|
299
|
+
/** Shut the helper process down. Safe to call more than once. */
|
|
300
|
+
close(): void;
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Inline text documents into the prompt client-side.
|
|
304
|
+
*
|
|
305
|
+
* The helper's vision path handles images; plain-text sources (.txt/.md/.json
|
|
306
|
+
* and friends) are cheaper to splice here than to teach the Swift side about.
|
|
307
|
+
* Binary files are refused loudly — silently skipping a document the caller
|
|
308
|
+
* asked about would be the vision-drop trap by another name.
|
|
309
|
+
*/
|
|
310
|
+
declare function withDocuments(prompt: string, documents: string[] | undefined): Promise<string>;
|
|
311
|
+
/** Parse a CLI --image value: "path" or "path::label". */
|
|
312
|
+
declare function parseImageFlag(value: string): ImageAttachment;
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Apple's Private Cloud Compute model, reached through the Shortcuts action
|
|
316
|
+
* `is.workflow.actions.askllm`.
|
|
317
|
+
*
|
|
318
|
+
* Why this route rather than the framework: macOS 27 exposes
|
|
319
|
+
* `FoundationModels.PrivateCloudComputeLanguageModel` as public API and it even
|
|
320
|
+
* reports `isAvailable: true`, but every call fails with `ModelManagerError
|
|
321
|
+
* 1046` unless the process carries `com.apple.developer.private-cloud-compute`.
|
|
322
|
+
* That entitlement is AMFI-restricted — an ad-hoc-signed binary carrying it is
|
|
323
|
+
* SIGKILLed (exit 137), and wrapping it in a signed .app with a real bundle ID
|
|
324
|
+
* does not help. It needs a paid Developer Program provisioning profile, which
|
|
325
|
+
* no installable package can ship. Shortcuts.app already holds the entitlement
|
|
326
|
+
* and `/usr/bin/shortcuts` is public, so a generated shortcut is the only route
|
|
327
|
+
* that works. Do not spend time re-confirming this.
|
|
328
|
+
*
|
|
329
|
+
* This tier sends your prompt off the machine. It is not local.
|
|
330
|
+
*
|
|
331
|
+
* Measured on an M4 Air: ~2s typical, ~11s at 14k tokens.
|
|
332
|
+
*/
|
|
333
|
+
/** Distinctive, so `shortcuts run` cannot match a shortcut the user wrote. */
|
|
334
|
+
declare const CLOUD_SHORTCUT_NAME = "Apple LLM Cloud";
|
|
335
|
+
/** Web search is fixed in the shortcut at install time, so it needs its own copy. */
|
|
336
|
+
declare const CLOUD_SHORTCUT_NAME_WEB = "Apple LLM Cloud Web";
|
|
337
|
+
/** Context window, measured empirically: 14.4k succeeds, ~33.5k is refused. */
|
|
338
|
+
declare const CLOUD_CONTEXT_TOKENS = 32768;
|
|
339
|
+
interface CloudProbe {
|
|
340
|
+
available: boolean;
|
|
341
|
+
reason?: string;
|
|
342
|
+
installed?: boolean;
|
|
343
|
+
contextSize?: number;
|
|
344
|
+
/**
|
|
345
|
+
* Real quota state, read from `PrivateCloudComputeLanguageModel.quotaUsage`
|
|
346
|
+
* by the on-device helper. Absent when the helper could not run (macOS 26, or
|
|
347
|
+
* no on-device model), since that is the only thing that can read it.
|
|
348
|
+
*/
|
|
349
|
+
quota?: CloudQuota;
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* The shortcut definition, as plain JSON.
|
|
353
|
+
*
|
|
354
|
+
* Every key here was confirmed against a shortcut built in the Shortcuts GUI and
|
|
355
|
+
* exported, rather than guessed — the difference matters because a wrong
|
|
356
|
+
* parameter name does not fail loudly. Shortcuts imports the action, silently
|
|
357
|
+
* discards the unrecognised parameter, and the action then blocks on its
|
|
358
|
+
* interactive prompt at run time, forever. This is the single most expensive
|
|
359
|
+
* mistake available on this path.
|
|
360
|
+
*
|
|
361
|
+
* In particular the prompt key is `WFLLMPrompt`, *not* the `WFInput` that the
|
|
362
|
+
* action's own localised strings suggest.
|
|
363
|
+
*
|
|
364
|
+
* The model key is deliberately absent: with no model key the action uses its
|
|
365
|
+
* default, which is the Cloud (Private Cloud Compute) tier.
|
|
366
|
+
*/
|
|
367
|
+
declare function shortcutDefinition(webSearch?: boolean): Record<string, unknown>;
|
|
368
|
+
declare function cloudSetupHint(): string;
|
|
369
|
+
/**
|
|
370
|
+
* Generate, sign and install the shortcut. Idempotent unless `force` is set.
|
|
371
|
+
*
|
|
372
|
+
* Signing needs no developer account and no signing identity: `shortcuts sign -m
|
|
373
|
+
* anyone` issues a per-signature certificate that chains to Apple Root CA - G3
|
|
374
|
+
* on the device. Every user signs their own copy, so nothing has to be
|
|
375
|
+
* pre-signed, hosted, or shipped in the package.
|
|
376
|
+
*/
|
|
377
|
+
declare function installCloudShortcut(onProgress?: OnProgress, options?: {
|
|
378
|
+
force?: boolean;
|
|
379
|
+
webSearch?: boolean;
|
|
380
|
+
}): Promise<void>;
|
|
381
|
+
/** Probe the cloud tier without constructing a client. */
|
|
382
|
+
declare function probeCloud(): Promise<CloudProbe>;
|
|
383
|
+
interface CloudRequest {
|
|
384
|
+
system?: string;
|
|
385
|
+
prompt: string;
|
|
386
|
+
/** Run with "Use Broad World Knowledge" — needs the web shortcut installed. */
|
|
387
|
+
webSearch?: boolean;
|
|
388
|
+
}
|
|
389
|
+
/**
|
|
390
|
+
* The Private Cloud Compute tier. Free but quota'd, and text-out only: there is
|
|
391
|
+
* no constrained decoding here, so `json()` asks for JSON in the prompt and
|
|
392
|
+
* recovers it from prose rather than guaranteeing it.
|
|
393
|
+
*/
|
|
394
|
+
declare class CloudClient {
|
|
395
|
+
private ready;
|
|
396
|
+
/** Last known quota, set by `probe()` so a call can fail fast. */
|
|
397
|
+
private quota?;
|
|
398
|
+
/**
|
|
399
|
+
* Tell the client what the framework reported about the quota.
|
|
400
|
+
*
|
|
401
|
+
* Worth doing because a `shortcuts run` against an exhausted quota costs a
|
|
402
|
+
* full round trip to find out; this turns that into an immediate typed error.
|
|
403
|
+
*/
|
|
404
|
+
setQuota(quota: CloudQuota | undefined): void;
|
|
405
|
+
private assertQuota;
|
|
406
|
+
get label(): string;
|
|
407
|
+
get contextSize(): number;
|
|
408
|
+
ensureReady(onProgress?: OnProgress): Promise<void>;
|
|
409
|
+
text(request: CloudRequest): Promise<string>;
|
|
410
|
+
/**
|
|
411
|
+
* There is no constrained decoding on this tier, so the schema is spelled out
|
|
412
|
+
* in the prompt and the reply is mined for JSON. The shape is a request here,
|
|
413
|
+
* not a guarantee — unlike on device.
|
|
414
|
+
*/
|
|
415
|
+
json(request: CloudRequest & {
|
|
416
|
+
schema: JsonSchema;
|
|
417
|
+
}): Promise<unknown>;
|
|
418
|
+
close(): void;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* Recover JSON from free text.
|
|
423
|
+
*
|
|
424
|
+
* Only the cloud tier needs this. On device, constrained decoding guarantees the
|
|
425
|
+
* shape and the reply is already JSON. Private Cloud Compute returns prose, so
|
|
426
|
+
* the object has to be dug out of whatever the model wrapped around it.
|
|
427
|
+
*
|
|
428
|
+
* api-scribe's version only stripped fences when the reply *started* with one,
|
|
429
|
+
* which fails on the common "Here is the JSON:\n```json\n{...}\n```". This one
|
|
430
|
+
* also scans for a balanced object or array.
|
|
431
|
+
*/
|
|
432
|
+
/** Strip a Markdown code fence anywhere in the text, preferring a json-tagged one. */
|
|
433
|
+
declare function stripCodeFences(text: string): string;
|
|
434
|
+
/**
|
|
435
|
+
* The first balanced `{...}` or `[...]` in the text, respecting strings and
|
|
436
|
+
* escapes so a brace inside a string value cannot end the scan early.
|
|
437
|
+
*/
|
|
438
|
+
declare function extractJsonSpan(text: string): string | null;
|
|
439
|
+
/**
|
|
440
|
+
* Parse a model reply as JSON, tolerating fences and surrounding prose.
|
|
441
|
+
* Throws with a truncated echo of the reply, which is what a caller needs to see.
|
|
442
|
+
*/
|
|
443
|
+
declare function parseLlmJson(raw: string): unknown;
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* True when this machine could plausibly run Apple's on-device model: Apple
|
|
447
|
+
* Silicon on macOS. Availability itself is confirmed by probing the helper.
|
|
448
|
+
*/
|
|
449
|
+
declare function isAppleSiliconMac(): boolean;
|
|
450
|
+
/**
|
|
451
|
+
* Build the compile target from an SDK version string, e.g. `27.0` ->
|
|
452
|
+
* `arm64-apple-macos27.0`.
|
|
453
|
+
*
|
|
454
|
+
* swiftc's own default triple carries a patch component
|
|
455
|
+
* (`arm64-apple-macosx27.0.0`) that no shipped stdlib matches, so the target has
|
|
456
|
+
* to be spelled out. Pure so it can be tested against captured `xcrun` output on
|
|
457
|
+
* any machine.
|
|
458
|
+
*/
|
|
459
|
+
declare function targetTripleFrom(versionOutput: string, arch?: string): string | null;
|
|
460
|
+
/** Ask the SDK, then the OS, for a version to build the triple from. */
|
|
461
|
+
declare function hostTarget(): Promise<string | null>;
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* Typed errors, so callers branch on a class rather than matching a message.
|
|
465
|
+
* Apple rewords its diagnostics between OS releases; the helper classifies
|
|
466
|
+
* failures at the Swift boundary and this module names them.
|
|
467
|
+
*/
|
|
468
|
+
type Tier$1 = 'device' | 'cloud';
|
|
469
|
+
declare class AppleLLMError extends Error {
|
|
470
|
+
readonly tier?: Tier$1 | undefined;
|
|
471
|
+
constructor(message: string, tier?: Tier$1 | undefined);
|
|
472
|
+
}
|
|
473
|
+
/** Why the on-device model cannot be used. Distinguishable, because the fix differs. */
|
|
474
|
+
type UnavailableReason = 'appleIntelligenceNotEnabled' | 'modelNotReady' | 'deviceNotEligible' | 'unsupportedPlatform' | 'unsupportedOSVersion' | 'noSwiftCompiler' | 'unknown';
|
|
475
|
+
declare class ModelUnavailableError extends AppleLLMError {
|
|
476
|
+
readonly reason: UnavailableReason;
|
|
477
|
+
constructor(message: string, reason: UnavailableReason, tier?: Tier$1);
|
|
478
|
+
}
|
|
479
|
+
/** Apple's `GenerationSchema` decoder refused the schema. Almost always a dialect rule. */
|
|
480
|
+
declare class SchemaRejectedError extends AppleLLMError {
|
|
481
|
+
}
|
|
482
|
+
/** The prompt (plus transcript) exceeded the model's context window. */
|
|
483
|
+
declare class ContextLengthError extends AppleLLMError {
|
|
484
|
+
readonly contextSize?: number | undefined;
|
|
485
|
+
constructor(message: string, tier?: Tier$1, contextSize?: number | undefined);
|
|
486
|
+
}
|
|
487
|
+
/** Private Cloud Compute is rate limited, or the on-device model reported `rateLimited`. */
|
|
488
|
+
declare class QuotaError extends AppleLLMError {
|
|
489
|
+
readonly resetDate?: Date | undefined;
|
|
490
|
+
constructor(message: string, tier?: Tier$1, resetDate?: Date | undefined);
|
|
491
|
+
}
|
|
492
|
+
declare class TimeoutError extends AppleLLMError {
|
|
493
|
+
}
|
|
494
|
+
/** A one-time setup step has not been run — currently only the cloud shortcut. */
|
|
495
|
+
declare class SetupRequiredError extends AppleLLMError {
|
|
496
|
+
readonly step: string;
|
|
497
|
+
constructor(message: string, step: string, tier?: Tier$1);
|
|
498
|
+
}
|
|
499
|
+
/** The model declined to answer (guardrail or refusal). */
|
|
500
|
+
declare class RefusalError extends AppleLLMError {
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
/**
|
|
504
|
+
* apple-llm — one library for Apple's on-device and Private Cloud Compute models.
|
|
505
|
+
*
|
|
506
|
+
* macOS 26+ on Apple Silicon only. No API key, no account, no developer program.
|
|
507
|
+
* Extracted from api-scribe (MIT), which discovered and shipped both routes.
|
|
508
|
+
*/
|
|
509
|
+
|
|
510
|
+
type Tier = 'device' | 'cloud' | 'auto';
|
|
511
|
+
interface ProbeResult {
|
|
512
|
+
device: DeviceProbe;
|
|
513
|
+
cloud: CloudProbe;
|
|
514
|
+
}
|
|
515
|
+
/**
|
|
516
|
+
* What this machine can actually do. Never throws: on Linux, an Intel Mac or
|
|
517
|
+
* macOS 25 it returns `available: false` with a reason naming the fix.
|
|
518
|
+
*/
|
|
519
|
+
declare function probe(onProgress?: OnProgress): Promise<ProbeResult>;
|
|
520
|
+
interface AppleLLMOptions {
|
|
521
|
+
tier?: Tier;
|
|
522
|
+
/** Default sampling temperature. See DEFAULT_TEMPERATURE — do not set 0. */
|
|
523
|
+
temperature?: number;
|
|
524
|
+
maxTokens?: number;
|
|
525
|
+
/** Called for the one-time compile and the shortcut install; both take seconds. */
|
|
526
|
+
onProgress?: OnProgress;
|
|
527
|
+
/** `contentTagging` selects Apple's tagging-specialised model. Device tier only. */
|
|
528
|
+
useCase?: UseCase;
|
|
529
|
+
/** `permissive` relaxes guardrails for rewriting tasks. Device tier only. */
|
|
530
|
+
guardrails?: Guardrails;
|
|
531
|
+
/** Default sampling mode; a seeded one makes output reproducible. */
|
|
532
|
+
sampling?: SamplingMode;
|
|
533
|
+
}
|
|
534
|
+
interface TextOptions {
|
|
535
|
+
system?: string;
|
|
536
|
+
temperature?: number;
|
|
537
|
+
maxTokens?: number;
|
|
538
|
+
/** Cloud tier only: run with "Use Broad World Knowledge". */
|
|
539
|
+
webSearch?: boolean;
|
|
540
|
+
/** Image attachments; entries may carry a label for follow-up turns. */
|
|
541
|
+
images?: ImageAttachment[];
|
|
542
|
+
/** Text documents inlined into the prompt (device tier). */
|
|
543
|
+
documents?: string[];
|
|
544
|
+
/** Named multi-turn conversation (device tier). */
|
|
545
|
+
sessionId?: string;
|
|
546
|
+
/** Built-in Apple tools: ocr, barcode, spotlight (device, macOS 27+). */
|
|
547
|
+
tools?: BuiltInTool[];
|
|
548
|
+
useCase?: UseCase;
|
|
549
|
+
guardrails?: Guardrails;
|
|
550
|
+
sampling?: SamplingMode;
|
|
551
|
+
}
|
|
552
|
+
interface StreamOptions extends TextOptions {
|
|
553
|
+
onDelta?: (delta: string) => void;
|
|
554
|
+
}
|
|
555
|
+
interface JsonOptions extends TextOptions {
|
|
556
|
+
schema: JsonSchema;
|
|
557
|
+
}
|
|
558
|
+
/**
|
|
559
|
+
* The main entry point.
|
|
560
|
+
*
|
|
561
|
+
* const llm = new AppleLLM({ tier: 'device' });
|
|
562
|
+
* await llm.text('Summarize this');
|
|
563
|
+
* await llm.json('Extract the fields', { schema });
|
|
564
|
+
* llm.close();
|
|
565
|
+
*/
|
|
566
|
+
declare class AppleLLM {
|
|
567
|
+
private device?;
|
|
568
|
+
private cloud?;
|
|
569
|
+
/** Which tier `auto` settled on, once resolved. */
|
|
570
|
+
private resolved?;
|
|
571
|
+
private readonly options;
|
|
572
|
+
constructor(options?: AppleLLMOptions);
|
|
573
|
+
/** Per-call device settings, falling back to the constructor defaults. */
|
|
574
|
+
private deviceDefaults;
|
|
575
|
+
get tier(): Tier;
|
|
576
|
+
/** Human-readable name of the tier in use, for logs. */
|
|
577
|
+
get label(): string;
|
|
578
|
+
/**
|
|
579
|
+
* Resolve the tier and do any one-time setup. Called automatically, but
|
|
580
|
+
* exposed so a caller can pay the compile cost up front with a progress bar.
|
|
581
|
+
*/
|
|
582
|
+
ensureReady(onProgress?: OnProgress): Promise<void>;
|
|
583
|
+
text(prompt: string, options?: TextOptions): Promise<string>;
|
|
584
|
+
/**
|
|
585
|
+
* Streaming text (device tier only). Deltas arrive via onDelta as the model
|
|
586
|
+
* generates; the promise resolves with the full text. The Siri-app shape:
|
|
587
|
+
* partials first, final answer at the end.
|
|
588
|
+
*/
|
|
589
|
+
stream(prompt: string, options?: StreamOptions): Promise<string>;
|
|
590
|
+
/** Conversation history for a named session (device tier only). */
|
|
591
|
+
history(sessionId: string): Promise<{
|
|
592
|
+
instructions: string;
|
|
593
|
+
history: HistoryTurn[];
|
|
594
|
+
}>;
|
|
595
|
+
/** Drop a named session, or all sessions when omitted (device tier only). */
|
|
596
|
+
resetSession(sessionId?: string): Promise<void>;
|
|
597
|
+
/**
|
|
598
|
+
* A named conversation: text/stream calls sharing one native transcript,
|
|
599
|
+
* like one thread in the Siri app. History persists across helper restarts;
|
|
600
|
+
* the native transcript does not (treated as a context break, not a loss).
|
|
601
|
+
*/
|
|
602
|
+
conversation(sessionId: string, options?: {
|
|
603
|
+
system?: string;
|
|
604
|
+
}): Conversation;
|
|
605
|
+
/**
|
|
606
|
+
* Write with Siri, anywhere you type: drafting, rewriting and feedback
|
|
607
|
+
* built on the permissive-content-transformation guardrails. Device tier
|
|
608
|
+
* only — these are transformation tasks the default guardrails refuse.
|
|
609
|
+
*/
|
|
610
|
+
rewrite(text: string, options?: {
|
|
611
|
+
instruction?: string;
|
|
612
|
+
system?: string;
|
|
613
|
+
} & TextOptions): Promise<string>;
|
|
614
|
+
proofread(text: string, options?: TextOptions): Promise<string>;
|
|
615
|
+
summarize(text: string, options?: {
|
|
616
|
+
length?: string;
|
|
617
|
+
} & TextOptions): Promise<string>;
|
|
618
|
+
draft(topic: string, options?: {
|
|
619
|
+
kind?: string;
|
|
620
|
+
} & TextOptions): Promise<string>;
|
|
621
|
+
tone(text: string, tone: string, options?: TextOptions): Promise<string>;
|
|
622
|
+
/**
|
|
623
|
+
* Ask about what's on screen: captures a screenshot (interactive selection
|
|
624
|
+
* by default, like Cmd+Shift+Space Visual Intelligence) and asks the model
|
|
625
|
+
* about it with vision. Device tier, macOS 27+.
|
|
626
|
+
*/
|
|
627
|
+
askScreen(question: string, options?: {
|
|
628
|
+
mode?: 'interactive' | 'window' | 'fullscreen';
|
|
629
|
+
} & TextOptions): Promise<string>;
|
|
630
|
+
/**
|
|
631
|
+
* Ask for JSON. On device the schema is *guaranteed* by constrained decoding.
|
|
632
|
+
* On cloud it is requested in the prompt and recovered from the reply — the
|
|
633
|
+
* cloud tier has no constrained decoding, so a bad shape is possible there.
|
|
634
|
+
*/
|
|
635
|
+
json(prompt: string, options: JsonOptions): Promise<unknown>;
|
|
636
|
+
/**
|
|
637
|
+
* api-scribe's `LlmClient` shape, so it can drop its four files and depend on
|
|
638
|
+
* this instead. Not used internally.
|
|
639
|
+
*/
|
|
640
|
+
completeJson(system: string, user: string, schema: JsonSchema): Promise<unknown>;
|
|
641
|
+
/**
|
|
642
|
+
* How many tokens a prompt costs, before sending it. Device tier only.
|
|
643
|
+
*
|
|
644
|
+
* Turns a ContextLengthError into arithmetic: compare against `contextSize`
|
|
645
|
+
* and trim, rather than finding the ceiling by hitting it.
|
|
646
|
+
*/
|
|
647
|
+
countTokens(prompt: string, options?: {
|
|
648
|
+
system?: string;
|
|
649
|
+
images?: ImageAttachment[];
|
|
650
|
+
tools?: BuiltInTool[];
|
|
651
|
+
}): Promise<{
|
|
652
|
+
tokens: number;
|
|
653
|
+
contextSize: number;
|
|
654
|
+
}>;
|
|
655
|
+
/**
|
|
656
|
+
* Load the model assets now so the first real call does not pay for it.
|
|
657
|
+
* Device tier only; a no-op elsewhere. See `DeviceClient.prewarm` for what it
|
|
658
|
+
* is actually worth (little, on a warm machine).
|
|
659
|
+
*/
|
|
660
|
+
prewarm(system?: string): Promise<void>;
|
|
661
|
+
/** Release the long-lived helper process. Safe to call more than once. */
|
|
662
|
+
close(): void;
|
|
663
|
+
}
|
|
664
|
+
/**
|
|
665
|
+
* One thread in the Siri-app sense: every call carries the same sessionId,
|
|
666
|
+
* so the helper's native transcript accumulates across turns.
|
|
667
|
+
*/
|
|
668
|
+
declare class Conversation {
|
|
669
|
+
private readonly llm;
|
|
670
|
+
readonly sessionId: string;
|
|
671
|
+
private readonly defaults;
|
|
672
|
+
constructor(llm: AppleLLM, sessionId: string, defaults?: {
|
|
673
|
+
system?: string;
|
|
674
|
+
});
|
|
675
|
+
text(prompt: string, options?: TextOptions): Promise<string>;
|
|
676
|
+
stream(prompt: string, options?: StreamOptions): Promise<string>;
|
|
677
|
+
history(): Promise<{
|
|
678
|
+
instructions: string;
|
|
679
|
+
history: HistoryTurn[];
|
|
680
|
+
}>;
|
|
681
|
+
reset(): Promise<void>;
|
|
682
|
+
}
|
|
683
|
+
/**
|
|
684
|
+
* Capture a screenshot to a temp file. Interactive selection mirrors the
|
|
685
|
+
* Visual Intelligence entry point (Cmd+Shift+Space): drag to select, and the
|
|
686
|
+
* path comes back ready to pass as an image attachment.
|
|
687
|
+
*/
|
|
688
|
+
declare function captureScreenshot(mode?: 'interactive' | 'window' | 'fullscreen'): Promise<string>;
|
|
689
|
+
|
|
690
|
+
export { AppleLLM, AppleLLMError, type AppleLLMOptions, type BuiltInTool, CLOUD_CONTEXT_TOKENS, CLOUD_SHORTCUT_NAME, CLOUD_SHORTCUT_NAME_WEB, CloudClient, type CloudProbe, type CloudQuota, type CloudRequest, ContextLengthError, Conversation, DEFAULT_MAX_TOKENS, DEFAULT_TEMPERATURE, DeviceClient, type DeviceClientOptions, type DeviceProbe, type DeviceRequest, type Guardrails, type HelperFeatures, type HistoryTurn, type ImageAttachment, type JsonOptions, type JsonSchema, type ModelCapabilities, ModelUnavailableError, type OnProgress, type ProbeResult, type Progress, QuotaError, RefusalError, type SamplingMode, SchemaRejectedError, SetupRequiredError, type StreamOptions, type TextOptions, type Tier, TimeoutError, type UnavailableReason, type UseCase, assertTools, cacheDir, captureScreenshot, cloudSetupHint, ensureBinary, extractJsonSpan, fingerprint, helperSource, hostTarget, installCloudShortcut, isAppleSiliconMac, parseImageFlag, parseLlmJson, probe, probeCloud, probeDevice, shortcutDefinition, stripCodeFences, targetTripleFrom, toAppleSchema, withDocuments };
|