react-native-litert-lm 0.4.0 → 0.4.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.
Files changed (35) hide show
  1. package/android/src/main/AndroidManifest.xml +3 -0
  2. package/android/src/main/java/com/margelo/nitro/dev/litert/litertlm/HybridLiteRTLM.kt +117 -0
  3. package/android/src/test/java/com/margelo/nitro/dev/litert/litertlm/HybridLiteRTLMTest.kt +22 -0
  4. package/ios/HybridLiteRTLM.swift +330 -35
  5. package/ios/Tests/HybridLiteRTLMTests.swift +58 -0
  6. package/lib/__mocks__/react-native-nitro-modules.d.ts +4 -0
  7. package/lib/__mocks__/react-native-nitro-modules.js +10 -0
  8. package/lib/__tests__/modelFactory.test.js +16 -0
  9. package/lib/hooks.js +27 -3
  10. package/lib/index.d.ts +6 -0
  11. package/lib/index.js +7 -3
  12. package/lib/modelFactory.js +20 -0
  13. package/lib/specs/LiteRTLM.nitro.d.ts +16 -0
  14. package/nitrogen/generated/android/LiteRTLMOnLoad.cpp +2 -2
  15. package/nitrogen/generated/android/c++/JHybridLiteRTLMSpec.cpp +32 -2
  16. package/nitrogen/generated/android/c++/JHybridLiteRTLMSpec.hpp +2 -0
  17. package/nitrogen/generated/android/kotlin/com/margelo/nitro/dev/litert/litertlm/HybridLiteRTLMSpec.kt +18 -0
  18. package/nitrogen/generated/ios/LiteRTLM-Swift-Cxx-Bridge.cpp +8 -8
  19. package/nitrogen/generated/ios/LiteRTLM-Swift-Cxx-Bridge.hpp +22 -22
  20. package/nitrogen/generated/ios/c++/HybridLiteRTLMSpecSwift.hpp +16 -0
  21. package/nitrogen/generated/ios/swift/HybridLiteRTLMSpec.swift +2 -0
  22. package/nitrogen/generated/ios/swift/HybridLiteRTLMSpec_cxx.swift +48 -0
  23. package/nitrogen/generated/shared/c++/HybridLiteRTLMSpec.cpp +2 -0
  24. package/nitrogen/generated/shared/c++/HybridLiteRTLMSpec.hpp +2 -0
  25. package/package.json +7 -4
  26. package/react-native-litert-lm.podspec +4 -2
  27. package/scripts/download-ios-frameworks.sh +4 -3
  28. package/scripts/framework-source.js +46 -0
  29. package/scripts/postinstall.js +39 -16
  30. package/src/__mocks__/react-native-nitro-modules.ts +10 -0
  31. package/src/__tests__/modelFactory.test.ts +28 -0
  32. package/src/hooks.ts +29 -7
  33. package/src/index.ts +7 -3
  34. package/src/modelFactory.ts +22 -0
  35. package/src/specs/LiteRTLM.nitro.ts +26 -0
@@ -18,6 +18,16 @@ exports.mockLiteRTLM = {
18
18
  onToken("token", true);
19
19
  return Promise.resolve();
20
20
  }),
21
+ sendMessageWithImageAsync: jest.fn((msg, imagePath, onToken) => {
22
+ onToken("Mock vision ", false);
23
+ onToken("token", true);
24
+ return Promise.resolve();
25
+ }),
26
+ sendMessageWithAudioAsync: jest.fn((msg, audioPath, onToken) => {
27
+ onToken("Mock audio ", false);
28
+ onToken("token", true);
29
+ return Promise.resolve();
30
+ }),
21
31
  getHistory: jest.fn(() => []),
22
32
  resetConversation: jest.fn(),
23
33
  getStats: jest.fn(() => ({
@@ -41,6 +41,22 @@ describe('modelFactory Security & Proxy Unit Tests', () => {
41
41
  expect(react_native_nitro_modules_1.mockLiteRTLM.sendMessageAsync).toHaveBeenCalled();
42
42
  expect(react_native_nitro_modules_1.mockLiteRTLM.getMemoryUsage).toHaveBeenCalled();
43
43
  });
44
+ it('should successfully proxy sendMessageWithImageAsync and record memory metrics when done', async () => {
45
+ const onToken = jest.fn();
46
+ await llm.sendMessageWithImageAsync("Vision prompt", "/path/to/image.jpg", onToken);
47
+ expect(onToken).toHaveBeenCalledWith("Mock vision ", false);
48
+ expect(onToken).toHaveBeenCalledWith("token", true);
49
+ expect(react_native_nitro_modules_1.mockLiteRTLM.sendMessageWithImageAsync).toHaveBeenCalledWith("Vision prompt", "/path/to/image.jpg", expect.any(Function));
50
+ expect(react_native_nitro_modules_1.mockLiteRTLM.getMemoryUsage).toHaveBeenCalled();
51
+ });
52
+ it('should successfully proxy sendMessageWithAudioAsync and record memory metrics when done', async () => {
53
+ const onToken = jest.fn();
54
+ await llm.sendMessageWithAudioAsync("Audio prompt", "/path/to/audio.wav", onToken);
55
+ expect(onToken).toHaveBeenCalledWith("Mock audio ", false);
56
+ expect(onToken).toHaveBeenCalledWith("token", true);
57
+ expect(react_native_nitro_modules_1.mockLiteRTLM.sendMessageWithAudioAsync).toHaveBeenCalledWith("Audio prompt", "/path/to/audio.wav", expect.any(Function));
58
+ expect(react_native_nitro_modules_1.mockLiteRTLM.getMemoryUsage).toHaveBeenCalled();
59
+ });
44
60
  it('should successfully access memoryTracker and getSnapshots when memory tracking is enabled', () => {
45
61
  expect(llm.memoryTracker).toBeDefined();
46
62
  expect(llm.memoryTracker?.getCapacity()).toBe(256);
package/lib/hooks.js CHANGED
@@ -28,6 +28,11 @@ function useModel(pathOrUrl, config) {
28
28
  const temperature = config?.temperature;
29
29
  const topK = config?.topK;
30
30
  const topP = config?.topP;
31
+ const validate = config?.validate;
32
+ const multimodal = config?.multimodal;
33
+ const tools = config?.tools;
34
+ const enableSpeculativeDecoding = config?.enableSpeculativeDecoding;
35
+ const toolsKey = tools ? JSON.stringify(tools) : undefined;
31
36
  // Build a stable config object from the destructured primitives
32
37
  const nativeConfig = (0, react_1.useMemo)(() => ({
33
38
  ...(backend !== undefined && { backend }),
@@ -36,7 +41,24 @@ function useModel(pathOrUrl, config) {
36
41
  ...(temperature !== undefined && { temperature }),
37
42
  ...(topK !== undefined && { topK }),
38
43
  ...(topP !== undefined && { topP }),
39
- }), [backend, systemPrompt, maxTokens, temperature, topK, topP]);
44
+ ...(validate !== undefined && { validate }),
45
+ ...(multimodal !== undefined && { multimodal }),
46
+ ...(tools !== undefined && { tools }),
47
+ ...(enableSpeculativeDecoding !== undefined && {
48
+ enableSpeculativeDecoding,
49
+ }),
50
+ }), [
51
+ backend,
52
+ systemPrompt,
53
+ maxTokens,
54
+ temperature,
55
+ topK,
56
+ topP,
57
+ validate,
58
+ multimodal,
59
+ toolsKey,
60
+ enableSpeculativeDecoding,
61
+ ]);
40
62
  /**
41
63
  * Refresh memory summary from the tracker's native buffer.
42
64
  */
@@ -99,13 +121,15 @@ function useModel(pathOrUrl, config) {
99
121
  return new Promise((resolve, reject) => {
100
122
  let fullResponse = "";
101
123
  try {
102
- modelRef.current?.sendMessageAsync(prompt, (token, done) => {
124
+ modelRef.current
125
+ ?.sendMessageAsync(prompt, (token, done) => {
103
126
  fullResponse += token;
104
127
  if (done) {
105
128
  refreshMemorySummary();
106
129
  resolve(fullResponse);
107
130
  }
108
- }).catch(reject);
131
+ })
132
+ .catch(reject);
109
133
  }
110
134
  catch (e) {
111
135
  reject(e);
package/lib/index.d.ts CHANGED
@@ -93,6 +93,12 @@ export declare function checkBackendSupport(backend: Backend): string | undefine
93
93
  * Check if multimodal features (image/audio) are supported on the current platform.
94
94
  * Returns an error message if not supported, undefined if OK.
95
95
  *
96
+ * Both iOS (v0.12.0 CLiteRTLM xcframework) and Android (LiteRT-LM SDK) ship the
97
+ * vision/audio executor ops, so there is no platform-level block. Whether a
98
+ * given call succeeds depends on the **loaded model**: only multimodal models
99
+ * (e.g. Gemma 3n) bundle the vision/audio executors. Pass `multimodal: true` to
100
+ * `loadModel` for such models, or rely on filename sniffing ("3n"/"gemma3").
101
+ *
96
102
  * @returns Error message if multimodal is not supported, undefined if OK
97
103
  *
98
104
  * @example
package/lib/index.js CHANGED
@@ -135,6 +135,12 @@ function checkBackendSupport(backend) {
135
135
  * Check if multimodal features (image/audio) are supported on the current platform.
136
136
  * Returns an error message if not supported, undefined if OK.
137
137
  *
138
+ * Both iOS (v0.12.0 CLiteRTLM xcframework) and Android (LiteRT-LM SDK) ship the
139
+ * vision/audio executor ops, so there is no platform-level block. Whether a
140
+ * given call succeeds depends on the **loaded model**: only multimodal models
141
+ * (e.g. Gemma 3n) bundle the vision/audio executors. Pass `multimodal: true` to
142
+ * `loadModel` for such models, or rely on filename sniffing ("3n"/"gemma3").
143
+ *
138
144
  * @returns Error message if multimodal is not supported, undefined if OK
139
145
  *
140
146
  * @example
@@ -149,9 +155,7 @@ function checkBackendSupport(backend) {
149
155
  * ```
150
156
  */
151
157
  function checkMultimodalSupport() {
152
- if (react_native_1.Platform.OS === "ios") {
153
- return "Multimodal (image/audio) is not available on iOS. The XCFramework lacks compiled vision and audio executor ops.";
154
- }
158
+ // Supported on both platforms with a multimodal model loaded.
155
159
  return undefined;
156
160
  }
157
161
  /**
@@ -95,6 +95,26 @@ function createLLM(options) {
95
95
  });
96
96
  };
97
97
  }
98
+ if (prop === "sendMessageWithImageAsync") {
99
+ return (message, imagePath, onToken) => {
100
+ return original.call(target, message, imagePath, (token, done) => {
101
+ onToken(token, done);
102
+ if (done) {
103
+ recordMemorySnapshot();
104
+ }
105
+ });
106
+ };
107
+ }
108
+ if (prop === "sendMessageWithAudioAsync") {
109
+ return (message, audioPath, onToken) => {
110
+ return original.call(target, message, audioPath, (token, done) => {
111
+ onToken(token, done);
112
+ if (done) {
113
+ recordMemorySnapshot();
114
+ }
115
+ });
116
+ };
117
+ }
98
118
  if (SNAPSHOT_TRIGGERS.has(prop)) {
99
119
  return async (...args) => {
100
120
  const result = await original.apply(target, args);
@@ -204,6 +204,14 @@ export interface LiteRTLM extends HybridObject<{
204
204
  * @returns The model's response text.
205
205
  */
206
206
  sendMessageWithImage(message: string, imagePath: string): Promise<string>;
207
+ /**
208
+ * Send a text message with an image and get a streaming response.
209
+ * Tokens are delivered via callback as they are generated.
210
+ * @param message User message text.
211
+ * @param imagePath Absolute path to an image file.
212
+ * @param onToken Callback invoked for each token (token, isDone).
213
+ */
214
+ sendMessageWithImageAsync(message: string, imagePath: string, onToken: (token: string, done: boolean) => void): Promise<void>;
207
215
  /**
208
216
  * Download a model file from a URL.
209
217
  * @param url URL to download from.
@@ -224,6 +232,14 @@ export interface LiteRTLM extends HybridObject<{
224
232
  * @returns The model's response text.
225
233
  */
226
234
  sendMessageWithAudio(message: string, audioPath: string): Promise<string>;
235
+ /**
236
+ * Send a text message with audio and get a streaming response.
237
+ * Tokens are delivered via callback as they are generated.
238
+ * @param message User message text.
239
+ * @param audioPath Absolute path to an audio file (WAV).
240
+ * @param onToken Callback invoked for each token (token, isDone).
241
+ */
242
+ sendMessageWithAudioAsync(message: string, audioPath: string, onToken: (token: string, done: boolean) => void): Promise<void>;
227
243
  /**
228
244
  * Send a unified multimodal message containing text and/or zero-copy binary buffers.
229
245
  * @param parts The message content parts (text, image, and/or audio).
@@ -16,8 +16,8 @@
16
16
  #include <NitroModules/HybridObjectRegistry.hpp>
17
17
 
18
18
  #include "JHybridLiteRTLMSpec.hpp"
19
- #include "JFunc_void_double.hpp"
20
19
  #include "JFunc_void_std__string_bool.hpp"
20
+ #include "JFunc_void_double.hpp"
21
21
  #include <NitroModules/DefaultConstructableObject.hpp>
22
22
 
23
23
  namespace margelo::nitro::litertlm {
@@ -43,8 +43,8 @@ void registerAllNatives() {
43
43
 
44
44
  // Register native JNI methods
45
45
  margelo::nitro::litertlm::JHybridLiteRTLMSpec::CxxPart::registerNatives();
46
- margelo::nitro::litertlm::JFunc_void_double_cxx::registerNatives();
47
46
  margelo::nitro::litertlm::JFunc_void_std__string_bool_cxx::registerNatives();
47
+ margelo::nitro::litertlm::JFunc_void_double_cxx::registerNatives();
48
48
 
49
49
  // Register Nitro Hybrid Objects
50
50
  HybridObjectRegistry::registerHybridObjectConstructor(
@@ -47,15 +47,15 @@ namespace margelo::nitro::litertlm { enum class PartType; }
47
47
  #include "ToolDefinition.hpp"
48
48
  #include "JToolDefinition.hpp"
49
49
  #include <functional>
50
- #include "JFunc_void_double.hpp"
50
+ #include "JFunc_void_std__string_bool.hpp"
51
51
  #include <NitroModules/JNICallable.hpp>
52
+ #include "JFunc_void_double.hpp"
52
53
  #include "MultimodalPart.hpp"
53
54
  #include "JMultimodalPart.hpp"
54
55
  #include "PartType.hpp"
55
56
  #include "JPartType.hpp"
56
57
  #include <NitroModules/ArrayBuffer.hpp>
57
58
  #include <NitroModules/JArrayBuffer.hpp>
58
- #include "JFunc_void_std__string_bool.hpp"
59
59
 
60
60
  namespace margelo::nitro::litertlm {
61
61
 
@@ -137,6 +137,21 @@ namespace margelo::nitro::litertlm {
137
137
  return __promise;
138
138
  }();
139
139
  }
140
+ std::shared_ptr<Promise<void>> JHybridLiteRTLMSpec::sendMessageWithImageAsync(const std::string& message, const std::string& imagePath, const std::function<void(const std::string& /* token */, bool /* done */)>& onToken) {
141
+ static const auto method = _javaPart->javaClassStatic()->getMethod<jni::local_ref<JPromise::javaobject>(jni::alias_ref<jni::JString> /* message */, jni::alias_ref<jni::JString> /* imagePath */, jni::alias_ref<JFunc_void_std__string_bool::javaobject> /* onToken */)>("sendMessageWithImageAsync_cxx");
142
+ auto __result = method(_javaPart, jni::make_jstring(message), jni::make_jstring(imagePath), JFunc_void_std__string_bool_cxx::fromCpp(onToken));
143
+ return [&]() {
144
+ auto __promise = Promise<void>::create();
145
+ __result->cthis()->addOnResolvedListener([=](const jni::alias_ref<jni::JObject>& /* unit */) {
146
+ __promise->resolve();
147
+ });
148
+ __result->cthis()->addOnRejectedListener([=](const jni::alias_ref<jni::JThrowable>& __throwable) {
149
+ jni::JniException __jniError(__throwable);
150
+ __promise->reject(std::make_exception_ptr(__jniError));
151
+ });
152
+ return __promise;
153
+ }();
154
+ }
140
155
  std::shared_ptr<Promise<std::string>> JHybridLiteRTLMSpec::downloadModel(const std::string& url, const std::string& fileName, const std::optional<std::function<void(double /* progress */)>>& onProgress) {
141
156
  static const auto method = _javaPart->javaClassStatic()->getMethod<jni::local_ref<JPromise::javaobject>(jni::alias_ref<jni::JString> /* url */, jni::alias_ref<jni::JString> /* fileName */, jni::alias_ref<JFunc_void_double::javaobject> /* onProgress */)>("downloadModel_cxx");
142
157
  auto __result = method(_javaPart, jni::make_jstring(url), jni::make_jstring(fileName), onProgress.has_value() ? JFunc_void_double_cxx::fromCpp(onProgress.value()) : nullptr);
@@ -184,6 +199,21 @@ namespace margelo::nitro::litertlm {
184
199
  return __promise;
185
200
  }();
186
201
  }
202
+ std::shared_ptr<Promise<void>> JHybridLiteRTLMSpec::sendMessageWithAudioAsync(const std::string& message, const std::string& audioPath, const std::function<void(const std::string& /* token */, bool /* done */)>& onToken) {
203
+ static const auto method = _javaPart->javaClassStatic()->getMethod<jni::local_ref<JPromise::javaobject>(jni::alias_ref<jni::JString> /* message */, jni::alias_ref<jni::JString> /* audioPath */, jni::alias_ref<JFunc_void_std__string_bool::javaobject> /* onToken */)>("sendMessageWithAudioAsync_cxx");
204
+ auto __result = method(_javaPart, jni::make_jstring(message), jni::make_jstring(audioPath), JFunc_void_std__string_bool_cxx::fromCpp(onToken));
205
+ return [&]() {
206
+ auto __promise = Promise<void>::create();
207
+ __result->cthis()->addOnResolvedListener([=](const jni::alias_ref<jni::JObject>& /* unit */) {
208
+ __promise->resolve();
209
+ });
210
+ __result->cthis()->addOnRejectedListener([=](const jni::alias_ref<jni::JThrowable>& __throwable) {
211
+ jni::JniException __jniError(__throwable);
212
+ __promise->reject(std::make_exception_ptr(__jniError));
213
+ });
214
+ return __promise;
215
+ }();
216
+ }
187
217
  std::shared_ptr<Promise<std::string>> JHybridLiteRTLMSpec::sendMultimodalMessage(const std::vector<MultimodalPart>& parts) {
188
218
  static const auto method = _javaPart->javaClassStatic()->getMethod<jni::local_ref<JPromise::javaobject>(jni::alias_ref<jni::JArrayClass<JMultimodalPart>> /* parts */)>("sendMultimodalMessage");
189
219
  auto __result = method(_javaPart, [&](auto&& __input) {
@@ -57,9 +57,11 @@ namespace margelo::nitro::litertlm {
57
57
  std::shared_ptr<Promise<void>> loadModel(const std::string& modelPath, const std::optional<LLMConfig>& config) override;
58
58
  std::shared_ptr<Promise<std::string>> sendMessage(const std::string& message) override;
59
59
  std::shared_ptr<Promise<std::string>> sendMessageWithImage(const std::string& message, const std::string& imagePath) override;
60
+ std::shared_ptr<Promise<void>> sendMessageWithImageAsync(const std::string& message, const std::string& imagePath, const std::function<void(const std::string& /* token */, bool /* done */)>& onToken) override;
60
61
  std::shared_ptr<Promise<std::string>> downloadModel(const std::string& url, const std::string& fileName, const std::optional<std::function<void(double /* progress */)>>& onProgress) override;
61
62
  std::shared_ptr<Promise<void>> deleteModel(const std::string& fileName) override;
62
63
  std::shared_ptr<Promise<std::string>> sendMessageWithAudio(const std::string& message, const std::string& audioPath) override;
64
+ std::shared_ptr<Promise<void>> sendMessageWithAudioAsync(const std::string& message, const std::string& audioPath, const std::function<void(const std::string& /* token */, bool /* done */)>& onToken) override;
63
65
  std::shared_ptr<Promise<std::string>> sendMultimodalMessage(const std::vector<MultimodalPart>& parts) override;
64
66
  std::shared_ptr<Promise<void>> sendMessageAsync(const std::string& message, const std::function<void(const std::string& /* token */, bool /* done */)>& onToken) override;
65
67
  std::vector<Message> getHistory() override;
@@ -41,6 +41,15 @@ abstract class HybridLiteRTLMSpec: HybridObject() {
41
41
  @Keep
42
42
  abstract fun sendMessageWithImage(message: String, imagePath: String): Promise<String>
43
43
 
44
+ abstract fun sendMessageWithImageAsync(message: String, imagePath: String, onToken: (token: String, done: Boolean) -> Unit): Promise<Unit>
45
+
46
+ @DoNotStrip
47
+ @Keep
48
+ private fun sendMessageWithImageAsync_cxx(message: String, imagePath: String, onToken: Func_void_std__string_bool): Promise<Unit> {
49
+ val __result = sendMessageWithImageAsync(message, imagePath, onToken)
50
+ return __result
51
+ }
52
+
44
53
  abstract fun downloadModel(url: String, fileName: String, onProgress: ((progress: Double) -> Unit)?): Promise<String>
45
54
 
46
55
  @DoNotStrip
@@ -58,6 +67,15 @@ abstract class HybridLiteRTLMSpec: HybridObject() {
58
67
  @Keep
59
68
  abstract fun sendMessageWithAudio(message: String, audioPath: String): Promise<String>
60
69
 
70
+ abstract fun sendMessageWithAudioAsync(message: String, audioPath: String, onToken: (token: String, done: Boolean) -> Unit): Promise<Unit>
71
+
72
+ @DoNotStrip
73
+ @Keep
74
+ private fun sendMessageWithAudioAsync_cxx(message: String, audioPath: String, onToken: Func_void_std__string_bool): Promise<Unit> {
75
+ val __result = sendMessageWithAudioAsync(message, audioPath, onToken)
76
+ return __result
77
+ }
78
+
61
79
  @DoNotStrip
62
80
  @Keep
63
81
  abstract fun sendMultimodalMessage(parts: Array<MultimodalPart>): Promise<String>
@@ -38,14 +38,6 @@ namespace margelo::nitro::litertlm::bridge::swift {
38
38
  };
39
39
  }
40
40
 
41
- // pragma MARK: std::function<void(double /* progress */)>
42
- Func_void_double create_Func_void_double(void* NON_NULL swiftClosureWrapper) noexcept {
43
- auto swiftClosure = LiteRTLM::Func_void_double::fromUnsafe(swiftClosureWrapper);
44
- return [swiftClosure = std::move(swiftClosure)](double progress) mutable -> void {
45
- swiftClosure.call(progress);
46
- };
47
- }
48
-
49
41
  // pragma MARK: std::function<void(const std::string& /* token */, bool /* done */)>
50
42
  Func_void_std__string_bool create_Func_void_std__string_bool(void* NON_NULL swiftClosureWrapper) noexcept {
51
43
  auto swiftClosure = LiteRTLM::Func_void_std__string_bool::fromUnsafe(swiftClosureWrapper);
@@ -54,6 +46,14 @@ namespace margelo::nitro::litertlm::bridge::swift {
54
46
  };
55
47
  }
56
48
 
49
+ // pragma MARK: std::function<void(double /* progress */)>
50
+ Func_void_double create_Func_void_double(void* NON_NULL swiftClosureWrapper) noexcept {
51
+ auto swiftClosure = LiteRTLM::Func_void_double::fromUnsafe(swiftClosureWrapper);
52
+ return [swiftClosure = std::move(swiftClosure)](double progress) mutable -> void {
53
+ swiftClosure.call(progress);
54
+ };
55
+ }
56
+
57
57
  // pragma MARK: std::shared_ptr<HybridLiteRTLMSpec>
58
58
  std::shared_ptr<HybridLiteRTLMSpec> create_std__shared_ptr_HybridLiteRTLMSpec_(void* NON_NULL swiftUnsafePointer) noexcept {
59
59
  LiteRTLM::HybridLiteRTLMSpec_cxx swiftPart = LiteRTLM::HybridLiteRTLMSpec_cxx::fromUnsafe(swiftUnsafePointer);
@@ -255,6 +255,28 @@ namespace margelo::nitro::litertlm::bridge::swift {
255
255
  return Func_void_std__string_Wrapper(std::move(value));
256
256
  }
257
257
 
258
+ // pragma MARK: std::function<void(const std::string& /* token */, bool /* done */)>
259
+ /**
260
+ * Specialized version of `std::function<void(const std::string&, bool)>`.
261
+ */
262
+ using Func_void_std__string_bool = std::function<void(const std::string& /* token */, bool /* done */)>;
263
+ /**
264
+ * Wrapper class for a `std::function<void(const std::string& / * token * /, bool / * done * /)>`, this can be used from Swift.
265
+ */
266
+ class Func_void_std__string_bool_Wrapper final {
267
+ public:
268
+ explicit Func_void_std__string_bool_Wrapper(std::function<void(const std::string& /* token */, bool /* done */)>&& func): _function(std::make_unique<std::function<void(const std::string& /* token */, bool /* done */)>>(std::move(func))) {}
269
+ inline void call(std::string token, bool done) const noexcept {
270
+ _function->operator()(token, done);
271
+ }
272
+ private:
273
+ std::unique_ptr<std::function<void(const std::string& /* token */, bool /* done */)>> _function;
274
+ } SWIFT_NONCOPYABLE;
275
+ Func_void_std__string_bool create_Func_void_std__string_bool(void* NON_NULL swiftClosureWrapper) noexcept;
276
+ inline Func_void_std__string_bool_Wrapper wrap_Func_void_std__string_bool(Func_void_std__string_bool value) noexcept {
277
+ return Func_void_std__string_bool_Wrapper(std::move(value));
278
+ }
279
+
258
280
  // pragma MARK: std::function<void(double /* progress */)>
259
281
  /**
260
282
  * Specialized version of `std::function<void(double)>`.
@@ -318,28 +340,6 @@ namespace margelo::nitro::litertlm::bridge::swift {
318
340
  return vector;
319
341
  }
320
342
 
321
- // pragma MARK: std::function<void(const std::string& /* token */, bool /* done */)>
322
- /**
323
- * Specialized version of `std::function<void(const std::string&, bool)>`.
324
- */
325
- using Func_void_std__string_bool = std::function<void(const std::string& /* token */, bool /* done */)>;
326
- /**
327
- * Wrapper class for a `std::function<void(const std::string& / * token * /, bool / * done * /)>`, this can be used from Swift.
328
- */
329
- class Func_void_std__string_bool_Wrapper final {
330
- public:
331
- explicit Func_void_std__string_bool_Wrapper(std::function<void(const std::string& /* token */, bool /* done */)>&& func): _function(std::make_unique<std::function<void(const std::string& /* token */, bool /* done */)>>(std::move(func))) {}
332
- inline void call(std::string token, bool done) const noexcept {
333
- _function->operator()(token, done);
334
- }
335
- private:
336
- std::unique_ptr<std::function<void(const std::string& /* token */, bool /* done */)>> _function;
337
- } SWIFT_NONCOPYABLE;
338
- Func_void_std__string_bool create_Func_void_std__string_bool(void* NON_NULL swiftClosureWrapper) noexcept;
339
- inline Func_void_std__string_bool_Wrapper wrap_Func_void_std__string_bool(Func_void_std__string_bool value) noexcept {
340
- return Func_void_std__string_bool_Wrapper(std::move(value));
341
- }
342
-
343
343
  // pragma MARK: std::vector<Message>
344
344
  /**
345
345
  * Specialized version of `std::vector<Message>`.
@@ -124,6 +124,14 @@ namespace margelo::nitro::litertlm {
124
124
  auto __value = std::move(__result.value());
125
125
  return __value;
126
126
  }
127
+ inline std::shared_ptr<Promise<void>> sendMessageWithImageAsync(const std::string& message, const std::string& imagePath, const std::function<void(const std::string& /* token */, bool /* done */)>& onToken) override {
128
+ auto __result = _swiftPart.sendMessageWithImageAsync(message, imagePath, onToken);
129
+ if (__result.hasError()) [[unlikely]] {
130
+ std::rethrow_exception(__result.error());
131
+ }
132
+ auto __value = std::move(__result.value());
133
+ return __value;
134
+ }
127
135
  inline std::shared_ptr<Promise<std::string>> downloadModel(const std::string& url, const std::string& fileName, const std::optional<std::function<void(double /* progress */)>>& onProgress) override {
128
136
  auto __result = _swiftPart.downloadModel(url, fileName, onProgress);
129
137
  if (__result.hasError()) [[unlikely]] {
@@ -148,6 +156,14 @@ namespace margelo::nitro::litertlm {
148
156
  auto __value = std::move(__result.value());
149
157
  return __value;
150
158
  }
159
+ inline std::shared_ptr<Promise<void>> sendMessageWithAudioAsync(const std::string& message, const std::string& audioPath, const std::function<void(const std::string& /* token */, bool /* done */)>& onToken) override {
160
+ auto __result = _swiftPart.sendMessageWithAudioAsync(message, audioPath, onToken);
161
+ if (__result.hasError()) [[unlikely]] {
162
+ std::rethrow_exception(__result.error());
163
+ }
164
+ auto __value = std::move(__result.value());
165
+ return __value;
166
+ }
151
167
  inline std::shared_ptr<Promise<std::string>> sendMultimodalMessage(const std::vector<MultimodalPart>& parts) override {
152
168
  auto __result = _swiftPart.sendMultimodalMessage(parts);
153
169
  if (__result.hasError()) [[unlikely]] {
@@ -16,9 +16,11 @@ public protocol HybridLiteRTLMSpec_protocol: HybridObject {
16
16
  func loadModel(modelPath: String, config: LLMConfig?) throws -> Promise<Void>
17
17
  func sendMessage(message: String) throws -> Promise<String>
18
18
  func sendMessageWithImage(message: String, imagePath: String) throws -> Promise<String>
19
+ func sendMessageWithImageAsync(message: String, imagePath: String, onToken: @escaping (_ token: String, _ done: Bool) -> Void) throws -> Promise<Void>
19
20
  func downloadModel(url: String, fileName: String, onProgress: ((_ progress: Double) -> Void)?) throws -> Promise<String>
20
21
  func deleteModel(fileName: String) throws -> Promise<Void>
21
22
  func sendMessageWithAudio(message: String, audioPath: String) throws -> Promise<String>
23
+ func sendMessageWithAudioAsync(message: String, audioPath: String, onToken: @escaping (_ token: String, _ done: Bool) -> Void) throws -> Promise<Void>
22
24
  func sendMultimodalMessage(parts: [MultimodalPart]) throws -> Promise<String>
23
25
  func sendMessageAsync(message: String, onToken: @escaping (_ token: String, _ done: Bool) -> Void) throws -> Promise<Void>
24
26
  func getHistory() throws -> [Message]
@@ -181,6 +181,30 @@ open class HybridLiteRTLMSpec_cxx {
181
181
  }
182
182
  }
183
183
 
184
+ @inline(__always)
185
+ public final func sendMessageWithImageAsync(message: std.string, imagePath: std.string, onToken: bridge.Func_void_std__string_bool) -> bridge.Result_std__shared_ptr_Promise_void___ {
186
+ do {
187
+ let __result = try self.__implementation.sendMessageWithImageAsync(message: String(message), imagePath: String(imagePath), onToken: { () -> (String, Bool) -> Void in
188
+ let __wrappedFunction = bridge.wrap_Func_void_std__string_bool(onToken)
189
+ return { (__token: String, __done: Bool) -> Void in
190
+ __wrappedFunction.call(std.string(__token), __done)
191
+ }
192
+ }())
193
+ let __resultCpp = { () -> bridge.std__shared_ptr_Promise_void__ in
194
+ let __promise = bridge.create_std__shared_ptr_Promise_void__()
195
+ let __promiseHolder = bridge.wrap_std__shared_ptr_Promise_void__(__promise)
196
+ __result
197
+ .then({ __result in __promiseHolder.resolve() })
198
+ .catch({ __error in __promiseHolder.reject(__error.toCpp()) })
199
+ return __promise
200
+ }()
201
+ return bridge.create_Result_std__shared_ptr_Promise_void___(__resultCpp)
202
+ } catch (let __error) {
203
+ let __exceptionPtr = __error.toCpp()
204
+ return bridge.create_Result_std__shared_ptr_Promise_void___(__exceptionPtr)
205
+ }
206
+ }
207
+
184
208
  @inline(__always)
185
209
  public final func downloadModel(url: std.string, fileName: std.string, onProgress: bridge.std__optional_std__function_void_double____progress______) -> bridge.Result_std__shared_ptr_Promise_std__string___ {
186
210
  do {
@@ -250,6 +274,30 @@ open class HybridLiteRTLMSpec_cxx {
250
274
  }
251
275
  }
252
276
 
277
+ @inline(__always)
278
+ public final func sendMessageWithAudioAsync(message: std.string, audioPath: std.string, onToken: bridge.Func_void_std__string_bool) -> bridge.Result_std__shared_ptr_Promise_void___ {
279
+ do {
280
+ let __result = try self.__implementation.sendMessageWithAudioAsync(message: String(message), audioPath: String(audioPath), onToken: { () -> (String, Bool) -> Void in
281
+ let __wrappedFunction = bridge.wrap_Func_void_std__string_bool(onToken)
282
+ return { (__token: String, __done: Bool) -> Void in
283
+ __wrappedFunction.call(std.string(__token), __done)
284
+ }
285
+ }())
286
+ let __resultCpp = { () -> bridge.std__shared_ptr_Promise_void__ in
287
+ let __promise = bridge.create_std__shared_ptr_Promise_void__()
288
+ let __promiseHolder = bridge.wrap_std__shared_ptr_Promise_void__(__promise)
289
+ __result
290
+ .then({ __result in __promiseHolder.resolve() })
291
+ .catch({ __error in __promiseHolder.reject(__error.toCpp()) })
292
+ return __promise
293
+ }()
294
+ return bridge.create_Result_std__shared_ptr_Promise_void___(__resultCpp)
295
+ } catch (let __error) {
296
+ let __exceptionPtr = __error.toCpp()
297
+ return bridge.create_Result_std__shared_ptr_Promise_void___(__exceptionPtr)
298
+ }
299
+ }
300
+
253
301
  @inline(__always)
254
302
  public final func sendMultimodalMessage(parts: bridge.std__vector_MultimodalPart_) -> bridge.Result_std__shared_ptr_Promise_std__string___ {
255
303
  do {
@@ -17,9 +17,11 @@ namespace margelo::nitro::litertlm {
17
17
  prototype.registerHybridMethod("loadModel", &HybridLiteRTLMSpec::loadModel);
18
18
  prototype.registerHybridMethod("sendMessage", &HybridLiteRTLMSpec::sendMessage);
19
19
  prototype.registerHybridMethod("sendMessageWithImage", &HybridLiteRTLMSpec::sendMessageWithImage);
20
+ prototype.registerHybridMethod("sendMessageWithImageAsync", &HybridLiteRTLMSpec::sendMessageWithImageAsync);
20
21
  prototype.registerHybridMethod("downloadModel", &HybridLiteRTLMSpec::downloadModel);
21
22
  prototype.registerHybridMethod("deleteModel", &HybridLiteRTLMSpec::deleteModel);
22
23
  prototype.registerHybridMethod("sendMessageWithAudio", &HybridLiteRTLMSpec::sendMessageWithAudio);
24
+ prototype.registerHybridMethod("sendMessageWithAudioAsync", &HybridLiteRTLMSpec::sendMessageWithAudioAsync);
23
25
  prototype.registerHybridMethod("sendMultimodalMessage", &HybridLiteRTLMSpec::sendMultimodalMessage);
24
26
  prototype.registerHybridMethod("sendMessageAsync", &HybridLiteRTLMSpec::sendMessageAsync);
25
27
  prototype.registerHybridMethod("getHistory", &HybridLiteRTLMSpec::getHistory);
@@ -69,9 +69,11 @@ namespace margelo::nitro::litertlm {
69
69
  virtual std::shared_ptr<Promise<void>> loadModel(const std::string& modelPath, const std::optional<LLMConfig>& config) = 0;
70
70
  virtual std::shared_ptr<Promise<std::string>> sendMessage(const std::string& message) = 0;
71
71
  virtual std::shared_ptr<Promise<std::string>> sendMessageWithImage(const std::string& message, const std::string& imagePath) = 0;
72
+ virtual std::shared_ptr<Promise<void>> sendMessageWithImageAsync(const std::string& message, const std::string& imagePath, const std::function<void(const std::string& /* token */, bool /* done */)>& onToken) = 0;
72
73
  virtual std::shared_ptr<Promise<std::string>> downloadModel(const std::string& url, const std::string& fileName, const std::optional<std::function<void(double /* progress */)>>& onProgress) = 0;
73
74
  virtual std::shared_ptr<Promise<void>> deleteModel(const std::string& fileName) = 0;
74
75
  virtual std::shared_ptr<Promise<std::string>> sendMessageWithAudio(const std::string& message, const std::string& audioPath) = 0;
76
+ virtual std::shared_ptr<Promise<void>> sendMessageWithAudioAsync(const std::string& message, const std::string& audioPath, const std::function<void(const std::string& /* token */, bool /* done */)>& onToken) = 0;
75
77
  virtual std::shared_ptr<Promise<std::string>> sendMultimodalMessage(const std::vector<MultimodalPart>& parts) = 0;
76
78
  virtual std::shared_ptr<Promise<void>> sendMessageAsync(const std::string& message, const std::function<void(const std::string& /* token */, bool /* done */)>& onToken) = 0;
77
79
  virtual std::vector<Message> getHistory() = 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-litert-lm",
3
- "version": "0.4.0",
3
+ "version": "0.4.2",
4
4
  "litertLm": {
5
5
  "version": "0.12.0",
6
6
  "androidMavenVersion": "0.12.0",
@@ -49,6 +49,7 @@
49
49
  "cpp",
50
50
  "nitrogen/generated",
51
51
  "scripts/postinstall.js",
52
+ "scripts/framework-source.js",
52
53
  "scripts/download-ios-frameworks.sh",
53
54
  "react-native.config.js",
54
55
  "react-native-litert-lm.podspec",
@@ -65,10 +66,12 @@
65
66
  "typecheck": "tsc --noEmit",
66
67
  "test": "jest",
67
68
  "lint": "eslint \"**/*.{js,ts,tsx}\" --fix",
68
- "prepack": "npm run build",
69
+ "prepack": "npx nitrogen && npm run build",
69
70
  "specs": "npx nitrogen",
70
71
  "clean": "rm -rf lib android/build ios/build ios/Frameworks nitrogen/generated",
71
72
  "download-frameworks": "scripts/download-ios-frameworks.sh",
73
+ "check-framework-release": "node scripts/check-framework-release.js",
74
+ "prepublishOnly": "node scripts/check-framework-release.js",
72
75
  "android": "expo run:android",
73
76
  "android:clean": "cd android && ./gradlew clean",
74
77
  "ios": "expo run:ios",
@@ -82,7 +85,7 @@
82
85
  "jest": "^30.4.2",
83
86
  "react": "^19.2.6",
84
87
  "react-native": "^0.85.3",
85
- "react-native-nitro-modules": "^0.35.4",
88
+ "react-native-nitro-modules": "^0.35.9",
86
89
  "react-test-renderer": "^19.2.6",
87
90
  "release-it": "^19.2.4",
88
91
  "ts-jest": "^29.4.10",
@@ -93,7 +96,7 @@
93
96
  "expo": ">=55.0.0",
94
97
  "react": "*",
95
98
  "react-native": "*",
96
- "react-native-nitro-modules": "^0.35.4"
99
+ "react-native-nitro-modules": "^0.35.9"
97
100
  },
98
101
  "peerDependenciesMeta": {
99
102
  "expo": {
@@ -23,8 +23,10 @@ Pod::Spec.new do |s|
23
23
  "nitrogen/generated/shared/c++/**/*.{hpp,cpp}",
24
24
  ]
25
25
 
26
- # Prebuilt LiteRT-LM C engine (static library).
27
- # Downloaded from Google's release via: scripts/download-ios-frameworks.sh
26
+ # Prebuilt LiteRT-LM C engine (xcframework). Not shipped in the npm tarball;
27
+ # fetched on install by scripts/postinstall.js (asset/tag defined in
28
+ # scripts/framework-source.js). Manual/upstream fallback:
29
+ # scripts/download-ios-frameworks.sh.
28
30
  s.vendored_frameworks = 'ios/Frameworks/CLiteRTLM.xcframework'
29
31
 
30
32
  s.pod_target_xcconfig = {
@@ -7,10 +7,11 @@ set -euo pipefail
7
7
 
8
8
  SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
9
9
  PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
10
- OUTPUT_DIR="$PROJECT_ROOT/ios/Frameworks"
11
10
 
12
- LITERT_LM_VERSION="$(node -e "console.log(require('$PROJECT_ROOT/package.json').litertLm.iosGitTag)")"
13
- RELEASE_URL="https://github.com/google-ai-edge/LiteRT-LM/releases/download/${LITERT_LM_VERSION}/CLiteRTLM.xcframework.zip"
11
+ # Resolve the asset URL + output dir from the shared single source of truth
12
+ # (scripts/framework-source.js) so this manual path can't drift from postinstall.
13
+ RELEASE_URL="$(node -e "console.log(require('$SCRIPT_DIR/framework-source').ASSET_URL)")"
14
+ OUTPUT_DIR="$(node -e "console.log(require('$SCRIPT_DIR/framework-source').FRAMEWORKS_DIR)")"
14
15
 
15
16
  # Skip if already present
16
17
  if [ -d "$OUTPUT_DIR/CLiteRTLM.xcframework" ]; then