llama-cpp-pro 0.2.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 (310) hide show
  1. package/CHANGELOG.md +295 -0
  2. package/LICENSE +21 -0
  3. package/LlamaCpp.podspec +33 -0
  4. package/LlamaCppCapacitor.podspec +33 -0
  5. package/Package.swift +29 -0
  6. package/README.md +93 -0
  7. package/android/build.gradle +88 -0
  8. package/android/src/main/AndroidManifest.xml +4 -0
  9. package/android/src/main/CMakeLists-arm64.txt +138 -0
  10. package/android/src/main/CMakeLists-x86_64.txt +141 -0
  11. package/android/src/main/CMakeLists.txt +138 -0
  12. package/android/src/main/java/ai/annadata/plugin/capacitor/LlamaCpp.java +1340 -0
  13. package/android/src/main/java/ai/annadata/plugin/capacitor/LlamaCppPlugin.java +814 -0
  14. package/android/src/main/java/ai/annadata/plugin/capacitor/ModelAdmissionController.java +214 -0
  15. package/android/src/main/jni-chat-session.cpp +261 -0
  16. package/android/src/main/jni-lora.cpp +197 -0
  17. package/android/src/main/jni-multimodal.cpp +167 -0
  18. package/android/src/main/jni-tts.cpp +290 -0
  19. package/android/src/main/jni-utils.h +148 -0
  20. package/android/src/main/jni.cpp +1808 -0
  21. package/android/src/main/jniLibs/arm64-v8a/libllama-cpp-arm64.so +0 -0
  22. package/android/src/main/res/.gitkeep +0 -0
  23. package/build-native.sh +280 -0
  24. package/cmake/desktop-metal-embed.cmake +30 -0
  25. package/cmake/desktop-sources.cmake +100 -0
  26. package/cmake/ggml-backends.cmake +44 -0
  27. package/cpp/LICENSE +21 -0
  28. package/cpp/README.md +15 -0
  29. package/cpp/anyascii.c +22223 -0
  30. package/cpp/anyascii.h +42 -0
  31. package/cpp/cap-completion.cpp +942 -0
  32. package/cpp/cap-completion.h +127 -0
  33. package/cpp/cap-embedding.cpp +193 -0
  34. package/cpp/cap-embedding.h +35 -0
  35. package/cpp/cap-ios-bridge.cpp +1810 -0
  36. package/cpp/cap-ios-bridge.h +61 -0
  37. package/cpp/cap-llama.cpp +412 -0
  38. package/cpp/cap-llama.h +161 -0
  39. package/cpp/cap-mtmd.hpp +602 -0
  40. package/cpp/cap-native-server.cpp +1095 -0
  41. package/cpp/cap-native-server.h +40 -0
  42. package/cpp/cap-tts.cpp +591 -0
  43. package/cpp/cap-tts.h +59 -0
  44. package/cpp/cap-wasm-fs.cpp +156 -0
  45. package/cpp/cap-wasm-jspi.cpp +19 -0
  46. package/cpp/cap-wasm-jspi.h +30 -0
  47. package/cpp/cap-wasm-vfs.cpp +22 -0
  48. package/cpp/chat-parser.cpp +393 -0
  49. package/cpp/chat-parser.h +120 -0
  50. package/cpp/chat.cpp +2315 -0
  51. package/cpp/chat.h +221 -0
  52. package/cpp/common.cpp +1664 -0
  53. package/cpp/common.h +744 -0
  54. package/cpp/ggml-alloc.c +1028 -0
  55. package/cpp/ggml-alloc.h +76 -0
  56. package/cpp/ggml-backend-impl.h +255 -0
  57. package/cpp/ggml-backend-reg.cpp +600 -0
  58. package/cpp/ggml-backend.cpp +2121 -0
  59. package/cpp/ggml-backend.h +354 -0
  60. package/cpp/ggml-common.h +1878 -0
  61. package/cpp/ggml-cpp.h +39 -0
  62. package/cpp/ggml-cpu/amx/amx.cpp +221 -0
  63. package/cpp/ggml-cpu/amx/amx.h +8 -0
  64. package/cpp/ggml-cpu/amx/common.h +91 -0
  65. package/cpp/ggml-cpu/amx/mmq.cpp +2512 -0
  66. package/cpp/ggml-cpu/amx/mmq.h +10 -0
  67. package/cpp/ggml-cpu/arch/arm/cpu-feats.cpp +94 -0
  68. package/cpp/ggml-cpu/arch/arm/quants.c +3650 -0
  69. package/cpp/ggml-cpu/arch/arm/repack.cpp +1891 -0
  70. package/cpp/ggml-cpu/arch/x86/cpu-feats.cpp +327 -0
  71. package/cpp/ggml-cpu/arch/x86/quants.c +3820 -0
  72. package/cpp/ggml-cpu/arch/x86/repack.cpp +6307 -0
  73. package/cpp/ggml-cpu/arch-fallback.h +215 -0
  74. package/cpp/ggml-cpu/binary-ops.cpp +158 -0
  75. package/cpp/ggml-cpu/binary-ops.h +16 -0
  76. package/cpp/ggml-cpu/common.h +73 -0
  77. package/cpp/ggml-cpu/ggml-cpu-impl.h +559 -0
  78. package/cpp/ggml-cpu/ggml-cpu.c +3578 -0
  79. package/cpp/ggml-cpu/ggml-cpu.cpp +672 -0
  80. package/cpp/ggml-cpu/ops.cpp +10587 -0
  81. package/cpp/ggml-cpu/ops.h +114 -0
  82. package/cpp/ggml-cpu/quants.c +1193 -0
  83. package/cpp/ggml-cpu/quants.h +97 -0
  84. package/cpp/ggml-cpu/repack.cpp +1982 -0
  85. package/cpp/ggml-cpu/repack.h +120 -0
  86. package/cpp/ggml-cpu/simd-mappings.h +1184 -0
  87. package/cpp/ggml-cpu/traits.cpp +36 -0
  88. package/cpp/ggml-cpu/traits.h +38 -0
  89. package/cpp/ggml-cpu/unary-ops.cpp +186 -0
  90. package/cpp/ggml-cpu/unary-ops.h +28 -0
  91. package/cpp/ggml-cpu/vec.cpp +348 -0
  92. package/cpp/ggml-cpu/vec.h +1121 -0
  93. package/cpp/ggml-cpu.h +145 -0
  94. package/cpp/ggml-impl.h +622 -0
  95. package/cpp/ggml-metal-impl.h +688 -0
  96. package/cpp/ggml-metal.h +66 -0
  97. package/cpp/ggml-metal.m +6833 -0
  98. package/cpp/ggml-metal.metal +10754 -0
  99. package/cpp/ggml-opt.cpp +1093 -0
  100. package/cpp/ggml-opt.h +256 -0
  101. package/cpp/ggml-quants.c +5324 -0
  102. package/cpp/ggml-quants.h +106 -0
  103. package/cpp/ggml-threading.cpp +12 -0
  104. package/cpp/ggml-threading.h +14 -0
  105. package/cpp/ggml.c +7108 -0
  106. package/cpp/ggml.h +2492 -0
  107. package/cpp/gguf.cpp +1358 -0
  108. package/cpp/gguf.h +202 -0
  109. package/cpp/json-partial.cpp +256 -0
  110. package/cpp/json-partial.h +38 -0
  111. package/cpp/json-schema-to-grammar.cpp +985 -0
  112. package/cpp/json-schema-to-grammar.h +21 -0
  113. package/cpp/llama-adapter.cpp +388 -0
  114. package/cpp/llama-adapter.h +76 -0
  115. package/cpp/llama-arch.cpp +2355 -0
  116. package/cpp/llama-arch.h +499 -0
  117. package/cpp/llama-batch.cpp +875 -0
  118. package/cpp/llama-batch.h +160 -0
  119. package/cpp/llama-chat.cpp +783 -0
  120. package/cpp/llama-chat.h +65 -0
  121. package/cpp/llama-context.cpp +2788 -0
  122. package/cpp/llama-context.h +306 -0
  123. package/cpp/llama-cparams.cpp +5 -0
  124. package/cpp/llama-cparams.h +41 -0
  125. package/cpp/llama-cpp.h +30 -0
  126. package/cpp/llama-grammar.cpp +1229 -0
  127. package/cpp/llama-grammar.h +173 -0
  128. package/cpp/llama-graph.cpp +1891 -0
  129. package/cpp/llama-graph.h +810 -0
  130. package/cpp/llama-hparams.cpp +180 -0
  131. package/cpp/llama-hparams.h +233 -0
  132. package/cpp/llama-impl.cpp +167 -0
  133. package/cpp/llama-impl.h +61 -0
  134. package/cpp/llama-io.cpp +15 -0
  135. package/cpp/llama-io.h +35 -0
  136. package/cpp/llama-kv-cache-iswa.cpp +318 -0
  137. package/cpp/llama-kv-cache-iswa.h +135 -0
  138. package/cpp/llama-kv-cache.cpp +2059 -0
  139. package/cpp/llama-kv-cache.h +374 -0
  140. package/cpp/llama-kv-cells.h +491 -0
  141. package/cpp/llama-memory-hybrid.cpp +258 -0
  142. package/cpp/llama-memory-hybrid.h +137 -0
  143. package/cpp/llama-memory-recurrent.cpp +1146 -0
  144. package/cpp/llama-memory-recurrent.h +179 -0
  145. package/cpp/llama-memory.cpp +59 -0
  146. package/cpp/llama-memory.h +119 -0
  147. package/cpp/llama-mmap.cpp +609 -0
  148. package/cpp/llama-mmap.h +68 -0
  149. package/cpp/llama-model-loader.cpp +1166 -0
  150. package/cpp/llama-model-loader.h +170 -0
  151. package/cpp/llama-model-saver.cpp +282 -0
  152. package/cpp/llama-model-saver.h +37 -0
  153. package/cpp/llama-model.cpp +19061 -0
  154. package/cpp/llama-model.h +491 -0
  155. package/cpp/llama-sampling.cpp +2575 -0
  156. package/cpp/llama-sampling.h +32 -0
  157. package/cpp/llama-vocab.cpp +3792 -0
  158. package/cpp/llama-vocab.h +176 -0
  159. package/cpp/llama.cpp +358 -0
  160. package/cpp/llama.h +1373 -0
  161. package/cpp/log.cpp +428 -0
  162. package/cpp/log.h +103 -0
  163. package/cpp/minja/chat-template.hpp +550 -0
  164. package/cpp/minja/minja.hpp +3009 -0
  165. package/cpp/nlohmann/json.hpp +25526 -0
  166. package/cpp/nlohmann/json_fwd.hpp +187 -0
  167. package/cpp/regex-partial.cpp +204 -0
  168. package/cpp/regex-partial.h +56 -0
  169. package/cpp/sampling.cpp +579 -0
  170. package/cpp/sampling.h +107 -0
  171. package/cpp/tools/mtmd/clip-impl.h +473 -0
  172. package/cpp/tools/mtmd/clip.cpp +4322 -0
  173. package/cpp/tools/mtmd/clip.h +106 -0
  174. package/cpp/tools/mtmd/miniaudio/miniaudio.h +93468 -0
  175. package/cpp/tools/mtmd/mtmd-audio.cpp +769 -0
  176. package/cpp/tools/mtmd/mtmd-audio.h +47 -0
  177. package/cpp/tools/mtmd/mtmd-helper.cpp +460 -0
  178. package/cpp/tools/mtmd/mtmd-helper.h +95 -0
  179. package/cpp/tools/mtmd/mtmd.cpp +1066 -0
  180. package/cpp/tools/mtmd/mtmd.h +302 -0
  181. package/cpp/tools/mtmd/stb/stb_image.h +7988 -0
  182. package/cpp/unicode-data.cpp +7034 -0
  183. package/cpp/unicode-data.h +20 -0
  184. package/cpp/unicode.cpp +1061 -0
  185. package/cpp/unicode.h +68 -0
  186. package/cpp/vendor/cpp-httplib/httplib.cpp +16509 -0
  187. package/cpp/vendor/cpp-httplib/httplib.h +3883 -0
  188. package/desktop/electron-builder.config.cjs +157 -0
  189. package/desktop/entitlements.mac.plist +12 -0
  190. package/desktop/package.json +11 -0
  191. package/desktop/resolve-package-root.cjs +88 -0
  192. package/desktop/src/main/backend-selector.cjs +148 -0
  193. package/desktop/src/main/gpu-probe.cjs +142 -0
  194. package/desktop/src/main/index.cjs +58 -0
  195. package/desktop/src/main/ipc-handlers.cjs +212 -0
  196. package/desktop/src/main/model-store.cjs +50 -0
  197. package/desktop/src/main/preload.cjs +39 -0
  198. package/desktop/src/main/sidecar-client.cjs +76 -0
  199. package/desktop/src/main/sidecar-manager.cjs +364 -0
  200. package/dist/docs.json +14357 -0
  201. package/dist/esm/definitions.d.ts +731 -0
  202. package/dist/esm/definitions.js +2 -0
  203. package/dist/esm/definitions.js.map +1 -0
  204. package/dist/esm/desktop.d.ts +37 -0
  205. package/dist/esm/desktop.js +133 -0
  206. package/dist/esm/desktop.js.map +1 -0
  207. package/dist/esm/index.d.ts +200 -0
  208. package/dist/esm/index.js +612 -0
  209. package/dist/esm/index.js.map +1 -0
  210. package/dist/esm/isomorphic/desktop.runtime.d.ts +37 -0
  211. package/dist/esm/isomorphic/desktop.runtime.js +36 -0
  212. package/dist/esm/isomorphic/desktop.runtime.js.map +1 -0
  213. package/dist/esm/isomorphic/errors.d.ts +6 -0
  214. package/dist/esm/isomorphic/errors.js +9 -0
  215. package/dist/esm/isomorphic/errors.js.map +1 -0
  216. package/dist/esm/isomorphic/model.admission.d.ts +17 -0
  217. package/dist/esm/isomorphic/model.admission.js +27 -0
  218. package/dist/esm/isomorphic/model.admission.js.map +1 -0
  219. package/dist/esm/isomorphic/model.scheduler.d.ts +31 -0
  220. package/dist/esm/isomorphic/model.scheduler.js +95 -0
  221. package/dist/esm/isomorphic/model.scheduler.js.map +1 -0
  222. package/dist/esm/isomorphic/provider.desktop.d.ts +40 -0
  223. package/dist/esm/isomorphic/provider.desktop.js +411 -0
  224. package/dist/esm/isomorphic/provider.desktop.js.map +1 -0
  225. package/dist/esm/isomorphic/provider.factory.d.ts +2 -0
  226. package/dist/esm/isomorphic/provider.factory.js +16 -0
  227. package/dist/esm/isomorphic/provider.factory.js.map +1 -0
  228. package/dist/esm/isomorphic/provider.interface.d.ts +76 -0
  229. package/dist/esm/isomorphic/provider.interface.js +2 -0
  230. package/dist/esm/isomorphic/provider.interface.js.map +1 -0
  231. package/dist/esm/isomorphic/provider.native.d.ts +18 -0
  232. package/dist/esm/isomorphic/provider.native.js +173 -0
  233. package/dist/esm/isomorphic/provider.native.js.map +1 -0
  234. package/dist/esm/isomorphic/provider.web.d.ts +88 -0
  235. package/dist/esm/isomorphic/provider.web.js +573 -0
  236. package/dist/esm/isomorphic/provider.web.js.map +1 -0
  237. package/dist/esm/isomorphic/sidecar-sse.d.ts +14 -0
  238. package/dist/esm/isomorphic/sidecar-sse.js +76 -0
  239. package/dist/esm/isomorphic/sidecar-sse.js.map +1 -0
  240. package/dist/esm/isomorphic/wasmMemoryCalibration.d.ts +26 -0
  241. package/dist/esm/isomorphic/wasmMemoryCalibration.js +60 -0
  242. package/dist/esm/isomorphic/wasmMemoryCalibration.js.map +1 -0
  243. package/dist/esm/isomorphic/wasmMemoryPolicy.d.ts +55 -0
  244. package/dist/esm/isomorphic/wasmMemoryPolicy.js +93 -0
  245. package/dist/esm/isomorphic/wasmMemoryPolicy.js.map +1 -0
  246. package/dist/esm/storage/manifest.d.ts +13 -0
  247. package/dist/esm/storage/manifest.js +87 -0
  248. package/dist/esm/storage/manifest.js.map +1 -0
  249. package/dist/esm/storage/opfs.store.d.ts +31 -0
  250. package/dist/esm/storage/opfs.store.js +241 -0
  251. package/dist/esm/storage/opfs.store.js.map +1 -0
  252. package/dist/esm/web.d.ts +206 -0
  253. package/dist/esm/web.js +521 -0
  254. package/dist/esm/web.js.map +1 -0
  255. package/dist/esm/workers/async-file.d.ts +13 -0
  256. package/dist/esm/workers/async-file.js +12 -0
  257. package/dist/esm/workers/async-file.js.map +1 -0
  258. package/dist/esm/workers/heapfs.d.ts +55 -0
  259. package/dist/esm/workers/heapfs.js +115 -0
  260. package/dist/esm/workers/heapfs.js.map +1 -0
  261. package/dist/esm/workers/wasm.engine.d.ts +86 -0
  262. package/dist/esm/workers/wasm.engine.js +504 -0
  263. package/dist/esm/workers/wasm.engine.js.map +1 -0
  264. package/dist/esm/workers/worker.protocol.d.ts +160 -0
  265. package/dist/esm/workers/worker.protocol.js +2 -0
  266. package/dist/esm/workers/worker.protocol.js.map +1 -0
  267. package/dist/plugin.cjs +3179 -0
  268. package/dist/plugin.cjs.map +1 -0
  269. package/dist/plugin.js +3181 -0
  270. package/dist/plugin.js.map +1 -0
  271. package/dist/wasm/llama_engine.d.ts +74 -0
  272. package/dist/wasm/llama_engine.js +1829 -0
  273. package/dist/wasm/llama_engine.wasm +0 -0
  274. package/dist/wasm/llama_engine_emscripten.mjs +2 -0
  275. package/dist/wasm/package.json +16 -0
  276. package/dist/workers/llm.worker.js +1226 -0
  277. package/dist/workers/llm.worker.js.map +7 -0
  278. package/extraResources/llama-wasm/llama_engine.d.ts +74 -0
  279. package/extraResources/llama-wasm/llama_engine.js +1829 -0
  280. package/extraResources/llama-wasm/llama_engine.wasm +0 -0
  281. package/extraResources/llama-wasm/llama_engine_emscripten.mjs +2 -0
  282. package/extraResources/llama-wasm/package.json +16 -0
  283. package/extraResources/sidecar/README.md +11 -0
  284. package/extraResources/sidecar/darwin-arm64 +0 -0
  285. package/extraResources/sidecar/darwin-x64 +0 -0
  286. package/ios/CMakeLists-arm64.txt +161 -0
  287. package/ios/CMakeLists-x86_64.txt +188 -0
  288. package/ios/CMakeLists.txt +161 -0
  289. package/ios/Frameworks/llama-cpp.framework/Info.plist +28 -0
  290. package/ios/Frameworks/llama-cpp.framework/llama-cpp +0 -0
  291. package/ios/Sources/LlamaCppCapacitor/LlamaCpp.swift +1365 -0
  292. package/ios/Sources/LlamaCppCapacitor/LlamaCppPlugin.swift +694 -0
  293. package/ios/Sources/LlamaCppCapacitor/LlamaNativeBridge.swift +349 -0
  294. package/ios/Sources/LlamaCppCapacitor/ModelAdmissionController.swift +177 -0
  295. package/ios/embed-metal-shaders.sh +24 -0
  296. package/ios/metal-embed.cmake +29 -0
  297. package/package.json +281 -0
  298. package/scripts/build-js-preserve-wasm.cjs +53 -0
  299. package/scripts/build-sidecar-linux.sh +6 -0
  300. package/scripts/build-sidecar-win.bat +19 -0
  301. package/scripts/build-sidecar.sh +184 -0
  302. package/scripts/embed-llama-ios-app-framework.sh +63 -0
  303. package/scripts/ensure-desktop-sidecar-bundle.cjs +103 -0
  304. package/scripts/ensure-llama-ios-xcframework.sh +108 -0
  305. package/scripts/fix-esm-extensions.cjs +45 -0
  306. package/scripts/prepare-js-dist.cjs +28 -0
  307. package/scripts/stage-desktop-resources.cjs +116 -0
  308. package/sidecar/CMakeLists.txt +192 -0
  309. package/sidecar/cap-sidecar-main.cpp +68 -0
  310. package/types/llama-cpp-pro.d.ts +441 -0
@@ -0,0 +1,1810 @@
1
+ // iOS / dlsym C bridge — mirrors android/jni.cpp context lifecycle and core calls.
2
+
3
+ #include "cap-llama.h"
4
+ #include "cap-completion.h"
5
+ #include "cap-embedding.h"
6
+ #include "cap-ios-bridge.h"
7
+ #include "json-schema-to-grammar.h"
8
+
9
+ #include <chrono>
10
+ #include <cstdint>
11
+ #include <cstdio>
12
+ #include <cstring>
13
+ #include <algorithm>
14
+ #include <filesystem>
15
+ #ifdef __EMSCRIPTEN__
16
+ #include <unistd.h>
17
+ #endif
18
+ #include <fstream>
19
+ #include <map>
20
+ #include <memory>
21
+ #include <mutex>
22
+ #include <string>
23
+ #include <vector>
24
+
25
+ #include "nlohmann/json.hpp"
26
+
27
+ using json = nlohmann::ordered_json;
28
+
29
+ bool capllama_verbose = false;
30
+
31
+ extern "C" void llama_embedding_register_context(int64_t contextId, void * contextPtr);
32
+ extern "C" void llama_embedding_unregister_context(int64_t contextId);
33
+ #ifdef CAPLLAMA_BUILD_WASM
34
+ extern "C" void cap_wasm_ensure_tmp_dir(void);
35
+ #endif
36
+
37
+ namespace {
38
+
39
+ std::mutex g_mutex;
40
+ std::map<int64_t, std::unique_ptr<capllama::llama_cap_context>> g_contexts;
41
+ int64_t g_next_id = 1;
42
+
43
+ thread_local std::string g_tls_str;
44
+
45
+ const char * tls_cstr(const std::string & s) {
46
+ g_tls_str = s;
47
+ return g_tls_str.c_str();
48
+ }
49
+
50
+ capllama::llama_cap_context * get_ctx(int64_t id) {
51
+ auto it = g_contexts.find(id);
52
+ if (it == g_contexts.end() || !it->second) {
53
+ return nullptr;
54
+ }
55
+ return it->second.get();
56
+ }
57
+
58
+ llama_model * resolve_model(capllama::llama_cap_context * ctx) {
59
+ if (!ctx) {
60
+ return nullptr;
61
+ }
62
+ if (ctx->model) {
63
+ return ctx->model;
64
+ }
65
+ if (ctx->ctx) {
66
+ return const_cast<llama_model *>(llama_get_model(ctx->ctx));
67
+ }
68
+ if (ctx->llama_init.model) {
69
+ return ctx->llama_init.model.get();
70
+ }
71
+ return nullptr;
72
+ }
73
+
74
+ void apply_params_json(common_params & cparams, const json * j) {
75
+ if (!j || !j->is_object()) {
76
+ return;
77
+ }
78
+ const json & p = *j;
79
+ try {
80
+ if (p.contains("embedding") && p["embedding"].is_boolean()) {
81
+ cparams.embedding = p["embedding"].get<bool>();
82
+ }
83
+ if (p.contains("n_ctx") && p["n_ctx"].is_number_integer()) {
84
+ cparams.n_ctx = p["n_ctx"].get<int>();
85
+ }
86
+ if (p.contains("n_batch") && p["n_batch"].is_number_integer()) {
87
+ cparams.n_batch = p["n_batch"].get<int>();
88
+ }
89
+ if (p.contains("n_threads") && p["n_threads"].is_number_integer()) {
90
+ cparams.cpuparams.n_threads = p["n_threads"].get<int>();
91
+ cparams.cpuparams_batch.n_threads = p["n_threads"].get<int>();
92
+ }
93
+ if (p.contains("n_gpu_layers") && p["n_gpu_layers"].is_number_integer()) {
94
+ cparams.n_gpu_layers = p["n_gpu_layers"].get<int>();
95
+ }
96
+ if (p.contains("use_mmap") && p["use_mmap"].is_boolean()) {
97
+ cparams.use_mmap = p["use_mmap"].get<bool>();
98
+ }
99
+ if (p.contains("use_mlock") && p["use_mlock"].is_boolean()) {
100
+ cparams.use_mlock = p["use_mlock"].get<bool>();
101
+ }
102
+ if (p.contains("chat_template") && p["chat_template"].is_string()) {
103
+ cparams.chat_template = p["chat_template"].get<std::string>();
104
+ }
105
+ if (p.contains("pooling_type")) {
106
+ if (p["pooling_type"].is_number_integer()) {
107
+ const int pooling_type_int = p["pooling_type"].get<int>();
108
+ if (pooling_type_int > 0) {
109
+ cparams.embedding = true;
110
+ }
111
+ } else if (p["pooling_type"].is_string()) {
112
+ const std::string pooling_type_str = p["pooling_type"].get<std::string>();
113
+ if (!pooling_type_str.empty() && pooling_type_str != "none") {
114
+ cparams.embedding = true;
115
+ }
116
+ }
117
+ }
118
+ } catch (...) {
119
+ }
120
+ }
121
+
122
+ static bool vfs_path_exists(const std::string & path) {
123
+ if (path.empty()) {
124
+ return false;
125
+ }
126
+ #ifdef __EMSCRIPTEN__
127
+ // Files written via Emscripten FS.open/write are visible to fopen/access but
128
+ // std::filesystem::exists can return false for MEMFS paths in some builds.
129
+ if (path[0] == '/') {
130
+ return access(path.c_str(), F_OK) == 0;
131
+ }
132
+ #endif
133
+ return std::filesystem::exists(path);
134
+ }
135
+
136
+ std::string resolve_model_path(const std::string & primary, const json * params_j) {
137
+ std::vector<std::string> candidates;
138
+ candidates.push_back(primary);
139
+ if (params_j && params_j->contains("search_paths") && (*params_j)["search_paths"].is_array()) {
140
+ for (const auto & el : (*params_j)["search_paths"]) {
141
+ if (el.is_string()) {
142
+ candidates.push_back(el.get<std::string>());
143
+ }
144
+ }
145
+ }
146
+ for (const auto & path : candidates) {
147
+ if (vfs_path_exists(path)) {
148
+ return path;
149
+ }
150
+ }
151
+ return {};
152
+ }
153
+
154
+ bool load_with_fallback(std::unique_ptr<capllama::llama_cap_context> & context, common_params cparams) {
155
+ #ifdef __EMSCRIPTEN__
156
+ fprintf(stderr,
157
+ "@@WASM_LOAD@@ phase=primary begin path=%s n_ctx=%d n_batch=%d n_threads=%d use_mmap=%d\n",
158
+ cparams.model.path.c_str(), cparams.n_ctx, cparams.n_batch,
159
+ cparams.cpuparams.n_threads, cparams.use_mmap ? 1 : 0);
160
+ #endif
161
+ try {
162
+ if (context->loadModel(cparams)) {
163
+ #ifdef __EMSCRIPTEN__
164
+ // P2: Enhanced phase marker with model path on success
165
+ fprintf(stderr, "@@WASM_LOAD@@ phase=primary ok model=%s n_ctx=%d n_batch=%d\n",
166
+ cparams.model.path.c_str(), cparams.n_ctx, cparams.n_batch);
167
+ #endif
168
+ return true;
169
+ }
170
+ #ifdef __EMSCRIPTEN__
171
+ // P2: Enhanced failure marker
172
+ fprintf(stderr, "@@WASM_LOAD@@ phase=primary failed (loadModel returned false) — check WASM memory\n");
173
+ #endif
174
+ } catch (const std::exception & e) {
175
+ fprintf(stderr, "loadModel exception: %s\n", e.what());
176
+ #ifdef __EMSCRIPTEN__
177
+ fprintf(stderr, "@@WASM_LOAD@@ phase=primary failed (C++ exception — no fallback, WASM trap may follow)\n");
178
+ #endif
179
+ return false;
180
+ } catch (...) {
181
+ fprintf(stderr, "loadModel unknown exception\n");
182
+ #ifdef __EMSCRIPTEN__
183
+ fprintf(stderr, "@@WASM_LOAD@@ phase=primary failed (unknown exception — no fallback)\n");
184
+ #endif
185
+ return false;
186
+ }
187
+
188
+ #ifdef __EMSCRIPTEN__
189
+ // Primary already uses effectiveNctx clamps (n_ctx<=512, n_batch<=8 for large async models).
190
+ // A second loadModel() on the same context wastes heap and confuses stderr ordering.
191
+ if (!cparams.embedding && cparams.n_ctx <= 512 && cparams.n_batch <= 8) {
192
+ fprintf(stderr,
193
+ "@@WASM_LOAD@@ skip fallback: params already at WASM minimum (n_ctx=%d n_batch=%d use_mmap=%d)\n",
194
+ cparams.n_ctx, cparams.n_batch, cparams.use_mmap ? 1 : 0);
195
+ return false;
196
+ }
197
+ #endif
198
+
199
+ common_params minimal;
200
+ minimal.model.path = cparams.model.path;
201
+ minimal.n_batch = 128;
202
+ minimal.n_gpu_layers = 0;
203
+ minimal.use_mlock = false;
204
+ minimal.numa = LM_GGML_NUMA_STRATEGY_DISABLED;
205
+ minimal.ctx_shift = false;
206
+ minimal.chat_template = cparams.chat_template;
207
+ minimal.embedding = cparams.embedding;
208
+ minimal.cont_batching = false;
209
+ minimal.n_parallel = 1;
210
+ minimal.antiprompt.clear();
211
+ minimal.vocab_only = false;
212
+ minimal.rope_scaling_type = LLAMA_ROPE_SCALING_TYPE_UNSPECIFIED;
213
+ minimal.yarn_ext_factor = -1.0f;
214
+ minimal.yarn_attn_factor = 1.0f;
215
+ minimal.yarn_beta_fast = 32.0f;
216
+ minimal.yarn_beta_slow = 1.0f;
217
+ minimal.yarn_orig_ctx = 0;
218
+ minimal.flash_attn = false;
219
+ minimal.n_keep = 0;
220
+ minimal.n_chunks = -1;
221
+ minimal.n_sequences = 1;
222
+ minimal.model_alias = "unknown";
223
+ #ifdef __EMSCRIPTEN__
224
+ // Never fall back to use_mmap=false on WASM — copying a 700 MB GGUF OOMs.
225
+ minimal.use_mmap = cparams.use_mmap;
226
+ minimal.n_ctx = cparams.embedding ? 64 : 64;
227
+ minimal.n_batch = cparams.embedding ? minimal.n_ctx : 32;
228
+ fprintf(stderr,
229
+ "@@WASM_LOAD@@ phase=fallback begin n_ctx=%d n_batch=%d use_mmap=%d\n",
230
+ minimal.n_ctx, minimal.n_batch, minimal.use_mmap ? 1 : 0);
231
+ #else
232
+ minimal.n_ctx = 256;
233
+ minimal.use_mmap = false;
234
+ #endif
235
+ const bool ok = context->loadModel(minimal);
236
+ #ifdef __EMSCRIPTEN__
237
+ fprintf(stderr, "@@WASM_LOAD@@ phase=fallback %s\n", ok ? "ok" : "failed");
238
+ #endif
239
+ return ok;
240
+ }
241
+
242
+ json default_chat_templates_json() {
243
+ json caps = {
244
+ {"tools", true}, {"toolCalls", true}, {"toolResponses", true},
245
+ {"systemRole", true}, {"parallelToolCalls", true}, {"toolCallId", true},
246
+ };
247
+ json minja = {
248
+ {"default", true},
249
+ {"defaultCaps", caps},
250
+ {"toolUse", true},
251
+ {"toolUseCaps", caps},
252
+ };
253
+ return json::object({{"llamaChat", true}, {"minja", minja}});
254
+ }
255
+
256
+ void parse_completion_params(capllama::llama_cap_context * ctx, const char * params_json, std::string & prompt_out, int & n_predict_out) {
257
+ std::string prompt_str = "Once upon a time";
258
+ int n_predict = 50;
259
+ double temperature = 0.7;
260
+ int top_k = 40;
261
+ double top_p = 0.95;
262
+ float penalty_repeat = 1.1f;
263
+
264
+ if (params_json && std::strlen(params_json) > 0) {
265
+ try {
266
+ json p = json::parse(params_json);
267
+ if (p.contains("prompt") && p["prompt"].is_string()) {
268
+ prompt_str = p["prompt"].get<std::string>();
269
+ }
270
+ if (p.contains("n_predict") && p["n_predict"].is_number_integer()) {
271
+ n_predict = p["n_predict"].get<int>();
272
+ }
273
+ if (p.contains("max_tokens") && p["max_tokens"].is_number_integer()) {
274
+ n_predict = p["max_tokens"].get<int>();
275
+ }
276
+ if (p.contains("temperature") && p["temperature"].is_number()) {
277
+ temperature = p["temperature"].get<double>();
278
+ }
279
+ if (p.contains("top_k") && p["top_k"].is_number_integer()) {
280
+ top_k = p["top_k"].get<int>();
281
+ }
282
+ if (p.contains("top_p") && p["top_p"].is_number()) {
283
+ top_p = p["top_p"].get<double>();
284
+ }
285
+ if (p.contains("penalty_repeat") && p["penalty_repeat"].is_number()) {
286
+ penalty_repeat = static_cast<float>(p["penalty_repeat"].get<double>());
287
+ } else if (p.contains("repeat_penalty") && p["repeat_penalty"].is_number()) {
288
+ penalty_repeat = static_cast<float>(p["repeat_penalty"].get<double>());
289
+ }
290
+ } catch (...) {
291
+ }
292
+ }
293
+
294
+ ctx->params.sampling.temp = static_cast<float>(temperature);
295
+ ctx->params.sampling.top_k = top_k;
296
+ ctx->params.sampling.top_p = static_cast<float>(top_p);
297
+ ctx->params.sampling.penalty_repeat = penalty_repeat;
298
+ ctx->params.n_predict = n_predict;
299
+ ctx->params.prompt = prompt_str;
300
+ prompt_out = prompt_str;
301
+ n_predict_out = n_predict;
302
+ }
303
+
304
+ void apply_completion_stops(capllama::llama_cap_context * ctx, const char * params_json) {
305
+ ctx->params.antiprompt.clear();
306
+ if (!params_json || !ctx) {
307
+ return;
308
+ }
309
+ try {
310
+ json p = json::parse(params_json);
311
+ if (p.contains("stop") && p["stop"].is_array()) {
312
+ for (const auto & el : p["stop"]) {
313
+ if (el.is_string()) {
314
+ const std::string s = el.get<std::string>();
315
+ if (!s.empty()) {
316
+ ctx->params.antiprompt.push_back(s);
317
+ }
318
+ }
319
+ }
320
+ }
321
+ } catch (...) {
322
+ }
323
+ }
324
+
325
+ bool ensure_completion(capllama::llama_cap_context * ctx) {
326
+ if (!ctx || !ctx->ctx || !ctx->model) {
327
+ return false;
328
+ }
329
+ if (!ctx->completion) {
330
+ try {
331
+ ctx->completion = new capllama::llama_cap_context_completion(ctx);
332
+ } catch (...) {
333
+ return false;
334
+ }
335
+ }
336
+ return ctx->completion != nullptr;
337
+ }
338
+
339
+ /** Rewind, apply stops, re-init sampler (must run after every parse_completion_params). */
340
+ bool prepare_completion_run(capllama::llama_cap_context * ctx, const char * params_json) {
341
+ if (!ensure_completion(ctx)) {
342
+ return false;
343
+ }
344
+ ctx->completion->rewind();
345
+ apply_completion_stops(ctx, params_json);
346
+ if (!ctx->completion->initSampling() || !ctx->completion->ctx_sampling) {
347
+ return false;
348
+ }
349
+ return true;
350
+ }
351
+
352
+ int run_completion_loop(
353
+ capllama::llama_cap_context * ctx,
354
+ int n_predict,
355
+ bool & hit_eos_out,
356
+ std::string & generated_text_out)
357
+ {
358
+ generated_text_out.clear();
359
+ hit_eos_out = false;
360
+ if (!ctx || !ctx->completion || !ctx->model) {
361
+ return 0;
362
+ }
363
+
364
+ const llama_vocab * vocab = llama_model_get_vocab(ctx->model);
365
+ int tokens_generated = 0;
366
+
367
+ ctx->completion->beginCompletion();
368
+
369
+ while (tokens_generated < n_predict &&
370
+ ctx->completion->has_next_token &&
371
+ !ctx->completion->is_interrupted) {
372
+ capllama::completion_token_output token_output = ctx->completion->doCompletion();
373
+ if (token_output.tok < 0) {
374
+ hit_eos_out = ctx->completion->stopped_eos;
375
+ #ifdef __EMSCRIPTEN__
376
+ fprintf(stderr, "@@WASM_GEN@@ token_break tok=%d eos=%d n=%d\n",
377
+ token_output.tok, hit_eos_out ? 1 : 0, tokens_generated);
378
+ #endif
379
+ break;
380
+ }
381
+ if (llama_vocab_is_eog(vocab, token_output.tok)) {
382
+ hit_eos_out = true;
383
+ #ifdef __EMSCRIPTEN__
384
+ fprintf(stderr, "@@WASM_GEN@@ eos_at=%d\n", tokens_generated);
385
+ #endif
386
+ break;
387
+ }
388
+ if (ctx->completion->stopped_word || ctx->completion->stopped_limit) {
389
+ break;
390
+ }
391
+ tokens_generated++;
392
+ }
393
+
394
+ generated_text_out = ctx->completion->generated_text;
395
+ ctx->completion->endCompletion();
396
+ return tokens_generated;
397
+ }
398
+
399
+ char * dup_c_string(const std::string & value) {
400
+ char * buffer = new (std::nothrow) char[value.size() + 1];
401
+ if (buffer == nullptr) {
402
+ return nullptr;
403
+ }
404
+ std::memcpy(buffer, value.c_str(), value.size() + 1);
405
+ return buffer;
406
+ }
407
+
408
+ std::vector<std::string> read_media_paths_json(const json & root) {
409
+ std::vector<std::string> media_paths;
410
+ if (!root.contains("media_paths") || !root["media_paths"].is_array()) {
411
+ return media_paths;
412
+ }
413
+ for (const auto & item : root["media_paths"]) {
414
+ if (item.is_string()) {
415
+ media_paths.push_back(item.get<std::string>());
416
+ }
417
+ }
418
+ return media_paths;
419
+ }
420
+
421
+ json build_run_completion_result(capllama::llama_cap_context_completion & completion) {
422
+ const auto partial = completion.getPartialOutput("");
423
+ const std::string text = !partial.content.empty() ? partial.content : completion.generated_text;
424
+
425
+ json timings = {
426
+ {"prompt_n", completion.num_prompt_tokens},
427
+ {"prompt_ms", 0},
428
+ {"prompt_per_token_ms", 0},
429
+ {"prompt_per_second", 0},
430
+ {"predicted_n", completion.num_tokens_predicted},
431
+ {"predicted_ms", 0},
432
+ {"predicted_per_token_ms", 0},
433
+ {"predicted_per_second", 0},
434
+ };
435
+
436
+ if (completion.parent_ctx != nullptr && completion.parent_ctx->ctx != nullptr) {
437
+ const auto perf = llama_perf_context(completion.parent_ctx->ctx);
438
+ timings["prompt_ms"] = perf.t_p_eval_ms;
439
+ timings["predicted_ms"] = perf.t_eval_ms;
440
+ if (completion.num_prompt_tokens > 0 && perf.t_p_eval_ms > 0) {
441
+ timings["prompt_per_token_ms"] = perf.t_p_eval_ms / completion.num_prompt_tokens;
442
+ timings["prompt_per_second"] = completion.num_prompt_tokens / (perf.t_p_eval_ms / 1000.0);
443
+ }
444
+ if (completion.num_tokens_predicted > 0 && perf.t_eval_ms > 0) {
445
+ timings["predicted_per_token_ms"] = perf.t_eval_ms / completion.num_tokens_predicted;
446
+ timings["predicted_per_second"] = completion.num_tokens_predicted / (perf.t_eval_ms / 1000.0);
447
+ }
448
+ }
449
+
450
+ return {
451
+ {"text", text},
452
+ {"reasoning_content", partial.reasoning_content},
453
+ {"tool_calls", json::array()},
454
+ {"content", text},
455
+ {"chat_format", completion.current_chat_format},
456
+ {"tokens_predicted", completion.num_tokens_predicted},
457
+ {"tokens_evaluated", completion.num_prompt_tokens},
458
+ {"truncated", completion.truncated},
459
+ {"stopped_eos", completion.stopped_eos},
460
+ {"stopped_word", completion.stopped_word},
461
+ {"stopped_limit", completion.stopped_limit},
462
+ {"stopping_word", completion.stopping_word},
463
+ {"context_full", completion.context_full},
464
+ {"interrupted", completion.is_interrupted},
465
+ {"tokens_cached", 0},
466
+ {"timings", timings},
467
+ };
468
+ }
469
+
470
+ void apply_run_completion_params(common_params & params, const json & root) {
471
+ if (root.contains("prompt") && root["prompt"].is_string()) {
472
+ params.prompt = root["prompt"].get<std::string>();
473
+ }
474
+ if (root.contains("n_predict") && root["n_predict"].is_number_integer()) {
475
+ params.n_predict = root["n_predict"].get<int>();
476
+ } else if (params.n_predict < 0) {
477
+ params.n_predict = 128;
478
+ }
479
+ if (root.contains("stop") && root["stop"].is_array()) {
480
+ params.antiprompt.clear();
481
+ for (const auto & item : root["stop"]) {
482
+ if (item.is_string()) {
483
+ params.antiprompt.push_back(item.get<std::string>());
484
+ }
485
+ }
486
+ }
487
+ if (root.contains("temperature") && root["temperature"].is_number()) {
488
+ params.sampling.temp = root["temperature"].get<float>();
489
+ }
490
+ if (root.contains("top_p") && root["top_p"].is_number()) {
491
+ params.sampling.top_p = root["top_p"].get<float>();
492
+ }
493
+ if (root.contains("top_k") && root["top_k"].is_number_integer()) {
494
+ params.sampling.top_k = root["top_k"].get<int>();
495
+ }
496
+ if (root.contains("repeat_penalty") && root["repeat_penalty"].is_number()) {
497
+ params.sampling.penalty_repeat = root["repeat_penalty"].get<float>();
498
+ }
499
+ if (root.contains("seed") && root["seed"].is_number_integer()) {
500
+ params.sampling.seed = root["seed"].get<uint32_t>();
501
+ }
502
+ }
503
+
504
+ } // namespace
505
+
506
+ extern "C" {
507
+
508
+ int64_t llama_init_context(const char * model_path, const char * params_json) {
509
+ if (!model_path) {
510
+ return -1;
511
+ }
512
+ try {
513
+ json params_j;
514
+ if (params_json && std::strlen(params_json) > 0) {
515
+ try {
516
+ params_j = json::parse(params_json);
517
+ } catch (...) {
518
+ params_j = json::object();
519
+ }
520
+ }
521
+
522
+ std::string primary(model_path);
523
+ if (primary.rfind("file://", 0) == 0) {
524
+ primary.erase(0, 7);
525
+ }
526
+ const json * pj = params_j.is_object() ? &params_j : nullptr;
527
+ std::string full_model_path = resolve_model_path(primary, pj);
528
+ if (full_model_path.empty()) {
529
+ fprintf(stderr, "llama_init_context: VFS path not found: %s\n", primary.c_str());
530
+ return -1;
531
+ }
532
+
533
+ auto context = std::make_unique<capllama::llama_cap_context>();
534
+ common_params cparams;
535
+ cparams.model.path = full_model_path;
536
+ cparams.n_ctx = 2048;
537
+ #ifdef __EMSCRIPTEN__
538
+ cparams.n_batch = 128;
539
+ #else
540
+ cparams.n_batch = 512;
541
+ #endif
542
+ cparams.n_gpu_layers = 0;
543
+ cparams.rope_freq_base = 10000.0f;
544
+ cparams.rope_freq_scale = 1.0f;
545
+ cparams.use_mmap = true;
546
+ cparams.use_mlock = false;
547
+ cparams.numa = LM_GGML_NUMA_STRATEGY_DISABLED;
548
+ cparams.ctx_shift = false;
549
+ cparams.chat_template = "";
550
+ cparams.embedding = false;
551
+ cparams.cont_batching = false;
552
+ cparams.n_parallel = 1;
553
+ cparams.antiprompt.clear();
554
+ cparams.vocab_only = false;
555
+ cparams.rope_scaling_type = LLAMA_ROPE_SCALING_TYPE_UNSPECIFIED;
556
+ cparams.yarn_ext_factor = -1.0f;
557
+ cparams.yarn_attn_factor = 1.0f;
558
+ cparams.yarn_beta_fast = 32.0f;
559
+ cparams.yarn_beta_slow = 1.0f;
560
+ cparams.yarn_orig_ctx = 0;
561
+ cparams.flash_attn = false;
562
+ cparams.n_keep = 0;
563
+ cparams.n_chunks = -1;
564
+ cparams.n_sequences = 1;
565
+ cparams.model_alias = "unknown";
566
+
567
+ apply_params_json(cparams, pj);
568
+
569
+ #ifdef __EMSCRIPTEN__
570
+ // Warmup runs llama_decode during load; skip on WASM to avoid post-init trap/OOM at 2GB heap.
571
+ cparams.warmup = false;
572
+ // Long chat templates exceed small n_ctx without shifting (LFM2 PWA loads n_ctx<=512).
573
+ cparams.ctx_shift = true;
574
+ // Non-pthread WASM: ggml-cpu lm_ggml_thread_create fails when n_threads > 1.
575
+ cparams.cpuparams.n_threads = 1;
576
+ cparams.cpuparams_batch.n_threads = 1;
577
+ if (cparams.embedding) {
578
+ // BERT/embedding models have no KV cache — ctx_shift is meaningless and the
579
+ // common_log LOG_WRN on the unsupported path is the first LOG_* call in load.
580
+ cparams.ctx_shift = false;
581
+ if (cparams.n_ctx > 128) {
582
+ cparams.n_ctx = 128;
583
+ }
584
+ // Encoder-only (BERT): llama_encode() requires n_ubatch >= n_tokens for the
585
+ // full sequence. n_ubatch = min(n_batch, n_ubatch) in llama_context ctor.
586
+ if (cparams.n_batch <= 0 || cparams.n_batch < cparams.n_ctx) {
587
+ cparams.n_batch = cparams.n_ctx;
588
+ } else if (cparams.n_batch > cparams.n_ctx) {
589
+ cparams.n_batch = cparams.n_ctx;
590
+ }
591
+ } else {
592
+ // Larger batches = fewer WASM decode round-trips during prompt prefill.
593
+ if (cparams.n_batch <= 0 || cparams.n_batch < 32) {
594
+ cparams.n_batch = std::min(cparams.n_ctx, 128);
595
+ } else if (cparams.n_batch > cparams.n_ctx) {
596
+ cparams.n_batch = cparams.n_ctx;
597
+ } else if (cparams.n_batch > 128) {
598
+ cparams.n_batch = 128;
599
+ }
600
+ if (cparams.n_ctx > 512) {
601
+ cparams.n_ctx = 512;
602
+ }
603
+ }
604
+ #else
605
+ if (cparams.embedding) {
606
+ cparams.ctx_shift = false;
607
+ if (cparams.n_ctx > 512) {
608
+ cparams.n_ctx = 512;
609
+ }
610
+ if (cparams.n_batch <= 0 || cparams.n_batch < cparams.n_ctx) {
611
+ cparams.n_batch = cparams.n_ctx;
612
+ } else if (cparams.n_batch > cparams.n_ctx) {
613
+ cparams.n_batch = cparams.n_ctx;
614
+ }
615
+ }
616
+ #endif
617
+
618
+ if (!load_with_fallback(context, cparams)) {
619
+ fprintf(stderr,
620
+ "llama_init_context: load failed for %s (n_ctx=%d n_batch=%d use_mmap=%d)\n",
621
+ full_model_path.c_str(), cparams.n_ctx, cparams.n_batch, cparams.use_mmap ? 1 : 0);
622
+ return -1;
623
+ }
624
+
625
+ int64_t id = g_next_id++;
626
+ capllama::llama_cap_context * raw = nullptr;
627
+ {
628
+ std::lock_guard<std::mutex> lock(g_mutex);
629
+ g_contexts[id] = std::move(context);
630
+ raw = g_contexts[id].get();
631
+ }
632
+ if (raw) {
633
+ llama_embedding_register_context(id, raw);
634
+ }
635
+ return id;
636
+ } catch (const std::exception & e) {
637
+ fprintf(stderr, "llama_init_context exception: %s\n", e.what());
638
+ return -1;
639
+ } catch (...) {
640
+ fprintf(stderr, "llama_init_context: unknown exception\n");
641
+ return -1;
642
+ }
643
+ }
644
+
645
+ void llama_release_context(int64_t context_id) {
646
+ try {
647
+ llama_embedding_unregister_context(context_id);
648
+ std::lock_guard<std::mutex> lock(g_mutex);
649
+ g_contexts.erase(context_id);
650
+ } catch (...) {
651
+ }
652
+ }
653
+
654
+ int32_t llama_context_n_embd(int64_t context_id) {
655
+ std::lock_guard<std::mutex> lock(g_mutex);
656
+ auto * ctx = get_ctx(context_id);
657
+ llama_model * model = resolve_model(ctx);
658
+ if (!model) {
659
+ return 0;
660
+ }
661
+ return llama_model_n_embd(model);
662
+ }
663
+
664
+ const char * llama_get_context_model_json(int64_t context_id) {
665
+ std::lock_guard<std::mutex> lock(g_mutex);
666
+ auto * ctx = get_ctx(context_id);
667
+ llama_model * model = resolve_model(ctx);
668
+ if (!ctx || !model) {
669
+ fprintf(stderr, "llama_get_context_model_json: no model for context %lld\n", (long long) context_id);
670
+ return tls_cstr("{}");
671
+ }
672
+ try {
673
+ int64_t size = static_cast<int64_t>(llama_model_size(model));
674
+ if (size <= 0 && !ctx->params.model.path.empty() && std::filesystem::exists(ctx->params.model.path)) {
675
+ size = static_cast<int64_t>(std::filesystem::file_size(ctx->params.model.path));
676
+ }
677
+ json out = {
678
+ {"path", ctx->params.model.path},
679
+ {"desc", std::string("GGUF model")},
680
+ {"size", size},
681
+ {"nEmbd", llama_model_n_embd(model)},
682
+ {"nParams", static_cast<int64_t>(llama_model_n_params(model))},
683
+ {"chatTemplates", default_chat_templates_json()},
684
+ {"metadata", json::object()},
685
+ };
686
+ return tls_cstr(out.dump());
687
+ } catch (...) {
688
+ return tls_cstr("{}");
689
+ }
690
+ }
691
+
692
+ const char * llama_completion(int64_t context_id, const char * params_json) {
693
+ try {
694
+ #ifdef __EMSCRIPTEN__
695
+ fprintf(stderr, "@@WASM_GEN@@ begin ctx=%lld\n", (long long) context_id);
696
+ #endif
697
+ std::lock_guard<std::mutex> lock(g_mutex);
698
+ auto * ctx = get_ctx(context_id);
699
+ if (!ctx || !ctx->ctx) {
700
+ return tls_cstr("{\"error\":\"invalid context\"}");
701
+ }
702
+
703
+ std::string prompt_str;
704
+ int n_predict = 50;
705
+ parse_completion_params(ctx, params_json, prompt_str, n_predict);
706
+
707
+ capllama::llama_cap_tokenize_result tokenize_result = ctx->tokenize(prompt_str, {});
708
+ std::vector<llama_token> prompt_tokens = tokenize_result.tokens;
709
+
710
+ if (!prepare_completion_run(ctx, params_json)) {
711
+ return tls_cstr("{\"error\":\"completion prepare failed\"}");
712
+ }
713
+
714
+ std::string generated_text;
715
+ int tokens_generated = 0;
716
+ bool hit_eos = false;
717
+
718
+ try {
719
+ ctx->completion->loadPrompt({});
720
+ if (ctx->completion->context_full) {
721
+ return tls_cstr("{\"error\":\"prompt exceeds n_ctx — reload with larger n_ctx or shorten prompt\"}");
722
+ }
723
+ #ifdef __EMSCRIPTEN__
724
+ const auto t0 = std::chrono::steady_clock::now();
725
+ fprintf(stderr, "@@WASM_GEN@@ prompt_ok tokens=%zu n_predict=%d n_past=%d n_batch=%d\n",
726
+ prompt_tokens.size(), n_predict, ctx->completion->n_past, ctx->params.n_batch);
727
+ #endif
728
+ tokens_generated = run_completion_loop(ctx, n_predict, hit_eos, generated_text);
729
+
730
+ #ifdef __EMSCRIPTEN__
731
+ if (tokens_generated == 0 && !ctx->completion->context_full) {
732
+ fprintf(stderr, "@@WASM_GEN@@ retry empty (eos=%d tok_break=%d)\n",
733
+ hit_eos ? 1 : 0, ctx->completion->stopped_word ? 1 : 0);
734
+ llama_memory_clear(llama_get_memory(ctx->ctx), true);
735
+ if (prepare_completion_run(ctx, params_json)) {
736
+ ctx->completion->loadPrompt({});
737
+ if (!ctx->completion->context_full) {
738
+ hit_eos = false;
739
+ tokens_generated = run_completion_loop(ctx, n_predict, hit_eos, generated_text);
740
+ }
741
+ }
742
+ }
743
+ const auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
744
+ std::chrono::steady_clock::now() - t0).count();
745
+ fprintf(stderr, "@@WASM_GEN@@ done predicted=%d ms=%lld\n",
746
+ tokens_generated, (long long) ms);
747
+ #endif
748
+ } catch (const std::exception & e) {
749
+ try {
750
+ if (ctx->completion) {
751
+ ctx->completion->endCompletion();
752
+ }
753
+ } catch (...) {
754
+ }
755
+ json err = {{"error", e.what()}};
756
+ return tls_cstr(err.dump());
757
+ }
758
+
759
+ const bool stopped_limit = tokens_generated >= n_predict;
760
+ const bool interrupted = ctx->completion && ctx->completion->is_interrupted;
761
+
762
+ json timings = {
763
+ {"prompt_n", static_cast<int>(prompt_tokens.size())},
764
+ {"predicted_n", tokens_generated},
765
+ {"prompt_ms", 0},
766
+ {"predicted_ms", 0},
767
+ {"prompt_per_token_ms", 0},
768
+ {"predicted_per_token_ms", 0},
769
+ {"prompt_per_second", 0},
770
+ {"predicted_per_second", 0},
771
+ };
772
+
773
+ json tool_calls = json::array();
774
+ json result = {
775
+ {"text", generated_text},
776
+ {"content", generated_text},
777
+ {"reasoning_content", ""},
778
+ {"tool_calls", tool_calls},
779
+ {"tokens_predicted", tokens_generated},
780
+ {"tokens_evaluated", static_cast<int>(prompt_tokens.size())},
781
+ {"truncated", false},
782
+ {"stopped_eos", hit_eos},
783
+ {"stopped_word", ""},
784
+ {"stopped_limit", stopped_limit},
785
+ {"stopping_word", ""},
786
+ {"context_full", false},
787
+ {"interrupted", interrupted},
788
+ {"chat_format", 0},
789
+ {"tokens_cached", 0},
790
+ {"timings", timings},
791
+ };
792
+ return tls_cstr(result.dump());
793
+ } catch (const std::exception & e) {
794
+ json err = {{"error", e.what()}};
795
+ return tls_cstr(err.dump());
796
+ } catch (...) {
797
+ return tls_cstr("{\"error\":\"unknown\"}");
798
+ }
799
+ }
800
+
801
+ void llama_stop_completion(int64_t context_id) {
802
+ std::lock_guard<std::mutex> lock(g_mutex);
803
+ auto * ctx = get_ctx(context_id);
804
+ if (ctx && ctx->completion) {
805
+ ctx->completion->is_interrupted = true;
806
+ }
807
+ }
808
+
809
+ const char * llama_get_formatted_chat(int64_t context_id, const char * messages_json, const char * chat_template,
810
+ const char * params_json) {
811
+ (void)params_json;
812
+ try {
813
+ std::lock_guard<std::mutex> lock(g_mutex);
814
+ auto * ctx = get_ctx(context_id);
815
+ if (!ctx) {
816
+ return tls_cstr("{\"error\":\"invalid context\"}");
817
+ }
818
+ std::string messages_str = messages_json ? messages_json : "";
819
+ std::string template_str = chat_template ? chat_template : "";
820
+ std::string prompt = ctx->getFormattedChat(messages_str, template_str);
821
+ json out = {
822
+ {"type", "llama-chat"},
823
+ {"prompt", prompt},
824
+ {"has_media", false},
825
+ {"media_paths", json::array()},
826
+ };
827
+ return tls_cstr(out.dump());
828
+ } catch (const std::exception & e) {
829
+ json err = {{"error", e.what()}};
830
+ return tls_cstr(err.dump());
831
+ }
832
+ }
833
+
834
+ bool llama_toggle_native_log(bool enabled) {
835
+ capllama_verbose = enabled;
836
+ return true;
837
+ }
838
+
839
+ // ---------------------------------------------------------------------------
840
+ // WASM-specific: load context from an in-memory byte buffer (#1 / #9).
841
+ // On Emscripten the bytes are written to the VFS at /tmp/; on other builds
842
+ // (wasm32-unknown-unknown) the caller must ensure file-I/O is provided.
843
+ // ---------------------------------------------------------------------------
844
+ #ifdef CAPLLAMA_BUILD_WASM
845
+
846
+ #include "cap-wasm-jspi.h"
847
+ #include <errno.h>
848
+ #include <sys/stat.h>
849
+
850
+ extern "C" void cap_wasm_ensure_tmp_dir(void);
851
+
852
+ static std::string g_wasm_tmp_path;
853
+
854
+ static void ensure_wasm_tmp_dir() {
855
+ cap_wasm_ensure_tmp_dir();
856
+ if (mkdir("/tmp", 0777) != 0 && errno != EEXIST) {
857
+ }
858
+ }
859
+
860
+ // Forward-declared to avoid duplicating all of llama_init_context's setup.
861
+ static int64_t init_context_with_cparams(common_params cparams);
862
+
863
+ int64_t llama_init_context_from_buffer(
864
+ const uint8_t * data,
865
+ size_t size,
866
+ const char * params_json)
867
+ {
868
+ if (!data || size == 0) {
869
+ return -1;
870
+ }
871
+
872
+ // Choose a unique temporary path so concurrent loads don't clash.
873
+ static int g_tmp_counter = 0;
874
+ ensure_wasm_tmp_dir();
875
+ std::string tmp_path = std::string("/tmp/wasm_model_") + std::to_string(g_tmp_counter++) + ".gguf";
876
+
877
+ // Write bytes to the in-process virtual filesystem (Emscripten MEMFS or
878
+ // a wasi-compatible /tmp/). If fopen fails here the target does not
879
+ // support this path; callers should check the return value.
880
+ FILE * f = fopen(tmp_path.c_str(), "wb");
881
+ if (!f) {
882
+ return -1;
883
+ }
884
+ size_t written = fwrite(data, 1, size, f);
885
+ fclose(f);
886
+ if (written != size) {
887
+ remove(tmp_path.c_str());
888
+ return -1;
889
+ }
890
+
891
+ int64_t id = llama_init_context(tmp_path.c_str(), params_json);
892
+ remove(tmp_path.c_str());
893
+ return id;
894
+ }
895
+
896
+ // ---------------------------------------------------------------------------
897
+ // Choice 3: stream model bytes from OPFS sync access handle into MEMFS.
898
+ // JS reads fixed-size chunks via FileSystemSyncAccessHandle and calls
899
+ // llama_model_vfs_write; llama_model_vfs_finish loads from the VFS path.
900
+ // Peak JS heap holds one chunk (~4MB), not the full GGUF.
901
+ // ---------------------------------------------------------------------------
902
+ static std::map<std::string, FILE *> g_model_vfs_writes;
903
+ static int g_vfs_stream_counter = 0;
904
+
905
+ const char * llama_model_vfs_begin() {
906
+ ensure_wasm_tmp_dir();
907
+ std::string path = std::string("/tmp/wasm_stream_") + std::to_string(g_vfs_stream_counter++) + ".gguf";
908
+ FILE * f = fopen(path.c_str(), "wb");
909
+ if (!f) {
910
+ return nullptr;
911
+ }
912
+ g_model_vfs_writes[path] = f;
913
+ return tls_cstr(path);
914
+ }
915
+
916
+ int llama_model_vfs_write(const char * path, const uint8_t * data, size_t len) {
917
+ if (!path || !data || len == 0) {
918
+ return -1;
919
+ }
920
+ auto it = g_model_vfs_writes.find(path);
921
+ if (it == g_model_vfs_writes.end() || !it->second) {
922
+ return -1;
923
+ }
924
+ return fwrite(data, 1, len, it->second) == len ? 0 : -1;
925
+ }
926
+
927
+ void llama_model_vfs_abort(const char * path) {
928
+ if (!path) {
929
+ return;
930
+ }
931
+ auto it = g_model_vfs_writes.find(path);
932
+ if (it != g_model_vfs_writes.end()) {
933
+ if (it->second) {
934
+ fclose(it->second);
935
+ }
936
+ g_model_vfs_writes.erase(it);
937
+ }
938
+ remove(path);
939
+ }
940
+
941
+ int64_t llama_model_vfs_finish(const char * path, const char * params_json) {
942
+ if (!path) {
943
+ return -1;
944
+ }
945
+ auto it = g_model_vfs_writes.find(path);
946
+ if (it != g_model_vfs_writes.end()) {
947
+ if (it->second) {
948
+ fclose(it->second);
949
+ }
950
+ g_model_vfs_writes.erase(it);
951
+ }
952
+ int64_t id = llama_init_context(path, params_json);
953
+ remove(path);
954
+ return id;
955
+ }
956
+
957
+ // Load from an existing VFS path (HeapFS / MEMFS file already populated).
958
+ int64_t llama_load_context_from_path(const char * path, const char * params_json) {
959
+ if (!path) {
960
+ return -1;
961
+ }
962
+ try {
963
+ return llama_init_context(path, params_json);
964
+ } catch (const std::exception & e) {
965
+ fprintf(stderr, "llama_load_context_from_path exception: %s\n", e.what());
966
+ return -1;
967
+ } catch (...) {
968
+ fprintf(stderr, "llama_load_context_from_path: unknown exception\n");
969
+ return -1;
970
+ }
971
+ }
972
+
973
+ #endif // CAPLLAMA_BUILD_WASM
974
+
975
+ // ---------------------------------------------------------------------------
976
+ // Streaming completion with per-token C callback (all native/desktop targets).
977
+ // Holds g_mutex for the full inference so the context cannot be released mid-stream.
978
+ // ---------------------------------------------------------------------------
979
+ const char * llama_completion_stream(
980
+ int64_t context_id,
981
+ const char * params_json,
982
+ void (* token_callback)(const char * token_text, void * user_data, int token_index),
983
+ void * user_data)
984
+ {
985
+ try {
986
+ std::lock_guard<std::mutex> lock(g_mutex);
987
+ auto * ctx = get_ctx(context_id);
988
+ if (!ctx || !ctx->ctx) {
989
+ return tls_cstr("{\"error\":\"invalid context\"}");
990
+ }
991
+
992
+ std::string prompt_str;
993
+ int n_predict = 50;
994
+ parse_completion_params(ctx, params_json, prompt_str, n_predict);
995
+
996
+ capllama::llama_cap_tokenize_result tokenize_result = ctx->tokenize(prompt_str, {});
997
+ std::vector<llama_token> prompt_tokens = tokenize_result.tokens;
998
+
999
+ if (!prepare_completion_run(ctx, params_json)) {
1000
+ return tls_cstr("{\"error\":\"completion prepare failed\"}");
1001
+ }
1002
+
1003
+ std::string generated_text;
1004
+ int tokens_generated = 0;
1005
+ bool hit_eos = false;
1006
+ const llama_vocab * vocab = llama_model_get_vocab(ctx->model);
1007
+
1008
+ try {
1009
+ ctx->completion->loadPrompt({});
1010
+ if (ctx->completion->context_full) {
1011
+ return tls_cstr("{\"error\":\"prompt exceeds n_ctx — reload with larger n_ctx or shorten prompt\"}");
1012
+ }
1013
+ ctx->completion->beginCompletion();
1014
+
1015
+ while (tokens_generated < n_predict &&
1016
+ ctx->completion->has_next_token &&
1017
+ !ctx->completion->is_interrupted) {
1018
+ capllama::completion_token_output token_output = ctx->completion->doCompletion();
1019
+ if (token_output.tok < 0) {
1020
+ hit_eos = ctx->completion->stopped_eos;
1021
+ break;
1022
+ }
1023
+ if (llama_vocab_is_eog(vocab, token_output.tok)) {
1024
+ hit_eos = true;
1025
+ break;
1026
+ }
1027
+ if (ctx->completion->stopped_word || ctx->completion->stopped_limit) {
1028
+ break;
1029
+ }
1030
+ std::string token_text = capllama::tokens_to_output_formatted_string(
1031
+ ctx->ctx, token_output.tok);
1032
+ generated_text += token_text;
1033
+
1034
+ if (token_callback) {
1035
+ #if defined(CAPLLAMA_BUILD_WASM_JSPI) && defined(__EMSCRIPTEN__)
1036
+ cap_wasm_jspi_token_callback(token_text.c_str(), user_data, tokens_generated);
1037
+ #else
1038
+ token_callback(token_text.c_str(), user_data, tokens_generated);
1039
+ #endif
1040
+ }
1041
+ tokens_generated++;
1042
+ }
1043
+ generated_text = ctx->completion->generated_text;
1044
+ ctx->completion->endCompletion();
1045
+ } catch (const std::exception & e) {
1046
+ try {
1047
+ if (ctx->completion) ctx->completion->endCompletion();
1048
+ } catch (...) {}
1049
+ json err = {{"error", e.what()}};
1050
+ return tls_cstr(err.dump());
1051
+ }
1052
+
1053
+ const bool stopped_limit = tokens_generated >= n_predict;
1054
+ const bool interrupted = ctx->completion && ctx->completion->is_interrupted;
1055
+
1056
+ json timings = {
1057
+ {"prompt_n", static_cast<int>(prompt_tokens.size())},
1058
+ {"predicted_n", tokens_generated},
1059
+ {"prompt_ms", 0},
1060
+ {"predicted_ms", 0},
1061
+ {"prompt_per_token_ms", 0},
1062
+ {"predicted_per_token_ms", 0},
1063
+ {"prompt_per_second", 0},
1064
+ {"predicted_per_second", 0},
1065
+ };
1066
+
1067
+ json result = {
1068
+ {"text", generated_text},
1069
+ {"content", generated_text},
1070
+ {"reasoning_content",""},
1071
+ {"tool_calls", json::array()},
1072
+ {"tokens_predicted", tokens_generated},
1073
+ {"tokens_evaluated", static_cast<int>(prompt_tokens.size())},
1074
+ {"truncated", false},
1075
+ {"stopped_eos", hit_eos},
1076
+ {"stopped_word", ""},
1077
+ {"stopped_limit", stopped_limit},
1078
+ {"stopping_word", ""},
1079
+ {"context_full", false},
1080
+ {"interrupted", interrupted},
1081
+ {"chat_format", 0},
1082
+ {"tokens_cached", 0},
1083
+ {"timings", timings},
1084
+ };
1085
+ return tls_cstr(result.dump());
1086
+ } catch (const std::exception & e) {
1087
+ json err = {{"error", e.what()}};
1088
+ return tls_cstr(err.dump());
1089
+ } catch (...) {
1090
+ return tls_cstr("{\"error\":\"unknown\"}");
1091
+ }
1092
+ }
1093
+
1094
+ const char * llama_model_info(const char * model_path, const char * skip_json) {
1095
+ (void)skip_json;
1096
+ if (!model_path || !std::strlen(model_path)) {
1097
+ return tls_cstr("{\"error\":\"invalid path\"}");
1098
+ }
1099
+ try {
1100
+ std::string p(model_path);
1101
+ if (!std::filesystem::exists(p)) {
1102
+ return tls_cstr("{\"error\":\"file not found\"}");
1103
+ }
1104
+ std::ifstream f(p, std::ios::binary);
1105
+ if (!f) {
1106
+ return tls_cstr("{\"error\":\"open failed\"}");
1107
+ }
1108
+ f.seekg(0, std::ios::end);
1109
+ auto sz = f.tellg();
1110
+ f.seekg(0, std::ios::beg);
1111
+ char magic[4];
1112
+ if (!f.read(magic, 4) || magic[0] != 'G' || magic[1] != 'G' || magic[2] != 'U' || magic[3] != 'F') {
1113
+ return tls_cstr("{\"error\":\"not GGUF\"}");
1114
+ }
1115
+ uint32_t version = 0;
1116
+ if (!f.read(reinterpret_cast<char *>(&version), sizeof(version))) {
1117
+ return tls_cstr("{\"error\":\"read version\"}");
1118
+ }
1119
+ json out = {
1120
+ {"path", p},
1121
+ {"size", static_cast<int64_t>(sz)},
1122
+ {"desc", std::string("GGUF Model (v") + std::to_string(version) + ")"},
1123
+ {"nEmbd", 0},
1124
+ {"nParams", 0},
1125
+ };
1126
+ return tls_cstr(out.dump());
1127
+ } catch (const std::exception & e) {
1128
+ json err = {{"error", e.what()}};
1129
+ return tls_cstr(err.dump());
1130
+ }
1131
+ }
1132
+
1133
+ const char * llama_cap_tokenize(int64_t context_id, const char * text, const char * image_paths_json) {
1134
+ try {
1135
+ std::vector<std::string> media_paths;
1136
+ if (image_paths_json && std::strlen(image_paths_json) > 0) {
1137
+ try {
1138
+ json arr = json::parse(image_paths_json);
1139
+ if (arr.is_array()) {
1140
+ for (const auto & el : arr) {
1141
+ if (el.is_string()) {
1142
+ media_paths.push_back(el.get<std::string>());
1143
+ }
1144
+ }
1145
+ }
1146
+ } catch (...) {
1147
+ }
1148
+ }
1149
+ std::lock_guard<std::mutex> lock(g_mutex);
1150
+ auto * ctx = get_ctx(context_id);
1151
+ if (!ctx || !ctx->ctx) {
1152
+ return tls_cstr("{\"error\":\"invalid context\"}");
1153
+ }
1154
+ std::string text_str = text ? text : "";
1155
+ capllama::llama_cap_tokenize_result tr = ctx->tokenize(text_str, media_paths);
1156
+ json tokens_j = json::array();
1157
+ for (llama_token t : tr.tokens) {
1158
+ tokens_j.push_back(static_cast<int>(t));
1159
+ }
1160
+ json bitmaps = json::array();
1161
+ for (const auto & h : tr.bitmap_hashes) {
1162
+ bitmaps.push_back(h);
1163
+ }
1164
+ json chunk_pos = json::array();
1165
+ for (auto v : tr.chunk_pos) {
1166
+ chunk_pos.push_back(v);
1167
+ }
1168
+ json chunk_pos_im = json::array();
1169
+ for (auto v : tr.chunk_pos_media) {
1170
+ chunk_pos_im.push_back(v);
1171
+ }
1172
+ json out = {
1173
+ {"tokens", tokens_j},
1174
+ {"has_images", tr.has_media},
1175
+ {"has_media", tr.has_media},
1176
+ {"bitmap_hashes", bitmaps},
1177
+ {"chunk_pos", chunk_pos},
1178
+ {"chunk_pos_images", chunk_pos_im},
1179
+ };
1180
+ return tls_cstr(out.dump());
1181
+ } catch (const std::exception & e) {
1182
+ json err = {{"error", e.what()}};
1183
+ return tls_cstr(err.dump());
1184
+ }
1185
+ }
1186
+
1187
+ const char * llama_cap_detokenize(int64_t context_id, const char * tokens_json) {
1188
+ try {
1189
+ std::lock_guard<std::mutex> lock(g_mutex);
1190
+ auto * ctx = get_ctx(context_id);
1191
+ if (!ctx || !ctx->ctx) {
1192
+ return tls_cstr("");
1193
+ }
1194
+ std::vector<llama_token> llama_tokens;
1195
+ if (tokens_json && std::strlen(tokens_json) > 0) {
1196
+ json arr = json::parse(tokens_json);
1197
+ if (arr.is_array()) {
1198
+ for (const auto & el : arr) {
1199
+ if (el.is_number_integer()) {
1200
+ llama_tokens.push_back(static_cast<llama_token>(el.get<int>()));
1201
+ }
1202
+ }
1203
+ }
1204
+ }
1205
+ std::string result = capllama::tokens_to_str(ctx->ctx, llama_tokens.begin(), llama_tokens.end());
1206
+ return tls_cstr(result);
1207
+ } catch (...) {
1208
+ return tls_cstr("");
1209
+ }
1210
+ }
1211
+
1212
+ const char * llama_convert_json_schema_to_grammar(const char * schema_json) {
1213
+ if (!schema_json || !std::strlen(schema_json)) {
1214
+ return tls_cstr("");
1215
+ }
1216
+ try {
1217
+ json schema = json::parse(schema_json);
1218
+ std::string g = json_schema_to_grammar(schema, false);
1219
+ return tls_cstr(g);
1220
+ } catch (const std::exception & e) {
1221
+ return tls_cstr("");
1222
+ }
1223
+ }
1224
+
1225
+ static std::string resolve_vfs_path(const char * path) {
1226
+ if (!path || !std::strlen(path)) {
1227
+ return {};
1228
+ }
1229
+ std::string primary(path);
1230
+ std::string resolved = resolve_model_path(primary, nullptr);
1231
+ if (!resolved.empty()) {
1232
+ return resolved;
1233
+ }
1234
+ if (primary.front() == '/') {
1235
+ return primary;
1236
+ }
1237
+ #ifdef CAPLLAMA_BUILD_WASM
1238
+ cap_wasm_ensure_tmp_dir();
1239
+ return std::string("/tmp/") + std::filesystem::path(primary).filename().string();
1240
+ #else
1241
+ return primary;
1242
+ #endif
1243
+ }
1244
+
1245
+ const char * llama_cap_rerank(int64_t context_id, const char * query, const char * documents_json) {
1246
+ try {
1247
+ std::lock_guard<std::mutex> lock(g_mutex);
1248
+ auto * ctx = get_ctx(context_id);
1249
+ if (!ctx || !ctx->ctx) {
1250
+ return tls_cstr("{\"error\":\"invalid context\"}");
1251
+ }
1252
+ if (!ensure_completion(ctx)) {
1253
+ return tls_cstr("{\"error\":\"completion init failed\"}");
1254
+ }
1255
+ std::string query_str = query ? query : "";
1256
+ std::vector<std::string> documents;
1257
+ if (documents_json && std::strlen(documents_json) > 0) {
1258
+ json arr = json::parse(documents_json);
1259
+ if (arr.is_array()) {
1260
+ for (const auto & el : arr) {
1261
+ if (el.is_string()) {
1262
+ documents.push_back(el.get<std::string>());
1263
+ }
1264
+ }
1265
+ }
1266
+ }
1267
+ auto scores = ctx->completion->rerank(query_str, documents);
1268
+ json results = json::array();
1269
+ std::vector<std::pair<size_t, float>> indexed;
1270
+ indexed.reserve(scores.size());
1271
+ for (size_t i = 0; i < scores.size(); ++i) {
1272
+ indexed.emplace_back(i, scores[i]);
1273
+ }
1274
+ std::sort(indexed.begin(), indexed.end(), [](const auto & a, const auto & b) {
1275
+ return a.second > b.second;
1276
+ });
1277
+ for (const auto & pr : indexed) {
1278
+ results.push_back({{"index", static_cast<int>(pr.first)}, {"score", pr.second}});
1279
+ }
1280
+ return tls_cstr(results.dump());
1281
+ } catch (const std::exception & e) {
1282
+ json err = {{"error", e.what()}};
1283
+ return tls_cstr(err.dump());
1284
+ }
1285
+ }
1286
+
1287
+ const char * llama_cap_bench(int64_t context_id, int pp, int tg, int pl, int nr) {
1288
+ try {
1289
+ std::lock_guard<std::mutex> lock(g_mutex);
1290
+ auto * ctx = get_ctx(context_id);
1291
+ if (!ctx || !ctx->ctx) {
1292
+ return tls_cstr("{\"error\":\"invalid context\"}");
1293
+ }
1294
+ if (!ensure_completion(ctx)) {
1295
+ return tls_cstr("{\"error\":\"completion init failed\"}");
1296
+ }
1297
+ return tls_cstr(ctx->completion->bench(pp, tg, pl, nr).c_str());
1298
+ } catch (const std::exception & e) {
1299
+ json err = {{"error", e.what()}};
1300
+ return tls_cstr(err.dump());
1301
+ }
1302
+ }
1303
+
1304
+ const char * llama_cap_save_session(int64_t context_id, const char * filepath, int token_size) {
1305
+ try {
1306
+ std::lock_guard<std::mutex> lock(g_mutex);
1307
+ auto * ctx = get_ctx(context_id);
1308
+ if (!ctx || !ctx->ctx) {
1309
+ return tls_cstr("{\"error\":\"invalid context\"}");
1310
+ }
1311
+ std::string path = resolve_vfs_path(filepath);
1312
+ if (path.empty()) {
1313
+ return tls_cstr("{\"error\":\"invalid session path\"}");
1314
+ }
1315
+ std::vector<llama_token> tokens;
1316
+ if (ctx->completion && !ctx->completion->embd.empty()) {
1317
+ tokens = ctx->completion->embd;
1318
+ }
1319
+ if (token_size > 0 && tokens.size() > static_cast<size_t>(token_size)) {
1320
+ tokens.resize(static_cast<size_t>(token_size));
1321
+ }
1322
+ if (!llama_state_save_file(ctx->ctx, path.c_str(), tokens.data(), tokens.size())) {
1323
+ return tls_cstr("{\"error\":\"session save failed\"}");
1324
+ }
1325
+ json out = {{"tokens_saved", static_cast<int>(tokens.size())}};
1326
+ return tls_cstr(out.dump());
1327
+ } catch (const std::exception & e) {
1328
+ json err = {{"error", e.what()}};
1329
+ return tls_cstr(err.dump());
1330
+ }
1331
+ }
1332
+
1333
+ const char * llama_cap_load_session(int64_t context_id, const char * filepath) {
1334
+ try {
1335
+ std::lock_guard<std::mutex> lock(g_mutex);
1336
+ auto * ctx = get_ctx(context_id);
1337
+ if (!ctx || !ctx->ctx) {
1338
+ return tls_cstr("{\"error\":\"invalid context\"}");
1339
+ }
1340
+ std::string path = resolve_vfs_path(filepath);
1341
+ if (path.empty()) {
1342
+ return tls_cstr("{\"error\":\"invalid session path\"}");
1343
+ }
1344
+ const size_t cap = 8192;
1345
+ std::vector<llama_token> tokens(cap);
1346
+ size_t n_loaded = 0;
1347
+ if (!llama_state_load_file(ctx->ctx, path.c_str(), tokens.data(), cap, &n_loaded)) {
1348
+ return tls_cstr("{\"error\":\"session load failed\"}");
1349
+ }
1350
+ tokens.resize(n_loaded);
1351
+ std::string prompt = capllama::tokens_to_str(ctx->ctx, tokens.begin(), tokens.end());
1352
+ json out = {
1353
+ {"tokens_loaded", static_cast<int>(n_loaded)},
1354
+ {"prompt", prompt},
1355
+ };
1356
+ return tls_cstr(out.dump());
1357
+ } catch (const std::exception & e) {
1358
+ json err = {{"error", e.what()}};
1359
+ return tls_cstr(err.dump());
1360
+ }
1361
+ }
1362
+
1363
+ const char * llama_cap_apply_lora(int64_t context_id, const char * lora_list_json) {
1364
+ try {
1365
+ std::lock_guard<std::mutex> lock(g_mutex);
1366
+ auto * ctx = get_ctx(context_id);
1367
+ if (!ctx || !ctx->model) {
1368
+ return tls_cstr("{\"error\":\"invalid context\"}");
1369
+ }
1370
+ std::vector<common_adapter_lora_info> lora;
1371
+ if (lora_list_json && std::strlen(lora_list_json) > 0) {
1372
+ json arr = json::parse(lora_list_json);
1373
+ if (arr.is_array()) {
1374
+ for (const auto & el : arr) {
1375
+ if (!el.is_object() || !el.contains("path") || !el["path"].is_string()) {
1376
+ continue;
1377
+ }
1378
+ common_adapter_lora_info la;
1379
+ la.path = resolve_vfs_path(el["path"].get<std::string>().c_str());
1380
+ if (la.path.empty()) {
1381
+ la.path = el["path"].get<std::string>();
1382
+ }
1383
+ la.scale = el.value("scaled", el.value("scale", 1.0f));
1384
+ lora.push_back(std::move(la));
1385
+ }
1386
+ }
1387
+ }
1388
+ if (ctx->applyLoraAdapters(lora) != 0) {
1389
+ return tls_cstr("{\"error\":\"apply lora failed\"}");
1390
+ }
1391
+ return tls_cstr("{\"ok\":true}");
1392
+ } catch (const std::exception & e) {
1393
+ json err = {{"error", e.what()}};
1394
+ return tls_cstr(err.dump());
1395
+ }
1396
+ }
1397
+
1398
+ const char * llama_cap_remove_lora(int64_t context_id) {
1399
+ try {
1400
+ std::lock_guard<std::mutex> lock(g_mutex);
1401
+ auto * ctx = get_ctx(context_id);
1402
+ if (!ctx) {
1403
+ return tls_cstr("{\"error\":\"invalid context\"}");
1404
+ }
1405
+ ctx->removeLoraAdapters();
1406
+ return tls_cstr("{\"ok\":true}");
1407
+ } catch (const std::exception & e) {
1408
+ json err = {{"error", e.what()}};
1409
+ return tls_cstr(err.dump());
1410
+ }
1411
+ }
1412
+
1413
+ const char * llama_cap_get_lora(int64_t context_id) {
1414
+ try {
1415
+ std::lock_guard<std::mutex> lock(g_mutex);
1416
+ auto * ctx = get_ctx(context_id);
1417
+ if (!ctx) {
1418
+ return tls_cstr("[]");
1419
+ }
1420
+ json arr = json::array();
1421
+ for (const auto & la : ctx->getLoadedLoraAdapters()) {
1422
+ arr.push_back({{"path", la.path}, {"scaled", la.scale}});
1423
+ }
1424
+ return tls_cstr(arr.dump());
1425
+ } catch (...) {
1426
+ return tls_cstr("[]");
1427
+ }
1428
+ }
1429
+
1430
+ const char * llama_cap_init_multimodal(int64_t context_id, const char * path, int use_gpu) {
1431
+ try {
1432
+ std::lock_guard<std::mutex> lock(g_mutex);
1433
+ auto * ctx = get_ctx(context_id);
1434
+ if (!ctx) {
1435
+ return tls_cstr("{\"ok\":false,\"error\":\"invalid context\"}");
1436
+ }
1437
+ std::string resolved = resolve_vfs_path(path);
1438
+ if (resolved.empty()) {
1439
+ return tls_cstr("{\"ok\":false,\"error\":\"mmproj path not found\"}");
1440
+ }
1441
+ const bool ok = ctx->initMultimodal(resolved, use_gpu != 0);
1442
+ return tls_cstr(json({{"ok", ok}}).dump());
1443
+ } catch (const std::exception & e) {
1444
+ json err = {{"ok", false}, {"error", e.what()}};
1445
+ return tls_cstr(err.dump());
1446
+ }
1447
+ }
1448
+
1449
+ const char * llama_cap_multimodal_status(int64_t context_id) {
1450
+ try {
1451
+ std::lock_guard<std::mutex> lock(g_mutex);
1452
+ auto * ctx = get_ctx(context_id);
1453
+ if (!ctx) {
1454
+ return tls_cstr("{\"enabled\":false,\"vision\":false,\"audio\":false}");
1455
+ }
1456
+ json out = {
1457
+ {"enabled", ctx->isMultimodalEnabled()},
1458
+ {"vision", ctx->isMultimodalSupportVision()},
1459
+ {"audio", ctx->isMultimodalSupportAudio()},
1460
+ };
1461
+ return tls_cstr(out.dump());
1462
+ } catch (...) {
1463
+ return tls_cstr("{\"enabled\":false,\"vision\":false,\"audio\":false}");
1464
+ }
1465
+ }
1466
+
1467
+ const char * llama_cap_release_multimodal(int64_t context_id) {
1468
+ try {
1469
+ std::lock_guard<std::mutex> lock(g_mutex);
1470
+ auto * ctx = get_ctx(context_id);
1471
+ if (ctx) {
1472
+ ctx->releaseMultimodal();
1473
+ }
1474
+ return tls_cstr("{\"ok\":true}");
1475
+ } catch (const std::exception & e) {
1476
+ json err = {{"error", e.what()}};
1477
+ return tls_cstr(err.dump());
1478
+ }
1479
+ }
1480
+
1481
+ const char * llama_cap_init_vocoder(int64_t context_id, const char * path, int n_batch) {
1482
+ try {
1483
+ std::lock_guard<std::mutex> lock(g_mutex);
1484
+ auto * ctx = get_ctx(context_id);
1485
+ if (!ctx) {
1486
+ return tls_cstr("{\"ok\":false,\"error\":\"invalid context\"}");
1487
+ }
1488
+ std::string resolved = resolve_vfs_path(path);
1489
+ if (resolved.empty()) {
1490
+ return tls_cstr("{\"ok\":false,\"error\":\"vocoder path not found\"}");
1491
+ }
1492
+ const bool ok = ctx->initVocoder(resolved, n_batch > 0 ? n_batch : -1);
1493
+ return tls_cstr(json({{"ok", ok}}).dump());
1494
+ } catch (const std::exception & e) {
1495
+ json err = {{"ok", false}, {"error", e.what()}};
1496
+ return tls_cstr(err.dump());
1497
+ }
1498
+ }
1499
+
1500
+ const char * llama_cap_vocoder_enabled(int64_t context_id) {
1501
+ try {
1502
+ std::lock_guard<std::mutex> lock(g_mutex);
1503
+ auto * ctx = get_ctx(context_id);
1504
+ if (!ctx) {
1505
+ return tls_cstr("{\"enabled\":false}");
1506
+ }
1507
+ return tls_cstr(json({{"enabled", ctx->isVocoderEnabled()}}).dump());
1508
+ } catch (...) {
1509
+ return tls_cstr("{\"enabled\":false}");
1510
+ }
1511
+ }
1512
+
1513
+ const char * llama_cap_release_vocoder(int64_t context_id) {
1514
+ try {
1515
+ std::lock_guard<std::mutex> lock(g_mutex);
1516
+ auto * ctx = get_ctx(context_id);
1517
+ if (ctx) {
1518
+ ctx->releaseVocoder();
1519
+ }
1520
+ return tls_cstr("{\"ok\":true}");
1521
+ } catch (const std::exception & e) {
1522
+ json err = {{"error", e.what()}};
1523
+ return tls_cstr(err.dump());
1524
+ }
1525
+ }
1526
+
1527
+ const char * llama_cap_formatted_audio_completion(
1528
+ int64_t context_id,
1529
+ const char * speaker_json,
1530
+ const char * text_to_speak)
1531
+ {
1532
+ try {
1533
+ std::lock_guard<std::mutex> lock(g_mutex);
1534
+ auto * ctx = get_ctx(context_id);
1535
+ if (!ctx || !ctx->isVocoderEnabled() || !ctx->tts_wrapper) {
1536
+ return tls_cstr("{\"error\":\"vocoder not enabled\"}");
1537
+ }
1538
+ json speaker = json::object();
1539
+ if (speaker_json && std::strlen(speaker_json) > 0) {
1540
+ speaker = json::parse(speaker_json);
1541
+ }
1542
+ auto result = ctx->tts_wrapper->getFormattedAudioCompletion(
1543
+ ctx,
1544
+ speaker.is_null() ? std::string("null") : speaker.dump(),
1545
+ text_to_speak ? text_to_speak : "");
1546
+ json out = {{"prompt", result.prompt}};
1547
+ if (result.grammar) {
1548
+ out["grammar"] = result.grammar;
1549
+ }
1550
+ return tls_cstr(out.dump());
1551
+ } catch (const std::exception & e) {
1552
+ json err = {{"error", e.what()}};
1553
+ return tls_cstr(err.dump());
1554
+ }
1555
+ }
1556
+
1557
+ const char * llama_cap_audio_guide_tokens(int64_t context_id, const char * text_to_speak) {
1558
+ try {
1559
+ std::lock_guard<std::mutex> lock(g_mutex);
1560
+ auto * ctx = get_ctx(context_id);
1561
+ if (!ctx || !ctx->isVocoderEnabled() || !ctx->tts_wrapper) {
1562
+ return tls_cstr("{\"error\":\"vocoder not enabled\"}");
1563
+ }
1564
+ auto tokens = ctx->tts_wrapper->getAudioCompletionGuideTokens(
1565
+ ctx,
1566
+ text_to_speak ? text_to_speak : "");
1567
+ json arr = json::array();
1568
+ for (llama_token t : tokens) {
1569
+ arr.push_back(static_cast<int>(t));
1570
+ }
1571
+ return tls_cstr(arr.dump());
1572
+ } catch (const std::exception & e) {
1573
+ json err = {{"error", e.what()}};
1574
+ return tls_cstr(err.dump());
1575
+ }
1576
+ }
1577
+
1578
+ const char * llama_cap_decode_audio_tokens(int64_t context_id, const char * tokens_json) {
1579
+ try {
1580
+ std::lock_guard<std::mutex> lock(g_mutex);
1581
+ auto * ctx = get_ctx(context_id);
1582
+ if (!ctx || !ctx->isVocoderEnabled() || !ctx->tts_wrapper) {
1583
+ return tls_cstr("{\"error\":\"vocoder not enabled\"}");
1584
+ }
1585
+ std::vector<llama_token> tokens;
1586
+ if (tokens_json && std::strlen(tokens_json) > 0) {
1587
+ json arr = json::parse(tokens_json);
1588
+ if (arr.is_array()) {
1589
+ for (const auto & el : arr) {
1590
+ if (el.is_number_integer()) {
1591
+ tokens.push_back(static_cast<llama_token>(el.get<int>()));
1592
+ }
1593
+ }
1594
+ }
1595
+ }
1596
+ auto audio = ctx->tts_wrapper->decodeAudioTokens(ctx, tokens);
1597
+ json arr = json::array();
1598
+ for (float v : audio) {
1599
+ arr.push_back(v);
1600
+ }
1601
+ return tls_cstr(arr.dump());
1602
+ } catch (const std::exception & e) {
1603
+ json err = {{"error", e.what()}};
1604
+ return tls_cstr(err.dump());
1605
+ }
1606
+ }
1607
+
1608
+ char * llama_run_completion(int64_t context_id, const char * params_json) {
1609
+ try {
1610
+ json root = json::object();
1611
+ if (params_json != nullptr && params_json[0] != '\0') {
1612
+ root = json::parse(params_json);
1613
+ }
1614
+
1615
+ capllama::llama_cap_context * context = nullptr;
1616
+ {
1617
+ std::lock_guard<std::mutex> lock(g_mutex);
1618
+ context = get_ctx(context_id);
1619
+ }
1620
+ if (context == nullptr || context->completion == nullptr) {
1621
+ return nullptr;
1622
+ }
1623
+
1624
+ if (context->params.embedding ||
1625
+ (context->ctx != nullptr && llama_pooling_type(context->ctx) != LLAMA_POOLING_TYPE_NONE)) {
1626
+ return nullptr;
1627
+ }
1628
+
1629
+ apply_run_completion_params(context->params, root);
1630
+ const std::vector<std::string> media_paths = read_media_paths_json(root);
1631
+ capllama::llama_cap_context_completion & completion = *context->completion;
1632
+
1633
+ completion.rewind();
1634
+ if (!completion.initSampling()) {
1635
+ return nullptr;
1636
+ }
1637
+ completion.loadPrompt(media_paths);
1638
+ completion.beginCompletion();
1639
+ while (completion.has_next_token && !completion.is_interrupted) {
1640
+ completion.doCompletion();
1641
+ }
1642
+ completion.endCompletion();
1643
+
1644
+ return dup_c_string(build_run_completion_result(completion).dump());
1645
+ } catch (...) {
1646
+ return nullptr;
1647
+ }
1648
+ }
1649
+
1650
+ void llama_free_completion_result(char * result_json) {
1651
+ delete[] result_json;
1652
+ }
1653
+
1654
+ char * llama_run_embedding_json(int64_t context_id, const char * text, const char * params_json) {
1655
+ if (text == nullptr || text[0] == '\0') {
1656
+ return nullptr;
1657
+ }
1658
+ try {
1659
+ const char * params = (params_json != nullptr && params_json[0] != '\0') ? params_json : "{}";
1660
+ float * vec = llama_embedding(context_id, text, params);
1661
+ if (vec == nullptr) {
1662
+ return nullptr;
1663
+ }
1664
+
1665
+ capllama::llama_cap_context * ctx = nullptr;
1666
+ {
1667
+ std::lock_guard<std::mutex> lock(g_mutex);
1668
+ ctx = get_ctx(context_id);
1669
+ }
1670
+ llama_model * model = resolve_model(ctx);
1671
+ if (model == nullptr) {
1672
+ return nullptr;
1673
+ }
1674
+ const int32_t n_embd = llama_model_n_embd(model);
1675
+ if (n_embd <= 0) {
1676
+ return nullptr;
1677
+ }
1678
+
1679
+ json arr = json::array();
1680
+ for (int32_t i = 0; i < n_embd; ++i) {
1681
+ arr.push_back(static_cast<double>(vec[i]));
1682
+ }
1683
+ json out = {{"embedding", arr}};
1684
+ return dup_c_string(out.dump());
1685
+ } catch (...) {
1686
+ return nullptr;
1687
+ }
1688
+ }
1689
+
1690
+ } // extern "C"
1691
+
1692
+ // ---------------------------------------------------------------------------
1693
+ // Public ABI aliases — the Swift LlamaNativeBridge looks up these exact symbols
1694
+ // via dlsym. They forward to the internal implementations above.
1695
+ // ---------------------------------------------------------------------------
1696
+ extern "C" {
1697
+
1698
+ char * llama_rerank_json(int64_t ctx_id, const char * query, const char * docs) {
1699
+ const char * r = llama_cap_rerank(ctx_id, query, docs);
1700
+ return r ? dup_c_string(r) : nullptr;
1701
+ }
1702
+
1703
+ char * llama_bench(int64_t ctx_id, int32_t pp, int32_t tg, int32_t pl, int32_t nr) {
1704
+ const char * r = llama_cap_bench(ctx_id, pp, tg, pl, nr);
1705
+ return r ? dup_c_string(r) : nullptr;
1706
+ }
1707
+
1708
+ char * llama_cap_load_session_file(int64_t ctx_id, const char * path) {
1709
+ const char * r = llama_cap_load_session(ctx_id, path);
1710
+ return r ? dup_c_string(r) : nullptr;
1711
+ }
1712
+
1713
+ int32_t llama_cap_save_session_file(int64_t ctx_id, const char * path, int32_t max_tokens) {
1714
+ const char * r = llama_cap_save_session(ctx_id, path, max_tokens);
1715
+ if (!r) return -1;
1716
+ try {
1717
+ json j = json::parse(r);
1718
+ return j.value("tokens_saved", -1);
1719
+ } catch (...) { return -1; }
1720
+ }
1721
+
1722
+ int32_t llama_apply_lora_adapters(int64_t ctx_id, const char * json_arr) {
1723
+ const char * r = llama_cap_apply_lora(ctx_id, json_arr);
1724
+ return (r && std::string(r).find("error") == std::string::npos) ? 0 : -1;
1725
+ }
1726
+
1727
+ void llama_remove_lora_adapters(int64_t ctx_id) {
1728
+ llama_cap_remove_lora(ctx_id);
1729
+ }
1730
+
1731
+ char * llama_get_loaded_lora_adapters(int64_t ctx_id, const char * /*unused*/) {
1732
+ const char * r = llama_cap_get_lora(ctx_id);
1733
+ return r ? dup_c_string(r) : nullptr;
1734
+ }
1735
+
1736
+ int32_t llama_init_multimodal(int64_t ctx_id, const char * path, int32_t use_gpu) {
1737
+ const char * r = llama_cap_init_multimodal(ctx_id, path, use_gpu);
1738
+ if (!r) return 0;
1739
+ try { return json::parse(r).value("ok", false) ? 1 : 0; } catch (...) { return 0; }
1740
+ }
1741
+
1742
+ int32_t llama_is_multimodal_enabled(int64_t ctx_id) {
1743
+ const char * r = llama_cap_multimodal_status(ctx_id);
1744
+ if (!r) return 0;
1745
+ try { return json::parse(r).value("enabled", false) ? 1 : 0; } catch (...) { return 0; }
1746
+ }
1747
+
1748
+ char * llama_get_multimodal_support(int64_t ctx_id, const char * /*unused*/) {
1749
+ const char * r = llama_cap_multimodal_status(ctx_id);
1750
+ if (!r) return dup_c_string("{\"vision\":false,\"audio\":false}");
1751
+ // Reformat to match expected {vision, audio} shape
1752
+ try {
1753
+ auto j = json::parse(r);
1754
+ json out = {{"vision", j.value("vision", false)}, {"audio", j.value("audio", false)}};
1755
+ return dup_c_string(out.dump());
1756
+ } catch (...) {
1757
+ return dup_c_string("{\"vision\":false,\"audio\":false}");
1758
+ }
1759
+ }
1760
+
1761
+ void llama_release_multimodal(int64_t ctx_id) {
1762
+ llama_cap_release_multimodal(ctx_id);
1763
+ }
1764
+
1765
+ int32_t llama_init_vocoder(int64_t ctx_id, const char * path, int32_t n_batch) {
1766
+ const char * r = llama_cap_init_vocoder(ctx_id, path, n_batch);
1767
+ if (!r) return 0;
1768
+ try { return json::parse(r).value("ok", false) ? 1 : 0; } catch (...) { return 0; }
1769
+ }
1770
+
1771
+ int32_t llama_is_vocoder_enabled(int64_t ctx_id) {
1772
+ const char * r = llama_cap_vocoder_enabled(ctx_id);
1773
+ if (!r) return 0;
1774
+ try { return json::parse(r).value("enabled", false) ? 1 : 0; } catch (...) { return 0; }
1775
+ }
1776
+
1777
+ char * llama_get_formatted_audio_completion(int64_t ctx_id, const char * speaker, const char * text) {
1778
+ const char * r = llama_cap_formatted_audio_completion(ctx_id, speaker, text);
1779
+ return r ? dup_c_string(r) : nullptr;
1780
+ }
1781
+
1782
+ char * llama_get_audio_completion_guide_tokens(int64_t ctx_id, const char * text) {
1783
+ const char * r = llama_cap_audio_guide_tokens(ctx_id, text);
1784
+ return r ? dup_c_string(r) : nullptr;
1785
+ }
1786
+
1787
+ char * llama_decode_audio_tokens(int64_t ctx_id, const char * tokens_json) {
1788
+ const char * r = llama_cap_decode_audio_tokens(ctx_id, tokens_json);
1789
+ return r ? dup_c_string(r) : nullptr;
1790
+ }
1791
+
1792
+ void llama_release_vocoder(int64_t ctx_id) {
1793
+ llama_cap_release_vocoder(ctx_id);
1794
+ }
1795
+
1796
+ char * llama_get_context_gpu_info(int64_t ctx_id, const char * /*unused*/) {
1797
+ std::lock_guard<std::mutex> lock(g_mutex);
1798
+ auto * ctx = get_ctx(ctx_id);
1799
+ if (!ctx) {
1800
+ return dup_c_string("{\"gpu\":false,\"reasonNoGPU\":\"Context not found\"}");
1801
+ }
1802
+ // n_gpu_layers > 0 means GPU was requested; if the model loaded it means Metal ran
1803
+ bool gpu = ctx->params.n_gpu_layers > 0;
1804
+ std::string reason = gpu ? "" : (ctx->params.n_gpu_layers == 0
1805
+ ? "n_gpu_layers=0 (CPU-only)" : "GPU layers requested but not active");
1806
+ json out = {{"gpu", gpu}, {"reasonNoGPU", reason}};
1807
+ return dup_c_string(out.dump());
1808
+ }
1809
+
1810
+ } // extern "C" (public ABI aliases)