cactus-react-native 1.2.0 → 1.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 (222) hide show
  1. package/README.md +573 -13
  2. package/android/CMakeLists.txt +4 -3
  3. package/android/src/main/java/com/margelo/nitro/cactus/HybridCactusFileSystem.kt +21 -4
  4. package/android/src/main/jniLibs/arm64-v8a/libcactus.a +0 -0
  5. package/android/src/main/jniLibs/arm64-v8a/libcactus_util.a +0 -0
  6. package/cpp/HybridCactus.cpp +112 -19
  7. package/cpp/HybridCactus.hpp +12 -3
  8. package/cpp/HybridCactusIndex.cpp +325 -0
  9. package/cpp/HybridCactusIndex.hpp +43 -0
  10. package/cpp/HybridCactusUtil.cpp +3 -3
  11. package/cpp/HybridCactusUtil.hpp +2 -1
  12. package/cpp/cactus_ffi.h +83 -2
  13. package/cpp/cactus_util.h +1 -1
  14. package/ios/HybridCactusFileSystem.swift +23 -2
  15. package/ios/cactus.xcframework/ios-arm64/cactus.framework/Headers/cactus.h +2 -0
  16. package/ios/cactus.xcframework/ios-arm64/cactus.framework/Headers/cactus_ffi.h +83 -2
  17. package/ios/cactus.xcframework/ios-arm64/cactus.framework/Headers/cactus_telemetry.h +656 -0
  18. package/ios/cactus.xcframework/ios-arm64/cactus.framework/Headers/{ffi_utils.h → cactus_utils.h} +104 -17
  19. package/ios/cactus.xcframework/ios-arm64/cactus.framework/Headers/engine.h +117 -7
  20. package/ios/cactus.xcframework/ios-arm64/cactus.framework/Headers/graph.h +91 -5
  21. package/ios/cactus.xcframework/ios-arm64/cactus.framework/Headers/kernel.h +15 -6
  22. package/ios/cactus.xcframework/ios-arm64/cactus.framework/Info.plist +0 -0
  23. package/ios/cactus.xcframework/ios-arm64/cactus.framework/cactus +0 -0
  24. package/ios/cactus.xcframework/ios-arm64-simulator/cactus.framework/Headers/cactus.h +2 -0
  25. package/ios/cactus.xcframework/ios-arm64-simulator/cactus.framework/Headers/cactus_ffi.h +83 -2
  26. package/ios/cactus.xcframework/ios-arm64-simulator/cactus.framework/Headers/cactus_telemetry.h +656 -0
  27. package/ios/cactus.xcframework/ios-arm64-simulator/cactus.framework/Headers/{ffi_utils.h → cactus_utils.h} +104 -17
  28. package/ios/cactus.xcframework/ios-arm64-simulator/cactus.framework/Headers/engine.h +117 -7
  29. package/ios/cactus.xcframework/ios-arm64-simulator/cactus.framework/Headers/graph.h +91 -5
  30. package/ios/cactus.xcframework/ios-arm64-simulator/cactus.framework/Headers/kernel.h +15 -6
  31. package/ios/cactus.xcframework/ios-arm64-simulator/cactus.framework/Info.plist +0 -0
  32. package/ios/cactus.xcframework/ios-arm64-simulator/cactus.framework/_CodeSignature/CodeResources +1 -1
  33. package/ios/cactus.xcframework/ios-arm64-simulator/cactus.framework/cactus +0 -0
  34. package/ios/cactus_util.xcframework/Info.plist +4 -4
  35. package/ios/cactus_util.xcframework/ios-arm64/cactus_util.framework/Headers/cactus_util.h +1 -1
  36. package/ios/cactus_util.xcframework/ios-arm64/cactus_util.framework/Headers/database.h +27 -0
  37. package/ios/cactus_util.xcframework/ios-arm64/cactus_util.framework/Info.plist +0 -0
  38. package/ios/cactus_util.xcframework/ios-arm64/cactus_util.framework/cactus_util +0 -0
  39. package/ios/cactus_util.xcframework/ios-arm64-simulator/cactus_util.framework/Headers/cactus_util.h +1 -1
  40. package/ios/cactus_util.xcframework/ios-arm64-simulator/cactus_util.framework/Headers/database.h +27 -0
  41. package/ios/cactus_util.xcframework/ios-arm64-simulator/cactus_util.framework/Info.plist +0 -0
  42. package/ios/cactus_util.xcframework/ios-arm64-simulator/cactus_util.framework/_CodeSignature/CodeResources +3 -3
  43. package/ios/cactus_util.xcframework/ios-arm64-simulator/cactus_util.framework/cactus_util +0 -0
  44. package/lib/module/api/Database.js +57 -3
  45. package/lib/module/api/Database.js.map +1 -1
  46. package/lib/module/classes/CactusIndex.js +45 -0
  47. package/lib/module/classes/CactusIndex.js.map +1 -0
  48. package/lib/module/classes/CactusLM.js +35 -10
  49. package/lib/module/classes/CactusLM.js.map +1 -1
  50. package/lib/module/classes/CactusSTT.js +20 -12
  51. package/lib/module/classes/CactusSTT.js.map +1 -1
  52. package/lib/module/config/CactusConfig.js +2 -0
  53. package/lib/module/config/CactusConfig.js.map +1 -1
  54. package/lib/module/constants/packageVersion.js +1 -1
  55. package/lib/module/hooks/useCactusIndex.js +175 -0
  56. package/lib/module/hooks/useCactusIndex.js.map +1 -0
  57. package/lib/module/hooks/useCactusLM.js +54 -2
  58. package/lib/module/hooks/useCactusLM.js.map +1 -1
  59. package/lib/module/hooks/useCactusSTT.js +2 -2
  60. package/lib/module/hooks/useCactusSTT.js.map +1 -1
  61. package/lib/module/index.js +2 -0
  62. package/lib/module/index.js.map +1 -1
  63. package/lib/module/native/Cactus.js +21 -5
  64. package/lib/module/native/Cactus.js.map +1 -1
  65. package/lib/module/native/CactusFileSystem.js +3 -0
  66. package/lib/module/native/CactusFileSystem.js.map +1 -1
  67. package/lib/module/native/CactusIndex.js +32 -0
  68. package/lib/module/native/CactusIndex.js.map +1 -0
  69. package/lib/module/native/CactusUtil.js +16 -3
  70. package/lib/module/native/CactusUtil.js.map +1 -1
  71. package/lib/module/native/index.js +1 -0
  72. package/lib/module/native/index.js.map +1 -1
  73. package/lib/module/specs/CactusIndex.nitro.js +4 -0
  74. package/lib/module/specs/CactusIndex.nitro.js.map +1 -0
  75. package/lib/module/telemetry/Telemetry.js +3 -1
  76. package/lib/module/telemetry/Telemetry.js.map +1 -1
  77. package/lib/module/types/CactusIndex.js +2 -0
  78. package/lib/module/types/CactusIndex.js.map +1 -0
  79. package/lib/module/types/CactusSTTModel.js +2 -0
  80. package/lib/module/types/CactusSTTModel.js.map +1 -0
  81. package/lib/typescript/src/api/Database.d.ts +7 -1
  82. package/lib/typescript/src/api/Database.d.ts.map +1 -1
  83. package/lib/typescript/src/classes/CactusIndex.d.ts +15 -0
  84. package/lib/typescript/src/classes/CactusIndex.d.ts.map +1 -0
  85. package/lib/typescript/src/classes/CactusLM.d.ts +5 -3
  86. package/lib/typescript/src/classes/CactusLM.d.ts.map +1 -1
  87. package/lib/typescript/src/classes/CactusSTT.d.ts +4 -4
  88. package/lib/typescript/src/classes/CactusSTT.d.ts.map +1 -1
  89. package/lib/typescript/src/config/CactusConfig.d.ts +1 -0
  90. package/lib/typescript/src/config/CactusConfig.d.ts.map +1 -1
  91. package/lib/typescript/src/constants/packageVersion.d.ts +1 -1
  92. package/lib/typescript/src/hooks/useCactusIndex.d.ts +14 -0
  93. package/lib/typescript/src/hooks/useCactusIndex.d.ts.map +1 -0
  94. package/lib/typescript/src/hooks/useCactusLM.d.ts +4 -2
  95. package/lib/typescript/src/hooks/useCactusLM.d.ts.map +1 -1
  96. package/lib/typescript/src/hooks/useCactusSTT.d.ts +3 -3
  97. package/lib/typescript/src/hooks/useCactusSTT.d.ts.map +1 -1
  98. package/lib/typescript/src/index.d.ts +5 -1
  99. package/lib/typescript/src/index.d.ts.map +1 -1
  100. package/lib/typescript/src/native/Cactus.d.ts +4 -2
  101. package/lib/typescript/src/native/Cactus.d.ts.map +1 -1
  102. package/lib/typescript/src/native/CactusFileSystem.d.ts +1 -0
  103. package/lib/typescript/src/native/CactusFileSystem.d.ts.map +1 -1
  104. package/lib/typescript/src/native/CactusIndex.d.ts +12 -0
  105. package/lib/typescript/src/native/CactusIndex.d.ts.map +1 -0
  106. package/lib/typescript/src/native/CactusUtil.d.ts.map +1 -1
  107. package/lib/typescript/src/native/index.d.ts +1 -0
  108. package/lib/typescript/src/native/index.d.ts.map +1 -1
  109. package/lib/typescript/src/specs/Cactus.nitro.d.ts +4 -2
  110. package/lib/typescript/src/specs/Cactus.nitro.d.ts.map +1 -1
  111. package/lib/typescript/src/specs/CactusFileSystem.nitro.d.ts +1 -0
  112. package/lib/typescript/src/specs/CactusFileSystem.nitro.d.ts.map +1 -1
  113. package/lib/typescript/src/specs/CactusIndex.nitro.d.ts +24 -0
  114. package/lib/typescript/src/specs/CactusIndex.nitro.d.ts.map +1 -0
  115. package/lib/typescript/src/specs/CactusUtil.nitro.d.ts +1 -1
  116. package/lib/typescript/src/specs/CactusUtil.nitro.d.ts.map +1 -1
  117. package/lib/typescript/src/types/CactusIndex.d.ts +34 -0
  118. package/lib/typescript/src/types/CactusIndex.d.ts.map +1 -0
  119. package/lib/typescript/src/types/CactusLM.d.ts +17 -0
  120. package/lib/typescript/src/types/CactusLM.d.ts.map +1 -1
  121. package/lib/typescript/src/types/CactusModel.d.ts +1 -0
  122. package/lib/typescript/src/types/CactusModel.d.ts.map +1 -1
  123. package/lib/typescript/src/types/CactusSTT.d.ts +1 -1
  124. package/lib/typescript/src/types/CactusSTT.d.ts.map +1 -1
  125. package/lib/typescript/src/types/CactusSTTModel.d.ts +8 -0
  126. package/lib/typescript/src/types/CactusSTTModel.d.ts.map +1 -0
  127. package/nitro.json +3 -0
  128. package/nitrogen/generated/android/c++/JDeviceInfo.hpp +1 -1
  129. package/nitrogen/generated/android/c++/JFunc_void_double.hpp +1 -1
  130. package/nitrogen/generated/android/c++/JHybridCactusCryptoSpec.cpp +1 -1
  131. package/nitrogen/generated/android/c++/JHybridCactusCryptoSpec.hpp +1 -1
  132. package/nitrogen/generated/android/c++/JHybridCactusDeviceInfoSpec.cpp +1 -1
  133. package/nitrogen/generated/android/c++/JHybridCactusDeviceInfoSpec.hpp +1 -1
  134. package/nitrogen/generated/android/c++/JHybridCactusFileSystemSpec.cpp +17 -1
  135. package/nitrogen/generated/android/c++/JHybridCactusFileSystemSpec.hpp +2 -1
  136. package/nitrogen/generated/android/c++/JHybridCactusImageSpec.cpp +1 -1
  137. package/nitrogen/generated/android/c++/JHybridCactusImageSpec.hpp +1 -1
  138. package/nitrogen/generated/android/cactus+autolinking.cmake +2 -1
  139. package/nitrogen/generated/android/cactus+autolinking.gradle +1 -1
  140. package/nitrogen/generated/android/cactusOnLoad.cpp +11 -1
  141. package/nitrogen/generated/android/cactusOnLoad.hpp +1 -1
  142. package/nitrogen/generated/android/kotlin/com/margelo/nitro/cactus/DeviceInfo.kt +1 -1
  143. package/nitrogen/generated/android/kotlin/com/margelo/nitro/cactus/Func_void_double.kt +1 -1
  144. package/nitrogen/generated/android/kotlin/com/margelo/nitro/cactus/HybridCactusCryptoSpec.kt +1 -1
  145. package/nitrogen/generated/android/kotlin/com/margelo/nitro/cactus/HybridCactusDeviceInfoSpec.kt +1 -1
  146. package/nitrogen/generated/android/kotlin/com/margelo/nitro/cactus/HybridCactusFileSystemSpec.kt +5 -1
  147. package/nitrogen/generated/android/kotlin/com/margelo/nitro/cactus/HybridCactusImageSpec.kt +1 -1
  148. package/nitrogen/generated/android/kotlin/com/margelo/nitro/cactus/cactusOnLoad.kt +1 -1
  149. package/nitrogen/generated/ios/Cactus+autolinking.rb +1 -1
  150. package/nitrogen/generated/ios/Cactus-Swift-Cxx-Bridge.cpp +1 -1
  151. package/nitrogen/generated/ios/Cactus-Swift-Cxx-Bridge.hpp +1 -1
  152. package/nitrogen/generated/ios/Cactus-Swift-Cxx-Umbrella.hpp +1 -1
  153. package/nitrogen/generated/ios/CactusAutolinking.mm +11 -1
  154. package/nitrogen/generated/ios/CactusAutolinking.swift +1 -1
  155. package/nitrogen/generated/ios/c++/HybridCactusCryptoSpecSwift.cpp +1 -1
  156. package/nitrogen/generated/ios/c++/HybridCactusCryptoSpecSwift.hpp +1 -1
  157. package/nitrogen/generated/ios/c++/HybridCactusDeviceInfoSpecSwift.cpp +1 -1
  158. package/nitrogen/generated/ios/c++/HybridCactusDeviceInfoSpecSwift.hpp +1 -1
  159. package/nitrogen/generated/ios/c++/HybridCactusFileSystemSpecSwift.cpp +1 -1
  160. package/nitrogen/generated/ios/c++/HybridCactusFileSystemSpecSwift.hpp +9 -1
  161. package/nitrogen/generated/ios/c++/HybridCactusImageSpecSwift.cpp +1 -1
  162. package/nitrogen/generated/ios/c++/HybridCactusImageSpecSwift.hpp +1 -1
  163. package/nitrogen/generated/ios/swift/DeviceInfo.swift +1 -1
  164. package/nitrogen/generated/ios/swift/Func_void.swift +1 -1
  165. package/nitrogen/generated/ios/swift/Func_void_DeviceInfo.swift +1 -1
  166. package/nitrogen/generated/ios/swift/Func_void_bool.swift +1 -1
  167. package/nitrogen/generated/ios/swift/Func_void_double.swift +1 -1
  168. package/nitrogen/generated/ios/swift/Func_void_std__exception_ptr.swift +1 -1
  169. package/nitrogen/generated/ios/swift/Func_void_std__optional_std__string_.swift +1 -1
  170. package/nitrogen/generated/ios/swift/Func_void_std__string.swift +1 -1
  171. package/nitrogen/generated/ios/swift/HybridCactusCryptoSpec.swift +1 -1
  172. package/nitrogen/generated/ios/swift/HybridCactusCryptoSpec_cxx.swift +1 -1
  173. package/nitrogen/generated/ios/swift/HybridCactusDeviceInfoSpec.swift +1 -1
  174. package/nitrogen/generated/ios/swift/HybridCactusDeviceInfoSpec_cxx.swift +1 -1
  175. package/nitrogen/generated/ios/swift/HybridCactusFileSystemSpec.swift +2 -1
  176. package/nitrogen/generated/ios/swift/HybridCactusFileSystemSpec_cxx.swift +20 -1
  177. package/nitrogen/generated/ios/swift/HybridCactusImageSpec.swift +1 -1
  178. package/nitrogen/generated/ios/swift/HybridCactusImageSpec_cxx.swift +1 -1
  179. package/nitrogen/generated/shared/c++/CactusIndexGetResult.hpp +84 -0
  180. package/nitrogen/generated/shared/c++/CactusIndexQueryResult.hpp +79 -0
  181. package/nitrogen/generated/shared/c++/DeviceInfo.hpp +1 -1
  182. package/nitrogen/generated/shared/c++/HybridCactusCryptoSpec.cpp +1 -1
  183. package/nitrogen/generated/shared/c++/HybridCactusCryptoSpec.hpp +1 -1
  184. package/nitrogen/generated/shared/c++/HybridCactusDeviceInfoSpec.cpp +1 -1
  185. package/nitrogen/generated/shared/c++/HybridCactusDeviceInfoSpec.hpp +1 -1
  186. package/nitrogen/generated/shared/c++/HybridCactusFileSystemSpec.cpp +2 -1
  187. package/nitrogen/generated/shared/c++/HybridCactusFileSystemSpec.hpp +2 -1
  188. package/nitrogen/generated/shared/c++/HybridCactusImageSpec.cpp +1 -1
  189. package/nitrogen/generated/shared/c++/HybridCactusImageSpec.hpp +1 -1
  190. package/nitrogen/generated/shared/c++/HybridCactusIndexSpec.cpp +27 -0
  191. package/nitrogen/generated/shared/c++/HybridCactusIndexSpec.hpp +76 -0
  192. package/nitrogen/generated/shared/c++/HybridCactusSpec.cpp +3 -1
  193. package/nitrogen/generated/shared/c++/HybridCactusSpec.hpp +6 -3
  194. package/nitrogen/generated/shared/c++/HybridCactusUtilSpec.cpp +1 -1
  195. package/nitrogen/generated/shared/c++/HybridCactusUtilSpec.hpp +2 -2
  196. package/package.json +2 -2
  197. package/src/api/Database.ts +80 -2
  198. package/src/classes/CactusIndex.ts +58 -0
  199. package/src/classes/CactusLM.ts +41 -11
  200. package/src/classes/CactusSTT.ts +24 -16
  201. package/src/config/CactusConfig.ts +3 -0
  202. package/src/constants/packageVersion.ts +1 -1
  203. package/src/hooks/useCactusIndex.ts +195 -0
  204. package/src/hooks/useCactusLM.ts +63 -3
  205. package/src/hooks/useCactusSTT.ts +4 -4
  206. package/src/index.tsx +17 -0
  207. package/src/native/Cactus.ts +39 -4
  208. package/src/native/CactusFileSystem.ts +4 -0
  209. package/src/native/CactusIndex.ts +54 -0
  210. package/src/native/CactusUtil.ts +19 -3
  211. package/src/native/index.ts +1 -0
  212. package/src/specs/Cactus.nitro.ts +13 -2
  213. package/src/specs/CactusFileSystem.nitro.ts +2 -0
  214. package/src/specs/CactusIndex.nitro.ts +31 -0
  215. package/src/specs/CactusUtil.nitro.ts +1 -1
  216. package/src/telemetry/Telemetry.ts +1 -1
  217. package/src/types/CactusIndex.ts +40 -0
  218. package/src/types/CactusLM.ts +21 -0
  219. package/src/types/CactusModel.ts +1 -0
  220. package/src/types/CactusSTT.ts +1 -1
  221. package/src/types/CactusSTTModel.ts +10 -0
  222. package/android/src/main/jniLibs/arm64-v8a/libcactus_util.so +0 -0
@@ -0,0 +1,656 @@
1
+ #ifndef CACTUS_TELEMETRY_H
2
+ #define CACTUS_TELEMETRY_H
3
+
4
+ #include <string>
5
+ #include <thread>
6
+ #include <sstream>
7
+ #include <iostream>
8
+ #include <chrono>
9
+ #include <mutex>
10
+ #include <map>
11
+ #include <iomanip>
12
+ #include <ctime>
13
+ #include <fstream>
14
+ #include <sys/stat.h>
15
+ #include "cactus_utils.h"
16
+
17
+ #if defined(__APPLE__)
18
+ #include <TargetConditionals.h>
19
+ #endif
20
+
21
+ #if defined(__APPLE__) && (!TARGET_OS_IPHONE) && !defined(__ANDROID__)
22
+ #define CACTUS_TELEMETRY_ENABLED
23
+ #include <curl/curl.h>
24
+ #include <sys/utsname.h>
25
+ #include <unistd.h>
26
+ #endif
27
+
28
+ namespace cactus {
29
+ namespace ffi {
30
+
31
+ enum class TelemetryEventType {
32
+ Init,
33
+ Completion,
34
+ Embedding,
35
+ Transcription
36
+ };
37
+
38
+ struct TelemetryMetrics {
39
+ TelemetryEventType event_type;
40
+ std::string model;
41
+
42
+ double ttft_ms = 0.0;
43
+ double tps = 0.0;
44
+ double response_time_ms = 0.0;
45
+ int tokens = 0;
46
+
47
+ bool success = false;
48
+ std::string message;
49
+
50
+ std::chrono::system_clock::time_point timestamp;
51
+ };
52
+
53
+
54
+ class HttpClient {
55
+ public:
56
+ struct Response {
57
+ bool success;
58
+ int status_code;
59
+ std::string body;
60
+ };
61
+
62
+ static Response postJson(
63
+ const std::string& url,
64
+ const std::map<std::string, std::string>& headers,
65
+ const std::string& json_body
66
+ );
67
+
68
+ private:
69
+ static size_t writeCallback(void* contents, size_t size, size_t nmemb, void* userp);
70
+ };
71
+
72
+ inline size_t HttpClient::writeCallback(void* contents, size_t size, size_t nmemb, void* userp) {
73
+ size_t totalSize = size * nmemb;
74
+ std::string* response = static_cast<std::string*>(userp);
75
+ response->append(static_cast<char*>(contents), totalSize);
76
+ return totalSize;
77
+ }
78
+
79
+ inline HttpClient::Response HttpClient::postJson(
80
+ [[maybe_unused]] const std::string& url,
81
+ [[maybe_unused]] const std::map<std::string, std::string>& headers,
82
+ [[maybe_unused]] const std::string& json_body
83
+ ) {
84
+ #ifdef CACTUS_TELEMETRY_ENABLED
85
+ Response response;
86
+ response.success = false;
87
+ response.status_code = 0;
88
+
89
+ CURL* curl = curl_easy_init();
90
+ if (!curl) {
91
+ return response;
92
+ }
93
+
94
+ curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
95
+
96
+ curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L);
97
+ curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 2L);
98
+
99
+ curl_easy_setopt(curl, CURLOPT_POST, 1L);
100
+
101
+ curl_easy_setopt(curl, CURLOPT_POSTFIELDS, json_body.c_str());
102
+ curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, json_body.length());
103
+
104
+ struct curl_slist* header_list = nullptr;
105
+ for (const auto& header : headers) {
106
+ std::string header_str = header.first + ": " + header.second;
107
+ header_list = curl_slist_append(header_list, header_str.c_str());
108
+ }
109
+ curl_easy_setopt(curl, CURLOPT_HTTPHEADER, header_list);
110
+
111
+ curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeCallback);
112
+ curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response.body);
113
+
114
+ curl_easy_setopt(curl, CURLOPT_TIMEOUT, 5L);
115
+
116
+ CURLcode res = curl_easy_perform(curl);
117
+
118
+ if (res == CURLE_OK) {
119
+ long response_code = 0;
120
+ curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code);
121
+ response.status_code = static_cast<int>(response_code);
122
+ response.success = (response_code >= 200 && response_code < 300);
123
+
124
+ if (!response.success && !response.body.empty()) {
125
+ std::cerr << "[Telemetry] Response body: " << response.body << std::endl;
126
+ }
127
+ } else {
128
+ std::cerr << "[Telemetry] HTTP POST failed: " << curl_easy_strerror(res) << std::endl;
129
+ }
130
+
131
+ if (header_list) {
132
+ curl_slist_free_all(header_list);
133
+ }
134
+ curl_easy_cleanup(curl);
135
+
136
+ return response;
137
+ #else
138
+ (void)url;
139
+ (void)headers;
140
+ (void)json_body;
141
+ Response response;
142
+ response.success = false;
143
+ response.status_code = 0;
144
+ return response;
145
+ #endif
146
+ }
147
+
148
+ class DeviceManager {
149
+ public:
150
+ static std::string getDeviceId();
151
+ static std::string getProjectId();
152
+ static std::map<std::string, std::string> getDeviceMetadata();
153
+ static std::string registerDevice(const std::string& device_id = "", const std::string& pro_key = "");
154
+
155
+ static void setProKey(const std::string& key);
156
+ static std::string getProKey();
157
+
158
+ private:
159
+ static std::string getConfigPath();
160
+ static std::map<std::string, std::string> readConfig();
161
+ static void writeConfig(const std::map<std::string, std::string>& config);
162
+
163
+ static std::string pro_key_;
164
+ };
165
+
166
+ inline std::string DeviceManager::pro_key_ = "";
167
+
168
+ inline void DeviceManager::setProKey(const std::string& key) {
169
+ pro_key_ = key;
170
+ }
171
+
172
+ inline std::string DeviceManager::getProKey() {
173
+ return pro_key_;
174
+ }
175
+
176
+ inline std::string DeviceManager::getConfigPath() {
177
+ const char* home = getenv("HOME");
178
+ if (!home) {
179
+ home = "/tmp";
180
+ }
181
+
182
+ std::string cactus_dir = std::string(home) + "/.cactus";
183
+
184
+ mkdir(cactus_dir.c_str(), 0755);
185
+
186
+ return cactus_dir + "/telemetry_config.json";
187
+ }
188
+
189
+ inline std::map<std::string, std::string> DeviceManager::readConfig() {
190
+ std::map<std::string, std::string> config;
191
+ std::string path = getConfigPath();
192
+ std::ifstream file(path);
193
+
194
+ if (file.is_open()) {
195
+ std::stringstream buffer;
196
+ buffer << file.rdbuf();
197
+ file.close();
198
+
199
+ std::string content = buffer.str();
200
+
201
+ const std::string project_id_key = "\"project_id\":\"";
202
+ size_t project_pos = content.find(project_id_key);
203
+ if (project_pos != std::string::npos) {
204
+ size_t start = project_pos + project_id_key.length();
205
+ size_t end = content.find("\"", start);
206
+ if (end != std::string::npos) {
207
+ config["project_id"] = content.substr(start, end - start);
208
+ }
209
+ }
210
+ }
211
+
212
+ return config;
213
+ }
214
+
215
+ inline void DeviceManager::writeConfig(const std::map<std::string, std::string>& config) {
216
+ std::string path = getConfigPath();
217
+ std::ofstream file(path);
218
+
219
+ if (file.is_open()) {
220
+ file << "{\n";
221
+
222
+ auto device_it = config.find("device_id");
223
+ if (device_it != config.end()) {
224
+ file << " \"device_id\":\"" << device_it->second << "\"";
225
+ }
226
+
227
+ auto project_it = config.find("project_id");
228
+ if (project_it != config.end()) {
229
+ if (device_it != config.end()) {
230
+ file << ",\n";
231
+ }
232
+ file << " \"project_id\":\"" << project_it->second << "\"";
233
+ }
234
+
235
+ file << "\n}\n";
236
+ file.close();
237
+ }
238
+ }
239
+
240
+ inline std::string DeviceManager::getDeviceId() {
241
+ auto config = readConfig();
242
+ std::string pro_key = getProKey();
243
+
244
+ std::string project_id = config["project_id"];
245
+ if (project_id.empty()) {
246
+ project_id = generateUUID();
247
+ }
248
+
249
+ config["project_id"] = project_id;
250
+ writeConfig(config);
251
+
252
+ const char* device_id_cstr = get_device_id(pro_key.c_str());
253
+ if (device_id_cstr != nullptr) {
254
+ std::string device_id = std::string(device_id_cstr);
255
+ size_t pipe_pos = device_id.find('|');
256
+ if (pipe_pos != std::string::npos) {
257
+ std::string device_part = device_id.substr(0, pipe_pos);
258
+ std::string pro_key_part = device_id.substr(pipe_pos + 1);
259
+ setProKey(pro_key_part);
260
+ return registerDevice(device_part, pro_key_part);
261
+ }
262
+ return device_id;
263
+ }
264
+ return registerDevice("", pro_key);
265
+ }
266
+
267
+ inline std::string DeviceManager::getProjectId() {
268
+ auto config = readConfig();
269
+ std::string project_id = config["project_id"];
270
+
271
+ if (!project_id.empty()) {
272
+ return project_id;
273
+ }
274
+
275
+ project_id = generateUUID();
276
+ std::cerr << "[Device Manager] Generated new project ID: " << project_id << std::endl;
277
+
278
+ config["project_id"] = project_id;
279
+ writeConfig(config);
280
+
281
+ return project_id;
282
+ }
283
+
284
+ inline std::string DeviceManager::registerDevice(const std::string& device_id, const std::string& pro_key) {
285
+ #ifdef CACTUS_TELEMETRY_ENABLED
286
+ static const std::string SUPABASE_URL = "https://vlqqczxwyaodtcdmdmlw.supabase.co";
287
+ static const std::string SUPABASE_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InZscXFjenh3eWFvZHRjZG1kbWx3Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTE1MTg2MzIsImV4cCI6MjA2NzA5NDYzMn0.nBzqGuK9j6RZ6mOPWU2boAC_5H9XDs-fPpo5P3WZYbI";
288
+
289
+ auto metadata = getDeviceMetadata();
290
+
291
+ std::ostringstream json;
292
+ json << "{";
293
+
294
+ if (!device_id.empty()) {
295
+ json << "\"device_id\":\"" << generateUUID() << "\"";
296
+ } else {
297
+ json << "\"device_data\":{"
298
+ << "\"model\":\"" << metadata["model"] << "\",";
299
+ json << "\"os\":\"" << metadata["os"] << "\",";
300
+ json << "\"os_version\":\"" << metadata["os_version"] << "\",";
301
+ json << "\"brand\":\"" << metadata["brand"] << "\"";
302
+ json << "}";
303
+ }
304
+
305
+ json << ",\"cactus_pro_key\":\"" << pro_key << "\"";
306
+ json << "}";
307
+
308
+ std::string payload = json.str();
309
+
310
+ std::map<std::string, std::string> headers;
311
+ headers["Content-Type"] = "application/json";
312
+
313
+ std::string url = SUPABASE_URL + "/functions/v1/device-registration";
314
+
315
+ auto response = HttpClient::postJson(url, headers, payload);
316
+
317
+ if (response.success && !response.body.empty()) {
318
+ const char* registered_id_cstr = register_app(response.body.c_str());
319
+ std::string registered_id = (registered_id_cstr && registered_id_cstr[0] != '\0')
320
+ ? std::string(registered_id_cstr)
321
+ : std::string();
322
+
323
+ if (!registered_id.empty()) {
324
+ std::cerr << "[Device Registration] SUCCESS - Device registered!" << std::endl;
325
+ return registered_id;
326
+ }
327
+
328
+ std::cerr << "[Device Registration] FAILED - Could not parse ID from response" << std::endl;
329
+ return "";
330
+ } else {
331
+ std::cerr << "[Device Registration] FAILED - Direct table insertion unsuccessful" << std::endl;
332
+ return "";
333
+ }
334
+ #else
335
+ return "";
336
+ #endif
337
+ }
338
+
339
+ inline std::map<std::string, std::string> DeviceManager::getDeviceMetadata() {
340
+ std::map<std::string, std::string> metadata;
341
+
342
+ #ifdef CACTUS_TELEMETRY_ENABLED
343
+ struct utsname system_info;
344
+ if (uname(&system_info) == 0) {
345
+ metadata["os"] = "macOS";
346
+ metadata["os_version"] = system_info.release;
347
+ metadata["architecture"] = system_info.machine;
348
+ metadata["model"] = system_info.machine;
349
+ metadata["brand"] = "apple";
350
+ }
351
+ #else
352
+ metadata["os"] = "unknown";
353
+ metadata["os_version"] = "unknown";
354
+ metadata["architecture"] = "unknown";
355
+ metadata["model"] = "unknown";
356
+ metadata["brand"] = "unknown";
357
+ #endif
358
+
359
+ return metadata;
360
+ }
361
+
362
+ class LogRecord {
363
+ public:
364
+ static std::string buildJson(
365
+ const TelemetryMetrics& metrics,
366
+ const std::string& project_id,
367
+ const std::string& device_id,
368
+ const std::string& telemetry_token
369
+ );
370
+
371
+ private:
372
+ static std::string escapeJson(const std::string& input);
373
+ static std::string formatTimestamp(const std::chrono::system_clock::time_point& timestamp);
374
+ static std::string eventTypeToString(TelemetryEventType type);
375
+ };
376
+
377
+ inline std::string LogRecord::escapeJson(const std::string& input) {
378
+ std::ostringstream output;
379
+ for (char c : input) {
380
+ switch (c) {
381
+ case '"': output << "\\\""; break;
382
+ case '\\': output << "\\\\"; break;
383
+ case '\b': output << "\\b"; break;
384
+ case '\f': output << "\\f"; break;
385
+ case '\n': output << "\\n"; break;
386
+ case '\r': output << "\\r"; break;
387
+ case '\t': output << "\\t"; break;
388
+ default:
389
+ if (static_cast<unsigned char>(c) < 0x20) {
390
+ output << "\\u" << std::hex << std::setw(4) << std::setfill('0')
391
+ << static_cast<int>(c);
392
+ } else {
393
+ output << c;
394
+ }
395
+ }
396
+ }
397
+ return output.str();
398
+ }
399
+
400
+ inline std::string LogRecord::formatTimestamp(const std::chrono::system_clock::time_point& timestamp) {
401
+ auto time_t = std::chrono::system_clock::to_time_t(timestamp);
402
+ auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
403
+ timestamp.time_since_epoch()) % 1000;
404
+
405
+ std::tm tm;
406
+ #ifdef _WIN32
407
+ gmtime_s(&tm, &time_t);
408
+ #else
409
+ gmtime_r(&time_t, &tm);
410
+ #endif
411
+
412
+ std::ostringstream oss;
413
+ oss << std::put_time(&tm, "%Y-%m-%dT%H:%M:%S")
414
+ << '.' << std::setfill('0') << std::setw(3) << ms.count() << 'Z';
415
+ return oss.str();
416
+ }
417
+
418
+ inline std::string LogRecord::eventTypeToString(TelemetryEventType type) {
419
+ switch (type) {
420
+ case TelemetryEventType::Init:
421
+ return "init";
422
+ case TelemetryEventType::Completion:
423
+ return "completion";
424
+ case TelemetryEventType::Embedding:
425
+ return "embedding";
426
+ case TelemetryEventType::Transcription:
427
+ return "transcription";
428
+ default:
429
+ return "unknown";
430
+ }
431
+ }
432
+
433
+ inline std::string LogRecord::buildJson(
434
+ const TelemetryMetrics& metrics,
435
+ const std::string& project_id,
436
+ const std::string& device_id,
437
+ const std::string& telemetry_token
438
+ ) {
439
+ std::ostringstream json;
440
+ json << std::fixed << std::setprecision(2);
441
+
442
+ json << "{";
443
+ json << "\"event_type\":\"" << eventTypeToString(metrics.event_type) << "\",";
444
+ json << "\"model\":\"" << escapeJson(metrics.model) << "\",";
445
+ json << "\"success\":" << (metrics.success ? "true" : "false") << ",";
446
+ json << "\"project_id\":\"" << project_id << "\",";
447
+ json << "\"device_id\":\"" << device_id << "\",";
448
+ json << "\"telemetry_token\":\"" << telemetry_token << "\",";
449
+ json << "\"framework\":\"cpp\",";
450
+ json << "\"framework_version\":\"" << getVersion() << "\"";
451
+
452
+ json << ",\"ttft\":" << metrics.ttft_ms;
453
+ json << ",\"tps\":" << metrics.tps;
454
+ json << ",\"response_time\":" << metrics.response_time_ms;
455
+ json << ",\"tokens\":" << metrics.tokens;
456
+
457
+ if (!metrics.message.empty()) {
458
+ json << ",\"message\":\"" << escapeJson(metrics.message) << "\"";
459
+ }
460
+
461
+ json << "}";
462
+ return json.str();
463
+ }
464
+
465
+ class CactusTelemetry {
466
+ public:
467
+ static CactusTelemetry& getInstance();
468
+
469
+ void setEnabled(bool enabled);
470
+ void setTelemetryToken(const std::string& token);
471
+ void setProjectId(const std::string& project_id);
472
+ void ensureInitialized();
473
+
474
+ void recordEvent(const TelemetryMetrics& metrics);
475
+
476
+ void recordInit(const std::string& model, bool success, const std::string& message = "");
477
+
478
+ void recordCompletion(const std::string& model, bool success,
479
+ double ttft_ms, double tps, double response_time_ms,
480
+ int tokens, const std::string& message = "");
481
+
482
+ void recordEmbedding(const std::string& model, bool success,
483
+ const std::string& message = "");
484
+
485
+ void recordTranscription(const std::string& model, bool success,
486
+ double ttft_ms, double tps, double response_time_ms,
487
+ int tokens, const std::string& message = "");
488
+
489
+ bool isEnabled() const;
490
+
491
+ private:
492
+ CactusTelemetry();
493
+ ~CactusTelemetry() = default;
494
+
495
+ CactusTelemetry(const CactusTelemetry&) = delete;
496
+ CactusTelemetry& operator=(const CactusTelemetry&) = delete;
497
+
498
+ void sendToSupabase(const TelemetryMetrics& metrics);
499
+
500
+ bool enabled_ = false;
501
+ bool initialized_ = false;
502
+ std::string telemetry_token_;
503
+ std::string project_id_;
504
+ std::string device_id_;
505
+
506
+ mutable std::mutex mutex_;
507
+ };
508
+
509
+ static const std::string SUPABASE_URL = "https://vlqqczxwyaodtcdmdmlw.supabase.co";
510
+ static const std::string SUPABASE_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InZscXFjenh3eWFvZHRjZG1kbWx3Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTE1MTg2MzIsImV4cCI6MjA2NzA5NDYzMn0.nBzqGuK9j6RZ6mOPWU2boAC_5H9XDs-fPpo5P3WZYbI";
511
+
512
+ inline CactusTelemetry& CactusTelemetry::getInstance() {
513
+ static CactusTelemetry instance;
514
+ return instance;
515
+ }
516
+
517
+ inline CactusTelemetry::CactusTelemetry() {
518
+ // Device ID and project ID are now initialized lazily
519
+ }
520
+
521
+ inline void CactusTelemetry::setEnabled(bool enabled) {
522
+ std::lock_guard<std::mutex> lock(mutex_);
523
+ enabled_ = enabled;
524
+ }
525
+
526
+ inline void CactusTelemetry::setTelemetryToken(const std::string& token) {
527
+ std::lock_guard<std::mutex> lock(mutex_);
528
+ telemetry_token_ = token;
529
+ }
530
+
531
+ inline void CactusTelemetry::setProjectId(const std::string& project_id) {
532
+ std::lock_guard<std::mutex> lock(mutex_);
533
+ project_id_ = project_id;
534
+ }
535
+
536
+ inline void CactusTelemetry::ensureInitialized() {
537
+ std::lock_guard<std::mutex> lock(mutex_);
538
+ if (!initialized_) {
539
+ std::string pro_key = DeviceManager::getProKey();
540
+ if (!pro_key.empty()) {
541
+ get_device_id(pro_key.c_str());
542
+ }
543
+ #ifdef CACTUS_TELEMETRY_ENABLED
544
+ device_id_ = DeviceManager::getDeviceId();
545
+ project_id_ = DeviceManager::getProjectId();
546
+ #endif
547
+ initialized_ = true;
548
+ }
549
+ }
550
+
551
+ inline bool CactusTelemetry::isEnabled() const {
552
+ std::lock_guard<std::mutex> lock(mutex_);
553
+ return enabled_ && !telemetry_token_.empty();
554
+ }
555
+
556
+ inline void CactusTelemetry::sendToSupabase([[maybe_unused]] const TelemetryMetrics& metrics) {
557
+ #ifdef CACTUS_TELEMETRY_ENABLED
558
+ std::string telemetry_token;
559
+ std::string project_id;
560
+ std::string device_id;
561
+ {
562
+ std::lock_guard<std::mutex> lock(mutex_);
563
+ telemetry_token = telemetry_token_;
564
+ project_id = project_id_;
565
+ device_id = device_id_;
566
+ }
567
+
568
+ std::string log_json = LogRecord::buildJson(metrics, project_id, device_id, telemetry_token);
569
+
570
+ std::string payload = "[" + log_json + "]";
571
+
572
+ std::map<std::string, std::string> headers;
573
+ headers["apikey"] = SUPABASE_KEY;
574
+ headers["Authorization"] = "Bearer " + SUPABASE_KEY;
575
+ headers["Content-Type"] = "application/json";
576
+ headers["Prefer"] = "return=minimal";
577
+ headers["Content-Profile"] = "cactus";
578
+
579
+ std::string url = SUPABASE_URL + "/rest/v1/logs";
580
+ HttpClient::postJson(url, headers, payload);
581
+ #else
582
+ (void)metrics;
583
+ #endif
584
+ }
585
+
586
+ inline void CactusTelemetry::recordEvent(const TelemetryMetrics& metrics) {
587
+ if (!isEnabled()) {
588
+ return;
589
+ }
590
+ std::thread([this, metrics]() {
591
+ sendToSupabase(metrics);
592
+ }).detach();
593
+ }
594
+
595
+ inline void CactusTelemetry::recordInit(const std::string& model, bool success,
596
+ const std::string& message) {
597
+ TelemetryMetrics metrics;
598
+ metrics.event_type = TelemetryEventType::Init;
599
+ metrics.model = model;
600
+ metrics.success = success;
601
+ metrics.message = message;
602
+ metrics.timestamp = std::chrono::system_clock::now();
603
+
604
+ recordEvent(metrics);
605
+ }
606
+
607
+ inline void CactusTelemetry::recordCompletion(const std::string& model, bool success,
608
+ double ttft_ms, double tps, double response_time_ms,
609
+ int tokens, const std::string& message) {
610
+ TelemetryMetrics metrics;
611
+ metrics.event_type = TelemetryEventType::Completion;
612
+ metrics.model = model;
613
+ metrics.success = success;
614
+ metrics.ttft_ms = ttft_ms;
615
+ metrics.tps = tps;
616
+ metrics.response_time_ms = response_time_ms;
617
+ metrics.tokens = tokens;
618
+ metrics.message = message;
619
+ metrics.timestamp = std::chrono::system_clock::now();
620
+
621
+ recordEvent(metrics);
622
+ }
623
+
624
+ inline void CactusTelemetry::recordEmbedding(const std::string& model, bool success,
625
+ const std::string& message) {
626
+ TelemetryMetrics metrics;
627
+ metrics.event_type = TelemetryEventType::Embedding;
628
+ metrics.model = model;
629
+ metrics.success = success;
630
+ metrics.message = message;
631
+ metrics.timestamp = std::chrono::system_clock::now();
632
+
633
+ recordEvent(metrics);
634
+ }
635
+
636
+ inline void CactusTelemetry::recordTranscription(const std::string& model, bool success,
637
+ double ttft_ms, double tps, double response_time_ms,
638
+ int tokens, const std::string& message) {
639
+ TelemetryMetrics metrics;
640
+ metrics.event_type = TelemetryEventType::Transcription;
641
+ metrics.model = model;
642
+ metrics.success = success;
643
+ metrics.response_time_ms = response_time_ms;
644
+ metrics.ttft_ms = ttft_ms;
645
+ metrics.tps = tps;
646
+ metrics.tokens = tokens;
647
+ metrics.message = message;
648
+ metrics.timestamp = std::chrono::system_clock::now();
649
+
650
+ recordEvent(metrics);
651
+ }
652
+
653
+ } // namespace ffi
654
+ } // namespace cactus
655
+
656
+ #endif // CACTUS_TELEMETRY_H