expo-local-llm 0.6.0 → 0.8.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/README.md +292 -110
- package/android/build/generated/source/buildConfig/debug/expo/modules/localllm/BuildConfig.java +10 -0
- package/android/build/intermediates/aapt_friendly_merged_manifests/debug/processDebugManifest/aapt/AndroidManifest.xml +7 -0
- package/android/build/intermediates/aapt_friendly_merged_manifests/debug/processDebugManifest/aapt/output-metadata.json +18 -0
- package/android/build/intermediates/aar_metadata/debug/writeDebugAarMetadata/aar-metadata.properties +6 -0
- package/android/build/intermediates/annotation_processor_list/debug/javaPreCompileDebug/annotationProcessors.json +1 -0
- package/android/build/intermediates/compile_r_class_jar/debug/generateDebugRFile/R.jar +0 -0
- package/android/build/intermediates/compile_symbol_list/debug/generateDebugRFile/R.txt +0 -0
- package/android/build/intermediates/incremental/debug/packageDebugResources/compile-file-map.properties +1 -0
- package/android/build/intermediates/incremental/debug/packageDebugResources/merger.xml +2 -0
- package/android/build/intermediates/incremental/mergeDebugAssets/merger.xml +2 -0
- package/android/build/intermediates/incremental/mergeDebugJniLibFolders/merger.xml +2 -0
- package/android/build/intermediates/incremental/mergeDebugShaders/merger.xml +2 -0
- package/android/build/intermediates/local_only_symbol_list/debug/parseDebugLocalResources/R-def.txt +2 -0
- package/android/build/intermediates/manifest_merge_blame_file/debug/processDebugManifest/manifest-merger-blame-debug-report.txt +7 -0
- package/android/build/intermediates/merged_manifest/debug/processDebugManifest/AndroidManifest.xml +7 -0
- package/android/build/intermediates/navigation_json/debug/extractDeepLinksDebug/navigation.json +1 -0
- package/android/build/intermediates/nested_resources_validation_report/debug/generateDebugResources/nestedResourcesValidationReport.txt +1 -0
- package/android/build/intermediates/symbol_list_with_package_name/debug/generateDebugRFile/package-aware-r.txt +1 -0
- package/android/build/outputs/logs/manifest-merger-debug-report.txt +14 -0
- package/android/build.gradle +9 -3
- package/android/src/main/java/expo/modules/localllm/ConversationHistory.kt +14 -8
- package/android/src/main/java/expo/modules/localllm/Exceptions.kt +16 -2
- package/android/src/main/java/expo/modules/localllm/ExpoLocalLlmModule.kt +46 -18
- package/android/src/main/java/expo/modules/localllm/GenerationErrors.kt +30 -0
- package/android/src/main/java/expo/modules/localllm/LLMSession.kt +310 -84
- package/android/src/test/java/expo/modules/localllm/ConversationHistoryTest.kt +96 -0
- package/android/src/test/java/expo/modules/localllm/GenerationErrorsTest.kt +48 -0
- package/build/ExpoLocalLlm.types.d.ts +142 -12
- package/build/ExpoLocalLlm.types.d.ts.map +1 -1
- package/build/ExpoLocalLlm.types.js.map +1 -1
- package/build/ExpoLocalLlmModule.d.ts +2 -0
- package/build/ExpoLocalLlmModule.d.ts.map +1 -1
- package/build/ExpoLocalLlmModule.js +7 -1
- package/build/ExpoLocalLlmModule.js.map +1 -1
- package/build/LLMSession.d.ts +8 -4
- package/build/LLMSession.d.ts.map +1 -1
- package/build/LLMSession.js +37 -2
- package/build/LLMSession.js.map +1 -1
- package/build/capabilities.d.ts +4 -0
- package/build/capabilities.d.ts.map +1 -0
- package/build/capabilities.js +64 -0
- package/build/capabilities.js.map +1 -0
- package/build/defineTool.d.ts +19 -0
- package/build/defineTool.d.ts.map +1 -0
- package/build/defineTool.js +20 -0
- package/build/defineTool.js.map +1 -0
- package/build/errors.d.ts +25 -0
- package/build/errors.d.ts.map +1 -0
- package/build/errors.js +54 -0
- package/build/errors.js.map +1 -0
- package/build/index.d.ts +5 -2
- package/build/index.d.ts.map +1 -1
- package/build/index.js +5 -2
- package/build/index.js.map +1 -1
- package/build/useLocalLLM.d.ts +15 -5
- package/build/useLocalLLM.d.ts.map +1 -1
- package/build/useLocalLLM.js +222 -45
- package/build/useLocalLLM.js.map +1 -1
- package/build/validateSchema.d.ts +14 -1
- package/build/validateSchema.d.ts.map +1 -1
- package/build/validateSchema.js +126 -0
- package/build/validateSchema.js.map +1 -1
- package/expo-module.config.json +2 -1
- package/ios/DynamicTool.swift +67 -57
- package/ios/Exceptions.swift +16 -10
- package/ios/ExpoLocalLlmModule.swift +5 -5
- package/ios/FoundationModelBridge.swift +90 -36
- package/ios/GenerationErrorCodes.swift +107 -0
- package/ios/GenerationErrorMapper.swift +27 -0
- package/ios/LLMSession.swift +528 -136
- package/package.json +21 -7
- package/src/ExpoLocalLlm.types.ts +175 -14
- package/src/ExpoLocalLlmModule.ts +7 -1
- package/src/LLMSession.ts +59 -4
- package/src/__tests__/ExpoLocalLlm.test.ts +32 -5
- package/src/__tests__/inference.typecheck.ts +119 -0
- package/src/__tests__/nativeApi.test.ts +253 -0
- package/src/__tests__/useLocalLLM.native.test.ts +944 -0
- package/src/__tests__/useLocalLLM.test.ts +25 -4
- package/src/__tests__/validateSchema.test.ts +94 -1
- package/src/capabilities.ts +79 -0
- package/src/defineTool.ts +23 -0
- package/src/errors.ts +57 -0
- package/src/index.ts +12 -1
- package/src/useLocalLLM.ts +289 -56
- package/src/validateSchema.ts +139 -1
package/README.md
CHANGED
|
@@ -1,22 +1,47 @@
|
|
|
1
1
|
# expo-local-llm
|
|
2
2
|
|
|
3
|
-
|
|
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.
|
|
4
|
+
|
|
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.
|
|
4
6
|
|
|
5
7
|
## Platform Requirements
|
|
6
8
|
|
|
7
9
|
| Platform | Requirement |
|
|
8
10
|
|----------|-------------|
|
|
9
11
|
| iOS | iOS 26+ with Apple Intelligence enabled. Loads on iOS 16.4+ without crashing (returns `notEligible`). Host project must target iOS 16.4+. |
|
|
10
|
-
| Android |
|
|
11
|
-
| Expo SDK | 52+ (peer
|
|
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. |
|
|
14
|
+
|
|
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).
|
|
16
|
+
|
|
17
|
+
For setup failures, unavailable models, and unsupported features, see [troubleshooting](docs/troubleshooting.md).
|
|
18
|
+
|
|
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.
|
|
12
37
|
|
|
13
38
|
## Installation
|
|
14
39
|
|
|
15
40
|
```bash
|
|
16
|
-
|
|
41
|
+
npx expo install expo-local-llm expo-dev-client
|
|
17
42
|
```
|
|
18
43
|
|
|
19
|
-
This module requires **iOS 16.4+**
|
|
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/):
|
|
20
45
|
|
|
21
46
|
```bash
|
|
22
47
|
npx expo install expo-build-properties
|
|
@@ -30,24 +55,58 @@ In `app.json`, add the plugin with the deployment target:
|
|
|
30
55
|
"plugins": [
|
|
31
56
|
[
|
|
32
57
|
"expo-build-properties",
|
|
33
|
-
{
|
|
58
|
+
{
|
|
59
|
+
"ios": { "deploymentTarget": "16.4" },
|
|
60
|
+
"android": { "minSdkVersion": 26 }
|
|
61
|
+
}
|
|
34
62
|
]
|
|
35
63
|
]
|
|
36
64
|
}
|
|
37
65
|
}
|
|
38
66
|
```
|
|
39
67
|
|
|
40
|
-
|
|
68
|
+
Build and install your development app on a supported physical device:
|
|
41
69
|
|
|
42
70
|
```bash
|
|
43
|
-
npx expo
|
|
71
|
+
npx expo run:ios --device
|
|
72
|
+
# or
|
|
73
|
+
npx expo run:android --device
|
|
44
74
|
```
|
|
45
75
|
|
|
46
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.)
|
|
47
77
|
|
|
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`.
|
|
79
|
+
|
|
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.
|
|
81
|
+
|
|
48
82
|
## Usage
|
|
49
83
|
|
|
84
|
+
### Extract typed data (iOS 26+)
|
|
85
|
+
|
|
86
|
+
For classification and extraction, use `generateObject()`. It infers the result type from your schema and validates the generated JSON before returning it:
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
import { generateObject, getCapabilities } from 'expo-local-llm';
|
|
90
|
+
|
|
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'
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
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.
|
|
105
|
+
|
|
106
|
+
### Stream text in a component
|
|
107
|
+
|
|
50
108
|
```tsx
|
|
109
|
+
import { Text, View } from 'react-native';
|
|
51
110
|
import { useLocalLLM } from 'expo-local-llm';
|
|
52
111
|
|
|
53
112
|
function Chat() {
|
|
@@ -65,9 +124,15 @@ function Chat() {
|
|
|
65
124
|
instructions: 'You are a helpful assistant.',
|
|
66
125
|
});
|
|
67
126
|
|
|
68
|
-
// Methods
|
|
127
|
+
// Methods become available after session creation and model readiness.
|
|
69
128
|
if (!streamResponse) {
|
|
70
|
-
return
|
|
129
|
+
return (
|
|
130
|
+
<Text>
|
|
131
|
+
{error ?? (availability === 'available'
|
|
132
|
+
? 'Preparing on-device AI...'
|
|
133
|
+
: `On-device AI: ${availability}`)}
|
|
134
|
+
</Text>
|
|
135
|
+
);
|
|
71
136
|
}
|
|
72
137
|
|
|
73
138
|
const handleSend = async (prompt: string) => {
|
|
@@ -87,27 +152,93 @@ function Chat() {
|
|
|
87
152
|
}
|
|
88
153
|
```
|
|
89
154
|
|
|
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()`.
|
|
158
|
+
|
|
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:
|
|
160
|
+
|
|
161
|
+
```tsx
|
|
162
|
+
const { errorCode, reset, streamResponse } = useLocalLLM({ instructions });
|
|
163
|
+
|
|
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
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
```ts
|
|
174
|
+
import { generate, getLocalLLMErrorCode } from 'expo-local-llm';
|
|
175
|
+
|
|
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
|
+
}
|
|
182
|
+
}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
#### Using with Effect
|
|
186
|
+
|
|
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:
|
|
188
|
+
|
|
189
|
+
```ts
|
|
190
|
+
import { Data, Effect } from 'effect';
|
|
191
|
+
import { generate, getLocalLLMErrorCode, type LocalLLMErrorCode } from 'expo-local-llm';
|
|
192
|
+
|
|
193
|
+
class LocalLLMFailure extends Data.TaggedError('LocalLLMFailure')<{
|
|
194
|
+
code: LocalLLMErrorCode | null;
|
|
195
|
+
message: string;
|
|
196
|
+
}> {}
|
|
197
|
+
|
|
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
|
+
});
|
|
207
|
+
|
|
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
|
+
);
|
|
215
|
+
```
|
|
216
|
+
|
|
90
217
|
### Tool Calling (iOS 26+)
|
|
91
218
|
|
|
92
219
|
```tsx
|
|
93
|
-
import { useLocalLLM } from 'expo-local-llm';
|
|
220
|
+
import { defineTool, useLocalLLM } from 'expo-local-llm';
|
|
221
|
+
|
|
222
|
+
const getWeather = defineTool({
|
|
223
|
+
name: 'getWeather',
|
|
224
|
+
description: 'Get the current weather for a city',
|
|
225
|
+
parameters: {
|
|
226
|
+
city: { type: 'string', description: 'The city name' },
|
|
227
|
+
unit: { type: 'string', description: 'Temperature unit', enum: ['celsius', 'fahrenheit'] },
|
|
228
|
+
},
|
|
229
|
+
// Typed from `parameters`: city is a string, unit is 'celsius' | 'fahrenheit'.
|
|
230
|
+
handler: async ({ city, unit }) => {
|
|
231
|
+
const res = await fetch(
|
|
232
|
+
`https://api.example.com/weather?city=${encodeURIComponent(city)}&unit=${unit}`
|
|
233
|
+
);
|
|
234
|
+
return JSON.stringify(await res.json());
|
|
235
|
+
},
|
|
236
|
+
});
|
|
94
237
|
|
|
95
238
|
function WeatherChat() {
|
|
96
239
|
const { streamResponse, streamedText, activeToolCalls, error } = useLocalLLM({
|
|
97
240
|
instructions: 'You are a helpful weather assistant.',
|
|
98
|
-
tools: [
|
|
99
|
-
{
|
|
100
|
-
name: 'getWeather',
|
|
101
|
-
description: 'Get the current weather for a city',
|
|
102
|
-
parameters: {
|
|
103
|
-
city: { type: 'string', description: 'The city name' },
|
|
104
|
-
},
|
|
105
|
-
handler: async (args) => {
|
|
106
|
-
const res = await fetch(`https://api.example.com/weather?city=${args.city}`);
|
|
107
|
-
return JSON.stringify(await res.json());
|
|
108
|
-
},
|
|
109
|
-
},
|
|
110
|
-
],
|
|
241
|
+
tools: [getWeather],
|
|
111
242
|
});
|
|
112
243
|
|
|
113
244
|
return (
|
|
@@ -121,11 +252,15 @@ function WeatherChat() {
|
|
|
121
252
|
}
|
|
122
253
|
```
|
|
123
254
|
|
|
255
|
+
`defineTool()` infers the handler's argument types from `parameters`; it returns the tool unchanged. Tools written inline in `tools` still work, with arguments typed as `Record<string, string | number | boolean>`.
|
|
256
|
+
|
|
257
|
+
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
|
+
|
|
124
259
|
### Structured Output
|
|
125
260
|
|
|
126
|
-
iOS 26+ uses Apple's constrained decoding (`DynamicGenerationSchema`)
|
|
127
|
-
|
|
128
|
-
|
|
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.
|
|
129
264
|
|
|
130
265
|
<p align="center">
|
|
131
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" />
|
|
@@ -134,7 +269,7 @@ guidance.
|
|
|
134
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`.*
|
|
135
270
|
|
|
136
271
|
```tsx
|
|
137
|
-
const { respond } = useLocalLLM({
|
|
272
|
+
const { respond, completedObject } = useLocalLLM({
|
|
138
273
|
responseFormat: 'json',
|
|
139
274
|
schema: {
|
|
140
275
|
name: { type: 'string', description: 'Recipe name' },
|
|
@@ -155,9 +290,15 @@ const { respond } = useLocalLLM({
|
|
|
155
290
|
},
|
|
156
291
|
});
|
|
157
292
|
|
|
158
|
-
|
|
293
|
+
if (respond) {
|
|
294
|
+
await respond('Give me a pasta recipe');
|
|
295
|
+
}
|
|
296
|
+
// completedObject is typed from the schema and validated against it:
|
|
297
|
+
// { name: string; ingredients: string[]; steps: string[]; difficulty: 'easy' | 'medium' | 'hard' } | null
|
|
159
298
|
```
|
|
160
299
|
|
|
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
|
+
|
|
161
302
|
For one-shot structured calls (classification, extraction) where you don't want conversation
|
|
162
303
|
history accumulating between calls, use `generate()` instead of a session:
|
|
163
304
|
|
|
@@ -172,18 +313,22 @@ const json = await generate('Classify this message: "how was my sleep?"', {
|
|
|
172
313
|
```
|
|
173
314
|
|
|
174
315
|
Streaming with a schema yields progressively-filled snapshots via `streamedJSON` (the current
|
|
175
|
-
partial as a JSON string) and `streamedObject` (the parsed value)
|
|
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`:
|
|
176
318
|
|
|
177
319
|
```tsx
|
|
178
|
-
const { streamResponse, streamedObject } = useLocalLLM({
|
|
320
|
+
const { streamResponse, streamedObject, completedObject } = useLocalLLM({
|
|
179
321
|
responseFormat: 'json',
|
|
180
322
|
schema: { /* ... */ },
|
|
181
323
|
});
|
|
182
324
|
|
|
183
|
-
await streamResponse('Give me a pasta recipe');
|
|
184
|
-
// streamedObject updates as the model fills in fields
|
|
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.
|
|
185
328
|
```
|
|
186
329
|
|
|
330
|
+
A cancelled stream keeps its partial `streamedObject` and leaves `completedObject` null.
|
|
331
|
+
|
|
187
332
|
#### Schema validation
|
|
188
333
|
|
|
189
334
|
`createLLMSession` validates the schema before crossing to native and throws `SchemaInvalidError`
|
|
@@ -203,6 +348,29 @@ if (!result.ok) {
|
|
|
203
348
|
|
|
204
349
|
## API
|
|
205
350
|
|
|
351
|
+
### `getCapabilities()`
|
|
352
|
+
|
|
353
|
+
Reports which features the loaded platform integration implements. Use this to decide whether to offer text, streaming, tools, or structured output. Check model availability separately before starting generation. A supported capability does not mean the OS model is currently ready.
|
|
354
|
+
|
|
355
|
+
The function does not download models, start inference, or opt the app into a cloud service.
|
|
356
|
+
|
|
357
|
+
Returns `platform`, `backend`, `status`, boolean `text`/`streaming`/`tools`/`structuredOutput` flags, `structuredOutputMode` (`'constrained'` or `'unsupported'`), and `experimental`. `experimental` is `true` on Android until Gemini Nano generation has been validated on real hardware; gate production features on it if you need verified behavior.
|
|
358
|
+
|
|
359
|
+
Capability `status` is `'available'`, `'moduleUnavailable'`, `'unsupportedOS'`, or `'unsupportedPlatform'`. Here `'available'` means the integration is supported; use the hook's `availability` for current model readiness.
|
|
360
|
+
|
|
361
|
+
### `getModuleDiagnostics()`
|
|
362
|
+
|
|
363
|
+
Reports whether native code loaded, with `available`, `reason`, `message`, and `setupInstructions`. A missing module is a build/setup problem, distinct from a device that cannot run the model.
|
|
364
|
+
|
|
365
|
+
```ts
|
|
366
|
+
import { getModuleDiagnostics } from 'expo-local-llm';
|
|
367
|
+
|
|
368
|
+
const diagnostics = getModuleDiagnostics();
|
|
369
|
+
if (!diagnostics.available) {
|
|
370
|
+
console.warn(diagnostics.message);
|
|
371
|
+
}
|
|
372
|
+
```
|
|
373
|
+
|
|
206
374
|
### `useLocalLLM(options?)`
|
|
207
375
|
|
|
208
376
|
#### Options
|
|
@@ -211,11 +379,11 @@ if (!result.ok) {
|
|
|
211
379
|
|--------|------|-------------|
|
|
212
380
|
| `instructions` | `string` | System instructions for the session |
|
|
213
381
|
| `options.temperature` | `number` | Sampling temperature |
|
|
214
|
-
| `options.maxTokens` | `number` | Max output tokens (
|
|
382
|
+
| `options.maxTokens` | `number` | Max output tokens (`maxOutputTokens` on Android; `maximumResponseTokens` on iOS). Native SDK/model limits apply. |
|
|
215
383
|
| `options.topK` | `number` | Top-K sampling (maps to `.random(top:)` on iOS) |
|
|
216
384
|
| `tools` | `ToolDefinition[]` | Tools the model can invoke (iOS 26+ only) |
|
|
217
385
|
| `toolTimeout` | `number` | Seconds before an unresolved tool call times out (default 30) |
|
|
218
|
-
| `responseFormat` | `"text" \| "json"` | Set to `"json"` for structured JSON output |
|
|
386
|
+
| `responseFormat` | `"text" \| "json"` | Set to `"json"` for structured JSON output; requires `schema` |
|
|
219
387
|
| `schema` | `Schema` | Schema for structured output (used with `responseFormat: "json"`) — see Structured Output section |
|
|
220
388
|
| `includeSchemaInPrompt` | `boolean` | iOS 26+: include the schema definition in the prompt. Default `true`. Set `false` when you've front-loaded few-shot examples. |
|
|
221
389
|
|
|
@@ -227,113 +395,127 @@ if (!result.ok) {
|
|
|
227
395
|
| `isGenerating` | `boolean` | Whether a generation is in progress |
|
|
228
396
|
| `streamedText` | `string` | Accumulated text from current text stream |
|
|
229
397
|
| `streamedJSON` | `string` | Current partial JSON from a schema-driven stream |
|
|
230
|
-
| `streamedObject` | `
|
|
398
|
+
| `streamedObject` | `DeepPartial<InferSchema<schema>> \| null` | Parsed value of `streamedJSON` (or `null` before any partial). Fields may be missing until generated. |
|
|
399
|
+
| `completedObject` | `InferSchema<schema> \| null` | The finished JSON-mode response, validated against the schema. `null` while generating and after a cancel. |
|
|
231
400
|
| `downloadProgress` | `number \| null` | Download progress 0-1 (Android only) |
|
|
232
401
|
| `error` | `string \| null` | Last error message |
|
|
402
|
+
| `errorCode` | `LocalLLMErrorCode \| null` | Code of the last error (see [Error codes](#error-codes)); `null` when there is none or it came from your own code |
|
|
233
403
|
| `activeToolCalls` | `ActiveToolCall[]` | In-flight tool calls (`{ callId, toolName }`) |
|
|
234
404
|
| `respond` | `(prompt: string) => Promise<string>` | Non-streaming generation. `undefined` when unavailable. |
|
|
235
|
-
| `session` | `LLMSession \| null` | The native session object. `null` when unavailable. |
|
|
405
|
+
| `session` | `LLMSession \| null` | The native session object. `null` before creation or when unavailable. Use the hook's generation methods for hook-managed state and tool dispatch. |
|
|
236
406
|
| `streamResponse` | `(prompt: string) => Promise<string>` | Streaming generation (updates `streamedText`). Resolves with the final text at stream end, or the partial text if cancelled; rejects on stream failure. `undefined` when model not `available`. |
|
|
237
|
-
| `cancelStream` | `() => Promise<void>` | Cancel the active stream
|
|
407
|
+
| `cancelStream` | `() => Promise<void>` | Cancel the active stream; the pending `streamResponse` promise resolves with the partial text. Available whenever a session exists, even if model readiness changes. |
|
|
238
408
|
| `reset` | `() => void` | Clear the conversation transcript, keeping instructions/tools/schema/options. Also cancels any in-flight stream. `undefined` only when there is no session. |
|
|
239
409
|
| `downloadModel` | `() => Promise<void>` | Trigger model download (Android). `undefined` when unavailable. |
|
|
240
410
|
|
|
241
411
|
### `generate(prompt, config?)`
|
|
242
412
|
|
|
243
413
|
One-shot stateless generation — creates a session, responds once, and releases it. Takes the
|
|
244
|
-
same config as `useLocalLLM`/`createLLMSession
|
|
414
|
+
same generation config as `useLocalLLM`/`createLLMSession`, except tool handlers are not supported. Use it for classification or extraction
|
|
245
415
|
calls where conversation history between calls is unwanted. Throws when the module or model
|
|
246
416
|
is unavailable (check `ExpoLocalLlmModule.getAvailability()` first if you need to gate).
|
|
247
417
|
|
|
418
|
+
Use `useLocalLLM()` for automatic tool dispatch. Low-level `createLLMSession()` consumers must subscribe to `toolCall` and resolve or reject each call themselves; passing handlers in its configuration does not install those listeners.
|
|
419
|
+
|
|
420
|
+
### `generateObject(prompt, config)`
|
|
421
|
+
|
|
422
|
+
One-shot structured generation with a required `schema`. Returns an object whose type is inferred from the schema, after validating every required field, primitive type, enum, array item, and nested object. Unexpected properties are rejected. Use inline schema literals or `as const satisfies Schema` to preserve enum literal types.
|
|
423
|
+
|
|
424
|
+
This helper requires constrained structured output support (currently iOS 26+). It releases its session on success and failure. Generation errors and model refusals still reject the call; schema validation establishes structure, not factual accuracy.
|
|
425
|
+
|
|
426
|
+
`validateStructuredOutput(value, schema)` is also available for checking an already-parsed value against a valid schema. It returns `{ ok: true }` or `{ ok: false, errors }` with field paths.
|
|
427
|
+
|
|
428
|
+
### `createLLMSession(config?)`
|
|
429
|
+
|
|
430
|
+
Creates a manually owned conversation. Release it when finished:
|
|
431
|
+
|
|
432
|
+
```ts
|
|
433
|
+
import { createLLMSession } from 'expo-local-llm';
|
|
434
|
+
|
|
435
|
+
const session = createLLMSession({ instructions: 'Keep answers concise.' });
|
|
436
|
+
try {
|
|
437
|
+
const text = await session.respond('Suggest a short title for a gardening journal.');
|
|
438
|
+
} finally {
|
|
439
|
+
session.release();
|
|
440
|
+
}
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
Low-level `respond(prompt, requestId?)` and `streamResponse(prompt, requestId?)` accept an optional request identifier. Generation events carry `requestId` so consumers can discard queued events from cancelled or reset requests. Omit it to let native code generate an identifier; `useLocalLLM()` manages identifiers and filtering automatically. Use a distinct identifier for each invocation.
|
|
444
|
+
|
|
445
|
+
### Error codes
|
|
446
|
+
|
|
447
|
+
Errors thrown or rejected by the library, and `streamError` events, carry one of these codes. Messages come from the OS and may change; codes do not.
|
|
448
|
+
|
|
449
|
+
On iOS 27, Foundation Models reports some failures with error types that only the iOS 27 SDK can name. Build with Xcode 27 to classify all of them. An app built with Xcode 26.4 still gets codes for the iOS 26 error types, but reports the newer ones as `ERR_GENERATION_FAILED`.
|
|
450
|
+
|
|
451
|
+
| Code | Meaning |
|
|
452
|
+
|------|---------|
|
|
453
|
+
| `ERR_CONTEXT_WINDOW_EXCEEDED` | The conversation no longer fits the model's context window. Reset or start a new session. |
|
|
454
|
+
| `ERR_GUARDRAIL_VIOLATION` | Safety guardrails blocked the prompt or response. |
|
|
455
|
+
| `ERR_REFUSAL` | The model declined to answer. |
|
|
456
|
+
| `ERR_UNSUPPORTED_LANGUAGE` | The prompt language or device locale is not supported. |
|
|
457
|
+
| `ERR_UNSUPPORTED_REQUEST` | The request uses a capability the model or helper cannot honour. |
|
|
458
|
+
| `ERR_RATE_LIMITED` | The system is throttling generation (quota, background use, load). Retry later. |
|
|
459
|
+
| `ERR_MODEL_UNAVAILABLE` | Model assets are missing or the device cannot run the model right now. |
|
|
460
|
+
| `ERR_DECODING_FAILED` | Output could not be decoded into the requested structure. |
|
|
461
|
+
| `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. |
|
|
463
|
+
| `ERR_GENERATION_FAILED` | Any other generation failure. |
|
|
464
|
+
| `ERR_SESSION_BUSY` | The session already has a generation in progress. |
|
|
465
|
+
| `ERR_SESSION_INVALID` | The session was released or is not ready. |
|
|
466
|
+
| `ERR_NOT_SUPPORTED` | On-device generation is not available on this OS version. |
|
|
467
|
+
| `ERR_RESPONSE_FORMAT_NOT_SUPPORTED` | Structured output is not supported on this platform. |
|
|
468
|
+
| `ERR_TOOL_NOT_SUPPORTED` | Tool calling is not supported on this platform. |
|
|
469
|
+
| `ERR_MODEL_DOWNLOAD_FAILED` | Android: downloading Gemini Nano failed. |
|
|
470
|
+
| `ERR_SCHEMA_INVALID` | A schema is malformed (`SchemaInvalidError`). |
|
|
471
|
+
| `ERR_STRUCTURED_OUTPUT_INVALID` | Generated JSON did not match the schema (`StructuredOutputValidationError`). |
|
|
472
|
+
| `ERR_UNAVAILABLE` | The native module is missing from this build. |
|
|
473
|
+
|
|
248
474
|
### `ModelAvailability`
|
|
249
475
|
|
|
250
|
-
`'available' | 'notEnabled' | 'notReady' | 'notEligible' | 'downloadRequired' | 'downloading' | 'unknown'`
|
|
476
|
+
`'available' | 'notEnabled' | 'notReady' | 'notEligible' | 'downloadRequired' | 'downloading' | 'unknown' | 'moduleUnavailable'`
|
|
477
|
+
|
|
478
|
+
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.
|
|
251
479
|
|
|
252
480
|
## Platform Asymmetries
|
|
253
481
|
|
|
254
482
|
| Concern | iOS | Android |
|
|
255
483
|
|---------|-----|---------|
|
|
256
484
|
| System instructions | Native `LanguageModelSession(instructions:)` | Prepended in prompt text |
|
|
257
|
-
|
|
|
485
|
+
| Token limits | OS/model-dependent | SDK/model-dependent |
|
|
258
486
|
| Model availability | Built-in to OS | May need download |
|
|
259
|
-
| Session history | Native session maintains it |
|
|
487
|
+
| Session history | Native session maintains it | Replayed in the prompt; last 10 exchanges, with role markers in conversation text neutralised |
|
|
260
488
|
| Streaming | `AsyncSequence` | Kotlin `Flow` |
|
|
261
489
|
|
|
262
490
|
## Known Limitations
|
|
263
491
|
|
|
264
|
-
-
|
|
265
|
-
-
|
|
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.
|
|
266
496
|
- **iOS**: Apple's Foundation Model may refuse certain categories of prompts (e.g. personal health data interpretation) due to built-in safety guardrails.
|
|
267
|
-
-
|
|
497
|
+
- Generation methods (`respond`, `streamResponse`) require a session and `availability === 'available'`. Cancellation remains available while a session exists.
|
|
268
498
|
- Tool calling is iOS 26+ only. Android will throw at session creation if tools are passed.
|
|
269
|
-
- Structured output on Android
|
|
270
|
-
decoding via `DynamicGenerationSchema`.
|
|
499
|
+
- Structured output on Android is unsupported. iOS 26+ uses constrained decoding via `DynamicGenerationSchema`.
|
|
271
500
|
|
|
272
501
|
## Why an Expo module?
|
|
273
502
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
-
|
|
278
|
-
|
|
279
|
-
- **
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
If you're on bare React Native without Expo, this module won't work — you'd need
|
|
292
|
-
to add `expo-modules-core` as a dependency or use a different library.
|
|
293
|
-
|
|
294
|
-
## How is this different from React Native AI? (as of August 2026)
|
|
295
|
-
|
|
296
|
-
[React Native AI](https://github.com/callstackincubator/ai) (formerly `@callstack/ai`)
|
|
297
|
-
is a Vercel AI SDK-compatible collection of on-device AI primitives, modularized
|
|
298
|
-
into per-backend packages: `@react-native-ai/apple`, `@react-native-ai/llama`,
|
|
299
|
-
`@react-native-ai/mlc`, and — new as of July 2026 — `@react-native-ai/adk`, which
|
|
300
|
-
wraps Google's Agent Development Kit to run on-device Gemini Nano (or cloud
|
|
301
|
-
Gemini) on Android. It covers text generation, tool calling, embeddings,
|
|
302
|
-
transcription, and speech synthesis.
|
|
303
|
-
|
|
304
|
-
`expo-local-llm` takes a narrower approach:
|
|
305
|
-
|
|
306
|
-
| | `expo-local-llm` | React Native AI |
|
|
307
|
-
|---|---|---|
|
|
308
|
-
| **Surface** | React hook (`useLocalLLM`) and a native session object | Vercel AI SDK provider (`generateText`, `streamText`, `embed`, `transcribe`, `speech`) |
|
|
309
|
-
| **iOS model** | Apple Foundation Models (system-provided) | Apple Foundation Models, Llama (via llama.rn), MLC LLM |
|
|
310
|
-
| **Android model** | Gemini Nano (system-provided) | Gemini Nano (via ADK/ML Kit GenAI), cloud Gemini, Llama, MLC |
|
|
311
|
-
| **Capabilities** | Text generation, tool calling, constrained JSON output | Text, tool calling, embeddings, transcription, speech synthesis, image input (ADK) |
|
|
312
|
-
| **Structured output** | Constrained decoding on iOS; instruction-based on Android | JSON mode via `responseMimeType` on ADK (no schema constraints); MLC on iOS |
|
|
313
|
-
| **Model management** | None — the OS handles it | Built-in for Apple/ADK; download/prepare for Llama/MLC |
|
|
314
|
-
| **Bundle size impact** | Near zero | Depends on which provider you install + model weights (ADK pulls in Google GenAI libraries) |
|
|
315
|
-
| **DevTools** | None | AI SDK Profiler via Rozenite (OpenTelemetry spans) |
|
|
316
|
-
| **Install path** | One `expo install`, autolinks | Per-package install; ADK needs New Architecture, `minSdkVersion` 26, and packaging excludes |
|
|
317
|
-
| **Dependencies** | None | Vercel AI SDK (v6 as of v0.12) and per-provider runtimes |
|
|
318
|
-
|
|
319
|
-
**Choose `expo-local-llm` if** you want the simplest path to on-device LLM in
|
|
320
|
-
an Expo app and are happy using whatever model the OS provides (Apple Foundation
|
|
321
|
-
Models on iOS, Gemini Nano on Android). There's nothing to configure, no weights
|
|
322
|
-
to bundle, and it works on both platforms with a single install.
|
|
323
|
-
|
|
324
|
-
**Choose React Native AI if** you need any of:
|
|
325
|
-
- Vercel AI SDK compatibility (drop-in replacement for cloud-LLM apps)
|
|
326
|
-
- Capabilities beyond text generation (embeddings, transcription, speech synthesis, image input)
|
|
327
|
-
- Specific models (Llama 3.2, Phi-3, Mistral, Qwen) on either iOS or Android
|
|
328
|
-
- A cloud Gemini fallback behind the same API as on-device Gemini Nano
|
|
329
|
-
- AI SDK Profiler DevTools for tracing
|
|
330
|
-
|
|
331
|
-
The biggest change since June 2026: React Native AI's `@react-native-ai/adk`
|
|
332
|
-
package closed the Android gap — it now offers system-provided Gemini Nano too,
|
|
333
|
-
with tool calling and multimodal input. The remaining differences are setup
|
|
334
|
-
weight (ADK requires New Architecture, `minSdkVersion` 26, and Gradle packaging
|
|
335
|
-
tweaks vs. a single `expo install` here) and API shape (Vercel AI SDK functions
|
|
336
|
-
vs. a React hook with a session object).
|
|
503
|
+
The [Expo Modules API](https://docs.expo.dev/modules/overview/) fits OS-provided AI APIs well:
|
|
504
|
+
|
|
505
|
+
- **Native session objects** — Expo's `SharedObject` and class DSL expose instance methods and session-scoped events directly to JavaScript.
|
|
506
|
+
- **Lifecycle integration** — native cleanup hooks support cancellation and resource release. `generate()` releases its session automatically; consumers of `createLLMSession()` must call `release()` when finished.
|
|
507
|
+
- **Swift and Kotlin** — both implementations use a consistent module definition style, without adding a separate inference engine.
|
|
508
|
+
- **Expo build workflows** — autolinking integrates with prebuild, local development builds, and EAS Build.
|
|
509
|
+
|
|
510
|
+
Expo does not improve model quality or remove device eligibility requirements. Its native-call performance is comparable to React Native Turbo Modules; autolinking is not unique to Expo. The benefit is a cohesive integration and maintenance experience.
|
|
511
|
+
|
|
512
|
+
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
|
+
|
|
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.
|
|
337
519
|
|
|
338
520
|
## Built with expo-local-llm
|
|
339
521
|
|
package/android/build/generated/source/buildConfig/debug/expo/modules/localllm/BuildConfig.java
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Automatically generated file. DO NOT MODIFY
|
|
3
|
+
*/
|
|
4
|
+
package expo.modules.localllm;
|
|
5
|
+
|
|
6
|
+
public final class BuildConfig {
|
|
7
|
+
public static final boolean DEBUG = Boolean.parseBoolean("true");
|
|
8
|
+
public static final String LIBRARY_PACKAGE_NAME = "expo.modules.localllm";
|
|
9
|
+
public static final String BUILD_TYPE = "debug";
|
|
10
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"artifactType": {
|
|
4
|
+
"type": "AAPT_FRIENDLY_MERGED_MANIFESTS",
|
|
5
|
+
"kind": "Directory"
|
|
6
|
+
},
|
|
7
|
+
"applicationId": "expo.modules.localllm",
|
|
8
|
+
"variantName": "debug",
|
|
9
|
+
"elements": [
|
|
10
|
+
{
|
|
11
|
+
"type": "SINGLE",
|
|
12
|
+
"filters": [],
|
|
13
|
+
"attributes": [],
|
|
14
|
+
"outputFile": "AndroidManifest.xml"
|
|
15
|
+
}
|
|
16
|
+
],
|
|
17
|
+
"elementType": "File"
|
|
18
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{}
|
|
Binary file
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#Sun Sep 27 14:00:13 EDT 2026
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
+
<merger version="3"><dataSet aapt-namespace="http://schemas.android.com/apk/res-auto" config="main$Generated" generated="true" ignore_pattern="!.svn:!.git:!.ds_store:!*.scc:.*:<dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~"><source path="/Users/swoleknee/chingu/expo-local-llm/android/src/main/res"/></dataSet><dataSet aapt-namespace="http://schemas.android.com/apk/res-auto" config="main" generated-set="main$Generated" ignore_pattern="!.svn:!.git:!.ds_store:!*.scc:.*:<dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~"><source path="/Users/swoleknee/chingu/expo-local-llm/android/src/main/res"/></dataSet><dataSet aapt-namespace="http://schemas.android.com/apk/res-auto" config="debug$Generated" generated="true" ignore_pattern="!.svn:!.git:!.ds_store:!*.scc:.*:<dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~"><source path="/Users/swoleknee/chingu/expo-local-llm/android/src/debug/res"/></dataSet><dataSet aapt-namespace="http://schemas.android.com/apk/res-auto" config="debug" generated-set="debug$Generated" ignore_pattern="!.svn:!.git:!.ds_store:!*.scc:.*:<dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~"><source path="/Users/swoleknee/chingu/expo-local-llm/android/src/debug/res"/></dataSet><dataSet aapt-namespace="http://schemas.android.com/apk/res-auto" config="generated$Generated" generated="true" ignore_pattern="!.svn:!.git:!.ds_store:!*.scc:.*:<dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~"><source path="/Users/swoleknee/chingu/expo-local-llm/android/build/generated/res/resValues/debug"/></dataSet><dataSet aapt-namespace="http://schemas.android.com/apk/res-auto" config="generated" generated-set="generated$Generated" ignore_pattern="!.svn:!.git:!.ds_store:!*.scc:.*:<dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~"><source path="/Users/swoleknee/chingu/expo-local-llm/android/build/generated/res/resValues/debug"/></dataSet><mergedItems/></merger>
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
+
<merger version="3"><dataSet config="main" ignore_pattern="!.svn:!.git:!.ds_store:!*.scc:.*:<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:.*:<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:.*:<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>
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
+
<merger version="3"><dataSet config="main" ignore_pattern="!.svn:!.git:!.ds_store:!*.scc:.*:<dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~"><source path="/Users/swoleknee/chingu/expo-local-llm/example/node_modules/expo-local-llm/android/src/main/jniLibs"/></dataSet><dataSet config="debug" ignore_pattern="!.svn:!.git:!.ds_store:!*.scc:.*:<dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~"><source path="/Users/swoleknee/chingu/expo-local-llm/example/node_modules/expo-local-llm/android/src/debug/jniLibs"/></dataSet></merger>
|