expo-local-llm 0.4.1 → 0.6.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 CHANGED
@@ -71,8 +71,8 @@ function Chat() {
71
71
  }
72
72
 
73
73
  const handleSend = async (prompt: string) => {
74
- // Streaming — updates streamedText live
75
- await streamResponse(prompt);
74
+ // Streaming — updates streamedText live, resolves with the final text
75
+ const text = await streamResponse(prompt);
76
76
 
77
77
  // Or non-streaming:
78
78
  // const response = await respond(prompt);
@@ -158,6 +158,19 @@ const { respond } = useLocalLLM({
158
158
  const recipe = JSON.parse(await respond('Give me a pasta recipe'));
159
159
  ```
160
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
+
161
174
  Streaming with a schema yields progressively-filled snapshots via `streamedJSON` (the current
162
175
  partial as a JSON string) and `streamedObject` (the parsed value):
163
176
 
@@ -198,8 +211,8 @@ if (!result.ok) {
198
211
  |--------|------|-------------|
199
212
  | `instructions` | `string` | System instructions for the session |
200
213
  | `options.temperature` | `number` | Sampling temperature |
201
- | `options.maxTokens` | `number` | Max output tokens (capped at 256 on Android) |
202
- | `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) |
203
216
  | `tools` | `ToolDefinition[]` | Tools the model can invoke (iOS 26+ only) |
204
217
  | `toolTimeout` | `number` | Seconds before an unresolved tool call times out (default 30) |
205
218
  | `responseFormat` | `"text" \| "json"` | Set to `"json"` for structured JSON output |
@@ -220,10 +233,18 @@ if (!result.ok) {
220
233
  | `activeToolCalls` | `ActiveToolCall[]` | In-flight tool calls (`{ callId, toolName }`) |
221
234
  | `respond` | `(prompt: string) => Promise<string>` | Non-streaming generation. `undefined` when unavailable. |
222
235
  | `session` | `LLMSession \| null` | The native session object. `null` when unavailable. |
223
- | `streamResponse` | `(prompt: string) => Promise<void>` | Streaming generation (updates `streamedText`). `undefined` when model not `available`. |
224
- | `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. |
225
239
  | `downloadModel` | `() => Promise<void>` | Trigger model download (Android). `undefined` when unavailable. |
226
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
+
227
248
  ### `ModelAvailability`
228
249
 
229
250
  `'available' | 'notEnabled' | 'notReady' | 'notEligible' | 'downloadRequired' | 'downloading' | 'unknown'`
@@ -270,13 +291,15 @@ a bare React Native library. Here's why:
270
291
  If you're on bare React Native without Expo, this module won't work — you'd need
271
292
  to add `expo-modules-core` as a dependency or use a different library.
272
293
 
273
- ## How is this different from React Native AI? (as of June 2026)
294
+ ## How is this different from React Native AI? (as of August 2026)
274
295
 
275
296
  [React Native AI](https://github.com/callstackincubator/ai) (formerly `@callstack/ai`)
276
297
  is a Vercel AI SDK-compatible collection of on-device AI primitives, modularized
277
298
  into per-backend packages: `@react-native-ai/apple`, `@react-native-ai/llama`,
278
- `@react-native-ai/mlc`. It covers text generation, embeddings, transcription, and
279
- speech synthesis.
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.
280
303
 
281
304
  `expo-local-llm` takes a narrower approach:
282
305
 
@@ -284,25 +307,34 @@ speech synthesis.
284
307
  |---|---|---|
285
308
  | **Surface** | React hook (`useLocalLLM`) and a native session object | Vercel AI SDK provider (`generateText`, `streamText`, `embed`, `transcribe`, `speech`) |
286
309
  | **iOS model** | Apple Foundation Models (system-provided) | Apple Foundation Models, Llama (via llama.rn), MLC LLM |
287
- | **Android model** | Gemini Nano (system-provided) | Llama, MLC (no built-in OS model) |
288
- | **Capabilities** | Text generation, tool calling, constrained JSON output | Text, embeddings, transcription, speech synthesis |
289
- | **Model management** | None — the OS handles it | Built-in for Apple; download/prepare for Llama/MLC |
290
- | **Bundle size impact** | Near zero | Depends on which provider you install + model weights |
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) |
291
315
  | **DevTools** | None | AI SDK Profiler via Rozenite (OpenTelemetry spans) |
292
- | **Install path** | One `expo install`, autolinks | Per-package install (Apple provider autolinks; Llama/MLC need extra setup) |
293
- | **Dependencies** | None | Vercel AI SDK (v6) and per-provider runtimes |
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 |
294
318
 
295
319
  **Choose `expo-local-llm` if** you want the simplest path to on-device LLM in
296
320
  an Expo app and are happy using whatever model the OS provides (Apple Foundation
297
321
  Models on iOS, Gemini Nano on Android). There's nothing to configure, no weights
298
- to bundle, and it works on both platforms.
322
+ to bundle, and it works on both platforms with a single install.
299
323
 
300
324
  **Choose React Native AI if** you need any of:
301
325
  - Vercel AI SDK compatibility (drop-in replacement for cloud-LLM apps)
302
- - Capabilities beyond text generation (embeddings, transcription, speech synthesis)
326
+ - Capabilities beyond text generation (embeddings, transcription, speech synthesis, image input)
303
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
304
329
  - AI SDK Profiler DevTools for tracing
305
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).
337
+
306
338
  ## Built with expo-local-llm
307
339
 
308
340
  - [PulseID](https://apps.apple.com/us/app/pulseid-heart-rate-camera/id6754331991) — Heart rate camera
@@ -16,6 +16,10 @@ class ConversationHistory(private val systemInstructions: String?) {
16
16
  trimHistory()
17
17
  }
18
18
 
19
+ fun clear() {
20
+ messages.clear()
21
+ }
22
+
19
23
  fun buildPrompt(): String {
20
24
  val sb = StringBuilder()
21
25
  if (!systemInstructions.isNullOrBlank()) {
@@ -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.startStream(prompt)
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
- fun startStream(prompt: String) {
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
- streamJob = scope.launch {
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
- var accumulated = ""
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
- history.addAssistantMessage(accumulated)
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()
@@ -10,11 +10,18 @@ export type ToolParameter = {
10
10
  description: string;
11
11
  enum?: string[];
12
12
  };
13
+ /**
14
+ * Arguments the model supplied for a tool call, parsed from the JSON it
15
+ * produced. The values are NOT validated against the tool's declared
16
+ * `parameters` — the model may omit fields, add extras, or use a different
17
+ * primitive type — so handlers should treat them as untrusted input.
18
+ */
19
+ export type ToolArguments = Record<string, unknown>;
13
20
  export type ToolDefinition = {
14
21
  name: string;
15
22
  description: string;
16
23
  parameters: Record<string, ToolParameter>;
17
- handler: (args: Record<string, any>) => Promise<string>;
24
+ handler: (args: ToolArguments) => Promise<string>;
18
25
  };
19
26
  export type ToolConfig = {
20
27
  name: string;
@@ -24,7 +31,7 @@ export type ToolConfig = {
24
31
  export type ToolCallEvent = {
25
32
  callId: string;
26
33
  toolName: string;
27
- arguments: Record<string, any>;
34
+ arguments: ToolArguments;
28
35
  };
29
36
  export type ActiveToolCall = {
30
37
  callId: string;
@@ -1 +1 @@
1
- {"version":3,"file":"ExpoLocalLlm.types.d.ts","sourceRoot":"","sources":["../src/ExpoLocalLlm.types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ExpoLocalLlm.types.d.ts","sourceRoot":"","sources":["../src/ExpoLocalLlm.types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,iBAAiB,GACzB,WAAW,GACX,YAAY,GACZ,UAAU,GACV,aAAa,GACb,kBAAkB,GAClB,aAAa,GACb,SAAS,CAAC;AAEd,MAAM,MAAM,iBAAiB,GAAG;IAC9B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC;AAIF,MAAM,MAAM,iBAAiB,GAAG,QAAQ,GAAG,QAAQ,GAAG,SAAS,CAAC;AAEhE,MAAM,MAAM,aAAa,GAAG;IAC1B,IAAI,EAAE,iBAAiB,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;CACjB,CAAC;AAEF;;;;;GAKG;AACH,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAEpD,MAAM,MAAM,cAAc,GAAG;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;IAC1C,OAAO,EAAE,CAAC,IAAI,EAAE,aAAa,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;CACnD,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;CAC3C,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,aAAa,CAAC;CAC1B,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAIF,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,MAAM,CAAC;AAE7C,MAAM,MAAM,WAAW,GACnB;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAA;CAAE,GACzD;IAAE,IAAI,EAAE,QAAQ,GAAG,SAAS,GAAG,SAAS,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,GAChE;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,WAAW,CAAA;CAAE,GAC3D;IACE,IAAI,EAAE,QAAQ,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;CACzC,CAAC;AAEN,MAAM,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;AAEjD,MAAM,MAAM,aAAa,GAAG;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,EAAE,iBAAiB,CAAC;IAC5B,KAAK,CAAC,EAAE,cAAc,EAAE,CAAC;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,qBAAqB,CAAC,EAAE,OAAO,CAAC;CACjC,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,OAAO,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,KAAK,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAAC;IACnC,OAAO,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,CAAC;IACvC,cAAc,EAAE,CAAC,KAAK,EAAE,mBAAmB,KAAK,IAAI,CAAC;IACrD,WAAW,EAAE,CAAC,KAAK,EAAE,gBAAgB,KAAK,IAAI,CAAC;IAC/C,QAAQ,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;CAC1C,CAAC;AAEF,MAAM,MAAM,wBAAwB,GAAG;IACrC,gBAAgB,EAAE,CAAC,KAAK,EAAE,gBAAgB,KAAK,IAAI,CAAC;IACpD,kBAAkB,EAAE,CAAC,KAAK,EAAE;QAAE,YAAY,EAAE,iBAAiB,CAAA;KAAE,KAAK,IAAI,CAAC;CAC1E,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"ExpoLocalLlm.types.js","sourceRoot":"","sources":["../src/ExpoLocalLlm.types.ts"],"names":[],"mappings":"","sourcesContent":["export type ModelAvailability =\n | \"available\"\n | \"notEnabled\"\n | \"notReady\"\n | \"notEligible\"\n | \"downloadRequired\"\n | \"downloading\"\n | \"unknown\";\n\nexport type GenerationOptions = {\n temperature?: number;\n maxTokens?: number;\n topK?: number;\n};\n\n// Tool calling types\n\nexport type ToolParameterType = \"string\" | \"number\" | \"boolean\";\n\nexport type ToolParameter = {\n type: ToolParameterType;\n description: string;\n enum?: string[];\n};\n\nexport type ToolDefinition = {\n name: string;\n description: string;\n parameters: Record<string, ToolParameter>;\n handler: (args: Record<string, any>) => Promise<string>;\n};\n\nexport type ToolConfig = {\n name: string;\n description: string;\n parameters: Record<string, ToolParameter>;\n};\n\nexport type ToolCallEvent = {\n callId: string;\n toolName: string;\n arguments: Record<string, any>;\n};\n\nexport type ActiveToolCall = {\n callId: string;\n toolName: string;\n};\n\n// Structured output types\n\nexport type ResponseFormat = \"text\" | \"json\";\n\nexport type SchemaField =\n | { type: \"string\"; description?: string; enum?: string[] }\n | { type: \"number\" | \"integer\" | \"boolean\"; description?: string }\n | { type: \"array\"; description?: string; items: SchemaField }\n | {\n type: \"object\";\n description?: string;\n properties: Record<string, SchemaField>;\n };\n\nexport type Schema = Record<string, SchemaField>;\n\nexport type SessionConfig = {\n instructions?: string;\n options?: GenerationOptions;\n tools?: ToolDefinition[];\n toolTimeout?: number;\n responseFormat?: ResponseFormat;\n schema?: Schema;\n includeSchemaInPrompt?: boolean;\n};\n\nexport type TokenEvent = {\n token: string;\n accumulated: string;\n};\n\nexport type StreamCompleteEvent = {\n text: string;\n};\n\nexport type PartialEvent = {\n json: string;\n complete: boolean;\n};\n\nexport type StreamErrorEvent = {\n error: string;\n};\n\nexport type DownloadProgress = {\n progress: number;\n};\n\nexport type LLMSessionEvents = {\n token: (event: TokenEvent) => void;\n partial: (event: PartialEvent) => void;\n streamComplete: (event: StreamCompleteEvent) => void;\n streamError: (event: StreamErrorEvent) => void;\n toolCall: (event: ToolCallEvent) => void;\n};\n\nexport type ExpoLocalLlmModuleEvents = {\n downloadProgress: (event: DownloadProgress) => void;\n availabilityChange: (event: { availability: ModelAvailability }) => void;\n};\n"]}
1
+ {"version":3,"file":"ExpoLocalLlm.types.js","sourceRoot":"","sources":["../src/ExpoLocalLlm.types.ts"],"names":[],"mappings":"","sourcesContent":["export type ModelAvailability =\n | \"available\"\n | \"notEnabled\"\n | \"notReady\"\n | \"notEligible\"\n | \"downloadRequired\"\n | \"downloading\"\n | \"unknown\";\n\nexport type GenerationOptions = {\n temperature?: number;\n maxTokens?: number;\n topK?: number;\n};\n\n// Tool calling types\n\nexport type ToolParameterType = \"string\" | \"number\" | \"boolean\";\n\nexport type ToolParameter = {\n type: ToolParameterType;\n description: string;\n enum?: string[];\n};\n\n/**\n * Arguments the model supplied for a tool call, parsed from the JSON it\n * produced. The values are NOT validated against the tool's declared\n * `parameters` — the model may omit fields, add extras, or use a different\n * primitive type — so handlers should treat them as untrusted input.\n */\nexport type ToolArguments = Record<string, unknown>;\n\nexport type ToolDefinition = {\n name: string;\n description: string;\n parameters: Record<string, ToolParameter>;\n handler: (args: ToolArguments) => Promise<string>;\n};\n\nexport type ToolConfig = {\n name: string;\n description: string;\n parameters: Record<string, ToolParameter>;\n};\n\nexport type ToolCallEvent = {\n callId: string;\n toolName: string;\n arguments: ToolArguments;\n};\n\nexport type ActiveToolCall = {\n callId: string;\n toolName: string;\n};\n\n// Structured output types\n\nexport type ResponseFormat = \"text\" | \"json\";\n\nexport type SchemaField =\n | { type: \"string\"; description?: string; enum?: string[] }\n | { type: \"number\" | \"integer\" | \"boolean\"; description?: string }\n | { type: \"array\"; description?: string; items: SchemaField }\n | {\n type: \"object\";\n description?: string;\n properties: Record<string, SchemaField>;\n };\n\nexport type Schema = Record<string, SchemaField>;\n\nexport type SessionConfig = {\n instructions?: string;\n options?: GenerationOptions;\n tools?: ToolDefinition[];\n toolTimeout?: number;\n responseFormat?: ResponseFormat;\n schema?: Schema;\n includeSchemaInPrompt?: boolean;\n};\n\nexport type TokenEvent = {\n token: string;\n accumulated: string;\n};\n\nexport type StreamCompleteEvent = {\n text: string;\n};\n\nexport type PartialEvent = {\n json: string;\n complete: boolean;\n};\n\nexport type StreamErrorEvent = {\n error: string;\n};\n\nexport type DownloadProgress = {\n progress: number;\n};\n\nexport type LLMSessionEvents = {\n token: (event: TokenEvent) => void;\n partial: (event: PartialEvent) => void;\n streamComplete: (event: StreamCompleteEvent) => void;\n streamError: (event: StreamErrorEvent) => void;\n toolCall: (event: ToolCallEvent) => void;\n};\n\nexport type ExpoLocalLlmModuleEvents = {\n downloadProgress: (event: DownloadProgress) => void;\n availabilityChange: (event: { availability: ModelAvailability }) => void;\n};\n"]}
@@ -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
- streamResponse: (prompt: string) => Promise<void>;
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,IAAI,CAAC,CAAC;IACnD,YAAY,EAAG,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACnC,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"}
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,CAoBvE;AAED;;;;;;;;;GASG;AACH,wBAAsB,QAAQ,CAC5B,MAAM,EAAE,MAAM,EACd,MAAM,GAAE,aAAkB,GACzB,OAAO,CAAC,MAAM,CAAC,CAOjB"}
@@ -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;
@@ -25,8 +37,29 @@ export function createLLMSession(config = {}) {
25
37
  // Strip handler functions before passing to native — native doesn't need them
26
38
  const nativeConfig = {
27
39
  ...config,
28
- tools: config.tools?.map(({ handler, ...rest }) => rest),
40
+ tools: config.tools?.map(({ handler: _handler, ...rest }) => rest),
29
41
  };
42
+ // SAFETY: the native module exposes `LLMSession` as an untyped shared-object
43
+ // class; its instances implement the `LLMSession` contract in this file.
30
44
  return new ExpoLocalLlmModule.LLMSession(nativeConfig);
31
45
  }
46
+ /**
47
+ * One-shot, stateless generation: creates a session, responds once, and
48
+ * releases the session. Use this for classification/extraction calls where
49
+ * conversation history is unwanted — no session bookkeeping, no transcript
50
+ * accumulation across calls.
51
+ *
52
+ * Throws `UnavailabilityError` when the native module is missing; rejects
53
+ * with the native error when the model is not available. Callers that need
54
+ * an availability check first can use `ExpoLocalLlmModule.getAvailability()`.
55
+ */
56
+ export async function generate(prompt, config = {}) {
57
+ const session = createLLMSession(config);
58
+ try {
59
+ return await session.respond(prompt);
60
+ }
61
+ finally {
62
+ session.release();
63
+ }
64
+ }
32
65
  //# sourceMappingURL=LLMSession.js.map
@@ -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,CAAqC;IACnD,YAAY,CAAuB;IACnC,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","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 streamResponse!: (prompt: string) => Promise<void>;\n cancelStream!: () => Promise<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"]}
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,QAAQ,EAAE,GAAG,IAAI,EAAE,EAAE,EAAE,CAAC,IAAI,CAAC;KACnE,CAAC;IACF,6EAA6E;IAC7E,yEAAyE;IACzE,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: _handler, ...rest }) => rest),\n };\n // SAFETY: the native module exposes `LLMSession` as an untyped shared-object\n // class; its instances implement the `LLMSession` contract in this file.\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";
@@ -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;AAC5D,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"}
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";
@@ -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;AAC5D,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 } 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"]}
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"]}
@@ -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<void>;
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,IAAI,CAAC,CAAC;IACnD,YAAY,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACnC,aAAa,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CACrC,CAAC;AAEF,wBAAgB,WAAW,CACzB,OAAO,GAAE,kBAAuB,GAC/B,iBAAiB,CAgSnB"}
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,CAsTnB"}
@@ -2,6 +2,8 @@ import { useEffect, useState, useCallback, useMemo, useRef } from "react";
2
2
  import ExpoLocalLlmModule from "./ExpoLocalLlmModule";
3
3
  import { createLLMSession, LLMSession } from "./LLMSession";
4
4
  export function useLocalLLM(options = {}) {
5
+ // SAFETY: native `getAvailability()` returns one of the `ModelAvailability`
6
+ // string literals; the bridge only types it as `string`.
5
7
  const [availability, setAvailability] = useState(() => ExpoLocalLlmModule
6
8
  ? ExpoLocalLlmModule.getAvailability()
7
9
  : "unknown");
@@ -154,7 +156,11 @@ export function useLocalLLM(options = {}) {
154
156
  subs.forEach((s) => s.remove());
155
157
  session?.release();
156
158
  };
157
- }, [session]);
159
+ // isJSONMode: used by the streamComplete listener. Today it can only
160
+ // change together with a session recreation (responseFormat/schema are
161
+ // in stableConfig), but depending on it directly removes the reliance
162
+ // on that invariant.
163
+ }, [session, isJSONMode]);
158
164
  const respond = useCallback(async (prompt) => {
159
165
  if (!session)
160
166
  throw new Error("Session not available");
@@ -181,13 +187,17 @@ export function useLocalLLM(options = {}) {
181
187
  setStreamedObject(null);
182
188
  setIsGenerating(true);
183
189
  try {
184
- await session.streamResponse(prompt);
190
+ // Resolves with the final text at stream end (or the partial text
191
+ // if cancelled); rejects if the stream fails.
192
+ return await session.streamResponse(prompt);
185
193
  }
186
194
  catch (e) {
187
195
  setRuntimeError(e.message);
188
- setIsGenerating(false);
189
196
  throw e;
190
197
  }
198
+ finally {
199
+ setIsGenerating(false);
200
+ }
191
201
  }, [session]);
192
202
  const cancelStream = useCallback(async () => {
193
203
  if (!session)
@@ -195,6 +205,17 @@ export function useLocalLLM(options = {}) {
195
205
  await session.cancelStream();
196
206
  setIsGenerating(false);
197
207
  }, [session]);
208
+ const reset = useCallback(() => {
209
+ if (!session)
210
+ return;
211
+ session.reset();
212
+ setStreamedText("");
213
+ setStreamedJSON("");
214
+ setStreamedObject(null);
215
+ setRuntimeError(null);
216
+ setActiveToolCalls([]);
217
+ setIsGenerating(false);
218
+ }, [session]);
198
219
  const downloadModel = useCallback(async () => {
199
220
  if (!ExpoLocalLlmModule)
200
221
  return;
@@ -226,6 +247,8 @@ export function useLocalLLM(options = {}) {
226
247
  respond: session && availability === "available" ? respond : undefined,
227
248
  streamResponse: session && availability === "available" ? streamResponse : undefined,
228
249
  cancelStream: session && availability === "available" ? cancelStream : undefined,
250
+ // Resetting is safe in any availability state — gate on session only.
251
+ reset: session ? reset : undefined,
229
252
  downloadModel,
230
253
  };
231
254
  }
@@ -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 // SAFETY: native `getAvailability()` returns one of the `ModelAvailability`\n // string literals; the bridge only types it as `string`.\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"]}