expo-local-llm 0.1.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.
Files changed (46) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +110 -0
  3. package/android/build.gradle +36 -0
  4. package/android/src/main/AndroidManifest.xml +2 -0
  5. package/android/src/main/java/expo/modules/localllm/ConversationHistory.kt +39 -0
  6. package/android/src/main/java/expo/modules/localllm/Enums.kt +13 -0
  7. package/android/src/main/java/expo/modules/localllm/Exceptions.kt +12 -0
  8. package/android/src/main/java/expo/modules/localllm/ExpoLocalLlmModule.kt +57 -0
  9. package/android/src/main/java/expo/modules/localllm/LLMSession.kt +119 -0
  10. package/android/src/main/java/expo/modules/localllm/Records.kt +23 -0
  11. package/build/ExpoLocalLlm.types.d.ts +35 -0
  12. package/build/ExpoLocalLlm.types.d.ts.map +1 -0
  13. package/build/ExpoLocalLlm.types.js +2 -0
  14. package/build/ExpoLocalLlm.types.js.map +1 -0
  15. package/build/ExpoLocalLlmModule.d.ts +10 -0
  16. package/build/ExpoLocalLlmModule.d.ts.map +1 -0
  17. package/build/ExpoLocalLlmModule.js +10 -0
  18. package/build/ExpoLocalLlmModule.js.map +1 -0
  19. package/build/LLMSession.d.ts +9 -0
  20. package/build/LLMSession.d.ts.map +1 -0
  21. package/build/LLMSession.js +16 -0
  22. package/build/LLMSession.js.map +1 -0
  23. package/build/index.d.ts +5 -0
  24. package/build/index.d.ts.map +1 -0
  25. package/build/index.js +5 -0
  26. package/build/index.js.map +1 -0
  27. package/build/useLocalLLM.d.ts +18 -0
  28. package/build/useLocalLLM.d.ts.map +1 -0
  29. package/build/useLocalLLM.js +121 -0
  30. package/build/useLocalLLM.js.map +1 -0
  31. package/expo-module.config.json +9 -0
  32. package/ios/Enums.swift +11 -0
  33. package/ios/Exceptions.swift +19 -0
  34. package/ios/ExpoLocalLlm.podspec +26 -0
  35. package/ios/ExpoLocalLlmModule.swift +56 -0
  36. package/ios/FoundationModelBridge.swift +59 -0
  37. package/ios/LLMSession.swift +68 -0
  38. package/ios/Records.swift +12 -0
  39. package/package.json +61 -0
  40. package/src/ExpoLocalLlm.types.ts +47 -0
  41. package/src/ExpoLocalLlmModule.ts +19 -0
  42. package/src/LLMSession.ts +19 -0
  43. package/src/__tests__/ExpoLocalLlm.test.ts +82 -0
  44. package/src/__tests__/useLocalLLM.test.ts +30 -0
  45. package/src/index.ts +4 -0
  46. package/src/useLocalLLM.ts +176 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 PulseID
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,110 @@
1
+ # expo-local-llm
2
+
3
+ Expo module for on-device LLM inference. Wraps Apple Foundation Models (iOS 26+) and Gemini Nano via ML Kit (Android).
4
+
5
+ ## Platform Requirements
6
+
7
+ | Platform | Requirement |
8
+ |----------|-------------|
9
+ | iOS | iOS 26+ with Apple Intelligence enabled. Loads on iOS 16+ without crashing (returns `notEligible`). |
10
+ | Android | Device with Gemini Nano support (Pixel 8+, Galaxy S25+). Model may require download. |
11
+
12
+ ## Installation
13
+
14
+ ```bash
15
+ npm install expo-local-llm
16
+ npx expo prebuild
17
+ ```
18
+
19
+ ## Usage
20
+
21
+ ```tsx
22
+ import { useLocalLLM } from 'expo-local-llm';
23
+
24
+ function Chat() {
25
+ const {
26
+ availability,
27
+ isGenerating,
28
+ streamedText,
29
+ error,
30
+ respond,
31
+ streamResponse,
32
+ cancelStream,
33
+ downloadModel,
34
+ downloadProgress,
35
+ } = useLocalLLM({
36
+ instructions: 'You are a helpful assistant.',
37
+ });
38
+
39
+ // Methods are undefined when module is unavailable (optionality pattern)
40
+ if (!streamResponse) {
41
+ return <Text>On-device LLM not available</Text>;
42
+ }
43
+
44
+ const handleSend = async (prompt: string) => {
45
+ // Streaming — updates streamedText live
46
+ await streamResponse(prompt);
47
+
48
+ // Or non-streaming:
49
+ // const response = await respond(prompt);
50
+ };
51
+
52
+ return (
53
+ <View>
54
+ <Text>Status: {availability}</Text>
55
+ {isGenerating && <Text>{streamedText}</Text>}
56
+ </View>
57
+ );
58
+ }
59
+ ```
60
+
61
+ ## API
62
+
63
+ ### `useLocalLLM(options?)`
64
+
65
+ #### Options
66
+
67
+ | Option | Type | Description |
68
+ |--------|------|-------------|
69
+ | `instructions` | `string` | System instructions for the session |
70
+ | `options.temperature` | `number` | Sampling temperature |
71
+ | `options.maxTokens` | `number` | Max output tokens (capped at 256 on Android) |
72
+ | `options.topK` | `number` | Top-K sampling |
73
+
74
+ #### Returns
75
+
76
+ | Property | Type | Description |
77
+ |----------|------|-------------|
78
+ | `availability` | `ModelAvailability` | Current model status |
79
+ | `isGenerating` | `boolean` | Whether a generation is in progress |
80
+ | `streamedText` | `string` | Accumulated text from current stream |
81
+ | `downloadProgress` | `number \| null` | Download progress 0-1 (Android only) |
82
+ | `error` | `string \| null` | Last error message |
83
+ | `respond` | `(prompt: string) => Promise<string>` | Non-streaming generation. `undefined` when unavailable. |
84
+ | `streamResponse` | `(prompt: string) => Promise<void>` | Streaming generation (updates `streamedText`). `undefined` when unavailable. |
85
+ | `cancelStream` | `() => Promise<void>` | Cancel active stream. `undefined` when unavailable. |
86
+ | `downloadModel` | `() => Promise<void>` | Trigger model download (Android). `undefined` when unavailable. |
87
+
88
+ ### `ModelAvailability`
89
+
90
+ `'available' | 'notEnabled' | 'notReady' | 'notEligible' | 'downloadRequired' | 'downloading' | 'unknown'`
91
+
92
+ ## Platform Asymmetries
93
+
94
+ | Concern | iOS | Android |
95
+ |---------|-----|---------|
96
+ | System instructions | Native `LanguageModelSession(instructions:)` | Prepended in prompt text |
97
+ | Output tokens | ~4K context, generous output | 256 max |
98
+ | Model availability | Built-in to OS | May need download |
99
+ | Session history | Native session maintains it | `ConversationHistory` class |
100
+ | Streaming | `AsyncSequence` | Kotlin `Flow` |
101
+
102
+ ## Known Limitations
103
+
104
+ - **v0.1.0**: Only one active stream at a time. Events are not scoped to session ID.
105
+ - **Android**: Gemini Nano SDK is in beta. API surface may change.
106
+ - No tool calling or structured output yet.
107
+
108
+ ## License
109
+
110
+ MIT
@@ -0,0 +1,36 @@
1
+ plugins {
2
+ id 'com.android.library'
3
+ id 'expo-module-gradle-plugin'
4
+ id 'org.jetbrains.kotlin.android'
5
+ }
6
+
7
+ group = 'expo.modules.localllm'
8
+ version = '0.1.0'
9
+
10
+ android {
11
+ namespace "expo.modules.localllm"
12
+ compileSdk 35
13
+
14
+ defaultConfig {
15
+ minSdk 26
16
+ versionCode 1
17
+ versionName "0.1.0"
18
+ }
19
+
20
+ compileOptions {
21
+ sourceCompatibility JavaVersion.VERSION_17
22
+ targetCompatibility JavaVersion.VERSION_17
23
+ }
24
+
25
+ kotlinOptions {
26
+ jvmTarget = '17'
27
+ }
28
+
29
+ lintOptions {
30
+ abortOnError false
31
+ }
32
+ }
33
+
34
+ dependencies {
35
+ implementation 'com.google.ai.edge.localagent:localagent:0.1.0'
36
+ }
@@ -0,0 +1,2 @@
1
+ <manifest>
2
+ </manifest>
@@ -0,0 +1,39 @@
1
+ package expo.modules.localllm
2
+
3
+ class ConversationHistory(private val systemInstructions: String?) {
4
+ private data class Message(val role: String, val content: String)
5
+
6
+ private val messages = mutableListOf<Message>()
7
+ private val maxExchanges = 10
8
+
9
+ fun addUserMessage(content: String) {
10
+ messages.add(Message("User", content))
11
+ trimHistory()
12
+ }
13
+
14
+ fun addAssistantMessage(content: String) {
15
+ messages.add(Message("Assistant", content))
16
+ trimHistory()
17
+ }
18
+
19
+ fun buildPrompt(): String {
20
+ val sb = StringBuilder()
21
+ if (!systemInstructions.isNullOrBlank()) {
22
+ sb.appendLine("[System] $systemInstructions")
23
+ sb.appendLine()
24
+ }
25
+ for (msg in messages) {
26
+ sb.appendLine("[${msg.role}] ${msg.content}")
27
+ }
28
+ return sb.toString().trim()
29
+ }
30
+
31
+ private fun trimHistory() {
32
+ // Keep last maxExchanges * 2 messages (user + assistant pairs)
33
+ val maxMessages = maxExchanges * 2
34
+ if (messages.size > maxMessages) {
35
+ val excess = messages.size - maxMessages
36
+ repeat(excess) { messages.removeAt(0) }
37
+ }
38
+ }
39
+ }
@@ -0,0 +1,13 @@
1
+ package expo.modules.localllm
2
+
3
+ import expo.modules.kotlin.types.Enumerable
4
+
5
+ enum class ModelAvailability(val value: String) : Enumerable {
6
+ available("available"),
7
+ notEnabled("notEnabled"),
8
+ notReady("notReady"),
9
+ notEligible("notEligible"),
10
+ downloadRequired("downloadRequired"),
11
+ downloading("downloading"),
12
+ unknown("unknown")
13
+ }
@@ -0,0 +1,12 @@
1
+ package expo.modules.localllm
2
+
3
+ import expo.modules.kotlin.exception.CodedException
4
+
5
+ class NotSupportedException :
6
+ CodedException("ERR_NOT_SUPPORTED", "On-device LLM is not available on this device", null)
7
+
8
+ class SessionInvalidException :
9
+ CodedException("ERR_SESSION_INVALID", "LLM session is invalid or has been destroyed", null)
10
+
11
+ class StreamException(message: String) :
12
+ CodedException("ERR_STREAM", message, null)
@@ -0,0 +1,57 @@
1
+ package expo.modules.localllm
2
+
3
+ import expo.modules.kotlin.modules.Module
4
+ import expo.modules.kotlin.modules.ModuleDefinition
5
+ import kotlinx.coroutines.CoroutineScope
6
+ import kotlinx.coroutines.Dispatchers
7
+ import kotlinx.coroutines.launch
8
+
9
+ class ExpoLocalLlmModule : Module() {
10
+ private val scope = CoroutineScope(Dispatchers.Main)
11
+
12
+ override fun definition() = ModuleDefinition {
13
+ Name("ExpoLocalLlm")
14
+
15
+ Events("downloadProgress", "availabilityChange")
16
+
17
+ Function("getAvailability") {
18
+ LLMSession.checkAvailability(appContext.reactContext!!).value
19
+ }
20
+
21
+ AsyncFunction("downloadModel") Coroutine { ->
22
+ val context = appContext.reactContext!!
23
+ // Immediately signal downloading state so UI can disable the download button
24
+ sendEvent("availabilityChange", mapOf("availability" to ModelAvailability.downloading.value))
25
+ LLMSession.downloadModel(context) { progress ->
26
+ sendEvent("downloadProgress", mapOf("progress" to progress))
27
+ }
28
+ // Re-check availability after download completes
29
+ val newAvailability = LLMSession.checkAvailability(context)
30
+ sendEvent("availabilityChange", mapOf("availability" to newAvailability.value))
31
+ }
32
+
33
+ Class(LLMSession::class) {
34
+ Constructor { config: SessionConfig ->
35
+ LLMSession(appContext.reactContext!!, config)
36
+ }
37
+
38
+ AsyncFunction("respond") Coroutine { session: LLMSession, prompt: String ->
39
+ session.respond(prompt)
40
+ }
41
+
42
+ AsyncFunction("streamResponse") { session: LLMSession, prompt: String ->
43
+ session.startStream(prompt)
44
+ }
45
+
46
+ AsyncFunction("cancelStream") { session: LLMSession ->
47
+ session.cancelStream()
48
+ }
49
+
50
+ Events("token", "streamComplete", "streamError")
51
+ }
52
+
53
+ OnActivityDestroys {
54
+ // Cleanup handled by SharedObject.deallocate() on each LLMSession
55
+ }
56
+ }
57
+ }
@@ -0,0 +1,119 @@
1
+ package expo.modules.localllm
2
+
3
+ import android.content.Context
4
+ import com.google.ai.edge.localagent.GenerativeModel
5
+ import com.google.ai.edge.localagent.GenerationConfig
6
+ import com.google.ai.edge.localagent.LocalAgent
7
+ import expo.modules.kotlin.sharedobjects.SharedObject
8
+ import kotlinx.coroutines.CancellationException
9
+ import kotlinx.coroutines.CoroutineScope
10
+ import kotlinx.coroutines.Dispatchers
11
+ import kotlinx.coroutines.Job
12
+ import kotlinx.coroutines.cancel
13
+ import kotlinx.coroutines.launch
14
+
15
+ class LLMSession(
16
+ private val context: Context,
17
+ config: SessionConfig
18
+ ) : SharedObject() {
19
+ private val history = ConversationHistory(config.instructions)
20
+ private val temperature = config.options?.temperature
21
+ private val maxTokens = config.options?.maxTokens
22
+ private val topK = config.options?.topK
23
+ private var model: GenerativeModel? = null
24
+ private var streamJob: Job? = null
25
+ private val scope = CoroutineScope(Dispatchers.Main)
26
+
27
+ companion object {
28
+ fun checkAvailability(context: Context): ModelAvailability {
29
+ return try {
30
+ val status = LocalAgent.getFeatureStatus(context)
31
+ when {
32
+ status.isAvailable -> ModelAvailability.available
33
+ status.isDownloading -> ModelAvailability.downloading
34
+ status.isDownloadRequired -> ModelAvailability.downloadRequired
35
+ else -> ModelAvailability.notEligible
36
+ }
37
+ } catch (e: Exception) {
38
+ ModelAvailability.notEligible
39
+ }
40
+ }
41
+
42
+ suspend fun downloadModel(context: Context, onProgress: (Float) -> Unit) {
43
+ try {
44
+ // TODO: ML Kit GenAI download API may provide progress callbacks
45
+ // once the SDK stabilizes. For now, report start/complete.
46
+ onProgress(0f)
47
+ LocalAgent.downloadModel(context)
48
+ onProgress(1f)
49
+ } catch (e: Exception) {
50
+ throw StreamException("Failed to download model: ${e.message}")
51
+ }
52
+ }
53
+ }
54
+
55
+ private suspend fun getOrCreateModel(): GenerativeModel {
56
+ if (model == null) {
57
+ val configBuilder = GenerationConfig.Builder()
58
+ temperature?.let { configBuilder.temperature = it }
59
+ maxTokens?.let { configBuilder.maxOutputTokens = it.coerceAtMost(256) }
60
+ topK?.let { configBuilder.topK = it }
61
+
62
+ model = LocalAgent.createGenerativeModel(
63
+ context = context,
64
+ generationConfig = configBuilder.build()
65
+ )
66
+ }
67
+ return model!!
68
+ }
69
+
70
+ suspend fun respond(prompt: String): String {
71
+ val genModel = getOrCreateModel()
72
+ history.addUserMessage(prompt)
73
+ val fullPrompt = history.buildPrompt()
74
+ val response = genModel.generateContent(fullPrompt)
75
+ val text = response.text ?: ""
76
+ history.addAssistantMessage(text)
77
+ return text
78
+ }
79
+
80
+ fun startStream(prompt: String) {
81
+ streamJob?.cancel()
82
+ history.addUserMessage(prompt)
83
+ val fullPrompt = history.buildPrompt()
84
+
85
+ streamJob = scope.launch {
86
+ try {
87
+ val genModel = getOrCreateModel()
88
+ var accumulated = ""
89
+ val responseStream = genModel.generateContentStream(fullPrompt)
90
+ responseStream.collect { chunk ->
91
+ val token = chunk.text ?: ""
92
+ accumulated += token
93
+ emit("token", mapOf(
94
+ "token" to token,
95
+ "accumulated" to accumulated
96
+ ))
97
+ }
98
+ history.addAssistantMessage(accumulated)
99
+ emit("streamComplete", mapOf("text" to accumulated))
100
+ } catch (e: CancellationException) {
101
+ // User-initiated cancellation — not an error
102
+ } catch (e: Exception) {
103
+ emit("streamError", mapOf("error" to (e.message ?: "Unknown error")))
104
+ }
105
+ }
106
+ }
107
+
108
+ fun cancelStream() {
109
+ streamJob?.cancel()
110
+ streamJob = null
111
+ }
112
+
113
+ override fun deallocate() {
114
+ super.deallocate()
115
+ streamJob?.cancel()
116
+ model = null
117
+ scope.cancel()
118
+ }
119
+ }
@@ -0,0 +1,23 @@
1
+ package expo.modules.localllm
2
+
3
+ import expo.modules.kotlin.records.Field
4
+ import expo.modules.kotlin.records.Record
5
+
6
+ class SessionConfig : Record {
7
+ @Field
8
+ var instructions: String? = null
9
+
10
+ @Field
11
+ var options: GenerationOptionsRecord? = null
12
+ }
13
+
14
+ class GenerationOptionsRecord : Record {
15
+ @Field
16
+ var temperature: Float? = null
17
+
18
+ @Field
19
+ var maxTokens: Int? = null
20
+
21
+ @Field
22
+ var topK: Int? = null
23
+ }
@@ -0,0 +1,35 @@
1
+ export type ModelAvailability = "available" | "notEnabled" | "notReady" | "notEligible" | "downloadRequired" | "downloading" | "unknown";
2
+ export type GenerationOptions = {
3
+ temperature?: number;
4
+ maxTokens?: number;
5
+ topK?: number;
6
+ };
7
+ export type SessionConfig = {
8
+ instructions?: string;
9
+ options?: GenerationOptions;
10
+ };
11
+ export type TokenEvent = {
12
+ token: string;
13
+ accumulated: string;
14
+ };
15
+ export type StreamCompleteEvent = {
16
+ text: string;
17
+ };
18
+ export type StreamErrorEvent = {
19
+ error: string;
20
+ };
21
+ export type DownloadProgress = {
22
+ progress: number;
23
+ };
24
+ export type LLMSessionEvents = {
25
+ token: (event: TokenEvent) => void;
26
+ streamComplete: (event: StreamCompleteEvent) => void;
27
+ streamError: (event: StreamErrorEvent) => void;
28
+ };
29
+ export type ExpoLocalLlmModuleEvents = {
30
+ downloadProgress: (event: DownloadProgress) => void;
31
+ availabilityChange: (event: {
32
+ availability: ModelAvailability;
33
+ }) => void;
34
+ };
35
+ //# sourceMappingURL=ExpoLocalLlm.types.d.ts.map
@@ -0,0 +1 @@
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;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,EAAE,iBAAiB,CAAC;CAC7B,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,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,cAAc,EAAE,CAAC,KAAK,EAAE,mBAAmB,KAAK,IAAI,CAAC;IACrD,WAAW,EAAE,CAAC,KAAK,EAAE,gBAAgB,KAAK,IAAI,CAAC;CAChD,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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=ExpoLocalLlm.types.js.map
@@ -0,0 +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\nexport type SessionConfig = {\n instructions?: string;\n options?: GenerationOptions;\n};\n\nexport type TokenEvent = {\n token: string;\n accumulated: string;\n};\n\nexport type StreamCompleteEvent = {\n text: string;\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 streamComplete: (event: StreamCompleteEvent) => void;\n streamError: (event: StreamErrorEvent) => void;\n};\n\nexport type ExpoLocalLlmModuleEvents = {\n downloadProgress: (event: DownloadProgress) => void;\n availabilityChange: (event: { availability: ModelAvailability }) => void;\n};\n"]}
@@ -0,0 +1,10 @@
1
+ import { NativeModule } from "expo-modules-core";
2
+ import type { ExpoLocalLlmModuleEvents } from "./ExpoLocalLlm.types";
3
+ declare class ExpoLocalLlmModuleType extends NativeModule<ExpoLocalLlmModuleEvents> {
4
+ getAvailability(): string;
5
+ downloadModel(): Promise<void>;
6
+ LLMSession: any;
7
+ }
8
+ declare let nativeModule: ExpoLocalLlmModuleType | null;
9
+ export default nativeModule;
10
+ //# sourceMappingURL=ExpoLocalLlmModule.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ExpoLocalLlmModule.d.ts","sourceRoot":"","sources":["../src/ExpoLocalLlmModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAuB,MAAM,mBAAmB,CAAC;AAEtE,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,sBAAsB,CAAC;AAErE,OAAO,OAAO,sBAAuB,SAAQ,YAAY,CAAC,wBAAwB,CAAC;IACjF,eAAe,IAAI,MAAM;IACzB,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC;IAC9B,UAAU,EAAE,GAAG,CAAC;CACjB;AAED,QAAA,IAAI,YAAY,EAAE,sBAAsB,GAAG,IAAI,CAAC;AAQhD,eAAe,YAAY,CAAC"}
@@ -0,0 +1,10 @@
1
+ import { requireNativeModule } from "expo-modules-core";
2
+ let nativeModule;
3
+ try {
4
+ nativeModule = requireNativeModule("ExpoLocalLlm");
5
+ }
6
+ catch {
7
+ nativeModule = null;
8
+ }
9
+ export default nativeModule;
10
+ //# sourceMappingURL=ExpoLocalLlmModule.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ExpoLocalLlmModule.js","sourceRoot":"","sources":["../src/ExpoLocalLlmModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAUtE,IAAI,YAA2C,CAAC;AAEhD,IAAI,CAAC;IACH,YAAY,GAAG,mBAAmB,CAAyB,cAAc,CAAC,CAAC;AAC7E,CAAC;AAAC,MAAM,CAAC;IACP,YAAY,GAAG,IAAI,CAAC;AACtB,CAAC;AAED,eAAe,YAAY,CAAC","sourcesContent":["import { NativeModule, requireNativeModule } from \"expo-modules-core\";\n\nimport type { ExpoLocalLlmModuleEvents } from \"./ExpoLocalLlm.types\";\n\ndeclare class ExpoLocalLlmModuleType extends NativeModule<ExpoLocalLlmModuleEvents> {\n getAvailability(): string;\n downloadModel(): Promise<void>;\n LLMSession: any;\n}\n\nlet nativeModule: ExpoLocalLlmModuleType | null;\n\ntry {\n nativeModule = requireNativeModule<ExpoLocalLlmModuleType>(\"ExpoLocalLlm\");\n} catch {\n nativeModule = null;\n}\n\nexport default nativeModule;\n"]}
@@ -0,0 +1,9 @@
1
+ import { SharedObject } from "expo-modules-core";
2
+ import type { SessionConfig, LLMSessionEvents } from "./ExpoLocalLlm.types";
3
+ export declare class LLMSession extends SharedObject<LLMSessionEvents> {
4
+ respond: (prompt: string) => Promise<string>;
5
+ streamResponse: (prompt: string) => Promise<void>;
6
+ cancelStream: () => Promise<void>;
7
+ }
8
+ export declare function createLLMSession(config?: SessionConfig): LLMSession;
9
+ //# sourceMappingURL=LLMSession.d.ts.map
@@ -0,0 +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,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAG5E,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;CACpC;AAED,wBAAgB,gBAAgB,CAAC,MAAM,GAAE,aAAkB,GAAG,UAAU,CAKvE"}
@@ -0,0 +1,16 @@
1
+ import { SharedObject, UnavailabilityError } from "expo-modules-core";
2
+ import ExpoLocalLlmModule from "./ExpoLocalLlmModule";
3
+ export class LLMSession extends SharedObject {
4
+ // Methods are implemented natively via the Class() DSL.
5
+ // TypeScript declarations provide type safety only.
6
+ respond;
7
+ streamResponse;
8
+ cancelStream;
9
+ }
10
+ export function createLLMSession(config = {}) {
11
+ if (!ExpoLocalLlmModule) {
12
+ throw new UnavailabilityError("ExpoLocalLlm", "createLLMSession");
13
+ }
14
+ return new ExpoLocalLlmModule.LLMSession(config);
15
+ }
16
+ //# sourceMappingURL=LLMSession.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LLMSession.js","sourceRoot":"","sources":["../src/LLMSession.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAGtE,OAAO,kBAAkB,MAAM,sBAAsB,CAAC;AAEtD,MAAM,OAAO,UAAW,SAAQ,YAA8B;IAC5D,wDAAwD;IACxD,oDAAoD;IACpD,OAAO,CAAuC;IAC9C,cAAc,CAAqC;IACnD,YAAY,CAAuB;CACpC;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;IACD,OAAO,IAAI,kBAAkB,CAAC,UAAU,CAAC,MAAM,CAAe,CAAC;AACjE,CAAC","sourcesContent":["import { SharedObject, UnavailabilityError } from \"expo-modules-core\";\n\nimport type { SessionConfig, LLMSessionEvents } from \"./ExpoLocalLlm.types\";\nimport ExpoLocalLlmModule from \"./ExpoLocalLlmModule\";\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}\n\nexport function createLLMSession(config: SessionConfig = {}): LLMSession {\n if (!ExpoLocalLlmModule) {\n throw new UnavailabilityError(\"ExpoLocalLlm\", \"createLLMSession\");\n }\n return new ExpoLocalLlmModule.LLMSession(config) as LLMSession;\n}\n"]}
@@ -0,0 +1,5 @@
1
+ export { useLocalLLM } from "./useLocalLLM";
2
+ export { LLMSession, createLLMSession } from "./LLMSession";
3
+ export { default as ExpoLocalLlmModule } from "./ExpoLocalLlmModule";
4
+ export * from "./ExpoLocalLlm.types";
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +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,cAAc,sBAAsB,CAAC"}
package/build/index.js ADDED
@@ -0,0 +1,5 @@
1
+ export { useLocalLLM } from "./useLocalLLM";
2
+ export { LLMSession, createLLMSession } from "./LLMSession";
3
+ export { default as ExpoLocalLlmModule } from "./ExpoLocalLlmModule";
4
+ export * from "./ExpoLocalLlm.types";
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +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,cAAc,sBAAsB,CAAC","sourcesContent":["export { useLocalLLM } from \"./useLocalLLM\";\nexport { LLMSession, createLLMSession } from \"./LLMSession\";\nexport { default as ExpoLocalLlmModule } from \"./ExpoLocalLlmModule\";\nexport * from \"./ExpoLocalLlm.types\";\n"]}
@@ -0,0 +1,18 @@
1
+ import type { ModelAvailability, SessionConfig } from "./ExpoLocalLlm.types";
2
+ import { LLMSession } from "./LLMSession";
3
+ type UseLocalLLMOptions = SessionConfig;
4
+ type UseLocalLLMResult = {
5
+ availability: ModelAvailability;
6
+ session: LLMSession | null;
7
+ isGenerating: boolean;
8
+ streamedText: string;
9
+ downloadProgress: number | null;
10
+ error: string | null;
11
+ respond?: (prompt: string) => Promise<string>;
12
+ streamResponse?: (prompt: string) => Promise<void>;
13
+ cancelStream?: () => Promise<void>;
14
+ downloadModel?: () => Promise<void>;
15
+ };
16
+ export declare function useLocalLLM(options?: UseLocalLLMOptions): UseLocalLLMResult;
17
+ export {};
18
+ //# sourceMappingURL=useLocalLLM.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useLocalLLM.d.ts","sourceRoot":"","sources":["../src/useLocalLLM.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,iBAAiB,EACjB,aAAa,EAKd,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,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,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,CAiJnB"}