@dr33m/react-native-litert-lm 0.5.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 (168) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +627 -0
  3. package/android/CMakeLists.txt +32 -0
  4. package/android/build.gradle +104 -0
  5. package/android/src/main/AndroidManifest.xml +19 -0
  6. package/android/src/main/java/com/margelo/nitro/dev/litert/litertlm/HybridLiteRTLM.kt +888 -0
  7. package/android/src/main/java/com/margelo/nitro/dev/litert/litertlm/HybridModelStore.kt +189 -0
  8. package/android/src/main/java/com/margelo/nitro/dev/litert/litertlm/LiteRTLMRegistry.kt +32 -0
  9. package/android/src/main/java/com/margelo/nitro/dev/litert/litertlm/MultimodalPartFactories.kt +15 -0
  10. package/android/src/main/java/com/margelo/nitro/dev/litert/litertlm/StreamingCallbackListener.kt +89 -0
  11. package/android/src/main/java/dev/litert/litertlm/LiteRTLMInitProvider.kt +57 -0
  12. package/android/src/main/java/dev/litert/litertlm/LiteRTLMPackage.kt +43 -0
  13. package/android/src/test/java/com/margelo/nitro/core/Promise.kt +46 -0
  14. package/android/src/test/java/com/margelo/nitro/dev/litert/litertlm/HybridLiteRTLMTest.kt +122 -0
  15. package/app.plugin.js +58 -0
  16. package/cpp/cpp-adapter.cpp +21 -0
  17. package/cpp/include/README.md +32 -0
  18. package/cpp/include/litert_lm_engine.h +516 -0
  19. package/cpp/include/stb_image.h +7988 -0
  20. package/ios/HybridLiteRTLM+Execute.swift +287 -0
  21. package/ios/HybridLiteRTLM+Streaming.swift +153 -0
  22. package/ios/HybridLiteRTLM.swift +541 -0
  23. package/ios/HybridModelStore.swift +206 -0
  24. package/ios/MultimodalPart+Factories.swift +22 -0
  25. package/ios/Tests/HybridLiteRTLMTests.swift +187 -0
  26. package/lib/__mocks__/react-native-nitro-modules.d.ts +81 -0
  27. package/lib/__mocks__/react-native-nitro-modules.js +98 -0
  28. package/lib/__tests__/hooks.test.d.ts +1 -0
  29. package/lib/__tests__/hooks.test.js +175 -0
  30. package/lib/__tests__/inferenceRouting.test.d.ts +1 -0
  31. package/lib/__tests__/inferenceRouting.test.js +28 -0
  32. package/lib/__tests__/memoryTracker.test.d.ts +1 -0
  33. package/lib/__tests__/memoryTracker.test.js +74 -0
  34. package/lib/__tests__/modelFactory.test.d.ts +1 -0
  35. package/lib/__tests__/modelFactory.test.js +107 -0
  36. package/lib/__tests__/modelPath.test.d.ts +1 -0
  37. package/lib/__tests__/modelPath.test.js +12 -0
  38. package/lib/__tests__/modelRegistry.test.d.ts +1 -0
  39. package/lib/__tests__/modelRegistry.test.js +61 -0
  40. package/lib/hooks.d.ts +46 -0
  41. package/lib/hooks.js +159 -0
  42. package/lib/index.d.ts +142 -0
  43. package/lib/index.js +184 -0
  44. package/lib/inferenceRouting.d.ts +18 -0
  45. package/lib/inferenceRouting.js +63 -0
  46. package/lib/memoryTracker.d.ts +128 -0
  47. package/lib/memoryTracker.js +155 -0
  48. package/lib/modelFactory.d.ts +24 -0
  49. package/lib/modelFactory.js +128 -0
  50. package/lib/modelPath.d.ts +8 -0
  51. package/lib/modelPath.js +18 -0
  52. package/lib/modelRegistry.d.ts +55 -0
  53. package/lib/modelRegistry.js +82 -0
  54. package/lib/specs/LiteRTLM.nitro.d.ts +380 -0
  55. package/lib/specs/LiteRTLM.nitro.js +2 -0
  56. package/nitrogen/generated/.gitattributes +1 -0
  57. package/nitrogen/generated/android/LiteRTLM+autolinking.cmake +83 -0
  58. package/nitrogen/generated/android/LiteRTLM+autolinking.gradle +27 -0
  59. package/nitrogen/generated/android/LiteRTLMOnLoad.cpp +74 -0
  60. package/nitrogen/generated/android/LiteRTLMOnLoad.hpp +34 -0
  61. package/nitrogen/generated/android/c++/JBackend.hpp +61 -0
  62. package/nitrogen/generated/android/c++/JExecuteResult.hpp +86 -0
  63. package/nitrogen/generated/android/c++/JFunc_void_double.hpp +75 -0
  64. package/nitrogen/generated/android/c++/JFunc_void_std__string_bool.hpp +76 -0
  65. package/nitrogen/generated/android/c++/JGenerationStats.hpp +77 -0
  66. package/nitrogen/generated/android/c++/JHybridLiteRTLMSpec.cpp +371 -0
  67. package/nitrogen/generated/android/c++/JHybridLiteRTLMSpec.hpp +83 -0
  68. package/nitrogen/generated/android/c++/JHybridModelStoreSpec.cpp +101 -0
  69. package/nitrogen/generated/android/c++/JHybridModelStoreSpec.hpp +67 -0
  70. package/nitrogen/generated/android/c++/JLLMConfig.hpp +125 -0
  71. package/nitrogen/generated/android/c++/JMemoryUsage.hpp +69 -0
  72. package/nitrogen/generated/android/c++/JMessage.hpp +63 -0
  73. package/nitrogen/generated/android/c++/JModelFile.hpp +69 -0
  74. package/nitrogen/generated/android/c++/JMultimodalPart.hpp +78 -0
  75. package/nitrogen/generated/android/c++/JPartType.hpp +61 -0
  76. package/nitrogen/generated/android/c++/JRole.hpp +61 -0
  77. package/nitrogen/generated/android/c++/JToolCall.hpp +61 -0
  78. package/nitrogen/generated/android/c++/JToolDefinition.hpp +65 -0
  79. package/nitrogen/generated/android/c++/JToolResponse.hpp +61 -0
  80. package/nitrogen/generated/android/kotlin/com/margelo/nitro/dev/litert/litertlm/Backend.kt +24 -0
  81. package/nitrogen/generated/android/kotlin/com/margelo/nitro/dev/litert/litertlm/ExecuteResult.kt +61 -0
  82. package/nitrogen/generated/android/kotlin/com/margelo/nitro/dev/litert/litertlm/Func_void_double.kt +80 -0
  83. package/nitrogen/generated/android/kotlin/com/margelo/nitro/dev/litert/litertlm/Func_void_std__string_bool.kt +80 -0
  84. package/nitrogen/generated/android/kotlin/com/margelo/nitro/dev/litert/litertlm/GenerationStats.kt +76 -0
  85. package/nitrogen/generated/android/kotlin/com/margelo/nitro/dev/litert/litertlm/HybridLiteRTLMSpec.kt +165 -0
  86. package/nitrogen/generated/android/kotlin/com/margelo/nitro/dev/litert/litertlm/HybridModelStoreSpec.kt +76 -0
  87. package/nitrogen/generated/android/kotlin/com/margelo/nitro/dev/litert/litertlm/LLMConfig.kt +106 -0
  88. package/nitrogen/generated/android/kotlin/com/margelo/nitro/dev/litert/litertlm/LiteRTLMOnLoad.kt +35 -0
  89. package/nitrogen/generated/android/kotlin/com/margelo/nitro/dev/litert/litertlm/MemoryUsage.kt +66 -0
  90. package/nitrogen/generated/android/kotlin/com/margelo/nitro/dev/litert/litertlm/Message.kt +56 -0
  91. package/nitrogen/generated/android/kotlin/com/margelo/nitro/dev/litert/litertlm/ModelFile.kt +66 -0
  92. package/nitrogen/generated/android/kotlin/com/margelo/nitro/dev/litert/litertlm/MultimodalPart.kt +71 -0
  93. package/nitrogen/generated/android/kotlin/com/margelo/nitro/dev/litert/litertlm/PartType.kt +24 -0
  94. package/nitrogen/generated/android/kotlin/com/margelo/nitro/dev/litert/litertlm/Role.kt +24 -0
  95. package/nitrogen/generated/android/kotlin/com/margelo/nitro/dev/litert/litertlm/ToolCall.kt +56 -0
  96. package/nitrogen/generated/android/kotlin/com/margelo/nitro/dev/litert/litertlm/ToolDefinition.kt +61 -0
  97. package/nitrogen/generated/android/kotlin/com/margelo/nitro/dev/litert/litertlm/ToolResponse.kt +56 -0
  98. package/nitrogen/generated/ios/LiteRTLM+autolinking.rb +62 -0
  99. package/nitrogen/generated/ios/LiteRTLM-Swift-Cxx-Bridge.cpp +98 -0
  100. package/nitrogen/generated/ios/LiteRTLM-Swift-Cxx-Bridge.hpp +585 -0
  101. package/nitrogen/generated/ios/LiteRTLM-Swift-Cxx-Umbrella.hpp +93 -0
  102. package/nitrogen/generated/ios/LiteRTLMAutolinking.mm +41 -0
  103. package/nitrogen/generated/ios/LiteRTLMAutolinking.swift +38 -0
  104. package/nitrogen/generated/ios/c++/HybridLiteRTLMSpecSwift.cpp +11 -0
  105. package/nitrogen/generated/ios/c++/HybridLiteRTLMSpecSwift.hpp +279 -0
  106. package/nitrogen/generated/ios/c++/HybridModelStoreSpecSwift.cpp +11 -0
  107. package/nitrogen/generated/ios/c++/HybridModelStoreSpecSwift.hpp +117 -0
  108. package/nitrogen/generated/ios/swift/Backend.swift +44 -0
  109. package/nitrogen/generated/ios/swift/ExecuteResult.swift +45 -0
  110. package/nitrogen/generated/ios/swift/Func_void.swift +46 -0
  111. package/nitrogen/generated/ios/swift/Func_void_ExecuteResult.swift +46 -0
  112. package/nitrogen/generated/ios/swift/Func_void_double.swift +46 -0
  113. package/nitrogen/generated/ios/swift/Func_void_std__exception_ptr.swift +46 -0
  114. package/nitrogen/generated/ios/swift/Func_void_std__string.swift +46 -0
  115. package/nitrogen/generated/ios/swift/Func_void_std__string_bool.swift +46 -0
  116. package/nitrogen/generated/ios/swift/GenerationStats.swift +54 -0
  117. package/nitrogen/generated/ios/swift/HybridLiteRTLMSpec.swift +75 -0
  118. package/nitrogen/generated/ios/swift/HybridLiteRTLMSpec_cxx.swift +516 -0
  119. package/nitrogen/generated/ios/swift/HybridModelStoreSpec.swift +59 -0
  120. package/nitrogen/generated/ios/swift/HybridModelStoreSpec_cxx.swift +203 -0
  121. package/nitrogen/generated/ios/swift/LLMConfig.swift +239 -0
  122. package/nitrogen/generated/ios/swift/MemoryUsage.swift +44 -0
  123. package/nitrogen/generated/ios/swift/Message.swift +34 -0
  124. package/nitrogen/generated/ios/swift/ModelFile.swift +44 -0
  125. package/nitrogen/generated/ios/swift/MultimodalPart.swift +101 -0
  126. package/nitrogen/generated/ios/swift/PartType.swift +44 -0
  127. package/nitrogen/generated/ios/swift/Role.swift +44 -0
  128. package/nitrogen/generated/ios/swift/ToolCall.swift +34 -0
  129. package/nitrogen/generated/ios/swift/ToolDefinition.swift +39 -0
  130. package/nitrogen/generated/ios/swift/ToolResponse.swift +34 -0
  131. package/nitrogen/generated/shared/c++/Backend.hpp +80 -0
  132. package/nitrogen/generated/shared/c++/ExecuteResult.hpp +94 -0
  133. package/nitrogen/generated/shared/c++/GenerationStats.hpp +103 -0
  134. package/nitrogen/generated/shared/c++/HybridLiteRTLMSpec.cpp +41 -0
  135. package/nitrogen/generated/shared/c++/HybridLiteRTLMSpec.hpp +109 -0
  136. package/nitrogen/generated/shared/c++/HybridModelStoreSpec.cpp +25 -0
  137. package/nitrogen/generated/shared/c++/HybridModelStoreSpec.hpp +71 -0
  138. package/nitrogen/generated/shared/c++/LLMConfig.hpp +134 -0
  139. package/nitrogen/generated/shared/c++/MemoryUsage.hpp +95 -0
  140. package/nitrogen/generated/shared/c++/Message.hpp +89 -0
  141. package/nitrogen/generated/shared/c++/ModelFile.hpp +95 -0
  142. package/nitrogen/generated/shared/c++/MultimodalPart.hpp +103 -0
  143. package/nitrogen/generated/shared/c++/PartType.hpp +80 -0
  144. package/nitrogen/generated/shared/c++/Role.hpp +80 -0
  145. package/nitrogen/generated/shared/c++/ToolCall.hpp +87 -0
  146. package/nitrogen/generated/shared/c++/ToolDefinition.hpp +91 -0
  147. package/nitrogen/generated/shared/c++/ToolResponse.hpp +87 -0
  148. package/package.json +115 -0
  149. package/react-native-litert-lm.podspec +62 -0
  150. package/react-native.config.js +16 -0
  151. package/scripts/download-ios-frameworks.sh +39 -0
  152. package/scripts/framework-source.js +46 -0
  153. package/scripts/postinstall.js +145 -0
  154. package/src/__mocks__/react-native-nitro-modules.ts +132 -0
  155. package/src/__tests__/hooks.test.ts +216 -0
  156. package/src/__tests__/inferenceRouting.test.ts +36 -0
  157. package/src/__tests__/memoryTracker.test.ts +87 -0
  158. package/src/__tests__/modelFactory.test.ts +153 -0
  159. package/src/__tests__/modelPath.test.ts +14 -0
  160. package/src/__tests__/modelRegistry.test.ts +73 -0
  161. package/src/hooks.ts +234 -0
  162. package/src/index.ts +214 -0
  163. package/src/inferenceRouting.ts +80 -0
  164. package/src/memoryTracker.ts +268 -0
  165. package/src/modelFactory.ts +163 -0
  166. package/src/modelPath.ts +16 -0
  167. package/src/modelRegistry.ts +104 -0
  168. package/src/specs/LiteRTLM.nitro.ts +451 -0
@@ -0,0 +1,175 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ // Configure React act environment
7
+ global.IS_REACT_ACT_ENVIRONMENT = true;
8
+ const hooks_1 = require("../hooks");
9
+ const react_native_nitro_modules_1 = require("../__mocks__/react-native-nitro-modules");
10
+ const react_test_renderer_1 = __importDefault(require("react-test-renderer"));
11
+ const react_1 = __importDefault(require("react"));
12
+ // Mock react-native
13
+ jest.mock('react-native', () => ({
14
+ Platform: {
15
+ OS: 'ios',
16
+ select: jest.fn((dict) => dict.ios),
17
+ },
18
+ }));
19
+ // Helper to render and test hooks using react-test-renderer
20
+ function renderHook(callback, initialProps) {
21
+ let result = { current: null };
22
+ const TestComponent = ({ props }) => {
23
+ result.current = callback(props);
24
+ return null;
25
+ };
26
+ let renderer;
27
+ react_test_renderer_1.default.act(() => {
28
+ renderer = react_test_renderer_1.default.create(react_1.default.createElement(TestComponent, { props: initialProps }));
29
+ });
30
+ const rerender = (newProps) => {
31
+ react_test_renderer_1.default.act(() => {
32
+ renderer.update(react_1.default.createElement(TestComponent, { props: newProps }));
33
+ });
34
+ };
35
+ const unmount = () => {
36
+ react_test_renderer_1.default.act(() => {
37
+ renderer.unmount();
38
+ });
39
+ };
40
+ return { result, rerender, unmount };
41
+ }
42
+ describe('useModel React Hook Unit Tests', () => {
43
+ beforeEach(() => {
44
+ jest.clearAllMocks();
45
+ });
46
+ it('should initialize with correct default state and call loadModel automatically when autoLoad is true', async () => {
47
+ let hookResult;
48
+ await react_test_renderer_1.default.act(async () => {
49
+ hookResult = renderHook(() => (0, hooks_1.useModel)('https://example.com/model.litertlm', { autoLoad: true }));
50
+ });
51
+ expect(hookResult.result.current.isReady).toBe(true);
52
+ expect(hookResult.result.current.isGenerating).toBe(false);
53
+ expect(hookResult.result.current.downloadProgress).toBe(1); // loadModel completed
54
+ expect(hookResult.result.current.error).toBeNull();
55
+ expect(react_native_nitro_modules_1.mockLiteRTLM.loadModel).toHaveBeenCalled();
56
+ });
57
+ it('should not call loadModel automatically when autoLoad is false', async () => {
58
+ let hookResult;
59
+ await react_test_renderer_1.default.act(async () => {
60
+ hookResult = renderHook(() => (0, hooks_1.useModel)('https://example.com/model.litertlm', { autoLoad: false }));
61
+ });
62
+ expect(hookResult.result.current.isReady).toBe(false);
63
+ expect(react_native_nitro_modules_1.mockLiteRTLM.loadModel).not.toHaveBeenCalled();
64
+ // Call load manually
65
+ await react_test_renderer_1.default.act(async () => {
66
+ await hookResult.result.current.load();
67
+ });
68
+ expect(hookResult.result.current.isReady).toBe(true);
69
+ expect(react_native_nitro_modules_1.mockLiteRTLM.loadModel).toHaveBeenCalled();
70
+ });
71
+ it('should handle model load failure gracefully', async () => {
72
+ react_native_nitro_modules_1.mockLiteRTLM.loadModel.mockRejectedValueOnce(new Error("Model load failed"));
73
+ let hookResult;
74
+ await react_test_renderer_1.default.act(async () => {
75
+ hookResult = renderHook(() => (0, hooks_1.useModel)('https://example.com/model.litertlm', { autoLoad: true }));
76
+ });
77
+ expect(hookResult.result.current.isReady).toBe(false);
78
+ expect(hookResult.result.current.error).toBe("Model load failed");
79
+ });
80
+ it('should generate text successfully and trigger memory summary update', async () => {
81
+ let hookResult;
82
+ await react_test_renderer_1.default.act(async () => {
83
+ hookResult = renderHook(() => (0, hooks_1.useModel)('https://example.com/model.litertlm', {
84
+ autoLoad: true,
85
+ enableMemoryTracking: true
86
+ }));
87
+ });
88
+ let response = "";
89
+ await react_test_renderer_1.default.act(async () => {
90
+ response = await hookResult.result.current.generate("Test prompt");
91
+ });
92
+ expect(response).toBe("Mock token");
93
+ expect(react_native_nitro_modules_1.mockLiteRTLM.execute).toHaveBeenCalledWith([{ type: "text", text: "Test prompt" }], undefined);
94
+ expect(hookResult.result.current.memorySummary).toBeDefined();
95
+ });
96
+ it('should reset conversation correctly', async () => {
97
+ let hookResult;
98
+ await react_test_renderer_1.default.act(async () => {
99
+ hookResult = renderHook(() => (0, hooks_1.useModel)('https://example.com/model.litertlm', { autoLoad: true }));
100
+ });
101
+ hookResult.result.current.reset();
102
+ expect(react_native_nitro_modules_1.mockLiteRTLM.resetConversation).toHaveBeenCalled();
103
+ });
104
+ it('should delete model file correctly', async () => {
105
+ let hookResult;
106
+ await react_test_renderer_1.default.act(async () => {
107
+ hookResult = renderHook(() => (0, hooks_1.useModel)('https://example.com/model.litertlm', { autoLoad: true }));
108
+ });
109
+ await react_test_renderer_1.default.act(async () => {
110
+ await hookResult.result.current.deleteModel();
111
+ });
112
+ expect(react_native_nitro_modules_1.mockLiteRTLM.deleteModel).toHaveBeenCalledWith('model.litertlm');
113
+ expect(hookResult.result.current.isReady).toBe(false);
114
+ expect(hookResult.result.current.downloadProgress).toBe(0);
115
+ });
116
+ it('should call close on unmount', async () => {
117
+ let hookResult;
118
+ await react_test_renderer_1.default.act(async () => {
119
+ hookResult = renderHook(() => (0, hooks_1.useModel)('https://example.com/model.litertlm', { autoLoad: false }));
120
+ });
121
+ hookResult.unmount();
122
+ expect(react_native_nitro_modules_1.mockLiteRTLM.close).toHaveBeenCalled();
123
+ });
124
+ it('should pass maxContextTokens and maxOutputTokens through to loadModel', async () => {
125
+ let hookResult;
126
+ await react_test_renderer_1.default.act(async () => {
127
+ hookResult = renderHook(() => (0, hooks_1.useModel)('https://example.com/model.litertlm', {
128
+ autoLoad: true,
129
+ maxContextTokens: 8192,
130
+ maxOutputTokens: 2048,
131
+ }));
132
+ });
133
+ expect(react_native_nitro_modules_1.mockLiteRTLM.loadModel).toHaveBeenCalled();
134
+ const callArgs = react_native_nitro_modules_1.mockLiteRTLM.loadModel.mock.calls[0];
135
+ const config = callArgs[1];
136
+ expect(config).toMatchObject({
137
+ maxContextTokens: 8192,
138
+ maxOutputTokens: 2048,
139
+ });
140
+ });
141
+ it('should pass legacy maxTokens through when new fields are not set', async () => {
142
+ let hookResult;
143
+ await react_test_renderer_1.default.act(async () => {
144
+ hookResult = renderHook(() => (0, hooks_1.useModel)('https://example.com/model.litertlm', {
145
+ autoLoad: true,
146
+ maxTokens: 512,
147
+ }));
148
+ });
149
+ expect(react_native_nitro_modules_1.mockLiteRTLM.loadModel).toHaveBeenCalled();
150
+ const callArgs = react_native_nitro_modules_1.mockLiteRTLM.loadModel.mock.calls[0];
151
+ const config = callArgs[1];
152
+ expect(config).toMatchObject({ maxTokens: 512 });
153
+ expect(config.maxContextTokens).toBeUndefined();
154
+ expect(config.maxOutputTokens).toBeUndefined();
155
+ });
156
+ it('should pass both new fields alongside legacy maxTokens when all are set', async () => {
157
+ let hookResult;
158
+ await react_test_renderer_1.default.act(async () => {
159
+ hookResult = renderHook(() => (0, hooks_1.useModel)('https://example.com/model.litertlm', {
160
+ autoLoad: true,
161
+ maxTokens: 512,
162
+ maxContextTokens: 4096,
163
+ maxOutputTokens: 1024,
164
+ }));
165
+ });
166
+ expect(react_native_nitro_modules_1.mockLiteRTLM.loadModel).toHaveBeenCalled();
167
+ const callArgs = react_native_nitro_modules_1.mockLiteRTLM.loadModel.mock.calls[0];
168
+ const config = callArgs[1];
169
+ expect(config).toMatchObject({
170
+ maxTokens: 512,
171
+ maxContextTokens: 4096,
172
+ maxOutputTokens: 1024,
173
+ });
174
+ });
175
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const inferenceRouting_1 = require("../inferenceRouting");
4
+ describe("inferenceRouting", () => {
5
+ it("routes sendMessage to a single text part", () => {
6
+ const route = (0, inferenceRouting_1.routeLegacyInference)("sendMessage", ["hello"]);
7
+ expect(route).toEqual({ parts: [(0, inferenceRouting_1.textPart)("hello")] });
8
+ });
9
+ it("routes sendMessageWithImageAsync with stream callback", () => {
10
+ const onToken = jest.fn();
11
+ const route = (0, inferenceRouting_1.routeLegacyInference)("sendMessageWithImageAsync", [
12
+ "describe",
13
+ "/img.jpg",
14
+ onToken,
15
+ ]);
16
+ expect(route).toEqual({
17
+ parts: [(0, inferenceRouting_1.textPart)("describe"), (0, inferenceRouting_1.imagePart)("/img.jpg")],
18
+ onToken,
19
+ });
20
+ });
21
+ it("returns null for unknown methods", () => {
22
+ expect((0, inferenceRouting_1.routeLegacyInference)("downloadModel", ["url", "file"])).toBeNull();
23
+ });
24
+ it("isLegacyInferenceMethod narrows known methods", () => {
25
+ expect((0, inferenceRouting_1.isLegacyInferenceMethod)("sendMessage")).toBe(true);
26
+ expect((0, inferenceRouting_1.isLegacyInferenceMethod)("close")).toBe(false);
27
+ });
28
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,74 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const memoryTracker_1 = require("../memoryTracker");
4
+ const react_native_nitro_modules_1 = require("react-native-nitro-modules");
5
+ describe('MemoryTracker Unit Tests', () => {
6
+ beforeEach(() => {
7
+ jest.clearAllMocks();
8
+ });
9
+ it('should allocate correct native-backed ArrayBuffer size on initialization', () => {
10
+ const tracker = (0, memoryTracker_1.createMemoryTracker)(10);
11
+ expect(react_native_nitro_modules_1.NitroModules.createNativeArrayBuffer).toHaveBeenCalledWith(10 * 4 * 8); // 10 snapshots * 4 fields * 8 bytes/Float64
12
+ expect(tracker.getCapacity()).toBe(10);
13
+ expect(tracker.getSnapshotCount()).toBe(0);
14
+ });
15
+ it('should record snapshots correctly and retrieve them', () => {
16
+ const tracker = (0, memoryTracker_1.createMemoryTracker)(5);
17
+ const snapshot1 = {
18
+ timestamp: 1000,
19
+ nativeHeapBytes: 100,
20
+ residentBytes: 200,
21
+ availableMemoryBytes: 500,
22
+ };
23
+ expect(tracker.record(snapshot1)).toBe(true);
24
+ expect(tracker.getSnapshotCount()).toBe(1);
25
+ expect(tracker.getLatestSnapshot()).toEqual(snapshot1);
26
+ const snapshots = tracker.getSnapshots();
27
+ expect(snapshots).toHaveLength(1);
28
+ expect(snapshots[0]).toEqual(snapshot1);
29
+ });
30
+ it('should reject new snapshots and return false when capacity is reached', () => {
31
+ const tracker = (0, memoryTracker_1.createMemoryTracker)(2);
32
+ expect(tracker.record({ timestamp: 1, nativeHeapBytes: 10, residentBytes: 20, availableMemoryBytes: 50 })).toBe(true);
33
+ expect(tracker.record({ timestamp: 2, nativeHeapBytes: 20, residentBytes: 30, availableMemoryBytes: 40 })).toBe(true);
34
+ expect(tracker.record({ timestamp: 3, nativeHeapBytes: 30, residentBytes: 40, availableMemoryBytes: 30 })).toBe(false);
35
+ expect(tracker.getSnapshotCount()).toBe(2);
36
+ });
37
+ it('should calculate correct peak resident memory size', () => {
38
+ const tracker = (0, memoryTracker_1.createMemoryTracker)(5);
39
+ tracker.record({ timestamp: 1, nativeHeapBytes: 100, residentBytes: 150, availableMemoryBytes: 1000 });
40
+ tracker.record({ timestamp: 2, nativeHeapBytes: 120, residentBytes: 300, availableMemoryBytes: 1000 });
41
+ tracker.record({ timestamp: 3, nativeHeapBytes: 110, residentBytes: 200, availableMemoryBytes: 1000 });
42
+ expect(tracker.getPeakMemory()).toBe(300);
43
+ });
44
+ it('should calculate accurate memory summary statistics', () => {
45
+ const tracker = (0, memoryTracker_1.createMemoryTracker)(5);
46
+ tracker.record({ timestamp: 1, nativeHeapBytes: 50, residentBytes: 100, availableMemoryBytes: 1000 });
47
+ tracker.record({ timestamp: 2, nativeHeapBytes: 150, residentBytes: 300, availableMemoryBytes: 800 });
48
+ tracker.record({ timestamp: 3, nativeHeapBytes: 100, residentBytes: 200, availableMemoryBytes: 900 });
49
+ const summary = tracker.getSummary();
50
+ expect(summary.snapshotCount).toBe(3);
51
+ expect(summary.peakResidentBytes).toBe(300);
52
+ expect(summary.averageResidentBytes).toBe(200); // (100 + 300 + 200) / 3
53
+ expect(summary.currentResidentBytes).toBe(200);
54
+ expect(summary.peakNativeHeapBytes).toBe(150);
55
+ expect(summary.currentNativeHeapBytes).toBe(100);
56
+ expect(summary.residentDeltaBytes).toBe(100); // currentRss(200) - firstRss(100)
57
+ expect(summary.trackerBufferSizeBytes).toBe(5 * 4 * 8);
58
+ });
59
+ it('should preserve buffer but reset internal state when reset() is called', () => {
60
+ const tracker = (0, memoryTracker_1.createMemoryTracker)(5);
61
+ tracker.record({ timestamp: 1, nativeHeapBytes: 50, residentBytes: 100, availableMemoryBytes: 1000 });
62
+ expect(tracker.getSnapshotCount()).toBe(1);
63
+ tracker.reset();
64
+ expect(tracker.getSnapshotCount()).toBe(0);
65
+ expect(tracker.getLatestSnapshot()).toBeUndefined();
66
+ expect(tracker.getSnapshots()).toEqual([]);
67
+ });
68
+ it('should allow standalone native ArrayBuffer allocation via createNativeBuffer', () => {
69
+ const size = 128;
70
+ const buffer = (0, memoryTracker_1.createNativeBuffer)(size);
71
+ expect(react_native_nitro_modules_1.NitroModules.createNativeArrayBuffer).toHaveBeenCalledWith(size);
72
+ expect(buffer.byteLength).toBe(size);
73
+ });
74
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,107 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const modelFactory_1 = require("../modelFactory");
4
+ const react_native_nitro_modules_1 = require("../__mocks__/react-native-nitro-modules");
5
+ describe('modelFactory Security & Proxy Unit Tests', () => {
6
+ let llm;
7
+ beforeEach(() => {
8
+ jest.clearAllMocks();
9
+ llm = (0, modelFactory_1.createLLM)({ enableMemoryTracking: true });
10
+ });
11
+ it('should block insecure HTTP downloads', async () => {
12
+ await expect(llm.loadModel('http://example.com/model.litertlm'))
13
+ .rejects.toThrow('Insecure HTTP URLs are not allowed for model downloads');
14
+ });
15
+ it('should allow secure HTTPS downloads and strip query parameters', async () => {
16
+ await llm.loadModel('https://example.com/model.litertlm?token=123');
17
+ expect(react_native_nitro_modules_1.mockLiteRTLM.downloadModel).toHaveBeenCalledWith('https://example.com/model.litertlm?token=123', 'model.litertlm', expect.any(Function));
18
+ expect(react_native_nitro_modules_1.mockLiteRTLM.loadModel).toHaveBeenCalledWith('/mock/path/model.litertlm', undefined);
19
+ });
20
+ it('should throw an error for invalid model URL', async () => {
21
+ await expect(llm.loadModel('https://example.com/'))
22
+ .rejects.toThrow('Invalid model URL: https://example.com/');
23
+ });
24
+ it('should successfully proxy sendMessage and record memory metrics', async () => {
25
+ const response = await llm.sendMessage("Test prompt");
26
+ expect(response).toBe("Mock token");
27
+ expect(react_native_nitro_modules_1.mockLiteRTLM.execute).toHaveBeenCalledWith([{ type: "text", text: "Test prompt" }], undefined);
28
+ expect(react_native_nitro_modules_1.mockLiteRTLM.getMemoryUsage).toHaveBeenCalled();
29
+ expect(llm.memoryTracker?.getSnapshotCount()).toBe(1); // sendMessage records one
30
+ });
31
+ it('should successfully proxy resetConversation and record memory metrics', async () => {
32
+ await llm.resetConversation();
33
+ expect(react_native_nitro_modules_1.mockLiteRTLM.resetConversation).toHaveBeenCalled();
34
+ expect(react_native_nitro_modules_1.mockLiteRTLM.getMemoryUsage).toHaveBeenCalled();
35
+ });
36
+ it('should successfully proxy sendMessageAsync and record memory metrics when done', async () => {
37
+ const onToken = jest.fn();
38
+ await llm.sendMessageAsync("Async prompt", onToken);
39
+ expect(onToken).toHaveBeenCalledWith("Mock ", false);
40
+ expect(onToken).toHaveBeenCalledWith("token", true);
41
+ expect(react_native_nitro_modules_1.mockLiteRTLM.execute).toHaveBeenCalledWith([{ type: "text", text: "Async prompt" }], expect.any(Function));
42
+ expect(react_native_nitro_modules_1.mockLiteRTLM.getMemoryUsage).toHaveBeenCalled();
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.execute).toHaveBeenCalledWith([
50
+ { type: "text", text: "Vision prompt" },
51
+ { type: "image", path: "/path/to/image.jpg" },
52
+ ], expect.any(Function));
53
+ expect(react_native_nitro_modules_1.mockLiteRTLM.getMemoryUsage).toHaveBeenCalled();
54
+ });
55
+ it('should successfully proxy sendMessageWithAudioAsync and record memory metrics when done', async () => {
56
+ const onToken = jest.fn();
57
+ await llm.sendMessageWithAudioAsync("Audio prompt", "/path/to/audio.wav", onToken);
58
+ expect(onToken).toHaveBeenCalledWith("Mock audio ", false);
59
+ expect(onToken).toHaveBeenCalledWith("token", true);
60
+ expect(react_native_nitro_modules_1.mockLiteRTLM.execute).toHaveBeenCalledWith([
61
+ { type: "text", text: "Audio prompt" },
62
+ { type: "audio", path: "/path/to/audio.wav" },
63
+ ], expect.any(Function));
64
+ expect(react_native_nitro_modules_1.mockLiteRTLM.getMemoryUsage).toHaveBeenCalled();
65
+ });
66
+ it('should successfully access memoryTracker and getSnapshots when memory tracking is enabled', () => {
67
+ expect(llm.memoryTracker).toBeDefined();
68
+ expect(llm.memoryTracker?.getCapacity()).toBe(256);
69
+ });
70
+ it('should not initialize memoryTracker when enableMemoryTracking option is false', () => {
71
+ const untrackedLLM = (0, modelFactory_1.createLLM)({ enableMemoryTracking: false });
72
+ expect(untrackedLLM.memoryTracker).toBeUndefined();
73
+ });
74
+ it('should forward maxContextTokens and maxOutputTokens config to native loadModel', async () => {
75
+ const config = {
76
+ backend: 'cpu',
77
+ maxContextTokens: 8192,
78
+ maxOutputTokens: 2048,
79
+ };
80
+ await llm.loadModel('/local/path/model.litertlm', config);
81
+ expect(react_native_nitro_modules_1.mockLiteRTLM.loadModel).toHaveBeenCalledWith('/local/path/model.litertlm', config);
82
+ });
83
+ it('should forward legacy maxTokens config to native loadModel for backward compat', async () => {
84
+ const config = {
85
+ backend: 'cpu',
86
+ maxTokens: 512,
87
+ };
88
+ await llm.loadModel('/local/path/model.litertlm', config);
89
+ expect(react_native_nitro_modules_1.mockLiteRTLM.loadModel).toHaveBeenCalledWith('/local/path/model.litertlm', config);
90
+ });
91
+ it('should forward execute call exactly to native execute', async () => {
92
+ const parts = [
93
+ { type: 'image', path: '/path/to/image.jpg', imageBuffer: new ArrayBuffer(10) }
94
+ ];
95
+ await llm.execute(parts);
96
+ expect(react_native_nitro_modules_1.mockLiteRTLM.execute).toHaveBeenCalledWith(parts, undefined);
97
+ });
98
+ it('should strip file:// prefix from paths in execute parts before forwarding to native execute', async () => {
99
+ const parts = [
100
+ { type: 'image', path: 'file:///path/to/image.jpg', imageBuffer: new ArrayBuffer(10) }
101
+ ];
102
+ await llm.execute(parts);
103
+ expect(react_native_nitro_modules_1.mockLiteRTLM.execute).toHaveBeenCalledWith([
104
+ { type: 'image', path: '/path/to/image.jpg', imageBuffer: expect.any(ArrayBuffer) }
105
+ ], undefined);
106
+ });
107
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const modelPath_1 = require("../modelPath");
4
+ describe("modelPath", () => {
5
+ it("strips query strings from URLs", () => {
6
+ expect((0, modelPath_1.extractFileName)("https://example.com/model.litertlm?token=abc")).toBe("model.litertlm");
7
+ });
8
+ it("resolveModelFileName uses basename for paths and URLs", () => {
9
+ expect((0, modelPath_1.resolveModelFileName)("/data/models/foo.litertlm")).toBe("foo.litertlm");
10
+ expect((0, modelPath_1.resolveModelFileName)("bare-name.bin")).toBe("bare-name.bin");
11
+ });
12
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,61 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const modelRegistry_1 = require("../modelRegistry");
4
+ const react_native_nitro_modules_1 = require("../__mocks__/react-native-nitro-modules");
5
+ describe('ModelRegistry Unit Tests', () => {
6
+ beforeEach(() => {
7
+ jest.clearAllMocks();
8
+ });
9
+ it('isCached should query native store correctly', () => {
10
+ react_native_nitro_modules_1.mockModelStore.isCached.mockReturnValueOnce(true);
11
+ const result = modelRegistry_1.ModelRegistry.isCached('https://example.com/test-model.litertlm');
12
+ expect(react_native_nitro_modules_1.mockModelStore.isCached).toHaveBeenCalledWith('test-model.litertlm');
13
+ expect(result).toBe(true);
14
+ });
15
+ it('getFilePath should return cached path', () => {
16
+ react_native_nitro_modules_1.mockModelStore.getFilePath.mockReturnValueOnce('/caches/test.bin');
17
+ const path = modelRegistry_1.ModelRegistry.getFilePath('test.bin');
18
+ expect(react_native_nitro_modules_1.mockModelStore.getFilePath).toHaveBeenCalledWith('test.bin');
19
+ expect(path).toBe('/caches/test.bin');
20
+ });
21
+ it('listCachedFiles should delegate to native', () => {
22
+ const mockFiles = [
23
+ {
24
+ fileName: 'model.bin',
25
+ absolutePath: '/caches/model.bin',
26
+ sizeBytes: 1000,
27
+ lastModifiedMs: 12345,
28
+ },
29
+ ];
30
+ react_native_nitro_modules_1.mockModelStore.listCachedFiles.mockReturnValueOnce(mockFiles);
31
+ const files = modelRegistry_1.ModelRegistry.listCachedFiles();
32
+ expect(react_native_nitro_modules_1.mockModelStore.listCachedFiles).toHaveBeenCalled();
33
+ expect(files).toEqual(mockFiles);
34
+ });
35
+ it('deleteFile should delegate delete to native', () => {
36
+ modelRegistry_1.ModelRegistry.deleteFile('https://example.com/model.bin?q=1');
37
+ expect(react_native_nitro_modules_1.mockModelStore.deleteFile).toHaveBeenCalledWith('model.bin');
38
+ });
39
+ it('resolveModel should throw error on HTTP urls', async () => {
40
+ await expect(modelRegistry_1.ModelRegistry.resolveModel('http://example.com/model.bin'))
41
+ .rejects.toThrow('Insecure HTTP URLs are not allowed for model downloads');
42
+ });
43
+ it('resolveModel should download HTTPS urls', async () => {
44
+ react_native_nitro_modules_1.mockModelStore.downloadFile.mockResolvedValueOnce('/downloaded/model.bin');
45
+ const path = await modelRegistry_1.ModelRegistry.resolveModel('https://example.com/model.bin', {
46
+ headers: { Authorization: 'Bearer test' },
47
+ });
48
+ expect(react_native_nitro_modules_1.mockModelStore.downloadFile).toHaveBeenCalledWith('https://example.com/model.bin', 'model.bin', JSON.stringify({ Authorization: 'Bearer test' }), expect.any(Function));
49
+ expect(path).toBe('/downloaded/model.bin');
50
+ });
51
+ it('resolveModel should return local paths directly', async () => {
52
+ const path = await modelRegistry_1.ModelRegistry.resolveModel('/local/path/model.bin');
53
+ expect(react_native_nitro_modules_1.mockModelStore.downloadFile).not.toHaveBeenCalled();
54
+ expect(path).toBe('/local/path/model.bin');
55
+ });
56
+ it('resolveModel should strip file:// prefix from local paths', async () => {
57
+ const path = await modelRegistry_1.ModelRegistry.resolveModel('file:///local/path/model.bin');
58
+ expect(react_native_nitro_modules_1.mockModelStore.downloadFile).not.toHaveBeenCalled();
59
+ expect(path).toBe('/local/path/model.bin');
60
+ });
61
+ });
package/lib/hooks.d.ts ADDED
@@ -0,0 +1,46 @@
1
+ import { LLMConfig } from "./index";
2
+ import type { LiteRTLMInstance } from "./modelFactory";
3
+ import type { MemoryTracker, MemoryTrackerSummary } from "./memoryTracker";
4
+ export interface UseModelConfig extends LLMConfig {
5
+ autoLoad?: boolean;
6
+ /**
7
+ * Enable memory tracking using native ArrayBuffers (v0.35+).
8
+ * When enabled, memory usage is tracked after each inference call
9
+ * using `NitroModules.createNativeArrayBuffer()` for zero-copy storage.
10
+ * @default false
11
+ */
12
+ enableMemoryTracking?: boolean;
13
+ /**
14
+ * Maximum number of memory snapshots to store.
15
+ * Each snapshot uses 32 bytes of native memory.
16
+ * @default 256
17
+ */
18
+ maxMemorySnapshots?: number;
19
+ }
20
+ export interface UseModelResult {
21
+ model: LiteRTLMInstance | null;
22
+ isReady: boolean;
23
+ isGenerating: boolean;
24
+ downloadProgress: number;
25
+ error: string | null;
26
+ generate: (prompt: string) => Promise<import("./specs/LiteRTLM.nitro").ExecuteResult>;
27
+ reset: () => void;
28
+ /**
29
+ * Delete the model file. If no fileName is provided, derives it from
30
+ * the URL/path passed to useModel.
31
+ */
32
+ deleteModel: (fileName?: string) => Promise<void>;
33
+ load: () => Promise<void>;
34
+ /**
35
+ * Memory tracker instance (available when enableMemoryTracking is true).
36
+ * Uses native ArrayBuffers allocated via `NitroModules.createNativeArrayBuffer()`
37
+ * for efficient, zero-copy memory usage tracking.
38
+ */
39
+ memoryTracker: MemoryTracker | null;
40
+ /**
41
+ * Current memory tracking summary (null if tracking is disabled).
42
+ * Updates automatically after each inference call.
43
+ */
44
+ memorySummary: MemoryTrackerSummary | null;
45
+ }
46
+ export declare function useModel(pathOrUrl: string, config?: UseModelConfig): UseModelResult;