expo-local-llm 0.8.0 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,53 +1,33 @@
1
1
  # expo-local-llm
2
2
 
3
- On-device intelligence for Expo apps, using the models provided by the operating system. Generate text, extract structured data, and build streaming interfaces without distributing model weights or writing native code.
3
+ [![npm](https://img.shields.io/npm/v/expo-local-llm)](https://www.npmjs.com/package/expo-local-llm)
4
+ [![CI](https://github.com/GijungKim/expo-local-llm/actions/workflows/ci.yml/badge.svg)](https://github.com/GijungKim/expo-local-llm/actions/workflows/ci.yml)
5
+ [![Native compile](https://github.com/GijungKim/expo-local-llm/actions/workflows/native.yml/badge.svg)](https://github.com/GijungKim/expo-local-llm/actions/workflows/native.yml)
6
+ [![License: MIT](https://img.shields.io/npm/l/expo-local-llm)](LICENSE)
4
7
 
5
- Wraps Apple Foundation Models (iOS 26+) and Gemini Nano (Android). Inference stays on device; any network access inside your tool handlers is controlled by your app. There is no automatic cloud fallback.
8
+ On-device language models for Expo apps, using the models the operating system already ships: Apple Foundation Models on iOS 26+ and Gemini Nano on Android. Generate text, stream it into a component, extract typed objects and call tools, with no model weights to bundle, no API key, and no network connection needed to generate.
6
9
 
7
- ## Platform Requirements
10
+ Inference stays on the device. There is no cloud fallback; network access happens only inside tool handlers you write. The package has no runtime dependencies.
8
11
 
9
- | Platform | Requirement |
10
- |----------|-------------|
11
- | iOS | iOS 26+ with Apple Intelligence enabled. Loads on iOS 16.4+ without crashing (returns `notEligible`). Host project must target iOS 16.4+. |
12
- | Android | Host app uses `minSdkVersion` 26+. Inference requires a Gemini Nano–capable device; most Android devices, including most mid-range phones, report `notEligible`. Build, module loading, eligibility, and unsupported-feature handling are device-verified; generation is not yet validated on Gemini Nano hardware, so `getCapabilities()` reports Android as `experimental: true`. |
13
- | Expo SDK | 52+ (peer range); the example targets SDK 57 / React Native 0.86. A peer range is not a tested compatibility matrix. |
12
+ ## At a glance
14
13
 
15
- **A native development build is required. Stock Expo Go cannot load this module.** A simulator can compile the module and, when the host Mac has Apple Intelligence enabled and ready, run Apple Foundation Models for real inference; physical hardware is still needed for latency and thermal measurements. See the [release verification matrix](docs/release-verification.md).
14
+ | | iOS 26+ (Foundation Models) | Android (Gemini Nano) |
15
+ |---|---|---|
16
+ | Text generation and streaming | ✅ Verified on device | ⚠️ Experimental: builds and reports eligibility; generation not yet validated on Gemini Nano hardware |
17
+ | Structured output (typed, schema-constrained) | ✅ Constrained decoding, validated in JS | ❌ Rejected explicitly |
18
+ | Tool calling (schema-constrained arguments) | ✅ | ❌ Rejected explicitly |
19
+ | Stable error codes | ✅ | ✅ |
20
+ | Model download | Managed by the OS | `downloadModel()` with progress |
16
21
 
17
- For setup failures, unavailable models, and unsupported features, see [troubleshooting](docs/troubleshooting.md).
22
+ Unsupported features fail with a clear error instead of degrading silently. `getCapabilities()` reports all of this at runtime, including `experimental: true` on Android. See [how this library is verified](#how-this-library-is-verified).
18
23
 
19
- ### Xcode 27 requires scene support
20
-
21
- Apps built with the iOS 27 SDK must adopt the UIKit scene lifecycle, or UIKit stops them at launch before any JavaScript runs. Expo SDK 57 apps opt in through `expo-build-properties` (Expo `57.0.23`+, `expo-build-properties` `57.0.20`+):
22
-
23
- ```json
24
- {
25
- "expo": {
26
- "plugins": [
27
- [
28
- "expo-build-properties",
29
- { "ios": { "deploymentTarget": "16.4", "enableSceneSupport": true } }
30
- ]
31
- ]
32
- }
33
- }
34
- ```
35
-
36
- Then regenerate and rebuild the native app. This is an app-level build setting, not something this library can enable for you.
37
-
38
- ## Installation
24
+ ## Quick start
39
25
 
40
26
  ```bash
41
- npx expo install expo-local-llm expo-dev-client
27
+ npx expo install expo-local-llm expo-dev-client expo-build-properties
42
28
  ```
43
29
 
44
- This module requires **iOS 16.4+** and **Android API 26+** deployment targets. If your app targets an earlier version, raise the relevant target via [`expo-build-properties`](https://docs.expo.dev/versions/latest/sdk/build-properties/):
45
-
46
- ```bash
47
- npx expo install expo-build-properties
48
- ```
49
-
50
- In `app.json`, add the plugin with the deployment target:
30
+ Set the deployment targets the module needs in `app.json`:
51
31
 
52
32
  ```json
53
33
  {
@@ -65,43 +45,69 @@ In `app.json`, add the plugin with the deployment target:
65
45
  }
66
46
  ```
67
47
 
68
- Build and install your development app on a supported physical device:
48
+ Build a development app, since stock Expo Go cannot load native modules:
69
49
 
70
50
  ```bash
71
51
  npx expo run:ios --device
72
- # or
73
- npx expo run:android --device
74
52
  ```
75
53
 
76
- > If your project targets an iOS version below 16.4, you'll hit `compiling for iOS X.Y, but module 'ExpoLocalLlm' has a minimum deployment target of iOS 16.4` at build time. (Apple Intelligence itself still requires iOS 26+ at runtime — the 16.4 floor is just for compilation; the module returns `notEligible` on iOS 16.4–25.)
54
+ Then stream a response:
77
55
 
78
- The iOS native build requires an Xcode SDK containing Foundation Models; the SDK 57 example requires Xcode 26.4 or newer. `expo run` generates native projects if they do not exist. For projects using Continuous Native Generation, regenerate and rebuild after changing native dependencies or native app configuration. Keep custom native changes in config plugins before using `prebuild --clean`.
56
+ ```tsx
57
+ import { Button, Text } from 'react-native';
58
+ import { useLocalLLM } from 'expo-local-llm';
79
59
 
80
- EAS Build is also supported as a build workflow: use a development profile with a compatible native toolchain. Installing this package or upgrading its native implementation requires a new binary; EAS Update cannot add native code. JavaScript-only changes can use the existing binary when they remain compatible with its native API.
60
+ export function Summary({ notes }: { notes: string }) {
61
+ const { availability, streamResponse, streamedText, error } = useLocalLLM({
62
+ instructions: 'Summarise the text in two sentences.',
63
+ });
81
64
 
82
- ## Usage
65
+ if (!streamResponse) return <Text>{error ?? `On-device AI: ${availability}`}</Text>;
66
+ return (
67
+ <>
68
+ <Button title="Summarise" onPress={() => streamResponse(notes)} />
69
+ <Text>{streamedText}</Text>
70
+ </>
71
+ );
72
+ }
73
+ ```
83
74
 
84
- ### Extract typed data (iOS 26+)
75
+ `streamResponse` is `undefined` until the model is ready, so the component shows why when it isn't: Apple Intelligence switched off, the model still downloading, or an ineligible device.
85
76
 
86
- For classification and extraction, use `generateObject()`. It infers the result type from your schema and validates the generated JSON before returning it:
77
+ ## Requirements
87
78
 
88
- ```ts
89
- import { generateObject, getCapabilities } from 'expo-local-llm';
79
+ | | Requirement |
80
+ |---|---|
81
+ | iOS | iOS 26+ with Apple Intelligence enabled to generate. The module loads on iOS 16.4+ and reports `notEligible` below 26. The host project must target iOS 16.4+, and building needs Xcode 26.4 or newer. |
82
+ | Android | `minSdkVersion` 26+. Generation needs a Gemini Nano–capable device; most Android phones, including most mid-range ones, report `notEligible`. |
83
+ | Expo SDK | 52+ peer range. The example app targets SDK 58 / React Native 0.88; the peer range is not a tested compatibility matrix. |
90
84
 
91
- const capabilities = getCapabilities();
92
- // Capability support and current model readiness are separate checks.
93
- if (capabilities.structuredOutputMode === 'constrained') {
94
- const result = await generateObject('Classify: "Can we move the meeting to Friday?"', {
95
- instructions: 'Classify the message by its primary intent.',
96
- schema: {
97
- intent: { type: 'string', enum: ['scheduling', 'question', 'other'] },
98
- },
99
- });
100
- // result.intent: 'scheduling' | 'question' | 'other'
85
+ A native development build is required, built locally or with EAS Build. Installing or upgrading the package needs a new binary; EAS Update cannot add native code. An iPhone simulator can run Foundation Models when the host Mac has Apple Intelligence enabled; latency and thermals still need real hardware.
86
+
87
+ If the project targets an iOS version below 16.4, the build fails with `module 'ExpoLocalLlm' has a minimum deployment target of iOS 16.4`. The 16.4 floor is for compilation only; generation still needs iOS 26.
88
+
89
+ For setup failures, unavailable models and unsupported features, see [troubleshooting](docs/troubleshooting.md).
90
+
91
+ ### Xcode 27 requires scene support
92
+
93
+ Apps built with the iOS 27 SDK must adopt the UIKit scene lifecycle, or UIKit stops them at launch before any JavaScript runs. Expo SDK 58 apps adopt it by default, so there is nothing to set. Expo SDK 57 apps opt in through `expo-build-properties` (Expo `57.0.23`+, `expo-build-properties` `57.0.20`+):
94
+
95
+ ```json
96
+ {
97
+ "expo": {
98
+ "plugins": [
99
+ [
100
+ "expo-build-properties",
101
+ { "ios": { "deploymentTarget": "16.4", "enableSceneSupport": true } }
102
+ ]
103
+ ]
104
+ }
101
105
  }
102
106
  ```
103
107
 
104
- Handle rejected requests in your app: supported capabilities do not imply that the model is downloaded, enabled, or ready. `generateObject()` rejects invalid JSON or schema mismatches with `StructuredOutputValidationError`; it never repairs output silently or falls back to cloud inference.
108
+ Then regenerate and rebuild the native app. This is an app-level build setting, not something this library can enable for you.
109
+
110
+ ## Usage
105
111
 
106
112
  ### Stream text in a component
107
113
 
@@ -152,71 +158,71 @@ function Chat() {
152
158
  }
153
159
  ```
154
160
 
155
- ### Handle errors
156
-
157
- Every error from this library carries a stable `code` (see [Error codes](#error-codes)), so you can respond to *why* generation failed rather than parsing OS messages. The hook exposes it as `errorCode`; for a caught error, use `getLocalLLMErrorCode()`.
161
+ ### One-shot generation
158
162
 
159
- Foundation Models has a small context window, so a long conversation eventually fails with `ERR_CONTEXT_WINDOW_EXCEEDED`. The library never drops turns silently; decide what your app should do:
163
+ For a single call with no conversation history, such as classifying or rewriting one message, use `generate()`. It creates a session, responds once and releases it:
160
164
 
161
- ```tsx
162
- const { errorCode, reset, streamResponse } = useLocalLLM({ instructions });
165
+ ```ts
166
+ import { generate } from 'expo-local-llm';
163
167
 
164
- if (errorCode === 'ERR_CONTEXT_WINDOW_EXCEEDED') {
165
- // Start over, or create a new session seeded with a summary of the chat.
166
- reset?.();
167
- }
168
- if (errorCode === 'ERR_GUARDRAIL_VIOLATION' || errorCode === 'ERR_REFUSAL') {
169
- // The model declined; ask the person to rephrase.
170
- }
168
+ const title = await generate('Suggest a short title for this note: ...', {
169
+ instructions: 'Reply with the title only.',
170
+ });
171
171
  ```
172
172
 
173
+ ### Extract typed data (iOS)
174
+
175
+ `generateObject()` infers the result type from the schema, constrains the model's decoding to it, and validates the JSON before returning it:
176
+
173
177
  ```ts
174
- import { generate, getLocalLLMErrorCode } from 'expo-local-llm';
178
+ import { generateObject, getCapabilities } from 'expo-local-llm';
175
179
 
176
- try {
177
- await generate(prompt);
178
- } catch (error) {
179
- if (error instanceof Error && getLocalLLMErrorCode(error) === 'ERR_RATE_LIMITED') {
180
- // The system is throttling on-device generation; retry later.
181
- }
180
+ if (getCapabilities().structuredOutputMode === 'constrained') {
181
+ const result = await generateObject('Classify: "Can we move the meeting to Friday?"', {
182
+ instructions: 'Classify the message by its primary intent.',
183
+ schema: {
184
+ intent: { type: 'string', enum: ['scheduling', 'question', 'other'] },
185
+ },
186
+ });
187
+ // result.intent: 'scheduling' | 'question' | 'other'
182
188
  }
183
189
  ```
184
190
 
185
- #### Using with Effect
191
+ A supported capability does not mean the model is ready, so handle rejected calls. A mismatch rejects with `StructuredOutputValidationError`; output is never repaired silently.
186
192
 
187
- The library has no runtime dependencies, so it does not ship [Effect](https://effect.website) bindings. The error codes map directly onto a tagged error, so a wrapper takes a few lines:
193
+ <p align="center">
194
+ <img src="https://raw.githubusercontent.com/GijungKim/expo-local-llm/main/docs/structured-output.png" alt="Structured output demo — recipe JSON with constrained 'difficulty' enum on iPhone" width="320" />
195
+ </p>
188
196
 
189
- ```ts
190
- import { Data, Effect } from 'effect';
191
- import { generate, getLocalLLMErrorCode, type LocalLLMErrorCode } from 'expo-local-llm';
197
+ *Real device output: the model fills in a recipe schema with `difficulty` constrained to `easy | medium | hard`. The streaming demo lives at `example/App.tsx`.*
192
198
 
193
- class LocalLLMFailure extends Data.TaggedError('LocalLLMFailure')<{
194
- code: LocalLLMErrorCode | null;
195
- message: string;
196
- }> {}
199
+ ### Structured output in the hook
197
200
 
198
- const generateEffect = (prompt: string) =>
199
- Effect.tryPromise({
200
- try: () => generate(prompt),
201
- catch: (error) =>
202
- new LocalLLMFailure({
203
- code: error instanceof Error ? getLocalLLMErrorCode(error) : null,
204
- message: error instanceof Error ? error.message : 'Generation failed',
205
- }),
206
- });
201
+ In JSON mode the hook streams a partial object, then publishes the validated result:
207
202
 
208
- // Recover from one failure by its code.
209
- const answer = generateEffect('Summarise my notes').pipe(
210
- Effect.catchIf(
211
- (failure) => failure.code === 'ERR_RATE_LIMITED',
212
- () => Effect.succeed('Try again in a moment.'),
213
- ),
214
- );
203
+ ```tsx
204
+ const { streamResponse, streamedObject, completedObject } = useLocalLLM({
205
+ responseFormat: 'json',
206
+ schema: {
207
+ name: { type: 'string', description: 'Recipe name' },
208
+ ingredients: { type: 'array', items: { type: 'string' } },
209
+ difficulty: { type: 'string', enum: ['easy', 'medium', 'hard'] },
210
+ },
211
+ });
212
+
213
+ await streamResponse?.('Give me a pasta recipe');
214
+ // streamedObject: fields appear as the model writes them (every field optional)
215
+ // completedObject: { name: string; ingredients: string[]; difficulty: 'easy' | 'medium' | 'hard' } | null
215
216
  ```
216
217
 
217
- ### Tool Calling (iOS 26+)
218
+ `respond` and `streamResponse` validate the final JSON and reject with `StructuredOutputValidationError` (`ERR_STRUCTURED_OUTPUT_INVALID`) if it does not match. A cancelled stream keeps its partial `streamedObject` and leaves `completedObject` null. `streamedJSON` holds the raw partial JSON.
219
+
220
+ Schemas are validated before they reach native code; a malformed one throws `SchemaInvalidError` with per-field paths. Call `validateSchema()` yourself when building schemas at runtime.
221
+
222
+ ### Tool calling (iOS)
218
223
 
219
224
  ```tsx
225
+ import { Text, View } from 'react-native';
220
226
  import { defineTool, useLocalLLM } from 'expo-local-llm';
221
227
 
222
228
  const getWeather = defineTool({
@@ -256,97 +262,69 @@ function WeatherChat() {
256
262
 
257
263
  Tool arguments use the same constrained decoding as structured output: the model fills in a schema built from `parameters`, so every declared parameter arrives with its declared type and any `enum` is respected. Validate values whose meaning matters (ranges, formats) as you would any input. If a handler throws or times out, the turn rejects with `ERR_TOOL_FAILED`.
258
264
 
259
- ### Structured Output
260
-
261
- iOS 26+ uses Apple's constrained decoding (`DynamicGenerationSchema`) for schema-driven output.
262
- Successful generation produces structured JSON; requests can still fail or be refused.
263
- Android structured output is unsupported and rejected explicitly, rather than silently generating ordinary text.
265
+ ### Handle errors
264
266
 
265
- <p align="center">
266
- <img src="https://raw.githubusercontent.com/GijungKim/expo-local-llm/main/docs/structured-output.png" alt="Structured output demo — recipe JSON with constrained 'difficulty' enum on iPhone" width="320" />
267
- </p>
267
+ Generation failures and the library's own checks carry a stable `code` (see [Error codes](#error-codes)), so you can respond to *why* generation failed rather than parsing OS messages. The hook exposes it as `errorCode`; for a caught error, use `getLocalLLMErrorCode()`.
268
268
 
269
- *Real device output: the model fills in a recipe schema with `difficulty` constrained to `easy | medium | hard`. The streaming demo lives at `example/App.tsx`.*
269
+ Foundation Models has a small context window, so a long conversation eventually fails with `ERR_CONTEXT_WINDOW_EXCEEDED`. On iOS the library never drops turns to make room (Android replays only the last 10 exchanges); decide what your app should do:
270
270
 
271
271
  ```tsx
272
- const { respond, completedObject } = useLocalLLM({
273
- responseFormat: 'json',
274
- schema: {
275
- name: { type: 'string', description: 'Recipe name' },
276
- ingredients: {
277
- type: 'array',
278
- items: { type: 'string' },
279
- description: 'List of ingredients',
280
- },
281
- steps: {
282
- type: 'array',
283
- items: { type: 'string' },
284
- description: 'Cooking steps',
285
- },
286
- difficulty: {
287
- type: 'string',
288
- enum: ['easy', 'medium', 'hard'],
289
- },
290
- },
291
- });
272
+ const { errorCode, reset } = useLocalLLM({ instructions: 'You are a helpful assistant.' });
292
273
 
293
- if (respond) {
294
- await respond('Give me a pasta recipe');
274
+ if (errorCode === 'ERR_CONTEXT_WINDOW_EXCEEDED') {
275
+ // Start over, or create a new session seeded with a summary of the chat.
276
+ reset?.();
277
+ }
278
+ if (errorCode === 'ERR_GUARDRAIL_VIOLATION' || errorCode === 'ERR_REFUSAL') {
279
+ // The model declined; ask the person to rephrase.
295
280
  }
296
- // completedObject is typed from the schema and validated against it:
297
- // { name: string; ingredients: string[]; steps: string[]; difficulty: 'easy' | 'medium' | 'hard' } | null
298
281
  ```
299
282
 
300
- In JSON mode, `respond` and `streamResponse` validate the final JSON against the schema and reject with `StructuredOutputValidationError` (`ERR_STRUCTURED_OUTPUT_INVALID`) if it does not match.
301
-
302
- For one-shot structured calls (classification, extraction) where you don't want conversation
303
- history accumulating between calls, use `generate()` instead of a session:
304
-
305
283
  ```ts
306
- import { generate } from 'expo-local-llm';
284
+ import { generate, getLocalLLMErrorCode } from 'expo-local-llm';
307
285
 
308
- const json = await generate('Classify this message: "how was my sleep?"', {
309
- instructions: 'Classify what the message asks for.',
310
- responseFormat: 'json',
311
- schema: { topic: { type: 'string', enum: ['sleep', 'heart', 'none'] } },
312
- });
286
+ try {
287
+ await generate('Summarise my notes');
288
+ } catch (error) {
289
+ if (error instanceof Error && getLocalLLMErrorCode(error) === 'ERR_RATE_LIMITED') {
290
+ // The system is throttling on-device generation; retry later.
291
+ }
292
+ }
313
293
  ```
314
294
 
315
- Streaming with a schema yields progressively-filled snapshots via `streamedJSON` (the current
316
- partial as a JSON string) and `streamedObject` (the parsed value, typed with every field optional).
317
- When the stream completes, the validated result lands in `completedObject`:
318
-
319
- ```tsx
320
- const { streamResponse, streamedObject, completedObject } = useLocalLLM({
321
- responseFormat: 'json',
322
- schema: { /* ... */ },
323
- });
324
-
325
- await streamResponse?.('Give me a pasta recipe');
326
- // streamedObject updates as the model fills in fields;
327
- // completedObject is set once the whole object has validated.
328
- ```
295
+ #### Using with Effect
329
296
 
330
- A cancelled stream keeps its partial `streamedObject` and leaves `completedObject` null.
297
+ The library has no runtime dependencies, so it does not ship [Effect](https://effect.website) bindings. The error codes map directly onto a tagged error, so a wrapper takes a few lines:
331
298
 
332
- #### Schema validation
299
+ ```ts
300
+ import { Data, Effect } from 'effect';
301
+ import { generate, getLocalLLMErrorCode, type LocalLLMErrorCode } from 'expo-local-llm';
333
302
 
334
- `createLLMSession` validates the schema before crossing to native and throws `SchemaInvalidError`
335
- with per-field paths if anything is malformed (missing `items`, `enum` on the wrong type, unknown
336
- `type`, etc.). You can also call `validateSchema()` directly when building schemas at runtime:
303
+ class LocalLLMFailure extends Data.TaggedError('LocalLLMFailure')<{
304
+ code: LocalLLMErrorCode | null;
305
+ message: string;
306
+ }> {}
337
307
 
338
- ```ts
339
- import { validateSchema } from 'expo-local-llm';
308
+ const generateEffect = (prompt: string) =>
309
+ Effect.tryPromise({
310
+ try: () => generate(prompt),
311
+ catch: (error) =>
312
+ new LocalLLMFailure({
313
+ code: error instanceof Error ? getLocalLLMErrorCode(error) : null,
314
+ message: error instanceof Error ? error.message : 'Generation failed',
315
+ }),
316
+ });
340
317
 
341
- const result = validateSchema(mySchema);
342
- if (!result.ok) {
343
- for (const err of result.errors) {
344
- console.warn(`${err.path}: ${err.message}`);
345
- }
346
- }
318
+ // Recover from one failure by its code.
319
+ const answer = generateEffect('Summarise my notes').pipe(
320
+ Effect.catchIf(
321
+ (failure) => failure.code === 'ERR_RATE_LIMITED',
322
+ () => Effect.succeed('Try again in a moment.'),
323
+ ),
324
+ );
347
325
  ```
348
326
 
349
- ## API
327
+ ## API reference
350
328
 
351
329
  ### `getCapabilities()`
352
330
 
@@ -384,7 +362,7 @@ if (!diagnostics.available) {
384
362
  | `tools` | `ToolDefinition[]` | Tools the model can invoke (iOS 26+ only) |
385
363
  | `toolTimeout` | `number` | Seconds before an unresolved tool call times out (default 30) |
386
364
  | `responseFormat` | `"text" \| "json"` | Set to `"json"` for structured JSON output; requires `schema` |
387
- | `schema` | `Schema` | Schema for structured output (used with `responseFormat: "json"`) — see Structured Output section |
365
+ | `schema` | `Schema` | Schema for structured output (used with `responseFormat: "json"`); see [Structured output in the hook](#structured-output-in-the-hook) |
388
366
  | `includeSchemaInPrompt` | `boolean` | iOS 26+: include the schema definition in the prompt. Default `true`. Set `false` when you've front-loaded few-shot examples. |
389
367
 
390
368
  #### Returns
@@ -459,7 +437,7 @@ On iOS 27, Foundation Models reports some failures with error types that only th
459
437
  | `ERR_MODEL_UNAVAILABLE` | Model assets are missing or the device cannot run the model right now. |
460
438
  | `ERR_DECODING_FAILED` | Output could not be decoded into the requested structure. |
461
439
  | `ERR_TIMEOUT` | Generation exceeded the system's time limit (iOS 27+). |
462
- | `ERR_TOOL_FAILED` | A tool handler threw, timed out, or was removed mid-call. |
440
+ | `ERR_TOOL_FAILED` | A tool handler threw or timed out, or the hook had no handler for the tool. |
463
441
  | `ERR_GENERATION_FAILED` | Any other generation failure. |
464
442
  | `ERR_SESSION_BUSY` | The session already has a generation in progress. |
465
443
  | `ERR_SESSION_INVALID` | The session was released or is not ready. |
@@ -477,28 +455,38 @@ On iOS 27, Foundation Models reports some failures with error types that only th
477
455
 
478
456
  Android readiness is checked asynchronously and may initially be `unknown`. Prefer `useLocalLLM()` for reactive readiness; low-level consumers should listen for the module's `availabilityChange` event instead of treating the first synchronous status read as final.
479
457
 
480
- ## Platform Asymmetries
458
+ ### `validateSchema(schema)` and `validateStructuredOutput(value, schema)`
459
+
460
+ `validateSchema` checks a schema and returns `{ ok: true }` or `{ ok: false, errors }` with field paths (missing `items`, `enum` on the wrong type, unknown `type`). `validateStructuredOutput` checks an already-parsed value against a valid schema the same way.
461
+
462
+ ## Platform differences and limitations
481
463
 
482
464
  | Concern | iOS | Android |
483
- |---------|-----|---------|
484
- | System instructions | Native `LanguageModelSession(instructions:)` | Prepended in prompt text |
485
- | Token limits | OS/model-dependent | SDK/model-dependent |
486
- | Model availability | Built-in to OS | May need download |
487
- | Session history | Native session maintains it | Replayed in the prompt; last 10 exchanges, with role markers in conversation text neutralised |
488
- | Streaming | `AsyncSequence` | Kotlin `Flow` |
489
-
490
- ## Known Limitations
491
-
492
- - Events belong to their session. Use a separate session for independent conversations.
493
- - A session accepts one generation at a time; overlapping requests reject. After cancellation, await the original generation promise before starting another request.
494
- - **Android**: Gemini Nano SDK is in beta and its API surface may change. Build, module loading, eligibility, and unsupported-feature rejection are device-verified; generation has not been validated on Gemini Nano hardware (`getCapabilities().experimental` is `true`).
495
- - **Context window**: neither platform trims history to fit the model's context. iOS rejects with `ERR_CONTEXT_WINDOW_EXCEEDED` when the transcript is too long; Android keeps the last 10 exchanges. Call `reset()` or start a new session when that happens.
496
- - **iOS**: Apple's Foundation Model may refuse certain categories of prompts (e.g. personal health data interpretation) due to built-in safety guardrails.
497
- - Generation methods (`respond`, `streamResponse`) require a session and `availability === 'available'`. Cancellation remains available while a session exists.
498
- - Tool calling is iOS 26+ only. Android will throw at session creation if tools are passed.
499
- - Structured output on Android is unsupported. iOS 26+ uses constrained decoding via `DynamicGenerationSchema`.
500
-
501
- ## Why an Expo module?
465
+ |---|---|---|
466
+ | System instructions | Native `LanguageModelSession(instructions:)` | Prepended to the prompt |
467
+ | Conversation history | Kept by the native session | Replayed in the prompt: the last 10 exchanges, with role markers in conversation text neutralised |
468
+ | Model availability | Built into the OS | May need `downloadModel()` |
469
+ | Token limits | OS and model dependent | SDK and model dependent |
470
+
471
+ - **Context window.** Neither platform trims history to fit the model's context; Android only caps it at 10 exchanges. iOS rejects with `ERR_CONTEXT_WINDOW_EXCEEDED`; call `reset()` or start a new session.
472
+ - **One generation per session.** Overlapping requests reject with `ERR_SESSION_BUSY`. After cancelling, await the original promise before starting another request. Use separate sessions for independent conversations; events belong to their session.
473
+ - **Guardrails.** Apple's model refuses some categories of prompt, such as interpreting personal health data. A refusal arrives as `ERR_GUARDRAIL_VIOLATION` or `ERR_REFUSAL`.
474
+ - **Android is experimental.** The Gemini Nano SDK is in beta and its API may change. Structured output and tools are rejected at session creation.
475
+ - **Readiness gates generation.** `respond` and `streamResponse` need a session and `availability === 'available'`; cancelling and resetting work whenever a session exists.
476
+
477
+ ## How this library is verified
478
+
479
+ - **Every pull request:** lint, unit tests, type-level tests, and a compile of a freshly generated Expo SDK 58 app on both platforms, with the iOS build using Xcode 26.4, the oldest supported toolchain. Android also runs JVM tests of the prompt assembly and error mapping.
480
+ - **Against the real model:** [`tools/model-check`](tools/model-check/README.md) runs the library's Foundation Models code on a Mac with Apple Intelligence: constrained tool arguments, error-code mapping and schema-constrained output. The release checklist runs it before each release.
481
+ - **On devices:** the [release verification record](docs/release-verification.md) lists what has been checked on an iPhone 15 Pro, a simulator and a Galaxy A17, and leaves untested scenarios marked as pending rather than assumed.
482
+
483
+ ## Choosing this library
484
+
485
+ Choose `expo-local-llm` for a small, Expo-first API around system-provided language models: short text generation, classification, extraction, and streaming interfaces on supported devices. You do not select or distribute model weights.
486
+
487
+ If you need downloadable models, embeddings, speech, or an AI SDK provider interface, evaluate a broader runtime such as [React Native AI](https://github.com/callstackincubator/ai). Check its current provider documentation for platform requirements. This library does not implement those capabilities or automatically route requests to a cloud model.
488
+
489
+ ### Why an Expo module?
502
490
 
503
491
  The [Expo Modules API](https://docs.expo.dev/modules/overview/) fits OS-provided AI APIs well:
504
492
 
@@ -511,16 +499,11 @@ Expo does not improve model quality or remove device eligibility requirements. I
511
499
 
512
500
  Existing React Native apps can use this library after [installing Expo Modules support](https://docs.expo.dev/bare/installing-expo-modules/). Expo-first does not mean Expo-only.
513
501
 
514
- ## Choosing this library
515
-
516
- Choose `expo-local-llm` for a small, Expo-first API around system-provided language models: short text generation, classification, extraction, and streaming interfaces on supported devices. You do not select or distribute model weights.
517
-
518
- If you need downloadable models, embeddings, speech, or an AI SDK provider interface, evaluate a broader runtime such as [React Native AI](https://github.com/callstackincubator/ai). Check its current provider documentation for platform requirements. This library does not implement those capabilities or automatically route requests to a cloud model.
519
-
520
- ## Built with expo-local-llm
502
+ ## Used by
521
503
 
522
- - [PulseID](https://apps.apple.com/us/app/pulseid-heart-rate-camera/id6754331991) — Heart rate camera
523
- - [Teamfit Tactics](https://apps.apple.com/us/app/teamfit-tactics-tft-fitness/id6757195493) — TFT fitness
504
+ - [Angular Native](https://github.com/ng-native/ng-native): wraps it as the `LanguageModel` service in `@ng-native/expo`
505
+ - [PulseID](https://apps.apple.com/us/app/pulseid-heart-rate-camera/id6754331991): heart rate camera
506
+ - [Teamfit Tactics](https://apps.apple.com/us/app/teamfit-tactics-tft-fitness/id6757195493): TFT fitness
524
507
 
525
508
  *Using this library? Open a PR to add your app.*
526
509
 
@@ -1,2 +1,2 @@
1
1
  <?xml version="1.0" encoding="utf-8"?>
2
- <merger version="3"><dataSet config="main" ignore_pattern="!.svn:!.git:!.ds_store:!*.scc:.*:&lt;dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~"><source path="/Users/swoleknee/chingu/expo-local-llm/example/node_modules/expo-local-llm/android/src/main/assets"/></dataSet><dataSet config="debug" ignore_pattern="!.svn:!.git:!.ds_store:!*.scc:.*:&lt;dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~"><source path="/Users/swoleknee/chingu/expo-local-llm/example/node_modules/expo-local-llm/android/src/debug/assets"/></dataSet><dataSet config="generated" ignore_pattern="!.svn:!.git:!.ds_store:!*.scc:.*:&lt;dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~"><source path="/Users/swoleknee/chingu/expo-local-llm/example/node_modules/expo-local-llm/android/build/intermediates/shader_assets/debug/compileDebugShaders/out"/></dataSet></merger>
2
+ <merger version="3"><dataSet config="main" ignore_pattern="!.svn:!.git:!.ds_store:!*.scc:.*:&lt;dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~"><source path="/Users/swoleknee/chingu/expo-local-llm/example/node_modules/expo-local-llm/android/src/main/assets"/></dataSet><dataSet config="debug" ignore_pattern="!.svn:!.git:!.ds_store:!*.scc:.*:&lt;dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~"><source path="/Users/swoleknee/chingu/expo-local-llm/example/node_modules/expo-local-llm/android/src/debug/assets"/></dataSet></merger>
@@ -16,6 +16,7 @@ android {
16
16
  minSdk 26
17
17
  versionCode 1
18
18
  versionName project.version
19
+ consumerProguardFiles 'proguard-rules.pro'
19
20
  }
20
21
 
21
22
  compileOptions {
@@ -0,0 +1,8 @@
1
+ # Consumer rules applied to apps that minify release builds (the default from
2
+ # Expo SDK 58). ML Kit discovers its components by instantiating the
3
+ # registrars named in its manifest metadata through reflection. ML Kit's own
4
+ # rules do not keep their no-argument constructors, so R8 removes them and
5
+ # ML Kit never initialises: availability checks then fail on every device.
6
+ -keep class * implements com.google.firebase.components.ComponentRegistrar {
7
+ <init>();
8
+ }
@@ -1,5 +1,6 @@
1
1
  package expo.modules.localllm
2
2
 
3
+ import android.util.Log
3
4
  import com.google.mlkit.genai.common.DownloadStatus
4
5
  import com.google.mlkit.genai.common.FeatureStatus
5
6
  import com.google.mlkit.genai.prompt.GenerateContentRequest
@@ -48,6 +49,8 @@ class LLMSession(
48
49
  private var disposed = false
49
50
 
50
51
  companion object {
52
+ private const val LOG_TAG = "ExpoLocalLlm"
53
+
51
54
  @Volatile
52
55
  private var cachedAvailability = ModelAvailability.unknown
53
56
  private val availabilityMutex = Mutex()
@@ -69,6 +72,7 @@ class LLMSession(
69
72
  throw e
70
73
  } catch (e: Exception) {
71
74
  // SDK/API failures do not prove that the device is ineligible.
75
+ Log.w(LOG_TAG, "Gemini Nano availability check failed", e)
72
76
  ModelAvailability.unknown
73
77
  } finally {
74
78
  // Generation.getClient() creates an independent engine-backed client.
@@ -1,6 +1,6 @@
1
1
  export type ModelAvailability = "available" | "moduleUnavailable" | "notEnabled" | "notReady" | "notEligible" | "downloadRequired" | "downloading" | "unknown";
2
2
  /**
3
- * Stable codes carried by every error this library raises, on both platforms.
3
+ * Stable codes carried by generation failures and the library's own checks, on both platforms.
4
4
  * Branch on these rather than on messages, which come from the OS and change.
5
5
  * Read one from any caught error with `getLocalLLMErrorCode`.
6
6
  */
@@ -23,7 +23,7 @@ export type LocalLLMErrorCode =
23
23
  | "ERR_DECODING_FAILED"
24
24
  /** Generation took longer than the system allows. */
25
25
  | "ERR_TIMEOUT"
26
- /** A tool call failed: the handler threw, timed out, or was unregistered. */
26
+ /** A tool call failed: the handler threw or timed out, or the hook had no handler for it. */
27
27
  | "ERR_TOOL_FAILED"
28
28
  /** Any other generation failure. */
29
29
  | "ERR_GENERATION_FAILED"
@@ -1 +1 @@
1
- {"version":3,"file":"ExpoLocalLlm.types.d.ts","sourceRoot":"","sources":["../src/ExpoLocalLlm.types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,iBAAiB,GACzB,WAAW,GACX,mBAAmB,GACnB,YAAY,GACZ,UAAU,GACV,aAAa,GACb,kBAAkB,GAClB,aAAa,GACb,SAAS,CAAC;AAEd;;;;GAIG;AACH,MAAM,MAAM,iBAAiB;AAC3B,gGAAgG;AAC9F,6BAA6B;AAC/B,2EAA2E;GACzE,yBAAyB;AAC3B,oCAAoC;GAClC,aAAa;AACf,gFAAgF;GAC9E,0BAA0B;AAC5B,+EAA+E;GAC7E,yBAAyB;AAC3B,yFAAyF;GACvF,kBAAkB;AACpB,4EAA4E;GAC1E,uBAAuB;AACzB,4EAA4E;GAC1E,qBAAqB;AACvB,qDAAqD;GACnD,aAAa;AACf,6EAA6E;GAC3E,iBAAiB;AACnB,oCAAoC;GAClC,uBAAuB;AACzB,yDAAyD;GACvD,kBAAkB;AACpB,qDAAqD;GACnD,qBAAqB;AACvB,gEAAgE;GAC9D,mBAAmB;AACrB,kEAAkE;GAChE,mCAAmC;AACrC,sDAAsD;GACpD,wBAAwB;AAC1B,oDAAoD;GAClD,2BAA2B;AAC7B,uEAAuE;GACrE,oBAAoB;AACtB,mDAAmD;GACjD,+BAA+B;AACjC,8EAA8E;GAC5E,iBAAiB,CAAC;AAEtB,MAAM,MAAM,iBAAiB,GAAG;IAC9B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC;AAIF,MAAM,MAAM,iBAAiB,GAAG,QAAQ,GAAG,QAAQ,GAAG,SAAS,CAAC;AAEhE,MAAM,MAAM,aAAa,GAAG;IAC1B,IAAI,EAAE,iBAAiB,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,iDAAiD;IACjD,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC1B,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;AAE3D,MAAM,MAAM,kBAAkB,CAAC,SAAS,SAAS,aAAa,IAC5D,SAAS,SAAS;IAChB,IAAI,EAAE,QAAQ,CAAC;IACf,IAAI,EAAE,SAAS,CAAC,MAAM,KAAK,SAAS,MAAM,CAAC,EAAE,CAAC;CAC/C,GACG,KAAK,GACL,SAAS,SAAS;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,GACpC,MAAM,GACN,SAAS,SAAS;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,GACpC,MAAM,GACN,SAAS,SAAS;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,GACrC,OAAO,GACP,KAAK,CAAC;AAEZ;;;;;;GAMG;AACH,MAAM,MAAM,kBAAkB,CAAC,UAAU,SAAS,cAAc,IAAI;IAClE,CAAC,UAAU,GAAG,IAAI,MAAM,UAAU,GAAG,kBAAkB,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;CACzE,CAAC;AAEF,0EAA0E;AAC1E,MAAM,MAAM,aAAa,GAAG,kBAAkB,CAAC,cAAc,CAAC,CAAC;AAE/D;;;GAGG;AACH,MAAM,MAAM,cAAc,CAAC,UAAU,SAAS,cAAc,GAAG,cAAc,IAC3E;IACE,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,UAAU,CAAC;IAGvB,OAAO,CAAC,IAAI,EAAE,kBAAkB,CAAC,UAAU,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CAChE,CAAC;AAEJ,MAAM,MAAM,UAAU,GAAG;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,cAAc,CAAC;CAC5B,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,aAAa,CAAC;CAC1B,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAIF,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,MAAM,CAAC;AAE7C,MAAM,MAAM,WAAW,GACnB;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,GAClE;IAAE,IAAI,EAAE,QAAQ,GAAG,SAAS,GAAG,SAAS,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,GAChE;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,WAAW,CAAA;CAAE,GAC3D;IACE,IAAI,EAAE,QAAQ,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;CACzC,CAAC;AAEN,MAAM,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;AAEjD,MAAM,MAAM,gBAAgB,CAAC,KAAK,SAAS,WAAW,IAAI,KAAK,SAAS;IACtE,IAAI,EAAE,QAAQ,CAAC;IACf,IAAI,EAAE,SAAS,CAAC,MAAM,KAAK,SAAS,MAAM,CAAC,EAAE,CAAC;CAC/C,GACG,KAAK,GACL,KAAK,SAAS;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,GAChC,MAAM,GACN,KAAK,SAAS;IAAE,IAAI,EAAE,QAAQ,GAAG,SAAS,CAAA;CAAE,GAC5C,MAAM,GACN,KAAK,SAAS;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,GACjC,OAAO,GACP,KAAK,SAAS;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,IAAI,SAAS,WAAW,CAAA;CAAE,GACtE,gBAAgB,CAAC,IAAI,CAAC,EAAE,GACxB,KAAK,SAAS;IACZ,IAAI,EAAE,QAAQ,CAAC;IACf,UAAU,EAAE,MAAM,UAAU,SAAS,MAAM,CAAC;CAC7C,GACD,WAAW,CAAC,UAAU,CAAC,GACvB,KAAK,CAAC;AAEV,MAAM,MAAM,WAAW,CAAC,UAAU,SAAS,MAAM,IAAI;IACnD,CAAC,UAAU,GAAG,IAAI,MAAM,UAAU,GAAG,gBAAgB,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;CACvE,CAAC;AAEF;;;GAGG;AACH,MAAM,MAAM,WAAW,CAAC,KAAK,IAAI,KAAK,SAAS,SAAS,CAAC,MAAM,IAAI,CAAC,EAAE,GAClE,WAAW,CAAC,IAAI,CAAC,EAAE,GACnB,KAAK,SAAS,MAAM,GACpB;KAAG,GAAG,IAAI,MAAM,KAAK,CAAC,CAAC,EAAE,WAAW,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;CAAE,GAClD,KAAK,CAAC;AAEV,MAAM,MAAM,oBAAoB,CAAC,UAAU,SAAS,MAAM,IAAI,IAAI,CAChE,aAAa,EACb,gBAAgB,GAAG,QAAQ,GAAG,OAAO,CACtC,GAAG;IACF,MAAM,EAAE,UAAU,CAAC;IACnB,+DAA+D;IAC/D,KAAK,CAAC,EAAE,KAAK,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAC/B;IACE,SAAS,EAAE,IAAI,CAAC;IAChB,MAAM,EAAE,IAAI,CAAC;IACb,OAAO,EAAE,IAAI,CAAC;IACd,iBAAiB,EAAE,SAAS,EAAE,CAAC;CAChC,GACD;IACE,SAAS,EAAE,KAAK,CAAC;IACjB,MAAM,EAAE,mBAAmB,CAAC;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,iBAAiB,EAAE,SAAS,MAAM,EAAE,CAAC;CACtC,CAAC;AAEN,MAAM,MAAM,oBAAoB,GAAG;IACjC,QAAQ,EAAE,KAAK,GAAG,SAAS,GAAG,aAAa,CAAC;IAC5C,OAAO,EAAE,uBAAuB,GAAG,YAAY,GAAG,IAAI,CAAC;IACvD,MAAM,EACF,WAAW,GACX,mBAAmB,GACnB,eAAe,GACf,qBAAqB,CAAC;IAC1B,IAAI,EAAE,OAAO,CAAC;IACd,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,EAAE,OAAO,CAAC;IACf,gBAAgB,EAAE,OAAO,CAAC;IAC1B,oBAAoB,EAAE,aAAa,GAAG,aAAa,CAAC;IACpD;;;OAGG;IACH,YAAY,EAAE,OAAO,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,EAAE,iBAAiB,CAAC;IAC5B,KAAK,CAAC,EAAE,cAAc,EAAE,CAAC;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,qBAAqB,CAAC,EAAE,OAAO,CAAC;CACjC,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,OAAO,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,iBAAiB,CAAC;CACzB,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,KAAK,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAAC;IACnC,OAAO,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,CAAC;IACvC,cAAc,EAAE,CAAC,KAAK,EAAE,mBAAmB,KAAK,IAAI,CAAC;IACrD,WAAW,EAAE,CAAC,KAAK,EAAE,gBAAgB,KAAK,IAAI,CAAC;IAC/C,QAAQ,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;CAC1C,CAAC;AAEF,MAAM,MAAM,wBAAwB,GAAG;IACrC,gBAAgB,EAAE,CAAC,KAAK,EAAE,gBAAgB,KAAK,IAAI,CAAC;IACpD,kBAAkB,EAAE,CAAC,KAAK,EAAE;QAAE,YAAY,EAAE,iBAAiB,CAAA;KAAE,KAAK,IAAI,CAAC;CAC1E,CAAC"}
1
+ {"version":3,"file":"ExpoLocalLlm.types.d.ts","sourceRoot":"","sources":["../src/ExpoLocalLlm.types.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"ExpoLocalLlm.types.js","sourceRoot":"","sources":["../src/ExpoLocalLlm.types.ts"],"names":[],"mappings":"","sourcesContent":["export type ModelAvailability =\n | \"available\"\n | \"moduleUnavailable\"\n | \"notEnabled\"\n | \"notReady\"\n | \"notEligible\"\n | \"downloadRequired\"\n | \"downloading\"\n | \"unknown\";\n\n/**\n * Stable codes carried by every error this library raises, on both platforms.\n * Branch on these rather than on messages, which come from the OS and change.\n * Read one from any caught error with `getLocalLLMErrorCode`.\n */\nexport type LocalLLMErrorCode =\n /** The conversation no longer fits the model's context window. Reset or start a new session. */\n | \"ERR_CONTEXT_WINDOW_EXCEEDED\"\n /** The platform's safety guardrails blocked the prompt or the response. */\n | \"ERR_GUARDRAIL_VIOLATION\"\n /** The model declined to answer. */\n | \"ERR_REFUSAL\"\n /** The prompt's language or the device locale is not supported by the model. */\n | \"ERR_UNSUPPORTED_LANGUAGE\"\n /** The request uses a schema feature or capability the model cannot honour. */\n | \"ERR_UNSUPPORTED_REQUEST\"\n /** The system is throttling generation (quota, background use, or load). Retry later. */\n | \"ERR_RATE_LIMITED\"\n /** The model's assets are missing or the device cannot run it right now. */\n | \"ERR_MODEL_UNAVAILABLE\"\n /** The model's output could not be decoded into the requested structure. */\n | \"ERR_DECODING_FAILED\"\n /** Generation took longer than the system allows. */\n | \"ERR_TIMEOUT\"\n /** A tool call failed: the handler threw, timed out, or was unregistered. */\n | \"ERR_TOOL_FAILED\"\n /** Any other generation failure. */\n | \"ERR_GENERATION_FAILED\"\n /** This session already has a generation in progress. */\n | \"ERR_SESSION_BUSY\"\n /** The session was released or never initialised. */\n | \"ERR_SESSION_INVALID\"\n /** On-device generation is not available on this OS version. */\n | \"ERR_NOT_SUPPORTED\"\n /** `responseFormat: \"json\"` is not supported on this platform. */\n | \"ERR_RESPONSE_FORMAT_NOT_SUPPORTED\"\n /** Tool calling is not supported on this platform. */\n | \"ERR_TOOL_NOT_SUPPORTED\"\n /** Android only: downloading Gemini Nano failed. */\n | \"ERR_MODEL_DOWNLOAD_FAILED\"\n /** The schema passed to a session or `generateObject` is malformed. */\n | \"ERR_SCHEMA_INVALID\"\n /** The generated JSON did not match the schema. */\n | \"ERR_STRUCTURED_OUTPUT_INVALID\"\n /** The native module is missing from this build (Expo Go, or not rebuilt). */\n | \"ERR_UNAVAILABLE\";\n\nexport type GenerationOptions = {\n temperature?: number;\n maxTokens?: number;\n topK?: number;\n};\n\n// Tool calling types\n\nexport type ToolParameterType = \"string\" | \"number\" | \"boolean\";\n\nexport type ToolParameter = {\n type: ToolParameterType;\n description: string;\n /** Allowed values for a `\"string\"` parameter. */\n enum?: readonly string[];\n};\n\nexport type ToolParameters = Record<string, ToolParameter>;\n\nexport type InferToolParameter<Parameter extends ToolParameter> =\n Parameter extends {\n type: \"string\";\n enum: readonly (infer Value extends string)[];\n }\n ? Value\n : Parameter extends { type: \"string\" }\n ? string\n : Parameter extends { type: \"number\" }\n ? number\n : Parameter extends { type: \"boolean\" }\n ? boolean\n : never;\n\n/**\n * Arguments the model supplied for a tool call. On iOS the model's decoding is\n * constrained to the tool's declared `parameters`, so every declared field is\n * present with its declared type and enum values are respected. Handlers\n * should still validate values that carry meaning (ranges, formats), as they\n * would for any other input.\n */\nexport type InferToolArguments<Parameters extends ToolParameters> = {\n -readonly [Key in keyof Parameters]: InferToolParameter<Parameters[Key]>;\n};\n\n/** Tool arguments when the tool's parameters are not known statically. */\nexport type ToolArguments = InferToolArguments<ToolParameters>;\n\n/**\n * A tool the model can call. Declare tools with `defineTool()` to have the\n * handler's arguments typed from `parameters`.\n */\nexport type ToolDefinition<Parameters extends ToolParameters = ToolParameters> =\n {\n name: string;\n description: string;\n parameters: Parameters;\n // Method syntax keeps a tool with specific parameters assignable to the\n // general `ToolDefinition` that `tools` arrays hold.\n handler(args: InferToolArguments<Parameters>): Promise<string>;\n };\n\nexport type ToolConfig = {\n name: string;\n description: string;\n parameters: ToolParameters;\n};\n\nexport type ToolCallEvent = {\n requestId?: string;\n callId: string;\n toolName: string;\n arguments: ToolArguments;\n};\n\nexport type ActiveToolCall = {\n callId: string;\n toolName: string;\n};\n\n// Structured output types\n\nexport type ResponseFormat = \"text\" | \"json\";\n\nexport type SchemaField =\n | { type: \"string\"; description?: string; enum?: readonly string[] }\n | { type: \"number\" | \"integer\" | \"boolean\"; description?: string }\n | { type: \"array\"; description?: string; items: SchemaField }\n | {\n type: \"object\";\n description?: string;\n properties: Record<string, SchemaField>;\n };\n\nexport type Schema = Record<string, SchemaField>;\n\nexport type InferSchemaField<Field extends SchemaField> = Field extends {\n type: \"string\";\n enum: readonly (infer Value extends string)[];\n}\n ? Value\n : Field extends { type: \"string\" }\n ? string\n : Field extends { type: \"number\" | \"integer\" }\n ? number\n : Field extends { type: \"boolean\" }\n ? boolean\n : Field extends { type: \"array\"; items: infer Item extends SchemaField }\n ? InferSchemaField<Item>[]\n : Field extends {\n type: \"object\";\n properties: infer Properties extends Schema;\n }\n ? InferSchema<Properties>\n : never;\n\nexport type InferSchema<Definition extends Schema> = {\n -readonly [Key in keyof Definition]: InferSchemaField<Definition[Key]>;\n};\n\n/**\n * An object still being generated: any field, at any depth, may be missing\n * until the model reaches it.\n */\nexport type DeepPartial<Value> = Value extends readonly (infer Item)[]\n ? DeepPartial<Item>[]\n : Value extends object\n ? { [Key in keyof Value]?: DeepPartial<Value[Key]> }\n : Value;\n\nexport type GenerateObjectConfig<Definition extends Schema> = Omit<\n SessionConfig,\n \"responseFormat\" | \"schema\" | \"tools\"\n> & {\n schema: Definition;\n /** One-shot helpers cannot service native tool-call events. */\n tools?: never;\n};\n\nexport type NativeModuleDiagnostics =\n | {\n available: true;\n reason: null;\n message: null;\n setupInstructions: readonly [];\n }\n | {\n available: false;\n reason: \"moduleUnavailable\";\n message: string;\n setupInstructions: readonly string[];\n };\n\nexport type LocalLLMCapabilities = {\n platform: \"ios\" | \"android\" | \"unsupported\";\n backend: \"appleFoundationModels\" | \"geminiNano\" | null;\n status:\n | \"available\"\n | \"moduleUnavailable\"\n | \"unsupportedOS\"\n | \"unsupportedPlatform\";\n text: boolean;\n streaming: boolean;\n tools: boolean;\n structuredOutput: boolean;\n structuredOutputMode: \"constrained\" | \"unsupported\";\n /**\n * True where generation has not been verified on real hardware. Android is\n * experimental until Gemini Nano generation is validated on a device.\n */\n experimental: boolean;\n};\n\nexport type SessionConfig = {\n instructions?: string;\n options?: GenerationOptions;\n tools?: ToolDefinition[];\n toolTimeout?: number;\n responseFormat?: ResponseFormat;\n schema?: Schema;\n includeSchemaInPrompt?: boolean;\n};\n\nexport type TokenEvent = {\n requestId?: string;\n token: string;\n accumulated: string;\n};\n\nexport type StreamCompleteEvent = {\n requestId?: string;\n text: string;\n};\n\nexport type PartialEvent = {\n requestId?: string;\n json: string;\n complete: boolean;\n};\n\nexport type StreamErrorEvent = {\n requestId?: string;\n error: string;\n code: LocalLLMErrorCode;\n};\n\nexport type DownloadProgress = {\n progress: number;\n};\n\nexport type LLMSessionEvents = {\n token: (event: TokenEvent) => void;\n partial: (event: PartialEvent) => void;\n streamComplete: (event: StreamCompleteEvent) => void;\n streamError: (event: StreamErrorEvent) => void;\n toolCall: (event: ToolCallEvent) => void;\n};\n\nexport type ExpoLocalLlmModuleEvents = {\n downloadProgress: (event: DownloadProgress) => void;\n availabilityChange: (event: { availability: ModelAvailability }) => void;\n};\n"]}
1
+ {"version":3,"file":"ExpoLocalLlm.types.js","sourceRoot":"","sources":["../src/ExpoLocalLlm.types.ts"],"names":[],"mappings":"","sourcesContent":["export type ModelAvailability =\n | \"available\"\n | \"moduleUnavailable\"\n | \"notEnabled\"\n | \"notReady\"\n | \"notEligible\"\n | \"downloadRequired\"\n | \"downloading\"\n | \"unknown\";\n\n/**\n * Stable codes carried by generation failures and the library's own checks, on both platforms.\n * Branch on these rather than on messages, which come from the OS and change.\n * Read one from any caught error with `getLocalLLMErrorCode`.\n */\nexport type LocalLLMErrorCode =\n /** The conversation no longer fits the model's context window. Reset or start a new session. */\n | \"ERR_CONTEXT_WINDOW_EXCEEDED\"\n /** The platform's safety guardrails blocked the prompt or the response. */\n | \"ERR_GUARDRAIL_VIOLATION\"\n /** The model declined to answer. */\n | \"ERR_REFUSAL\"\n /** The prompt's language or the device locale is not supported by the model. */\n | \"ERR_UNSUPPORTED_LANGUAGE\"\n /** The request uses a schema feature or capability the model cannot honour. */\n | \"ERR_UNSUPPORTED_REQUEST\"\n /** The system is throttling generation (quota, background use, or load). Retry later. */\n | \"ERR_RATE_LIMITED\"\n /** The model's assets are missing or the device cannot run it right now. */\n | \"ERR_MODEL_UNAVAILABLE\"\n /** The model's output could not be decoded into the requested structure. */\n | \"ERR_DECODING_FAILED\"\n /** Generation took longer than the system allows. */\n | \"ERR_TIMEOUT\"\n /** A tool call failed: the handler threw or timed out, or the hook had no handler for it. */\n | \"ERR_TOOL_FAILED\"\n /** Any other generation failure. */\n | \"ERR_GENERATION_FAILED\"\n /** This session already has a generation in progress. */\n | \"ERR_SESSION_BUSY\"\n /** The session was released or never initialised. */\n | \"ERR_SESSION_INVALID\"\n /** On-device generation is not available on this OS version. */\n | \"ERR_NOT_SUPPORTED\"\n /** `responseFormat: \"json\"` is not supported on this platform. */\n | \"ERR_RESPONSE_FORMAT_NOT_SUPPORTED\"\n /** Tool calling is not supported on this platform. */\n | \"ERR_TOOL_NOT_SUPPORTED\"\n /** Android only: downloading Gemini Nano failed. */\n | \"ERR_MODEL_DOWNLOAD_FAILED\"\n /** The schema passed to a session or `generateObject` is malformed. */\n | \"ERR_SCHEMA_INVALID\"\n /** The generated JSON did not match the schema. */\n | \"ERR_STRUCTURED_OUTPUT_INVALID\"\n /** The native module is missing from this build (Expo Go, or not rebuilt). */\n | \"ERR_UNAVAILABLE\";\n\nexport type GenerationOptions = {\n temperature?: number;\n maxTokens?: number;\n topK?: number;\n};\n\n// Tool calling types\n\nexport type ToolParameterType = \"string\" | \"number\" | \"boolean\";\n\nexport type ToolParameter = {\n type: ToolParameterType;\n description: string;\n /** Allowed values for a `\"string\"` parameter. */\n enum?: readonly string[];\n};\n\nexport type ToolParameters = Record<string, ToolParameter>;\n\nexport type InferToolParameter<Parameter extends ToolParameter> =\n Parameter extends {\n type: \"string\";\n enum: readonly (infer Value extends string)[];\n }\n ? Value\n : Parameter extends { type: \"string\" }\n ? string\n : Parameter extends { type: \"number\" }\n ? number\n : Parameter extends { type: \"boolean\" }\n ? boolean\n : never;\n\n/**\n * Arguments the model supplied for a tool call. On iOS the model's decoding is\n * constrained to the tool's declared `parameters`, so every declared field is\n * present with its declared type and enum values are respected. Handlers\n * should still validate values that carry meaning (ranges, formats), as they\n * would for any other input.\n */\nexport type InferToolArguments<Parameters extends ToolParameters> = {\n -readonly [Key in keyof Parameters]: InferToolParameter<Parameters[Key]>;\n};\n\n/** Tool arguments when the tool's parameters are not known statically. */\nexport type ToolArguments = InferToolArguments<ToolParameters>;\n\n/**\n * A tool the model can call. Declare tools with `defineTool()` to have the\n * handler's arguments typed from `parameters`.\n */\nexport type ToolDefinition<Parameters extends ToolParameters = ToolParameters> =\n {\n name: string;\n description: string;\n parameters: Parameters;\n // Method syntax keeps a tool with specific parameters assignable to the\n // general `ToolDefinition` that `tools` arrays hold.\n handler(args: InferToolArguments<Parameters>): Promise<string>;\n };\n\nexport type ToolConfig = {\n name: string;\n description: string;\n parameters: ToolParameters;\n};\n\nexport type ToolCallEvent = {\n requestId?: string;\n callId: string;\n toolName: string;\n arguments: ToolArguments;\n};\n\nexport type ActiveToolCall = {\n callId: string;\n toolName: string;\n};\n\n// Structured output types\n\nexport type ResponseFormat = \"text\" | \"json\";\n\nexport type SchemaField =\n | { type: \"string\"; description?: string; enum?: readonly string[] }\n | { type: \"number\" | \"integer\" | \"boolean\"; description?: string }\n | { type: \"array\"; description?: string; items: SchemaField }\n | {\n type: \"object\";\n description?: string;\n properties: Record<string, SchemaField>;\n };\n\nexport type Schema = Record<string, SchemaField>;\n\nexport type InferSchemaField<Field extends SchemaField> = Field extends {\n type: \"string\";\n enum: readonly (infer Value extends string)[];\n}\n ? Value\n : Field extends { type: \"string\" }\n ? string\n : Field extends { type: \"number\" | \"integer\" }\n ? number\n : Field extends { type: \"boolean\" }\n ? boolean\n : Field extends { type: \"array\"; items: infer Item extends SchemaField }\n ? InferSchemaField<Item>[]\n : Field extends {\n type: \"object\";\n properties: infer Properties extends Schema;\n }\n ? InferSchema<Properties>\n : never;\n\nexport type InferSchema<Definition extends Schema> = {\n -readonly [Key in keyof Definition]: InferSchemaField<Definition[Key]>;\n};\n\n/**\n * An object still being generated: any field, at any depth, may be missing\n * until the model reaches it.\n */\nexport type DeepPartial<Value> = Value extends readonly (infer Item)[]\n ? DeepPartial<Item>[]\n : Value extends object\n ? { [Key in keyof Value]?: DeepPartial<Value[Key]> }\n : Value;\n\nexport type GenerateObjectConfig<Definition extends Schema> = Omit<\n SessionConfig,\n \"responseFormat\" | \"schema\" | \"tools\"\n> & {\n schema: Definition;\n /** One-shot helpers cannot service native tool-call events. */\n tools?: never;\n};\n\nexport type NativeModuleDiagnostics =\n | {\n available: true;\n reason: null;\n message: null;\n setupInstructions: readonly [];\n }\n | {\n available: false;\n reason: \"moduleUnavailable\";\n message: string;\n setupInstructions: readonly string[];\n };\n\nexport type LocalLLMCapabilities = {\n platform: \"ios\" | \"android\" | \"unsupported\";\n backend: \"appleFoundationModels\" | \"geminiNano\" | null;\n status:\n | \"available\"\n | \"moduleUnavailable\"\n | \"unsupportedOS\"\n | \"unsupportedPlatform\";\n text: boolean;\n streaming: boolean;\n tools: boolean;\n structuredOutput: boolean;\n structuredOutputMode: \"constrained\" | \"unsupported\";\n /**\n * True where generation has not been verified on real hardware. Android is\n * experimental until Gemini Nano generation is validated on a device.\n */\n experimental: boolean;\n};\n\nexport type SessionConfig = {\n instructions?: string;\n options?: GenerationOptions;\n tools?: ToolDefinition[];\n toolTimeout?: number;\n responseFormat?: ResponseFormat;\n schema?: Schema;\n includeSchemaInPrompt?: boolean;\n};\n\nexport type TokenEvent = {\n requestId?: string;\n token: string;\n accumulated: string;\n};\n\nexport type StreamCompleteEvent = {\n requestId?: string;\n text: string;\n};\n\nexport type PartialEvent = {\n requestId?: string;\n json: string;\n complete: boolean;\n};\n\nexport type StreamErrorEvent = {\n requestId?: string;\n error: string;\n code: LocalLLMErrorCode;\n};\n\nexport type DownloadProgress = {\n progress: number;\n};\n\nexport type LLMSessionEvents = {\n token: (event: TokenEvent) => void;\n partial: (event: PartialEvent) => void;\n streamComplete: (event: StreamCompleteEvent) => void;\n streamError: (event: StreamErrorEvent) => void;\n toolCall: (event: ToolCallEvent) => void;\n};\n\nexport type ExpoLocalLlmModuleEvents = {\n downloadProgress: (event: DownloadProgress) => void;\n availabilityChange: (event: { availability: ModelAvailability }) => void;\n};\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "expo-local-llm",
3
- "version": "0.8.0",
3
+ "version": "0.8.1",
4
4
  "description": "Expo module for on-device LLM inference via Apple Foundation Models (iOS) and Gemini Nano (Android)",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",
@@ -48,19 +48,19 @@
48
48
  "homepage": "https://github.com/GijungKim/expo-local-llm#readme",
49
49
  "devDependencies": {
50
50
  "@oxlint/plugins": "1.88.0",
51
- "@react-native/jest-preset": "0.86.3",
52
- "@types/react": "~19.2.4",
51
+ "@react-native/jest-preset": "0.88.0",
52
+ "@types/react": "~19.3.0",
53
53
  "@types/react-test-renderer": "19.1.0",
54
54
  "eslint": "10.11.0",
55
- "expo": "~57.0.28",
55
+ "expo": "~58.0.11",
56
56
  "expo-module-scripts": "^56.0.2",
57
- "expo-modules-core": "~57.0.22",
58
- "jest-expo": "~57.0.6",
57
+ "expo-modules-core": "~58.0.19",
58
+ "jest-expo": "~58.0.11",
59
59
  "knip": "6.41.0",
60
60
  "oxlint": "1.88.0",
61
- "react": "19.2.3",
62
- "react-native": "0.86.3",
63
- "react-test-renderer": "19.2.3",
61
+ "react": "19.3.0",
62
+ "react-native": "0.88.0",
63
+ "react-test-renderer": "19.3.0",
64
64
  "typescript": "6.0.3"
65
65
  },
66
66
  "jest": {
@@ -9,7 +9,7 @@ export type ModelAvailability =
9
9
  | "unknown";
10
10
 
11
11
  /**
12
- * Stable codes carried by every error this library raises, on both platforms.
12
+ * Stable codes carried by generation failures and the library's own checks, on both platforms.
13
13
  * Branch on these rather than on messages, which come from the OS and change.
14
14
  * Read one from any caught error with `getLocalLLMErrorCode`.
15
15
  */
@@ -32,7 +32,7 @@ export type LocalLLMErrorCode =
32
32
  | "ERR_DECODING_FAILED"
33
33
  /** Generation took longer than the system allows. */
34
34
  | "ERR_TIMEOUT"
35
- /** A tool call failed: the handler threw, timed out, or was unregistered. */
35
+ /** A tool call failed: the handler threw or timed out, or the hook had no handler for it. */
36
36
  | "ERR_TOOL_FAILED"
37
37
  /** Any other generation failure. */
38
38
  | "ERR_GENERATION_FAILED"