react-native-litert-lm 0.3.7 → 0.4.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 (86) hide show
  1. package/README.md +153 -135
  2. package/android/build.gradle +12 -0
  3. package/android/src/main/AndroidManifest.xml +5 -0
  4. package/android/src/main/java/com/margelo/nitro/dev/litert/litertlm/HybridLiteRTLM.kt +159 -62
  5. package/android/src/main/java/dev/litert/litertlm/LiteRTLMPackage.kt +19 -2
  6. package/android/src/test/java/com/margelo/nitro/core/Promise.kt +46 -0
  7. package/android/src/test/java/com/margelo/nitro/dev/litert/litertlm/HybridLiteRTLMTest.kt +83 -0
  8. package/ios/HybridLiteRTLM.swift +1058 -0
  9. package/ios/Tests/HybridLiteRTLMTests.swift +67 -0
  10. package/lib/__mocks__/react-native-nitro-modules.d.ts +61 -0
  11. package/lib/__mocks__/react-native-nitro-modules.js +50 -0
  12. package/lib/__tests__/hooks.test.d.ts +1 -0
  13. package/lib/__tests__/hooks.test.js +124 -0
  14. package/lib/__tests__/memoryTracker.test.d.ts +1 -0
  15. package/lib/__tests__/memoryTracker.test.js +74 -0
  16. package/lib/__tests__/modelFactory.test.d.ts +1 -0
  17. package/lib/__tests__/modelFactory.test.js +52 -0
  18. package/lib/hooks.js +1 -1
  19. package/lib/index.d.ts +0 -2
  20. package/lib/index.js +1 -5
  21. package/lib/modelFactory.js +62 -63
  22. package/lib/specs/LiteRTLM.nitro.d.ts +71 -2
  23. package/nitrogen/generated/android/c++/JHybridLiteRTLMSpec.cpp +62 -7
  24. package/nitrogen/generated/android/c++/JHybridLiteRTLMSpec.hpp +3 -1
  25. package/nitrogen/generated/android/c++/JLLMConfig.hpp +40 -3
  26. package/nitrogen/generated/android/c++/JMultimodalPart.hpp +74 -0
  27. package/nitrogen/generated/android/c++/JPartType.hpp +61 -0
  28. package/nitrogen/generated/android/c++/JToolDefinition.hpp +65 -0
  29. package/nitrogen/generated/android/kotlin/com/margelo/nitro/dev/litert/litertlm/GenerationStats.kt +23 -0
  30. package/nitrogen/generated/android/kotlin/com/margelo/nitro/dev/litert/litertlm/HybridLiteRTLMSpec.kt +10 -2
  31. package/nitrogen/generated/android/kotlin/com/margelo/nitro/dev/litert/litertlm/LLMConfig.kt +46 -3
  32. package/nitrogen/generated/android/kotlin/com/margelo/nitro/dev/litert/litertlm/MemoryUsage.kt +19 -0
  33. package/nitrogen/generated/android/kotlin/com/margelo/nitro/dev/litert/litertlm/Message.kt +15 -0
  34. package/nitrogen/generated/android/kotlin/com/margelo/nitro/dev/litert/litertlm/MultimodalPart.kt +66 -0
  35. package/nitrogen/generated/android/kotlin/com/margelo/nitro/dev/litert/litertlm/PartType.kt +24 -0
  36. package/nitrogen/generated/android/kotlin/com/margelo/nitro/dev/litert/litertlm/ToolDefinition.kt +61 -0
  37. package/nitrogen/generated/ios/LiteRTLM-Swift-Cxx-Bridge.cpp +57 -1
  38. package/nitrogen/generated/ios/LiteRTLM-Swift-Cxx-Bridge.hpp +414 -3
  39. package/nitrogen/generated/ios/LiteRTLM-Swift-Cxx-Umbrella.hpp +41 -3
  40. package/nitrogen/generated/ios/LiteRTLMAutolinking.mm +4 -6
  41. package/nitrogen/generated/ios/LiteRTLMAutolinking.swift +10 -0
  42. package/nitrogen/generated/ios/c++/HybridLiteRTLMSpecSwift.cpp +11 -0
  43. package/nitrogen/generated/ios/c++/HybridLiteRTLMSpecSwift.hpp +224 -0
  44. package/nitrogen/generated/ios/swift/Backend.swift +44 -0
  45. package/nitrogen/generated/ios/swift/Func_void.swift +46 -0
  46. package/nitrogen/generated/ios/swift/Func_void_double.swift +46 -0
  47. package/nitrogen/generated/ios/swift/Func_void_std__exception_ptr.swift +46 -0
  48. package/nitrogen/generated/ios/swift/Func_void_std__string.swift +46 -0
  49. package/nitrogen/generated/ios/swift/Func_void_std__string_bool.swift +46 -0
  50. package/nitrogen/generated/ios/swift/GenerationStats.swift +54 -0
  51. package/nitrogen/generated/ios/swift/HybridLiteRTLMSpec.swift +69 -0
  52. package/nitrogen/generated/ios/swift/HybridLiteRTLMSpec_cxx.swift +383 -0
  53. package/nitrogen/generated/ios/swift/LLMConfig.swift +203 -0
  54. package/nitrogen/generated/ios/swift/MemoryUsage.swift +44 -0
  55. package/nitrogen/generated/ios/swift/Message.swift +34 -0
  56. package/nitrogen/generated/ios/swift/MultimodalPart.swift +83 -0
  57. package/nitrogen/generated/ios/swift/PartType.swift +44 -0
  58. package/nitrogen/generated/ios/swift/Role.swift +44 -0
  59. package/nitrogen/generated/ios/swift/ToolDefinition.swift +39 -0
  60. package/nitrogen/generated/shared/c++/HybridLiteRTLMSpec.cpp +2 -0
  61. package/nitrogen/generated/shared/c++/HybridLiteRTLMSpec.hpp +7 -2
  62. package/nitrogen/generated/shared/c++/LLMConfig.hpp +22 -2
  63. package/nitrogen/generated/shared/c++/MultimodalPart.hpp +99 -0
  64. package/nitrogen/generated/shared/c++/PartType.hpp +80 -0
  65. package/nitrogen/generated/shared/c++/ToolDefinition.hpp +91 -0
  66. package/package.json +16 -8
  67. package/react-native-litert-lm.podspec +15 -19
  68. package/scripts/download-ios-frameworks.sh +14 -48
  69. package/scripts/postinstall.js +1 -2
  70. package/src/__mocks__/react-native-nitro-modules.ts +48 -0
  71. package/src/__tests__/hooks.test.ts +153 -0
  72. package/src/__tests__/memoryTracker.test.ts +87 -0
  73. package/src/__tests__/modelFactory.test.ts +68 -0
  74. package/src/hooks.ts +1 -1
  75. package/src/index.ts +0 -7
  76. package/src/modelFactory.ts +82 -80
  77. package/src/specs/LiteRTLM.nitro.ts +80 -2
  78. package/cpp/HybridLiteRTLM.cpp +0 -939
  79. package/cpp/HybridLiteRTLM.hpp +0 -169
  80. package/cpp/IOSDownloadHelper.h +0 -24
  81. package/ios/IOSDownloadHelper.mm +0 -129
  82. package/scripts/build-ios-engine.sh +0 -302
  83. package/scripts/stubs/cxx_bridge_stubs.cc +0 -224
  84. package/scripts/stubs/gemma_model_constraint_provider.cc +0 -46
  85. package/scripts/stubs/llguidance_stubs.c +0 -101
  86. package/src/templates.ts +0 -105
@@ -54,92 +54,94 @@ export function createLLM(options?: {
54
54
  }
55
55
  };
56
56
 
57
- return {
58
- ...native,
59
- memoryTracker: tracker,
60
- loadModel: async (
61
- pathOrUrl: string,
62
- config?: LLMConfig,
63
- onDownloadProgress?: (progress: number) => void,
64
- ) => {
65
- let modelPath = pathOrUrl;
66
-
67
- // Check if it's a URL — enforce HTTPS for model downloads
68
- if (pathOrUrl.startsWith("http://") || pathOrUrl.startsWith("https://")) {
69
- if (pathOrUrl.startsWith("http://")) {
70
- throw new Error(
71
- "Insecure HTTP URLs are not allowed for model downloads. " +
72
- "Use HTTPS instead: " +
73
- pathOrUrl.replace("http://", "https://"),
74
- );
75
- }
76
-
77
- // Extract filename from URL
78
- const fileName = pathOrUrl.split("/").pop();
79
- if (!fileName) {
80
- throw new Error(`Invalid model URL: ${pathOrUrl}`);
81
- }
82
-
83
- console.log(`Checking model at ${pathOrUrl}...`);
84
- modelPath = await native.downloadModel(
85
- pathOrUrl,
86
- fileName,
87
- (progress) => {
88
- onDownloadProgress?.(progress);
89
- },
57
+ const augmentedLoadModel = async (
58
+ pathOrUrl: string,
59
+ config?: LLMConfig,
60
+ onDownloadProgress?: (progress: number) => void,
61
+ ) => {
62
+ let modelPath = pathOrUrl;
63
+
64
+ // Check if it's a URL — enforce HTTPS for model downloads
65
+ if (pathOrUrl.startsWith("http://") || pathOrUrl.startsWith("https://")) {
66
+ if (pathOrUrl.startsWith("http://")) {
67
+ throw new Error(
68
+ "Insecure HTTP URLs are not allowed for model downloads. " +
69
+ "Use HTTPS instead: " +
70
+ pathOrUrl.replace("http://", "https://"),
90
71
  );
91
- console.log(`Model downloaded to: ${modelPath}`);
92
72
  }
93
73
 
94
- const result = await native.loadModel(modelPath, config);
95
-
96
- // Record initial memory snapshot after model load
97
- if (tracker) {
98
- tracker.reset();
99
- recordMemorySnapshot();
74
+ // Extract filename from URL, stripping query parameters
75
+ const urlWithoutQuery = pathOrUrl.split("?")[0];
76
+ const fileName = urlWithoutQuery.split("/").pop();
77
+ if (!fileName) {
78
+ throw new Error(`Invalid model URL: ${pathOrUrl}`);
100
79
  }
101
80
 
102
- return result;
103
- },
104
- sendMessage: async (...args: Parameters<typeof native.sendMessage>) => {
105
- const result = await native.sendMessage(...args);
81
+ console.log(`Checking model at ${pathOrUrl}...`);
82
+ modelPath = await native.downloadModel(
83
+ pathOrUrl,
84
+ fileName,
85
+ (progress) => {
86
+ onDownloadProgress?.(progress);
87
+ },
88
+ );
89
+ console.log(`Model downloaded to: ${modelPath}`);
90
+ }
91
+
92
+ const result = await native.loadModel(modelPath, config);
93
+
94
+ // Record initial memory snapshot after model load
95
+ if (tracker) {
96
+ tracker.reset();
106
97
  recordMemorySnapshot();
107
- return result;
108
- },
109
- sendMessageAsync: (...args: Parameters<typeof native.sendMessageAsync>) => {
110
- const [message, onToken] = args;
111
- native.sendMessageAsync(message, (token, done) => {
112
- onToken(token, done);
113
- if (done) {
98
+ }
99
+
100
+ return result;
101
+ };
102
+
103
+ const SNAPSHOT_TRIGGERS = new Set([
104
+ "sendMessage",
105
+ "sendMessageWithImage",
106
+ "sendMessageWithAudio",
107
+ "resetConversation",
108
+ ]);
109
+
110
+ return new Proxy(native, {
111
+ get(target, prop, receiver) {
112
+ if (prop === "memoryTracker") {
113
+ return tracker;
114
+ }
115
+ if (prop === "loadModel") {
116
+ return augmentedLoadModel;
117
+ }
118
+
119
+ const original = Reflect.get(target, prop, receiver);
120
+ if (typeof original !== "function") {
121
+ return original;
122
+ }
123
+
124
+ if (prop === "sendMessageAsync") {
125
+ return (message: string, onToken: (token: string, done: boolean) => void) => {
126
+ return original.call(target, message, (token: string, done: boolean) => {
127
+ onToken(token, done);
128
+ if (done) {
129
+ recordMemorySnapshot();
130
+ }
131
+ });
132
+ };
133
+ }
134
+
135
+ if (SNAPSHOT_TRIGGERS.has(prop as string)) {
136
+ return async (...args: any[]) => {
137
+ const result = await original.apply(target, args);
114
138
  recordMemorySnapshot();
115
- }
116
- });
117
- },
118
- sendMessageWithImage: async (
119
- ...args: Parameters<typeof native.sendMessageWithImage>
120
- ) => {
121
- const result = await native.sendMessageWithImage(...args);
122
- recordMemorySnapshot();
123
- return result;
124
- },
125
- sendMessageWithAudio: async (
126
- ...args: Parameters<typeof native.sendMessageWithAudio>
127
- ) => {
128
- const result = await native.sendMessageWithAudio(...args);
129
- recordMemorySnapshot();
130
- return result;
131
- },
132
- getHistory: native.getHistory.bind(native),
133
- resetConversation: () => {
134
- native.resetConversation();
135
- // KV cache is cleared on reset, record the drop
136
- recordMemorySnapshot();
139
+ return result;
140
+ };
141
+ }
142
+
143
+ return original.bind(target);
137
144
  },
138
- isReady: native.isReady.bind(native),
139
- getStats: native.getStats.bind(native),
140
- getMemoryUsage: native.getMemoryUsage.bind(native),
141
- close: native.close.bind(native),
142
- downloadModel: native.downloadModel.bind(native),
143
- deleteModel: native.deleteModel.bind(native),
144
- };
145
+ }) as unknown as LiteRTLMInstance;
145
146
  }
147
+
@@ -17,6 +17,37 @@ export type Backend = "cpu" | "gpu" | "npu";
17
17
  */
18
18
  export type Role = "user" | "model" | "system";
19
19
 
20
+ /**
21
+ * Definition for a function/tool that the model can request to execute.
22
+ */
23
+ export interface ToolDefinition {
24
+ /** Name of the function/tool */
25
+ name: string;
26
+ /** Human-readable description of what the function/tool does */
27
+ description: string;
28
+ /** JSON schema defining parameter names and types (stringified) */
29
+ parametersJson: string;
30
+ }
31
+
32
+ /**
33
+ * The part type for a multimodal message content part.
34
+ */
35
+ export type PartType = "text" | "image" | "audio";
36
+
37
+ /**
38
+ * A part of a unified multimodal message payload.
39
+ */
40
+ export interface MultimodalPart {
41
+ /** The part type: 'text', 'image', or 'audio' */
42
+ type: PartType;
43
+ /** The plain text content, if type is 'text' */
44
+ text?: string;
45
+ /** Raw image binary data, if type is 'image' (zero-copy ArrayBuffer mapping) */
46
+ imageBuffer?: ArrayBuffer;
47
+ /** Raw audio binary data, if type is 'audio' (zero-copy ArrayBuffer mapping) */
48
+ audioBuffer?: ArrayBuffer;
49
+ }
50
+
20
51
  /**
21
52
  * Configuration options for loading an LLM.
22
53
  */
@@ -68,6 +99,41 @@ export interface LLMConfig {
68
99
  * @default 0.95
69
100
  */
70
101
  topP?: number;
102
+
103
+ /**
104
+ * Whether to run engine validation after loading the model.
105
+ * When enabled, sends a quick test inference ("Hi") and waits up to 30s
106
+ * for a response to confirm the backend works. This is useful for GPU/NPU
107
+ * backends that may silently fail during inference (they can initialize
108
+ * without error but produce no tokens).
109
+ *
110
+ * Validation is **always a no-op on CPU** — the CPU backend is inherently
111
+ * reliable and never needs validation.
112
+ *
113
+ * Disabled by default because it adds significant latency (5-30s) to model loading.
114
+ * Enable only to catch GPU/NPU silent failure issues during development.
115
+ *
116
+ * @default false
117
+ */
118
+ validate?: boolean;
119
+
120
+ /**
121
+ * Whether this is a multimodal model.
122
+ * When enabled, the engine handles image/audio tokens properly.
123
+ * If not specified, the system will fall back to filename sniffing.
124
+ */
125
+ multimodal?: boolean;
126
+
127
+ /**
128
+ * List of tools/functions that the model can call.
129
+ */
130
+ tools?: ToolDefinition[];
131
+
132
+ /**
133
+ * Whether to enable speculative decoding (multi-token prediction) if supported by the model.
134
+ * @default false
135
+ */
136
+ enableSpeculativeDecoding?: boolean;
71
137
  }
72
138
 
73
139
  /**
@@ -135,7 +201,7 @@ export interface MemoryUsage {
135
201
  * ```
136
202
  */
137
203
  export interface LiteRTLM extends HybridObject<{
138
- ios: "c++";
204
+ ios: "swift";
139
205
  android: "kotlin";
140
206
  }> {
141
207
  /**
@@ -187,6 +253,13 @@ export interface LiteRTLM extends HybridObject<{
187
253
  */
188
254
  sendMessageWithAudio(message: string, audioPath: string): Promise<string>;
189
255
 
256
+ /**
257
+ * Send a unified multimodal message containing text and/or zero-copy binary buffers.
258
+ * @param parts The message content parts (text, image, and/or audio).
259
+ * @returns The model's response text.
260
+ */
261
+ sendMultimodalMessage(parts: MultimodalPart[]): Promise<string>;
262
+
190
263
  /**
191
264
  * Send a message with streaming response.
192
265
  * Tokens are delivered via callback as they are generated.
@@ -196,7 +269,7 @@ export interface LiteRTLM extends HybridObject<{
196
269
  sendMessageAsync(
197
270
  message: string,
198
271
  onToken: (token: string, done: boolean) => void,
199
- ): void;
272
+ ): Promise<void>;
200
273
 
201
274
  /**
202
275
  * Get the current conversation history.
@@ -219,6 +292,11 @@ export interface LiteRTLM extends HybridObject<{
219
292
  */
220
293
  getStats(): GenerationStats;
221
294
 
295
+ /**
296
+ * Count tokens in a text string. Returns -1 if unavailable.
297
+ */
298
+ countTokens(text: string): number;
299
+
222
300
  /**
223
301
  * Get real memory usage from the native runtime.
224
302
  * Uses OS-level APIs to report actual memory consumption.