expo-local-llm 0.4.0 → 0.5.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 +30 -8
- package/android/src/main/java/expo/modules/localllm/ConversationHistory.kt +4 -0
- package/android/src/main/java/expo/modules/localllm/ExpoLocalLlmModule.kt +6 -2
- package/android/src/main/java/expo/modules/localllm/LLMSession.kt +36 -7
- package/build/LLMSession.d.ts +24 -1
- package/build/LLMSession.d.ts.map +1 -1
- package/build/LLMSession.js +31 -0
- package/build/LLMSession.js.map +1 -1
- package/build/index.d.ts +1 -1
- package/build/index.d.ts.map +1 -1
- package/build/index.js +1 -1
- package/build/index.js.map +1 -1
- package/build/useLocalLLM.d.ts +2 -1
- package/build/useLocalLLM.d.ts.map +1 -1
- package/build/useLocalLLM.js +24 -3
- package/build/useLocalLLM.js.map +1 -1
- package/build/validateSchema.d.ts.map +1 -1
- package/build/validateSchema.js.map +1 -1
- package/ios/DynamicTool.swift +34 -10
- package/ios/ExpoLocalLlmModule.swift +6 -2
- package/ios/FoundationModelBridge.swift +51 -10
- package/ios/LLMSession.swift +84 -30
- package/package.json +4 -4
- package/src/LLMSession.ts +35 -1
- package/src/__tests__/ExpoLocalLlm.test.ts +18 -2
- package/src/__tests__/validateSchema.test.ts +3 -3
- package/src/index.ts +1 -1
- package/src/useLocalLLM.ts +47 -26
- package/src/validateSchema.ts +4 -2
package/README.md
CHANGED
|
@@ -8,6 +8,7 @@ Expo module for on-device LLM inference. Wraps Apple Foundation Models (iOS 26+)
|
|
|
8
8
|
|----------|-------------|
|
|
9
9
|
| iOS | iOS 26+ with Apple Intelligence enabled. Loads on iOS 16.4+ without crashing (returns `notEligible`). Host project must target iOS 16.4+. |
|
|
10
10
|
| Android | Device with Gemini Nano support (Pixel 8+, Galaxy S25+). Model may require download. |
|
|
11
|
+
| Expo SDK | 52+ (peer requirement). Tested against SDK 56 and SDK 57 (React Native 0.86). |
|
|
11
12
|
|
|
12
13
|
## Installation
|
|
13
14
|
|
|
@@ -15,7 +16,7 @@ Expo module for on-device LLM inference. Wraps Apple Foundation Models (iOS 26+)
|
|
|
15
16
|
npm install expo-local-llm
|
|
16
17
|
```
|
|
17
18
|
|
|
18
|
-
This module requires **iOS 16.4+** as a compile target. Expo SDK 56
|
|
19
|
+
This module requires **iOS 16.4+** as a compile target. Expo SDK 56+ defaults to 16.4 (the same as the module's floor), but if you're on an older SDK or have customized the deployment target, raise it via [`expo-build-properties`](https://docs.expo.dev/versions/latest/sdk/build-properties/):
|
|
19
20
|
|
|
20
21
|
```bash
|
|
21
22
|
npx expo install expo-build-properties
|
|
@@ -70,8 +71,8 @@ function Chat() {
|
|
|
70
71
|
}
|
|
71
72
|
|
|
72
73
|
const handleSend = async (prompt: string) => {
|
|
73
|
-
// Streaming — updates streamedText live
|
|
74
|
-
await streamResponse(prompt);
|
|
74
|
+
// Streaming — updates streamedText live, resolves with the final text
|
|
75
|
+
const text = await streamResponse(prompt);
|
|
75
76
|
|
|
76
77
|
// Or non-streaming:
|
|
77
78
|
// const response = await respond(prompt);
|
|
@@ -157,6 +158,19 @@ const { respond } = useLocalLLM({
|
|
|
157
158
|
const recipe = JSON.parse(await respond('Give me a pasta recipe'));
|
|
158
159
|
```
|
|
159
160
|
|
|
161
|
+
For one-shot structured calls (classification, extraction) where you don't want conversation
|
|
162
|
+
history accumulating between calls, use `generate()` instead of a session:
|
|
163
|
+
|
|
164
|
+
```ts
|
|
165
|
+
import { generate } from 'expo-local-llm';
|
|
166
|
+
|
|
167
|
+
const json = await generate('Classify this message: "how was my sleep?"', {
|
|
168
|
+
instructions: 'Classify what the message asks for.',
|
|
169
|
+
responseFormat: 'json',
|
|
170
|
+
schema: { topic: { type: 'string', enum: ['sleep', 'heart', 'none'] } },
|
|
171
|
+
});
|
|
172
|
+
```
|
|
173
|
+
|
|
160
174
|
Streaming with a schema yields progressively-filled snapshots via `streamedJSON` (the current
|
|
161
175
|
partial as a JSON string) and `streamedObject` (the parsed value):
|
|
162
176
|
|
|
@@ -197,8 +211,8 @@ if (!result.ok) {
|
|
|
197
211
|
|--------|------|-------------|
|
|
198
212
|
| `instructions` | `string` | System instructions for the session |
|
|
199
213
|
| `options.temperature` | `number` | Sampling temperature |
|
|
200
|
-
| `options.maxTokens` | `number` | Max output tokens (capped at 256 on Android) |
|
|
201
|
-
| `options.topK` | `number` | Top-K sampling |
|
|
214
|
+
| `options.maxTokens` | `number` | Max output tokens (capped at 256 on Android; maps to `maximumResponseTokens` on iOS) |
|
|
215
|
+
| `options.topK` | `number` | Top-K sampling (maps to `.random(top:)` on iOS) |
|
|
202
216
|
| `tools` | `ToolDefinition[]` | Tools the model can invoke (iOS 26+ only) |
|
|
203
217
|
| `toolTimeout` | `number` | Seconds before an unresolved tool call times out (default 30) |
|
|
204
218
|
| `responseFormat` | `"text" \| "json"` | Set to `"json"` for structured JSON output |
|
|
@@ -219,10 +233,18 @@ if (!result.ok) {
|
|
|
219
233
|
| `activeToolCalls` | `ActiveToolCall[]` | In-flight tool calls (`{ callId, toolName }`) |
|
|
220
234
|
| `respond` | `(prompt: string) => Promise<string>` | Non-streaming generation. `undefined` when unavailable. |
|
|
221
235
|
| `session` | `LLMSession \| null` | The native session object. `null` when unavailable. |
|
|
222
|
-
| `streamResponse` | `(prompt: string) => Promise<
|
|
223
|
-
| `cancelStream` | `() => Promise<void>` | Cancel active stream. `undefined` when unavailable. |
|
|
236
|
+
| `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. Actually stops inference (not just token delivery); the pending `streamResponse` promise resolves with the partial text. `undefined` when unavailable. |
|
|
238
|
+
| `reset` | `() => void` | Clear the conversation transcript, keeping instructions/tools/schema/options. Also cancels any in-flight stream. `undefined` only when there is no session. |
|
|
224
239
|
| `downloadModel` | `() => Promise<void>` | Trigger model download (Android). `undefined` when unavailable. |
|
|
225
240
|
|
|
241
|
+
### `generate(prompt, config?)`
|
|
242
|
+
|
|
243
|
+
One-shot stateless generation — creates a session, responds once, and releases it. Takes the
|
|
244
|
+
same config as `useLocalLLM`/`createLLMSession`. Use it for classification or extraction
|
|
245
|
+
calls where conversation history between calls is unwanted. Throws when the module or model
|
|
246
|
+
is unavailable (check `ExpoLocalLlmModule.getAvailability()` first if you need to gate).
|
|
247
|
+
|
|
226
248
|
### `ModelAvailability`
|
|
227
249
|
|
|
228
250
|
`'available' | 'notEnabled' | 'notReady' | 'notEligible' | 'downloadRequired' | 'downloading' | 'unknown'`
|
|
@@ -269,7 +291,7 @@ a bare React Native library. Here's why:
|
|
|
269
291
|
If you're on bare React Native without Expo, this module won't work — you'd need
|
|
270
292
|
to add `expo-modules-core` as a dependency or use a different library.
|
|
271
293
|
|
|
272
|
-
## How is this different from React Native AI? (as of
|
|
294
|
+
## How is this different from React Native AI? (as of June 2026)
|
|
273
295
|
|
|
274
296
|
[React Native AI](https://github.com/callstackincubator/ai) (formerly `@callstack/ai`)
|
|
275
297
|
is a Vercel AI SDK-compatible collection of on-device AI primitives, modularized
|
|
@@ -42,14 +42,18 @@ class ExpoLocalLlmModule : Module() {
|
|
|
42
42
|
session.respond(prompt)
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
AsyncFunction("streamResponse") { session: LLMSession, prompt: String ->
|
|
46
|
-
session.
|
|
45
|
+
AsyncFunction("streamResponse") Coroutine { session: LLMSession, prompt: String ->
|
|
46
|
+
session.streamResponse(prompt)
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
AsyncFunction("cancelStream") { session: LLMSession ->
|
|
50
50
|
session.cancelStream()
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
Function("reset") { session: LLMSession ->
|
|
54
|
+
session.reset()
|
|
55
|
+
}
|
|
56
|
+
|
|
53
57
|
// Tool calling stubs — not yet supported on Android
|
|
54
58
|
Function("registerTool") { _: LLMSession, _: ToolConfig ->
|
|
55
59
|
throw ToolNotSupportedException()
|
|
@@ -77,17 +77,24 @@ class LLMSession(
|
|
|
77
77
|
return text
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
-
|
|
80
|
+
/**
|
|
81
|
+
* Stream a response, emitting `token` events along the way. Resolves with
|
|
82
|
+
* the final text when the stream completes, or with the partial text
|
|
83
|
+
* produced so far if the stream is cancelled. Emits `streamComplete` only
|
|
84
|
+
* on natural completion, `streamError` on failure.
|
|
85
|
+
*/
|
|
86
|
+
suspend fun streamResponse(prompt: String): String {
|
|
81
87
|
streamJob?.cancel()
|
|
82
88
|
history.addUserMessage(prompt)
|
|
83
89
|
val fullPrompt = history.buildPrompt()
|
|
84
90
|
|
|
85
|
-
|
|
91
|
+
var accumulated = ""
|
|
92
|
+
var failure: Exception? = null
|
|
93
|
+
var completed = false
|
|
94
|
+
val job = scope.launch {
|
|
86
95
|
try {
|
|
87
96
|
val genModel = getOrCreateModel()
|
|
88
|
-
|
|
89
|
-
val responseStream = genModel.generateContentStream(fullPrompt)
|
|
90
|
-
responseStream.collect { chunk ->
|
|
97
|
+
genModel.generateContentStream(fullPrompt).collect { chunk ->
|
|
91
98
|
val token = chunk.text ?: ""
|
|
92
99
|
accumulated += token
|
|
93
100
|
emit("token", mapOf(
|
|
@@ -95,14 +102,26 @@ class LLMSession(
|
|
|
95
102
|
"accumulated" to accumulated
|
|
96
103
|
))
|
|
97
104
|
}
|
|
98
|
-
|
|
99
|
-
emit("streamComplete", mapOf("text" to accumulated))
|
|
105
|
+
completed = true
|
|
100
106
|
} catch (e: CancellationException) {
|
|
101
107
|
// User-initiated cancellation — not an error
|
|
108
|
+
throw e
|
|
102
109
|
} catch (e: Exception) {
|
|
110
|
+
failure = e
|
|
103
111
|
emit("streamError", mapOf("error" to (e.message ?: "Unknown error")))
|
|
104
112
|
}
|
|
105
113
|
}
|
|
114
|
+
streamJob = job
|
|
115
|
+
job.join()
|
|
116
|
+
|
|
117
|
+
failure?.let { throw StreamException(it.message ?: "Unknown error") }
|
|
118
|
+
if (accumulated.isNotEmpty()) {
|
|
119
|
+
history.addAssistantMessage(accumulated)
|
|
120
|
+
}
|
|
121
|
+
if (completed) {
|
|
122
|
+
emit("streamComplete", mapOf("text" to accumulated))
|
|
123
|
+
}
|
|
124
|
+
return accumulated
|
|
106
125
|
}
|
|
107
126
|
|
|
108
127
|
fun cancelStream() {
|
|
@@ -110,6 +129,16 @@ class LLMSession(
|
|
|
110
129
|
streamJob = null
|
|
111
130
|
}
|
|
112
131
|
|
|
132
|
+
/**
|
|
133
|
+
* Clear the conversation history (keeps instructions and generation
|
|
134
|
+
* options). Cancels any in-flight stream.
|
|
135
|
+
*/
|
|
136
|
+
fun reset() {
|
|
137
|
+
streamJob?.cancel()
|
|
138
|
+
streamJob = null
|
|
139
|
+
history.clear()
|
|
140
|
+
}
|
|
141
|
+
|
|
113
142
|
override fun deallocate() {
|
|
114
143
|
super.deallocate()
|
|
115
144
|
streamJob?.cancel()
|
package/build/LLMSession.d.ts
CHANGED
|
@@ -2,12 +2,35 @@ import { SharedObject } from "expo-modules-core";
|
|
|
2
2
|
import type { SessionConfig, LLMSessionEvents, ToolConfig } from "./ExpoLocalLlm.types";
|
|
3
3
|
export declare class LLMSession extends SharedObject<LLMSessionEvents> {
|
|
4
4
|
respond: (prompt: string) => Promise<string>;
|
|
5
|
-
|
|
5
|
+
/**
|
|
6
|
+
* Streams a response, emitting `token`/`partial` events along the way.
|
|
7
|
+
* Resolves with the final text when the stream completes, or with the
|
|
8
|
+
* partial text produced so far if the stream is cancelled via
|
|
9
|
+
* `cancelStream()`. Rejects if the stream fails.
|
|
10
|
+
*/
|
|
11
|
+
streamResponse: (prompt: string) => Promise<string>;
|
|
6
12
|
cancelStream: () => Promise<void>;
|
|
13
|
+
/**
|
|
14
|
+
* Clears the conversation transcript while keeping instructions, tools,
|
|
15
|
+
* schema, and generation options. Cancels any in-flight stream. Cheap —
|
|
16
|
+
* the model weights are OS-shared; only the conversation state is rebuilt.
|
|
17
|
+
*/
|
|
18
|
+
reset: () => void;
|
|
7
19
|
registerTool: (config: ToolConfig) => void;
|
|
8
20
|
unregisterTool: (name: string) => void;
|
|
9
21
|
resolveToolCall: (callId: string, result: string) => void;
|
|
10
22
|
rejectToolCall: (callId: string, error: string) => void;
|
|
11
23
|
}
|
|
12
24
|
export declare function createLLMSession(config?: SessionConfig): LLMSession;
|
|
25
|
+
/**
|
|
26
|
+
* One-shot, stateless generation: creates a session, responds once, and
|
|
27
|
+
* releases the session. Use this for classification/extraction calls where
|
|
28
|
+
* conversation history is unwanted — no session bookkeeping, no transcript
|
|
29
|
+
* accumulation across calls.
|
|
30
|
+
*
|
|
31
|
+
* Throws `UnavailabilityError` when the native module is missing; rejects
|
|
32
|
+
* with the native error when the model is not available. Callers that need
|
|
33
|
+
* an availability check first can use `ExpoLocalLlmModule.getAvailability()`.
|
|
34
|
+
*/
|
|
35
|
+
export declare function generate(prompt: string, config?: SessionConfig): Promise<string>;
|
|
13
36
|
//# sourceMappingURL=LLMSession.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LLMSession.d.ts","sourceRoot":"","sources":["../src/LLMSession.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAuB,MAAM,mBAAmB,CAAC;AAEtE,OAAO,KAAK,EACV,aAAa,EACb,gBAAgB,EAChB,UAAU,EACX,MAAM,sBAAsB,CAAC;AAI9B,qBAAa,UAAW,SAAQ,YAAY,CAAC,gBAAgB,CAAC;IAG5D,OAAO,EAAG,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAC9C,cAAc,EAAG,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,
|
|
1
|
+
{"version":3,"file":"LLMSession.d.ts","sourceRoot":"","sources":["../src/LLMSession.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAuB,MAAM,mBAAmB,CAAC;AAEtE,OAAO,KAAK,EACV,aAAa,EACb,gBAAgB,EAChB,UAAU,EACX,MAAM,sBAAsB,CAAC;AAI9B,qBAAa,UAAW,SAAQ,YAAY,CAAC,gBAAgB,CAAC;IAG5D,OAAO,EAAG,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAC9C;;;;;OAKG;IACH,cAAc,EAAG,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACrD,YAAY,EAAG,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACnC;;;;OAIG;IACH,KAAK,EAAG,MAAM,IAAI,CAAC;IACnB,YAAY,EAAG,CAAC,MAAM,EAAE,UAAU,KAAK,IAAI,CAAC;IAC5C,cAAc,EAAG,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,eAAe,EAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3D,cAAc,EAAG,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CAC1D;AAED,wBAAgB,gBAAgB,CAAC,MAAM,GAAE,aAAkB,GAAG,UAAU,CAkBvE;AAED;;;;;;;;;GASG;AACH,wBAAsB,QAAQ,CAC5B,MAAM,EAAE,MAAM,EACd,MAAM,GAAE,aAAkB,GACzB,OAAO,CAAC,MAAM,CAAC,CAOjB"}
|
package/build/LLMSession.js
CHANGED
|
@@ -5,8 +5,20 @@ export class LLMSession extends SharedObject {
|
|
|
5
5
|
// Methods are implemented natively via the Class() DSL.
|
|
6
6
|
// TypeScript declarations provide type safety only.
|
|
7
7
|
respond;
|
|
8
|
+
/**
|
|
9
|
+
* Streams a response, emitting `token`/`partial` events along the way.
|
|
10
|
+
* Resolves with the final text when the stream completes, or with the
|
|
11
|
+
* partial text produced so far if the stream is cancelled via
|
|
12
|
+
* `cancelStream()`. Rejects if the stream fails.
|
|
13
|
+
*/
|
|
8
14
|
streamResponse;
|
|
9
15
|
cancelStream;
|
|
16
|
+
/**
|
|
17
|
+
* Clears the conversation transcript while keeping instructions, tools,
|
|
18
|
+
* schema, and generation options. Cancels any in-flight stream. Cheap —
|
|
19
|
+
* the model weights are OS-shared; only the conversation state is rebuilt.
|
|
20
|
+
*/
|
|
21
|
+
reset;
|
|
10
22
|
registerTool;
|
|
11
23
|
unregisterTool;
|
|
12
24
|
resolveToolCall;
|
|
@@ -29,4 +41,23 @@ export function createLLMSession(config = {}) {
|
|
|
29
41
|
};
|
|
30
42
|
return new ExpoLocalLlmModule.LLMSession(nativeConfig);
|
|
31
43
|
}
|
|
44
|
+
/**
|
|
45
|
+
* One-shot, stateless generation: creates a session, responds once, and
|
|
46
|
+
* releases the session. Use this for classification/extraction calls where
|
|
47
|
+
* conversation history is unwanted — no session bookkeeping, no transcript
|
|
48
|
+
* accumulation across calls.
|
|
49
|
+
*
|
|
50
|
+
* Throws `UnavailabilityError` when the native module is missing; rejects
|
|
51
|
+
* with the native error when the model is not available. Callers that need
|
|
52
|
+
* an availability check first can use `ExpoLocalLlmModule.getAvailability()`.
|
|
53
|
+
*/
|
|
54
|
+
export async function generate(prompt, config = {}) {
|
|
55
|
+
const session = createLLMSession(config);
|
|
56
|
+
try {
|
|
57
|
+
return await session.respond(prompt);
|
|
58
|
+
}
|
|
59
|
+
finally {
|
|
60
|
+
session.release();
|
|
61
|
+
}
|
|
62
|
+
}
|
|
32
63
|
//# sourceMappingURL=LLMSession.js.map
|
package/build/LLMSession.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LLMSession.js","sourceRoot":"","sources":["../src/LLMSession.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAOtE,OAAO,kBAAkB,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAEtE,MAAM,OAAO,UAAW,SAAQ,YAA8B;IAC5D,wDAAwD;IACxD,oDAAoD;IACpD,OAAO,CAAuC;IAC9C,cAAc,
|
|
1
|
+
{"version":3,"file":"LLMSession.js","sourceRoot":"","sources":["../src/LLMSession.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAOtE,OAAO,kBAAkB,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAEtE,MAAM,OAAO,UAAW,SAAQ,YAA8B;IAC5D,wDAAwD;IACxD,oDAAoD;IACpD,OAAO,CAAuC;IAC9C;;;;;OAKG;IACH,cAAc,CAAuC;IACrD,YAAY,CAAuB;IACnC;;;;OAIG;IACH,KAAK,CAAc;IACnB,YAAY,CAAgC;IAC5C,cAAc,CAA0B;IACxC,eAAe,CAA4C;IAC3D,cAAc,CAA2C;CAC1D;AAED,MAAM,UAAU,gBAAgB,CAAC,SAAwB,EAAE;IACzD,IAAI,CAAC,kBAAkB,EAAE,CAAC;QACxB,MAAM,IAAI,mBAAmB,CAAC,cAAc,EAAE,kBAAkB,CAAC,CAAC;IACpE,CAAC;IAED,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;QAClB,MAAM,MAAM,GAAG,cAAc,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QAC7C,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC;YACf,MAAM,IAAI,kBAAkB,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QAC9C,CAAC;IACH,CAAC;IAED,8EAA8E;IAC9E,MAAM,YAAY,GAAG;QACnB,GAAG,MAAM;QACT,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,EAAE,OAAO,EAAE,GAAG,IAAI,EAAE,EAAE,EAAE,CAAC,IAAI,CAAC;KACzD,CAAC;IACF,OAAO,IAAI,kBAAkB,CAAC,UAAU,CAAC,YAAY,CAAe,CAAC;AACvE,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,QAAQ,CAC5B,MAAc,EACd,SAAwB,EAAE;IAE1B,MAAM,OAAO,GAAG,gBAAgB,CAAC,MAAM,CAAC,CAAC;IACzC,IAAI,CAAC;QACH,OAAO,MAAM,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IACvC,CAAC;YAAS,CAAC;QACT,OAAO,CAAC,OAAO,EAAE,CAAC;IACpB,CAAC;AACH,CAAC","sourcesContent":["import { SharedObject, UnavailabilityError } from \"expo-modules-core\";\n\nimport type {\n SessionConfig,\n LLMSessionEvents,\n ToolConfig,\n} from \"./ExpoLocalLlm.types\";\nimport ExpoLocalLlmModule from \"./ExpoLocalLlmModule\";\nimport { validateSchema, SchemaInvalidError } from \"./validateSchema\";\n\nexport class LLMSession extends SharedObject<LLMSessionEvents> {\n // Methods are implemented natively via the Class() DSL.\n // TypeScript declarations provide type safety only.\n respond!: (prompt: string) => Promise<string>;\n /**\n * Streams a response, emitting `token`/`partial` events along the way.\n * Resolves with the final text when the stream completes, or with the\n * partial text produced so far if the stream is cancelled via\n * `cancelStream()`. Rejects if the stream fails.\n */\n streamResponse!: (prompt: string) => Promise<string>;\n cancelStream!: () => Promise<void>;\n /**\n * Clears the conversation transcript while keeping instructions, tools,\n * schema, and generation options. Cancels any in-flight stream. Cheap —\n * the model weights are OS-shared; only the conversation state is rebuilt.\n */\n reset!: () => void;\n registerTool!: (config: ToolConfig) => void;\n unregisterTool!: (name: string) => void;\n resolveToolCall!: (callId: string, result: string) => void;\n rejectToolCall!: (callId: string, error: string) => void;\n}\n\nexport function createLLMSession(config: SessionConfig = {}): LLMSession {\n if (!ExpoLocalLlmModule) {\n throw new UnavailabilityError(\"ExpoLocalLlm\", \"createLLMSession\");\n }\n\n if (config.schema) {\n const result = validateSchema(config.schema);\n if (!result.ok) {\n throw new SchemaInvalidError(result.errors);\n }\n }\n\n // Strip handler functions before passing to native — native doesn't need them\n const nativeConfig = {\n ...config,\n tools: config.tools?.map(({ handler, ...rest }) => rest),\n };\n return new ExpoLocalLlmModule.LLMSession(nativeConfig) as LLMSession;\n}\n\n/**\n * One-shot, stateless generation: creates a session, responds once, and\n * releases the session. Use this for classification/extraction calls where\n * conversation history is unwanted — no session bookkeeping, no transcript\n * accumulation across calls.\n *\n * Throws `UnavailabilityError` when the native module is missing; rejects\n * with the native error when the model is not available. Callers that need\n * an availability check first can use `ExpoLocalLlmModule.getAvailability()`.\n */\nexport async function generate(\n prompt: string,\n config: SessionConfig = {}\n): Promise<string> {\n const session = createLLMSession(config);\n try {\n return await session.respond(prompt);\n } finally {\n session.release();\n }\n}\n"]}
|
package/build/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { useLocalLLM } from "./useLocalLLM";
|
|
2
|
-
export { LLMSession, createLLMSession } from "./LLMSession";
|
|
2
|
+
export { LLMSession, createLLMSession, generate } from "./LLMSession";
|
|
3
3
|
export { default as ExpoLocalLlmModule } from "./ExpoLocalLlmModule";
|
|
4
4
|
export { validateSchema, SchemaInvalidError, type SchemaValidationError, type SchemaValidationResult, } from "./validateSchema";
|
|
5
5
|
export * from "./ExpoLocalLlm.types";
|
package/build/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,UAAU,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AACtE,OAAO,EAAE,OAAO,IAAI,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AACrE,OAAO,EACL,cAAc,EACd,kBAAkB,EAClB,KAAK,qBAAqB,EAC1B,KAAK,sBAAsB,GAC5B,MAAM,kBAAkB,CAAC;AAC1B,cAAc,sBAAsB,CAAC"}
|
package/build/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { useLocalLLM } from "./useLocalLLM";
|
|
2
|
-
export { LLMSession, createLLMSession } from "./LLMSession";
|
|
2
|
+
export { LLMSession, createLLMSession, generate } from "./LLMSession";
|
|
3
3
|
export { default as ExpoLocalLlmModule } from "./ExpoLocalLlmModule";
|
|
4
4
|
export { validateSchema, SchemaInvalidError, } from "./validateSchema";
|
|
5
5
|
export * from "./ExpoLocalLlm.types";
|
package/build/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,UAAU,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AACtE,OAAO,EAAE,OAAO,IAAI,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AACrE,OAAO,EACL,cAAc,EACd,kBAAkB,GAGnB,MAAM,kBAAkB,CAAC;AAC1B,cAAc,sBAAsB,CAAC","sourcesContent":["export { useLocalLLM } from \"./useLocalLLM\";\nexport { LLMSession, createLLMSession, generate } from \"./LLMSession\";\nexport { default as ExpoLocalLlmModule } from \"./ExpoLocalLlmModule\";\nexport {\n validateSchema,\n SchemaInvalidError,\n type SchemaValidationError,\n type SchemaValidationResult,\n} from \"./validateSchema\";\nexport * from \"./ExpoLocalLlm.types\";\n"]}
|
package/build/useLocalLLM.d.ts
CHANGED
|
@@ -12,8 +12,9 @@ type UseLocalLLMResult = {
|
|
|
12
12
|
error: string | null;
|
|
13
13
|
activeToolCalls: ActiveToolCall[];
|
|
14
14
|
respond?: (prompt: string) => Promise<string>;
|
|
15
|
-
streamResponse?: (prompt: string) => Promise<
|
|
15
|
+
streamResponse?: (prompt: string) => Promise<string>;
|
|
16
16
|
cancelStream?: () => Promise<void>;
|
|
17
|
+
reset?: () => void;
|
|
17
18
|
downloadModel?: () => Promise<void>;
|
|
18
19
|
};
|
|
19
20
|
export declare function useLocalLLM(options?: UseLocalLLMOptions): UseLocalLLMResult;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useLocalLLM.d.ts","sourceRoot":"","sources":["../src/useLocalLLM.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,iBAAiB,EACjB,aAAa,EAQb,cAAc,EACf,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EAAoB,UAAU,EAAE,MAAM,cAAc,CAAC;AAE5D,KAAK,kBAAkB,GAAG,aAAa,CAAC;AAExC,KAAK,iBAAiB,GAAG;IACvB,YAAY,EAAE,iBAAiB,CAAC;IAChC,OAAO,EAAE,UAAU,GAAG,IAAI,CAAC;IAC3B,YAAY,EAAE,OAAO,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,OAAO,CAAC;IACxB,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,eAAe,EAAE,cAAc,EAAE,CAAC;IAClC,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAC9C,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,
|
|
1
|
+
{"version":3,"file":"useLocalLLM.d.ts","sourceRoot":"","sources":["../src/useLocalLLM.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,iBAAiB,EACjB,aAAa,EAQb,cAAc,EACf,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EAAoB,UAAU,EAAE,MAAM,cAAc,CAAC;AAE5D,KAAK,kBAAkB,GAAG,aAAa,CAAC;AAExC,KAAK,iBAAiB,GAAG;IACvB,YAAY,EAAE,iBAAiB,CAAC;IAChC,OAAO,EAAE,UAAU,GAAG,IAAI,CAAC;IAC3B,YAAY,EAAE,OAAO,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,OAAO,CAAC;IACxB,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,eAAe,EAAE,cAAc,EAAE,CAAC;IAClC,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAC9C,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACrD,YAAY,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACnC,KAAK,CAAC,EAAE,MAAM,IAAI,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CACrC,CAAC;AAEF,wBAAgB,WAAW,CACzB,OAAO,GAAE,kBAAuB,GAC/B,iBAAiB,CAoTnB"}
|
package/build/useLocalLLM.js
CHANGED
|
@@ -154,7 +154,11 @@ export function useLocalLLM(options = {}) {
|
|
|
154
154
|
subs.forEach((s) => s.remove());
|
|
155
155
|
session?.release();
|
|
156
156
|
};
|
|
157
|
-
|
|
157
|
+
// isJSONMode: used by the streamComplete listener. Today it can only
|
|
158
|
+
// change together with a session recreation (responseFormat/schema are
|
|
159
|
+
// in stableConfig), but depending on it directly removes the reliance
|
|
160
|
+
// on that invariant.
|
|
161
|
+
}, [session, isJSONMode]);
|
|
158
162
|
const respond = useCallback(async (prompt) => {
|
|
159
163
|
if (!session)
|
|
160
164
|
throw new Error("Session not available");
|
|
@@ -181,13 +185,17 @@ export function useLocalLLM(options = {}) {
|
|
|
181
185
|
setStreamedObject(null);
|
|
182
186
|
setIsGenerating(true);
|
|
183
187
|
try {
|
|
184
|
-
|
|
188
|
+
// Resolves with the final text at stream end (or the partial text
|
|
189
|
+
// if cancelled); rejects if the stream fails.
|
|
190
|
+
return await session.streamResponse(prompt);
|
|
185
191
|
}
|
|
186
192
|
catch (e) {
|
|
187
193
|
setRuntimeError(e.message);
|
|
188
|
-
setIsGenerating(false);
|
|
189
194
|
throw e;
|
|
190
195
|
}
|
|
196
|
+
finally {
|
|
197
|
+
setIsGenerating(false);
|
|
198
|
+
}
|
|
191
199
|
}, [session]);
|
|
192
200
|
const cancelStream = useCallback(async () => {
|
|
193
201
|
if (!session)
|
|
@@ -195,6 +203,17 @@ export function useLocalLLM(options = {}) {
|
|
|
195
203
|
await session.cancelStream();
|
|
196
204
|
setIsGenerating(false);
|
|
197
205
|
}, [session]);
|
|
206
|
+
const reset = useCallback(() => {
|
|
207
|
+
if (!session)
|
|
208
|
+
return;
|
|
209
|
+
session.reset();
|
|
210
|
+
setStreamedText("");
|
|
211
|
+
setStreamedJSON("");
|
|
212
|
+
setStreamedObject(null);
|
|
213
|
+
setRuntimeError(null);
|
|
214
|
+
setActiveToolCalls([]);
|
|
215
|
+
setIsGenerating(false);
|
|
216
|
+
}, [session]);
|
|
198
217
|
const downloadModel = useCallback(async () => {
|
|
199
218
|
if (!ExpoLocalLlmModule)
|
|
200
219
|
return;
|
|
@@ -226,6 +245,8 @@ export function useLocalLLM(options = {}) {
|
|
|
226
245
|
respond: session && availability === "available" ? respond : undefined,
|
|
227
246
|
streamResponse: session && availability === "available" ? streamResponse : undefined,
|
|
228
247
|
cancelStream: session && availability === "available" ? cancelStream : undefined,
|
|
248
|
+
// Resetting is safe in any availability state — gate on session only.
|
|
249
|
+
reset: session ? reset : undefined,
|
|
229
250
|
downloadModel,
|
|
230
251
|
};
|
|
231
252
|
}
|
package/build/useLocalLLM.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useLocalLLM.js","sourceRoot":"","sources":["../src/useLocalLLM.ts"],"names":[],"mappings":"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","sourcesContent":["import { useEffect, useState, useCallback, useMemo, useRef } from \"react\";\n\nimport type {\n ModelAvailability,\n SessionConfig,\n TokenEvent,\n PartialEvent,\n StreamCompleteEvent,\n StreamErrorEvent,\n DownloadProgress,\n ToolDefinition,\n ToolCallEvent,\n ActiveToolCall,\n} from \"./ExpoLocalLlm.types\";\nimport ExpoLocalLlmModule from \"./ExpoLocalLlmModule\";\nimport { createLLMSession, LLMSession } from \"./LLMSession\";\n\ntype UseLocalLLMOptions = SessionConfig;\n\ntype UseLocalLLMResult = {\n availability: ModelAvailability;\n session: LLMSession | null;\n isGenerating: boolean;\n streamedText: string;\n streamedJSON: string;\n streamedObject: unknown;\n downloadProgress: number | null;\n error: string | null;\n activeToolCalls: ActiveToolCall[];\n respond?: (prompt: string) => Promise<string>;\n streamResponse?: (prompt: string) => Promise<void>;\n cancelStream?: () => Promise<void>;\n downloadModel?: () => Promise<void>;\n};\n\nexport function useLocalLLM(\n options: UseLocalLLMOptions = {},\n): UseLocalLLMResult {\n const [availability, setAvailability] = useState<ModelAvailability>(() =>\n ExpoLocalLlmModule\n ? (ExpoLocalLlmModule.getAvailability() as ModelAvailability)\n : \"unknown\",\n );\n const [isGenerating, setIsGenerating] = useState(false);\n const [streamedText, setStreamedText] = useState(\"\");\n const [streamedJSON, setStreamedJSON] = useState(\"\");\n const [streamedObject, setStreamedObject] = useState<unknown>(null);\n const [downloadProgress, setDownloadProgress] = useState<number | null>(null);\n const [runtimeError, setRuntimeError] = useState<string | null>(null);\n const [activeToolCalls, setActiveToolCalls] = useState<ActiveToolCall[]>([]);\n\n // Keep a ref to the current tool handlers so the event listener\n // always sees the latest handlers without needing to recreate the session.\n const toolHandlersRef = useRef<Map<string, ToolDefinition[\"handler\"]>>(\n new Map(),\n );\n\n // Update handler ref whenever tools change\n useEffect(() => {\n const handlers = new Map<string, ToolDefinition[\"handler\"]>();\n if (options.tools) {\n for (const tool of options.tools) {\n handlers.set(tool.name, tool.handler);\n }\n }\n toolHandlersRef.current = handlers;\n }, [options.tools]);\n\n // Stabilize options to avoid unnecessary session recreation\n // Include tool names/descriptions so session recreates when tools change structurally\n const toolsFingerprint = useMemo(\n () =>\n options.tools\n ?.map(\n (t) => `${t.name}:${t.description}:${JSON.stringify(t.parameters)}`,\n )\n .join(\"|\") ?? \"\",\n [options.tools],\n );\n\n // Fingerprint the schema so session recreates when it changes\n const schemaFingerprint = useMemo(\n () => (options.schema ? JSON.stringify(options.schema) : \"\"),\n [options.schema],\n );\n\n const stableConfig = useMemo(\n () => options,\n [\n options.instructions,\n options.options?.temperature,\n options.options?.maxTokens,\n options.options?.topK,\n options.toolTimeout,\n options.responseFormat,\n options.includeSchemaInPrompt,\n schemaFingerprint,\n toolsFingerprint,\n ],\n );\n\n const isJSONMode = options.responseFormat === \"json\" && !!options.schema;\n\n const sessionResult = useMemo<{\n session: LLMSession | null;\n creationError: string | null;\n }>(() => {\n if (!ExpoLocalLlmModule) return { session: null, creationError: null };\n try {\n return { session: createLLMSession(stableConfig), creationError: null };\n } catch (e: any) {\n return {\n session: null,\n creationError: e.message ?? \"Failed to create LLM session\",\n };\n }\n }, [stableConfig]);\n const session = sessionResult.session;\n const error = sessionResult.creationError ?? runtimeError;\n\n useEffect(() => {\n if (!ExpoLocalLlmModule) return;\n\n const subs: { remove(): void }[] = [];\n\n subs.push(\n ExpoLocalLlmModule.addListener(\n \"downloadProgress\",\n (event: DownloadProgress) => {\n setDownloadProgress(event.progress);\n },\n ),\n );\n subs.push(\n ExpoLocalLlmModule.addListener(\"availabilityChange\", (event) => {\n setAvailability(event.availability);\n }),\n );\n\n if (session) {\n subs.push(\n session.addListener(\"token\", (event: TokenEvent) => {\n setStreamedText(event.accumulated);\n }),\n );\n subs.push(\n session.addListener(\"partial\", (event: PartialEvent) => {\n setStreamedJSON(event.json);\n try {\n setStreamedObject(JSON.parse(event.json));\n } catch {\n // Partial may not be parseable yet; keep the previous parsed value.\n }\n }),\n );\n subs.push(\n session.addListener(\"streamComplete\", (event: StreamCompleteEvent) => {\n if (isJSONMode) {\n setStreamedJSON(event.text);\n try {\n setStreamedObject(JSON.parse(event.text));\n } catch {\n // Final JSON should always parse; leave previous value if it doesn't.\n }\n } else {\n setStreamedText(event.text);\n }\n setIsGenerating(false);\n }),\n );\n subs.push(\n session.addListener(\"streamError\", (event: StreamErrorEvent) => {\n setRuntimeError(event.error);\n setIsGenerating(false);\n }),\n );\n\n // Tool call event listener\n subs.push(\n session.addListener(\"toolCall\", (event: ToolCallEvent) => {\n const activeCall = { callId: event.callId, toolName: event.toolName };\n setActiveToolCalls((prev) => [...prev, activeCall]);\n\n const removeActiveCall = () => {\n setActiveToolCalls((prev) =>\n prev.filter((c) => c.callId !== event.callId),\n );\n };\n\n const handler = toolHandlersRef.current.get(event.toolName);\n if (!handler) {\n removeActiveCall();\n try {\n session.rejectToolCall(\n event.callId,\n `No handler registered for tool: ${event.toolName}`,\n );\n } catch {\n // Ignore if session is already torn down\n }\n return;\n }\n\n let result: Promise<string>;\n try {\n result = handler(event.arguments);\n } catch (e: any) {\n // Handler threw synchronously before returning a promise\n removeActiveCall();\n try {\n session.rejectToolCall(\n event.callId,\n e.message || \"Tool handler failed\",\n );\n } catch {\n // Ignore if session is already torn down\n }\n return;\n }\n\n result\n .then((value) => {\n removeActiveCall();\n session.resolveToolCall(event.callId, value);\n })\n .catch((e: any) => {\n removeActiveCall();\n try {\n session.rejectToolCall(\n event.callId,\n e.message || \"Tool handler failed\",\n );\n } catch {\n // Ignore if session is already torn down\n }\n });\n }),\n );\n }\n\n return () => {\n subs.forEach((s) => s.remove());\n session?.release();\n };\n }, [session]);\n\n const respond = useCallback(\n async (prompt: string): Promise<string> => {\n if (!session) throw new Error(\"Session not available\");\n setRuntimeError(null);\n setIsGenerating(true);\n try {\n const result = await session.respond(prompt);\n return result;\n } catch (e: any) {\n setRuntimeError(e.message);\n throw e;\n } finally {\n setIsGenerating(false);\n }\n },\n [session],\n );\n\n const streamResponse = useCallback(\n async (prompt: string): Promise<void> => {\n if (!session) throw new Error(\"Session not available\");\n setRuntimeError(null);\n setStreamedText(\"\");\n setStreamedJSON(\"\");\n setStreamedObject(null);\n setIsGenerating(true);\n try {\n await session.streamResponse(prompt);\n } catch (e: any) {\n setRuntimeError(e.message);\n setIsGenerating(false);\n throw e;\n }\n },\n [session],\n );\n\n const cancelStream = useCallback(async (): Promise<void> => {\n if (!session) return;\n await session.cancelStream();\n setIsGenerating(false);\n }, [session]);\n\n const downloadModel = useCallback(async (): Promise<void> => {\n if (!ExpoLocalLlmModule) return;\n await ExpoLocalLlmModule.downloadModel();\n }, []);\n\n if (!ExpoLocalLlmModule) {\n return {\n availability: \"notEligible\",\n session: null,\n isGenerating: false,\n streamedText: \"\",\n streamedJSON: \"\",\n streamedObject: null,\n downloadProgress: null,\n error: null,\n activeToolCalls: [],\n };\n }\n\n return {\n availability,\n session,\n isGenerating,\n streamedText,\n streamedJSON,\n streamedObject,\n downloadProgress,\n error,\n activeToolCalls,\n respond: session && availability === \"available\" ? respond : undefined,\n streamResponse:\n session && availability === \"available\" ? streamResponse : undefined,\n cancelStream:\n session && availability === \"available\" ? cancelStream : undefined,\n downloadModel,\n };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"useLocalLLM.js","sourceRoot":"","sources":["../src/useLocalLLM.ts"],"names":[],"mappings":"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","sourcesContent":["import { useEffect, useState, useCallback, useMemo, useRef } from \"react\";\n\nimport type {\n ModelAvailability,\n SessionConfig,\n TokenEvent,\n PartialEvent,\n StreamCompleteEvent,\n StreamErrorEvent,\n DownloadProgress,\n ToolDefinition,\n ToolCallEvent,\n ActiveToolCall,\n} from \"./ExpoLocalLlm.types\";\nimport ExpoLocalLlmModule from \"./ExpoLocalLlmModule\";\nimport { createLLMSession, LLMSession } from \"./LLMSession\";\n\ntype UseLocalLLMOptions = SessionConfig;\n\ntype UseLocalLLMResult = {\n availability: ModelAvailability;\n session: LLMSession | null;\n isGenerating: boolean;\n streamedText: string;\n streamedJSON: string;\n streamedObject: unknown;\n downloadProgress: number | null;\n error: string | null;\n activeToolCalls: ActiveToolCall[];\n respond?: (prompt: string) => Promise<string>;\n streamResponse?: (prompt: string) => Promise<string>;\n cancelStream?: () => Promise<void>;\n reset?: () => void;\n downloadModel?: () => Promise<void>;\n};\n\nexport function useLocalLLM(\n options: UseLocalLLMOptions = {}\n): UseLocalLLMResult {\n const [availability, setAvailability] = useState<ModelAvailability>(() =>\n ExpoLocalLlmModule\n ? (ExpoLocalLlmModule.getAvailability() as ModelAvailability)\n : \"unknown\"\n );\n const [isGenerating, setIsGenerating] = useState(false);\n const [streamedText, setStreamedText] = useState(\"\");\n const [streamedJSON, setStreamedJSON] = useState(\"\");\n const [streamedObject, setStreamedObject] = useState<unknown>(null);\n const [downloadProgress, setDownloadProgress] = useState<number | null>(null);\n const [runtimeError, setRuntimeError] = useState<string | null>(null);\n const [activeToolCalls, setActiveToolCalls] = useState<ActiveToolCall[]>([]);\n\n // Keep a ref to the current tool handlers so the event listener\n // always sees the latest handlers without needing to recreate the session.\n const toolHandlersRef = useRef<Map<string, ToolDefinition[\"handler\"]>>(\n new Map()\n );\n\n // Update handler ref whenever tools change\n useEffect(() => {\n const handlers = new Map<string, ToolDefinition[\"handler\"]>();\n if (options.tools) {\n for (const tool of options.tools) {\n handlers.set(tool.name, tool.handler);\n }\n }\n toolHandlersRef.current = handlers;\n }, [options.tools]);\n\n // Stabilize options to avoid unnecessary session recreation\n // Include tool names/descriptions so session recreates when tools change structurally\n const toolsFingerprint = useMemo(\n () =>\n options.tools\n ?.map(\n (t) => `${t.name}:${t.description}:${JSON.stringify(t.parameters)}`\n )\n .join(\"|\") ?? \"\",\n [options.tools]\n );\n\n // Fingerprint the schema so session recreates when it changes\n const schemaFingerprint = useMemo(\n () => (options.schema ? JSON.stringify(options.schema) : \"\"),\n [options.schema]\n );\n\n const stableConfig = useMemo(\n () => options,\n [\n options.instructions,\n options.options?.temperature,\n options.options?.maxTokens,\n options.options?.topK,\n options.toolTimeout,\n options.responseFormat,\n options.includeSchemaInPrompt,\n schemaFingerprint,\n toolsFingerprint,\n ]\n );\n\n const isJSONMode = options.responseFormat === \"json\" && !!options.schema;\n\n const sessionResult = useMemo<{\n session: LLMSession | null;\n creationError: string | null;\n }>(() => {\n if (!ExpoLocalLlmModule) return { session: null, creationError: null };\n try {\n return { session: createLLMSession(stableConfig), creationError: null };\n } catch (e: any) {\n return {\n session: null,\n creationError: e.message ?? \"Failed to create LLM session\",\n };\n }\n }, [stableConfig]);\n const session = sessionResult.session;\n const error = sessionResult.creationError ?? runtimeError;\n\n useEffect(() => {\n if (!ExpoLocalLlmModule) return;\n\n const subs: { remove(): void }[] = [];\n\n subs.push(\n ExpoLocalLlmModule.addListener(\n \"downloadProgress\",\n (event: DownloadProgress) => {\n setDownloadProgress(event.progress);\n }\n )\n );\n subs.push(\n ExpoLocalLlmModule.addListener(\"availabilityChange\", (event) => {\n setAvailability(event.availability);\n })\n );\n\n if (session) {\n subs.push(\n session.addListener(\"token\", (event: TokenEvent) => {\n setStreamedText(event.accumulated);\n })\n );\n subs.push(\n session.addListener(\"partial\", (event: PartialEvent) => {\n setStreamedJSON(event.json);\n try {\n setStreamedObject(JSON.parse(event.json));\n } catch {\n // Partial may not be parseable yet; keep the previous parsed value.\n }\n })\n );\n subs.push(\n session.addListener(\"streamComplete\", (event: StreamCompleteEvent) => {\n if (isJSONMode) {\n setStreamedJSON(event.text);\n try {\n setStreamedObject(JSON.parse(event.text));\n } catch {\n // Final JSON should always parse; leave previous value if it doesn't.\n }\n } else {\n setStreamedText(event.text);\n }\n setIsGenerating(false);\n })\n );\n subs.push(\n session.addListener(\"streamError\", (event: StreamErrorEvent) => {\n setRuntimeError(event.error);\n setIsGenerating(false);\n })\n );\n\n // Tool call event listener\n subs.push(\n session.addListener(\"toolCall\", (event: ToolCallEvent) => {\n const activeCall = { callId: event.callId, toolName: event.toolName };\n setActiveToolCalls((prev) => [...prev, activeCall]);\n\n const removeActiveCall = () => {\n setActiveToolCalls((prev) =>\n prev.filter((c) => c.callId !== event.callId)\n );\n };\n\n const handler = toolHandlersRef.current.get(event.toolName);\n if (!handler) {\n removeActiveCall();\n try {\n session.rejectToolCall(\n event.callId,\n `No handler registered for tool: ${event.toolName}`\n );\n } catch {\n // Ignore if session is already torn down\n }\n return;\n }\n\n let result: Promise<string>;\n try {\n result = handler(event.arguments);\n } catch (e: any) {\n // Handler threw synchronously before returning a promise\n removeActiveCall();\n try {\n session.rejectToolCall(\n event.callId,\n e.message || \"Tool handler failed\"\n );\n } catch {\n // Ignore if session is already torn down\n }\n return;\n }\n\n result\n .then((value) => {\n removeActiveCall();\n session.resolveToolCall(event.callId, value);\n })\n .catch((e: any) => {\n removeActiveCall();\n try {\n session.rejectToolCall(\n event.callId,\n e.message || \"Tool handler failed\"\n );\n } catch {\n // Ignore if session is already torn down\n }\n });\n })\n );\n }\n\n return () => {\n subs.forEach((s) => s.remove());\n session?.release();\n };\n // isJSONMode: used by the streamComplete listener. Today it can only\n // change together with a session recreation (responseFormat/schema are\n // in stableConfig), but depending on it directly removes the reliance\n // on that invariant.\n }, [session, isJSONMode]);\n\n const respond = useCallback(\n async (prompt: string): Promise<string> => {\n if (!session) throw new Error(\"Session not available\");\n setRuntimeError(null);\n setIsGenerating(true);\n try {\n const result = await session.respond(prompt);\n return result;\n } catch (e: any) {\n setRuntimeError(e.message);\n throw e;\n } finally {\n setIsGenerating(false);\n }\n },\n [session]\n );\n\n const streamResponse = useCallback(\n async (prompt: string): Promise<string> => {\n if (!session) throw new Error(\"Session not available\");\n setRuntimeError(null);\n setStreamedText(\"\");\n setStreamedJSON(\"\");\n setStreamedObject(null);\n setIsGenerating(true);\n try {\n // Resolves with the final text at stream end (or the partial text\n // if cancelled); rejects if the stream fails.\n return await session.streamResponse(prompt);\n } catch (e: any) {\n setRuntimeError(e.message);\n throw e;\n } finally {\n setIsGenerating(false);\n }\n },\n [session]\n );\n\n const cancelStream = useCallback(async (): Promise<void> => {\n if (!session) return;\n await session.cancelStream();\n setIsGenerating(false);\n }, [session]);\n\n const reset = useCallback((): void => {\n if (!session) return;\n session.reset();\n setStreamedText(\"\");\n setStreamedJSON(\"\");\n setStreamedObject(null);\n setRuntimeError(null);\n setActiveToolCalls([]);\n setIsGenerating(false);\n }, [session]);\n\n const downloadModel = useCallback(async (): Promise<void> => {\n if (!ExpoLocalLlmModule) return;\n await ExpoLocalLlmModule.downloadModel();\n }, []);\n\n if (!ExpoLocalLlmModule) {\n return {\n availability: \"notEligible\",\n session: null,\n isGenerating: false,\n streamedText: \"\",\n streamedJSON: \"\",\n streamedObject: null,\n downloadProgress: null,\n error: null,\n activeToolCalls: [],\n };\n }\n\n return {\n availability,\n session,\n isGenerating,\n streamedText,\n streamedJSON,\n streamedObject,\n downloadProgress,\n error,\n activeToolCalls,\n respond: session && availability === \"available\" ? respond : undefined,\n streamResponse:\n session && availability === \"available\" ? streamResponse : undefined,\n cancelStream:\n session && availability === \"available\" ? cancelStream : undefined,\n // Resetting is safe in any availability state — gate on session only.\n reset: session ? reset : undefined,\n downloadModel,\n };\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validateSchema.d.ts","sourceRoot":"","sources":["../src/validateSchema.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AAEnD,MAAM,MAAM,qBAAqB,GAAG;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAC9B;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,GACZ;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,qBAAqB,EAAE,CAAA;CAAE,CAAC;AAEnD,qBAAa,kBAAmB,SAAQ,KAAK;IAC3C,QAAQ,CAAC,MAAM,EAAE,qBAAqB,EAAE,CAAC;gBAC7B,MAAM,EAAE,qBAAqB,EAAE;CAM5C;
|
|
1
|
+
{"version":3,"file":"validateSchema.d.ts","sourceRoot":"","sources":["../src/validateSchema.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AAEnD,MAAM,MAAM,qBAAqB,GAAG;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAC9B;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,GACZ;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,qBAAqB,EAAE,CAAA;CAAE,CAAC;AAEnD,qBAAa,kBAAmB,SAAQ,KAAK;IAC3C,QAAQ,CAAC,MAAM,EAAE,qBAAqB,EAAE,CAAC;gBAC7B,MAAM,EAAE,qBAAqB,EAAE;CAM5C;AAuFD,wBAAgB,cAAc,CAAC,MAAM,EAAE,OAAO,GAAG,sBAAsB,CAmBtE;AAGD,YAAY,EAAE,MAAM,EAAE,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validateSchema.js","sourceRoot":"","sources":["../src/validateSchema.ts"],"names":[],"mappings":"AAWA,MAAM,OAAO,kBAAmB,SAAQ,KAAK;IAClC,MAAM,CAA0B;IACzC,YAAY,MAA+B;QACzC,MAAM,OAAO,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC5E,KAAK,CAAC,oBAAoB,OAAO,EAAE,CAAC,CAAC;QACrC,IAAI,CAAC,IAAI,GAAG,oBAAoB,CAAC;QACjC,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACvB,CAAC;CACF;AAED,MAAM,aAAa,GAAG,IAAI,GAAG,CAAC;IAC5B,QAAQ;IACR,QAAQ;IACR,SAAS;IACT,SAAS;IACT,OAAO;IACP,QAAQ;CACT,CAAC,CAAC;AAEH,SAAS,aAAa,CAAC,CAAU;IAC/B,OAAO,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;AAClE,CAAC;AAED,SAAS,aAAa,CACpB,KAAc,EACd,IAAY,EACZ,MAA+B;IAE/B,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,EAAE,CAAC;QAC1B,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,yBAAyB,EAAE,CAAC,CAAC;QAC1D,OAAO;IACT,CAAC;IAED,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC;IACxB,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;QACzD,MAAM,CAAC,IAAI,CAAC;YACV,IAAI;YACJ,OAAO,EAAE,8EAA8E,IAAI,CAAC,SAAS,
|
|
1
|
+
{"version":3,"file":"validateSchema.js","sourceRoot":"","sources":["../src/validateSchema.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Schema } from \"./ExpoLocalLlm.types\";\n\nexport type SchemaValidationError = {\n path: string;\n message: string;\n};\n\nexport type SchemaValidationResult =\n | { ok: true }\n | { ok: false; errors: SchemaValidationError[] };\n\nexport class SchemaInvalidError extends Error {\n readonly errors: SchemaValidationError[];\n constructor(errors: SchemaValidationError[]) {\n const summary = errors.map((e) => ` - ${e.path}: ${e.message}`).join(\"\\n\");\n super(`Invalid schema:\\n${summary}`);\n this.name = \"SchemaInvalidError\";\n this.errors = errors;\n }\n}\n\nconst ALLOWED_TYPES = new Set([\n \"string\",\n \"number\",\n \"integer\",\n \"boolean\",\n \"array\",\n \"object\",\n]);\n\nfunction isPlainObject(v: unknown): v is Record<string, unknown> {\n return typeof v === \"object\" && v !== null && !Array.isArray(v);\n}\n\nfunction validateField(\n field: unknown,\n path: string,\n errors: SchemaValidationError[]\n): void {\n if (!isPlainObject(field)) {\n errors.push({ path, message: \"field must be an object\" });\n return;\n }\n\n const type = field.type;\n if (typeof type !== \"string\" || !ALLOWED_TYPES.has(type)) {\n errors.push({\n path,\n message: `type must be one of: string, number, integer, boolean, array, object (got: ${JSON.stringify(\n type\n )})`,\n });\n return;\n }\n\n if (\"enum\" in field) {\n if (type !== \"string\") {\n errors.push({\n path,\n message: `enum is only valid on type 'string' (got type: '${type}')`,\n });\n } else if (!Array.isArray(field.enum) || field.enum.length === 0) {\n errors.push({\n path,\n message: \"enum must be a non-empty array of strings\",\n });\n } else if (!field.enum.every((v) => typeof v === \"string\")) {\n errors.push({\n path,\n message: \"enum values must all be strings\",\n });\n }\n }\n\n if (type === \"array\") {\n if (!(\"items\" in field)) {\n errors.push({ path, message: \"type 'array' requires 'items'\" });\n } else {\n validateField(field.items, `${path}.items`, errors);\n }\n }\n\n if (type === \"object\") {\n if (!(\"properties\" in field)) {\n errors.push({ path, message: \"type 'object' requires 'properties'\" });\n } else if (!isPlainObject(field.properties)) {\n errors.push({\n path: `${path}.properties`,\n message: \"properties must be an object\",\n });\n } else {\n const entries = Object.entries(field.properties);\n if (entries.length === 0) {\n errors.push({\n path: `${path}.properties`,\n message: \"properties must be non-empty\",\n });\n } else {\n for (const [key, subField] of entries) {\n validateField(subField, `${path}.${key}`, errors);\n }\n }\n }\n }\n}\n\nexport function validateSchema(schema: unknown): SchemaValidationResult {\n const errors: SchemaValidationError[] = [];\n\n if (!isPlainObject(schema)) {\n errors.push({ path: \"$\", message: \"schema must be an object\" });\n return { ok: false, errors };\n }\n\n const entries = Object.entries(schema);\n if (entries.length === 0) {\n errors.push({ path: \"$\", message: \"schema must be a non-empty object\" });\n return { ok: false, errors };\n }\n\n for (const [key, field] of entries) {\n validateField(field, key, errors);\n }\n\n return errors.length === 0 ? { ok: true } : { ok: false, errors };\n}\n\n// Re-export Schema so callers building schemas at runtime have one import surface.\nexport type { Schema };\n"]}
|
package/ios/DynamicTool.swift
CHANGED
|
@@ -78,13 +78,17 @@ final class DynamicTool: Tool {
|
|
|
78
78
|
return try await withCheckedThrowingContinuation { continuation in
|
|
79
79
|
continuationStore.store(callId: callId, continuation: continuation)
|
|
80
80
|
|
|
81
|
-
// Schedule timeout
|
|
82
|
-
|
|
81
|
+
// Schedule timeout — cancelled by the store when the call resolves,
|
|
82
|
+
// so it doesn't linger for the full duration after a fast handler.
|
|
83
|
+
let timeoutSeconds = self.timeoutSeconds
|
|
84
|
+
let timeoutTask = Task { [continuationStore] in
|
|
83
85
|
try? await Task.sleep(for: .seconds(timeoutSeconds))
|
|
86
|
+
guard !Task.isCancelled else { return }
|
|
84
87
|
if let timedOut = continuationStore.remove(callId: callId) {
|
|
85
88
|
timedOut.resume(throwing: DynamicToolError.timeout)
|
|
86
89
|
}
|
|
87
90
|
}
|
|
91
|
+
continuationStore.attachTimeout(callId: callId, task: timeoutTask)
|
|
88
92
|
}
|
|
89
93
|
}
|
|
90
94
|
|
|
@@ -111,20 +115,39 @@ final class DynamicTool: Tool {
|
|
|
111
115
|
/// Thread-safe store for pending tool call continuations.
|
|
112
116
|
@available(iOS 26, *)
|
|
113
117
|
final class ToolContinuationStore: @unchecked Sendable {
|
|
114
|
-
private
|
|
118
|
+
private struct Pending {
|
|
119
|
+
let continuation: CheckedContinuation<String, Error>
|
|
120
|
+
var timeoutTask: Task<Void, Never>?
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
private var pending: [String: Pending] = [:]
|
|
115
124
|
private let lock = NSLock()
|
|
116
125
|
|
|
117
126
|
func store(callId: String, continuation: CheckedContinuation<String, Error>) {
|
|
118
127
|
lock.lock()
|
|
119
|
-
|
|
128
|
+
pending[callId] = Pending(continuation: continuation, timeoutTask: nil)
|
|
120
129
|
lock.unlock()
|
|
121
130
|
}
|
|
122
131
|
|
|
132
|
+
/// Attach the timeout task for a call so it can be cancelled on resolve.
|
|
133
|
+
/// If the call already resolved, the task is cancelled immediately.
|
|
134
|
+
func attachTimeout(callId: String, task: Task<Void, Never>) {
|
|
135
|
+
lock.lock()
|
|
136
|
+
if pending[callId] != nil {
|
|
137
|
+
pending[callId]?.timeoutTask = task
|
|
138
|
+
lock.unlock()
|
|
139
|
+
} else {
|
|
140
|
+
lock.unlock()
|
|
141
|
+
task.cancel()
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
123
145
|
func remove(callId: String) -> CheckedContinuation<String, Error>? {
|
|
124
146
|
lock.lock()
|
|
125
|
-
let
|
|
147
|
+
let entry = pending.removeValue(forKey: callId)
|
|
126
148
|
lock.unlock()
|
|
127
|
-
|
|
149
|
+
entry?.timeoutTask?.cancel()
|
|
150
|
+
return entry?.continuation
|
|
128
151
|
}
|
|
129
152
|
|
|
130
153
|
/// Resolve a pending continuation with a result string.
|
|
@@ -144,11 +167,12 @@ final class ToolContinuationStore: @unchecked Sendable {
|
|
|
144
167
|
/// Cancel all pending continuations (e.g., on session teardown).
|
|
145
168
|
func cancelAll() {
|
|
146
169
|
lock.lock()
|
|
147
|
-
let
|
|
148
|
-
|
|
170
|
+
let entries = pending
|
|
171
|
+
pending.removeAll()
|
|
149
172
|
lock.unlock()
|
|
150
|
-
for (_,
|
|
151
|
-
|
|
173
|
+
for (_, entry) in entries {
|
|
174
|
+
entry.timeoutTask?.cancel()
|
|
175
|
+
entry.continuation.resume(throwing: CancellationError())
|
|
152
176
|
}
|
|
153
177
|
}
|
|
154
178
|
}
|