expo-local-llm 0.2.0 → 0.2.2

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
@@ -163,6 +163,28 @@ const recipe = JSON.parse(await respond('Give me a pasta recipe'));
163
163
  - Tool calling is iOS 26+ only. Android will throw at session creation if tools are passed.
164
164
  - Structured output uses instruction-based JSON guidance, not Apple's constrained decoding.
165
165
 
166
+ ## Why an Expo module?
167
+
168
+ This is built as an [Expo Module](https://docs.expo.dev/modules/overview/), not
169
+ a bare React Native library. Here's why:
170
+
171
+ - **`expo install` and go** — no manual Xcode/Gradle linking, no `pod install`
172
+ surprises. Works with `npx expo prebuild` and managed workflow out of the box.
173
+ - **SharedObject lifecycle** — `LLMSession` extends Expo's `SharedObject`, which
174
+ handles native memory management, event subscriptions, and cleanup
175
+ automatically when the JS object is garbage collected. In bare RN you'd wire
176
+ this yourself with `NativeEventEmitter` and manual release calls.
177
+ - **Class DSL** — the native module exposes `LLMSession` as a first-class JS
178
+ object with instance methods (`session.respond()`, `session.streamResponse()`)
179
+ rather than flat module-level functions with session IDs. This is an Expo
180
+ Modules API feature that doesn't exist in the classic RN bridge.
181
+ - **Cross-platform parity** — Expo's Kotlin DSL mirrors the Swift DSL, so the
182
+ iOS and Android modules have the same structure. Adding Android tool calling
183
+ later means implementing the same interface, not building a separate bridge.
184
+
185
+ If you're on bare React Native without Expo, this module won't work — you'd need
186
+ to add `expo-modules-core` as a dependency or use a different library.
187
+
166
188
  ## How is this different from @callstack/ai? (as of March 2026)
167
189
 
168
190
  [@callstack/ai](https://github.com/callstackincubator/ai) is a full-featured
@@ -52,8 +52,6 @@ public class ExpoLocalLlmModule: Module {
52
52
  Function("rejectToolCall") { (session: LLMSession, callId: String, error: String) in
53
53
  try session.rejectToolCall(callId: callId, error: error)
54
54
  }
55
-
56
- Events("token", "streamComplete", "streamError", "toolCall")
57
55
  }
58
56
 
59
57
  OnAppEntersForeground {
@@ -23,7 +23,8 @@ enum FoundationModelBridge {
23
23
  }
24
24
  }
25
25
 
26
- static func createSession(instructions: String?, tools: [any Tool] = []) -> LanguageModelSession {
26
+ static func createSession(instructions: String?, dynamicTools: [DynamicTool] = []) -> LanguageModelSession {
27
+ let tools: [any Tool] = dynamicTools
27
28
  if tools.isEmpty {
28
29
  if let instructions {
29
30
  return LanguageModelSession(instructions: instructions)
@@ -113,7 +113,7 @@ class LLMSession: SharedObject {
113
113
  // Cancel any pending tool calls from the old session
114
114
  store.cancelAll()
115
115
 
116
- var tools: [any Tool] = []
116
+ var dynamicTools: [DynamicTool] = []
117
117
  registeredTools.removeAll()
118
118
 
119
119
  for config in toolConfigs {
@@ -138,12 +138,12 @@ class LLMSession: SharedObject {
138
138
  ])
139
139
  }
140
140
  registeredTools[config.name] = tool
141
- tools.append(tool)
141
+ dynamicTools.append(tool)
142
142
  }
143
143
 
144
144
  nativeSession = FoundationModelBridge.createSession(
145
145
  instructions: sessionInstructions,
146
- tools: tools
146
+ dynamicTools: dynamicTools
147
147
  )
148
148
  }
149
149
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "expo-local-llm",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "Expo module for on-device LLM inference via Apple Foundation Models (iOS) and Gemini Nano (Android)",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",