@bendyline/gezel-inference 1.0.1 → 1.0.3

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 (264) hide show
  1. package/dist/catalog/chat-models/de/deepseek-v4.1-flash-552b-q2/manifest.json +120 -0
  2. package/dist/catalog/chat-models/de/deepseek-v4.1-flash-552b-q2/versions/1.0.0/manifest.json +21 -0
  3. package/dist/catalog/chat-models/gl/glm-5.3-flash-320b-q2/manifest.json +6 -5
  4. package/dist/catalog/chat-models/gl/glm-5.3-flash-320b-q2/versions/1.0.2/manifest.json +27 -0
  5. package/dist/catalog/chat-models/gl/glm-5.3-flash-320b-q2/versions/1.0.3/manifest.json +27 -0
  6. package/dist/catalog/chat-models/index.json +1 -1
  7. package/dist/catalog/chat-models/ne/nemotron3.5-bolt-74b-iq2/manifest.json +157 -0
  8. package/dist/catalog/chat-models/ne/nemotron3.5-bolt-74b-iq2/versions/1.0.0/manifest.json +30 -0
  9. package/dist/catalog/chat-models/ne/nemotron3.5-super-vl-120b-nvfp4/manifest.json +156 -0
  10. package/dist/catalog/chat-models/ne/nemotron3.5-super-vl-120b-nvfp4/versions/1.0.0/manifest.json +28 -0
  11. package/dist/catalog/chat-models/raw-index.json +1 -1
  12. package/dist/catalog/knowledge-catalogs/be/bendyline-how-to-books/keywords.json +56 -0
  13. package/dist/catalog/knowledge-catalogs/ca/caselaw-alaska-part-0001/keywords.json +12 -0
  14. package/dist/catalog/knowledge-catalogs/ca/caselaw-nevada-part-0001/keywords.json +13 -0
  15. package/dist/catalog/knowledge-catalogs/ca/caselaw-us-300-399-part-0001/keywords.json +14 -0
  16. package/dist/catalog/knowledge-catalogs/ca/caselaw-us-347-349/keywords.json +17 -0
  17. package/dist/catalog/knowledge-catalogs/ca/caselaw-utah-part-0001/keywords.json +12 -0
  18. package/dist/catalog/knowledge-catalogs/ca/caselaw-utah-part-0002/keywords.json +12 -0
  19. package/dist/catalog/knowledge-catalogs/ca/caselaw-washington-part-0001/keywords.json +12 -0
  20. package/dist/catalog/knowledge-catalogs/ca/caselaw-washington-part-0002/keywords.json +12 -0
  21. package/dist/catalog/knowledge-catalogs/ca/caselaw-washington-part-0003/keywords.json +12 -0
  22. package/dist/catalog/knowledge-catalogs/ca/caselaw-washington-part-0004/keywords.json +12 -0
  23. package/dist/catalog/knowledge-catalogs/ca/caselaw-washington-part-0005/keywords.json +12 -0
  24. package/dist/catalog/knowledge-catalogs/ca/caselaw-wyoming-part-0001/keywords.json +12 -0
  25. package/dist/catalog/knowledge-catalogs/index.json +1 -1
  26. package/dist/catalog/knowledge-catalogs/mi/microsoftdocs-aspnet-core-en/keywords.json +21 -0
  27. package/dist/catalog/knowledge-catalogs/mi/microsoftdocs-azure-ai-platform-en/keywords.json +27 -0
  28. package/dist/catalog/knowledge-catalogs/mi/microsoftdocs-azure-apps-compute-en/keywords.json +23 -0
  29. package/dist/catalog/knowledge-catalogs/mi/microsoftdocs-azure-data-aiot-en/keywords.json +26 -0
  30. package/dist/catalog/knowledge-catalogs/mi/microsoftdocs-azure-management-hybrid-en/keywords.json +28 -0
  31. package/dist/catalog/knowledge-catalogs/mi/microsoftdocs-azure-network-security-en/keywords.json +29 -0
  32. package/dist/catalog/knowledge-catalogs/mi/microsoftdocs-dotnet-en/keywords.json +21 -0
  33. package/dist/catalog/knowledge-catalogs/mi/microsoftdocs-entity-framework-en/keywords.json +14 -0
  34. package/dist/catalog/knowledge-catalogs/mi/microsoftdocs-microsoft-365-en/keywords.json +20 -0
  35. package/dist/catalog/knowledge-catalogs/mi/microsoftdocs-powershell-en/keywords.json +24 -0
  36. package/dist/catalog/knowledge-catalogs/mi/microsoftdocs-sql-server-en/keywords.json +27 -0
  37. package/dist/catalog/knowledge-catalogs/mi/microsoftdocs-windows-apps-en/keywords.json +18 -0
  38. package/dist/catalog/knowledge-catalogs/mi/microsoftdocs-windows-server-en/keywords.json +28 -0
  39. package/dist/catalog/knowledge-catalogs/raw-index.json +1 -1
  40. package/dist/catalog/knowledge-catalogs/re/regional-american-south/keywords.json +1700 -0
  41. package/dist/catalog/knowledge-catalogs/re/regional-american-south/versions/2026.10.5/manifest.json +133 -0
  42. package/dist/catalog/knowledge-catalogs/re/regional-andes-amazon/keywords.json +750 -0
  43. package/dist/catalog/knowledge-catalogs/re/regional-andes-amazon/versions/2026.10.5/manifest.json +133 -0
  44. package/dist/catalog/knowledge-catalogs/re/regional-australia-nz/keywords.json +1281 -0
  45. package/dist/catalog/knowledge-catalogs/re/regional-australia-nz/versions/2026.10.5/manifest.json +133 -0
  46. package/dist/catalog/knowledge-catalogs/re/regional-brazil/keywords.json +1125 -0
  47. package/dist/catalog/knowledge-catalogs/re/regional-brazil/versions/2026.10.5/manifest.json +133 -0
  48. package/dist/catalog/knowledge-catalogs/re/regional-britain-ireland/keywords.json +3287 -0
  49. package/dist/catalog/knowledge-catalogs/re/regional-britain-ireland/versions/2026.10.5/manifest.json +133 -0
  50. package/dist/catalog/knowledge-catalogs/re/regional-california-southwest/keywords.json +1835 -0
  51. package/dist/catalog/knowledge-catalogs/re/regional-california-southwest/versions/2026.10.5/manifest.json +133 -0
  52. package/dist/catalog/knowledge-catalogs/re/regional-canada-alaska/keywords.json +1184 -0
  53. package/dist/catalog/knowledge-catalogs/re/regional-canada-alaska/versions/2026.10.5/manifest.json +133 -0
  54. package/dist/catalog/knowledge-catalogs/re/regional-central-eastern-europe/keywords.json +1503 -0
  55. package/dist/catalog/knowledge-catalogs/re/regional-central-eastern-europe/versions/2026.10.5/manifest.json +133 -0
  56. package/dist/catalog/knowledge-catalogs/re/regional-east-asia/keywords.json +2836 -0
  57. package/dist/catalog/knowledge-catalogs/re/regional-east-asia/versions/2026.10.5/manifest.json +133 -0
  58. package/dist/catalog/knowledge-catalogs/re/regional-east-southern-africa/keywords.json +813 -0
  59. package/dist/catalog/knowledge-catalogs/re/regional-east-southern-africa/versions/2026.10.5/manifest.json +133 -0
  60. package/dist/catalog/knowledge-catalogs/re/regional-mediterranean/keywords.json +2082 -0
  61. package/dist/catalog/knowledge-catalogs/re/regional-mediterranean/versions/2026.10.5/manifest.json +133 -0
  62. package/dist/catalog/knowledge-catalogs/re/regional-mexico-central-america/keywords.json +1481 -0
  63. package/dist/catalog/knowledge-catalogs/re/regional-mexico-central-america/versions/2026.10.5/manifest.json +133 -0
  64. package/dist/catalog/knowledge-catalogs/re/regional-middle-east/keywords.json +1272 -0
  65. package/dist/catalog/knowledge-catalogs/re/regional-middle-east/versions/2026.10.5/manifest.json +133 -0
  66. package/dist/catalog/knowledge-catalogs/re/regional-midwest/keywords.json +758 -0
  67. package/dist/catalog/knowledge-catalogs/re/regional-midwest/versions/2026.10.5/manifest.json +133 -0
  68. package/dist/catalog/knowledge-catalogs/re/regional-north-africa/keywords.json +896 -0
  69. package/dist/catalog/knowledge-catalogs/re/regional-north-africa/versions/2026.10.5/manifest.json +133 -0
  70. package/dist/catalog/knowledge-catalogs/re/regional-northeast/keywords.json +1281 -0
  71. package/dist/catalog/knowledge-catalogs/re/regional-northeast/versions/2026.10.5/manifest.json +133 -0
  72. package/dist/catalog/knowledge-catalogs/re/regional-northern-eurasia/keywords.json +346 -0
  73. package/dist/catalog/knowledge-catalogs/re/regional-northern-eurasia/versions/2026.10.5/manifest.json +133 -0
  74. package/dist/catalog/knowledge-catalogs/re/regional-northern-europe/keywords.json +2688 -0
  75. package/dist/catalog/knowledge-catalogs/re/regional-northern-europe/versions/2026.10.5/manifest.json +133 -0
  76. package/dist/catalog/knowledge-catalogs/re/regional-pacific-islands/keywords.json +973 -0
  77. package/dist/catalog/knowledge-catalogs/re/regional-pacific-islands/versions/2026.10.5/manifest.json +133 -0
  78. package/dist/catalog/knowledge-catalogs/re/regional-pacific-northwest/keywords.json +937 -0
  79. package/dist/catalog/knowledge-catalogs/re/regional-pacific-northwest/versions/2026.10.5/manifest.json +133 -0
  80. package/dist/catalog/knowledge-catalogs/re/regional-rockies-plains/keywords.json +608 -0
  81. package/dist/catalog/knowledge-catalogs/re/regional-rockies-plains/versions/2026.10.5/manifest.json +133 -0
  82. package/dist/catalog/knowledge-catalogs/re/regional-south-asia/keywords.json +633 -0
  83. package/dist/catalog/knowledge-catalogs/re/regional-south-asia/versions/2026.10.5/manifest.json +133 -0
  84. package/dist/catalog/knowledge-catalogs/re/regional-southeast-asia/keywords.json +2697 -0
  85. package/dist/catalog/knowledge-catalogs/re/regional-southeast-asia/versions/2026.10.5/manifest.json +133 -0
  86. package/dist/catalog/knowledge-catalogs/re/regional-southern-cone/keywords.json +606 -0
  87. package/dist/catalog/knowledge-catalogs/re/regional-southern-cone/versions/2026.10.5/manifest.json +133 -0
  88. package/dist/catalog/knowledge-catalogs/re/regional-west-central-africa/keywords.json +564 -0
  89. package/dist/catalog/knowledge-catalogs/re/regional-west-central-africa/versions/2026.10.5/manifest.json +133 -0
  90. package/dist/catalog/knowledge-catalogs/re/regional-western-europe/keywords.json +3247 -0
  91. package/dist/catalog/knowledge-catalogs/re/regional-western-europe-east/manifest.json +47 -0
  92. package/dist/catalog/knowledge-catalogs/re/regional-western-europe-east/versions/2026.10.5/manifest.json +133 -0
  93. package/dist/catalog/knowledge-catalogs/re/regional-western-europe-west/manifest.json +47 -0
  94. package/dist/catalog/knowledge-catalogs/re/regional-western-europe-west/versions/2026.10.5/manifest.json +133 -0
  95. package/dist/catalog/knowledge-catalogs/wi/wikipedia-arts/keywords.json +5437 -0
  96. package/dist/catalog/knowledge-catalogs/wi/wikipedia-arts/manifest.json +1 -1
  97. package/dist/catalog/knowledge-catalogs/wi/wikipedia-arts/versions/2026.10.1/manifest.json +181 -0
  98. package/dist/catalog/knowledge-catalogs/wi/wikipedia-astronomy/keywords.json +4009 -0
  99. package/dist/catalog/knowledge-catalogs/wi/wikipedia-astronomy/manifest.json +1 -1
  100. package/dist/catalog/knowledge-catalogs/wi/wikipedia-astronomy/versions/2026.10.1/manifest.json +181 -0
  101. package/dist/catalog/knowledge-catalogs/wi/wikipedia-biology/keywords.json +4009 -0
  102. package/dist/catalog/knowledge-catalogs/wi/wikipedia-biology/manifest.json +1 -1
  103. package/dist/catalog/knowledge-catalogs/wi/wikipedia-biology/versions/2026.10.1/manifest.json +217 -0
  104. package/dist/catalog/knowledge-catalogs/wi/wikipedia-business/keywords.json +5583 -0
  105. package/dist/catalog/knowledge-catalogs/wi/wikipedia-business/manifest.json +1 -1
  106. package/dist/catalog/knowledge-catalogs/wi/wikipedia-business/versions/2026.10.1/manifest.json +197 -0
  107. package/dist/catalog/knowledge-catalogs/wi/wikipedia-film-tv/keywords.json +6386 -0
  108. package/dist/catalog/knowledge-catalogs/wi/wikipedia-film-tv/manifest.json +2 -2
  109. package/dist/catalog/knowledge-catalogs/wi/wikipedia-film-tv/versions/2026.10.1/manifest.json +197 -0
  110. package/dist/catalog/knowledge-catalogs/wi/wikipedia-food-drink/keywords.json +4009 -0
  111. package/dist/catalog/knowledge-catalogs/wi/wikipedia-food-drink/manifest.json +1 -1
  112. package/dist/catalog/knowledge-catalogs/wi/wikipedia-food-drink/versions/2026.10.1/manifest.json +185 -0
  113. package/dist/catalog/knowledge-catalogs/wi/wikipedia-games/keywords.json +5672 -0
  114. package/dist/catalog/knowledge-catalogs/wi/wikipedia-games/manifest.json +1 -1
  115. package/dist/catalog/knowledge-catalogs/wi/wikipedia-games/versions/2026.10.1/manifest.json +137 -0
  116. package/dist/catalog/knowledge-catalogs/wi/wikipedia-geography/manifest.json +68 -0
  117. package/dist/catalog/knowledge-catalogs/wi/wikipedia-geography/versions/2026.10.1/manifest.json +217 -0
  118. package/dist/catalog/knowledge-catalogs/wi/wikipedia-history/manifest.json +68 -0
  119. package/dist/catalog/knowledge-catalogs/wi/wikipedia-history/versions/2026.10.1/manifest.json +217 -0
  120. package/dist/catalog/knowledge-catalogs/wi/wikipedia-language/keywords.json +4009 -0
  121. package/dist/catalog/knowledge-catalogs/wi/wikipedia-language/manifest.json +1 -1
  122. package/dist/catalog/knowledge-catalogs/wi/wikipedia-language/versions/2026.10.1/manifest.json +141 -0
  123. package/dist/catalog/knowledge-catalogs/wi/wikipedia-law/keywords.json +4009 -0
  124. package/dist/catalog/knowledge-catalogs/wi/wikipedia-law/manifest.json +1 -1
  125. package/dist/catalog/knowledge-catalogs/wi/wikipedia-law/versions/2026.10.1/manifest.json +185 -0
  126. package/dist/catalog/knowledge-catalogs/wi/wikipedia-literature/keywords.json +5719 -0
  127. package/dist/catalog/knowledge-catalogs/wi/wikipedia-literature/manifest.json +1 -1
  128. package/dist/catalog/knowledge-catalogs/wi/wikipedia-literature/versions/2026.10.1/manifest.json +193 -0
  129. package/dist/catalog/knowledge-catalogs/wi/wikipedia-mathematics/keywords.json +4009 -0
  130. package/dist/catalog/knowledge-catalogs/wi/wikipedia-mathematics/manifest.json +1 -1
  131. package/dist/catalog/knowledge-catalogs/wi/wikipedia-mathematics/versions/2026.10.1/manifest.json +189 -0
  132. package/dist/catalog/knowledge-catalogs/wi/wikipedia-medicine/keywords.json +16009 -0
  133. package/dist/catalog/knowledge-catalogs/wi/wikipedia-medicine/manifest.json +1 -1
  134. package/dist/catalog/knowledge-catalogs/wi/wikipedia-medicine/versions/2026.10.1/manifest.json +185 -0
  135. package/dist/catalog/knowledge-catalogs/wi/wikipedia-military/keywords.json +5843 -0
  136. package/dist/catalog/knowledge-catalogs/wi/wikipedia-military/manifest.json +1 -1
  137. package/dist/catalog/knowledge-catalogs/wi/wikipedia-military/versions/2026.10.1/manifest.json +189 -0
  138. package/dist/catalog/knowledge-catalogs/wi/wikipedia-music/keywords.json +6264 -0
  139. package/dist/catalog/knowledge-catalogs/wi/wikipedia-music/manifest.json +1 -1
  140. package/dist/catalog/knowledge-catalogs/wi/wikipedia-music/versions/2026.10.1/manifest.json +197 -0
  141. package/dist/catalog/knowledge-catalogs/wi/wikipedia-people-arts/manifest.json +66 -0
  142. package/dist/catalog/knowledge-catalogs/wi/wikipedia-people-arts/versions/2026.10.1/manifest.json +209 -0
  143. package/dist/catalog/knowledge-catalogs/wi/wikipedia-people-business/manifest.json +61 -0
  144. package/dist/catalog/knowledge-catalogs/wi/wikipedia-people-business/versions/2026.10.1/manifest.json +189 -0
  145. package/dist/catalog/knowledge-catalogs/wi/wikipedia-people-entertainment/manifest.json +64 -0
  146. package/dist/catalog/knowledge-catalogs/wi/wikipedia-people-entertainment/versions/2026.10.1/manifest.json +201 -0
  147. package/dist/catalog/knowledge-catalogs/wi/wikipedia-people-military/manifest.json +60 -0
  148. package/dist/catalog/knowledge-catalogs/wi/wikipedia-people-military/versions/2026.10.1/manifest.json +185 -0
  149. package/dist/catalog/knowledge-catalogs/wi/wikipedia-people-other/manifest.json +55 -0
  150. package/dist/catalog/knowledge-catalogs/wi/wikipedia-people-other/versions/2026.10.1/manifest.json +165 -0
  151. package/dist/catalog/knowledge-catalogs/wi/wikipedia-people-politics/manifest.json +63 -0
  152. package/dist/catalog/knowledge-catalogs/wi/wikipedia-people-politics/versions/2026.10.1/manifest.json +197 -0
  153. package/dist/catalog/knowledge-catalogs/wi/wikipedia-people-religion/manifest.json +64 -0
  154. package/dist/catalog/knowledge-catalogs/wi/wikipedia-people-religion/versions/2026.10.1/manifest.json +201 -0
  155. package/dist/catalog/knowledge-catalogs/wi/wikipedia-people-science/manifest.json +65 -0
  156. package/dist/catalog/knowledge-catalogs/wi/wikipedia-people-science/versions/2026.10.1/manifest.json +205 -0
  157. package/dist/catalog/knowledge-catalogs/wi/wikipedia-people-sports/manifest.json +73 -0
  158. package/dist/catalog/knowledge-catalogs/wi/wikipedia-people-sports/versions/2026.10.1/manifest.json +237 -0
  159. package/dist/catalog/knowledge-catalogs/wi/wikipedia-politics/keywords.json +5447 -0
  160. package/dist/catalog/knowledge-catalogs/wi/wikipedia-politics/manifest.json +1 -1
  161. package/dist/catalog/knowledge-catalogs/wi/wikipedia-politics/versions/2026.10.1/manifest.json +197 -0
  162. package/dist/catalog/knowledge-catalogs/wi/wikipedia-religion-philosophy/keywords.json +4009 -0
  163. package/dist/catalog/knowledge-catalogs/wi/wikipedia-religion-philosophy/manifest.json +1 -1
  164. package/dist/catalog/knowledge-catalogs/wi/wikipedia-religion-philosophy/versions/2026.10.1/manifest.json +189 -0
  165. package/dist/catalog/knowledge-catalogs/wi/wikipedia-science/keywords.json +4009 -0
  166. package/dist/catalog/knowledge-catalogs/wi/wikipedia-science/manifest.json +1 -1
  167. package/dist/catalog/knowledge-catalogs/wi/wikipedia-science/versions/2026.10.1/manifest.json +185 -0
  168. package/dist/catalog/knowledge-catalogs/wi/wikipedia-sports/keywords.json +5209 -0
  169. package/dist/catalog/knowledge-catalogs/wi/wikipedia-sports/manifest.json +1 -1
  170. package/dist/catalog/knowledge-catalogs/wi/wikipedia-sports/versions/2026.10.1/manifest.json +193 -0
  171. package/dist/catalog/knowledge-catalogs/wi/wikipedia-technology/keywords.json +6537 -0
  172. package/dist/catalog/knowledge-catalogs/wi/wikipedia-technology/manifest.json +1 -1
  173. package/dist/catalog/knowledge-catalogs/wi/wikipedia-technology/versions/2026.10.1/manifest.json +185 -0
  174. package/dist/catalog/knowledge-catalogs/wi/wikipedia-transportation/keywords.json +5989 -0
  175. package/dist/catalog/knowledge-catalogs/wi/wikipedia-transportation/manifest.json +1 -1
  176. package/dist/catalog/knowledge-catalogs/wi/wikipedia-transportation/versions/2026.10.1/manifest.json +189 -0
  177. package/dist/catalog/provenance.json +1 -1
  178. package/dist/engines/native-file-manifest.json +483 -413
  179. package/dist/engines/native-manifest.d.ts +4 -4
  180. package/dist/engines/native-manifest.js +36 -34
  181. package/dist/engines/resolver.js +3 -1
  182. package/dist/fs/atomic.d.ts +4 -1
  183. package/dist/fs/atomic.js +7 -1
  184. package/dist/http/openai-compat/chat-target.d.ts +1 -1
  185. package/dist/http/openai-compat/chat-target.js +1 -1
  186. package/dist/http/openai-compat/engine-failure.d.ts +25 -0
  187. package/dist/http/openai-compat/engine-failure.js +76 -0
  188. package/dist/http/openai-compat/responses-streaming.js +2 -1
  189. package/dist/http/openai-compat/responses-translate.d.ts +1 -1
  190. package/dist/http/openai-compat/translate.d.ts +1 -1
  191. package/dist/http/routes/engines.js +35 -0
  192. package/dist/http/routes/v1-chat.js +11 -2
  193. package/dist/http/routes/v1-knowledge.js +4 -0
  194. package/dist/http/routes/v1-models.js +6 -4
  195. package/dist/http/routes/v1-responses.d.ts +1 -1
  196. package/dist/http/routes/v1-responses.js +7 -1
  197. package/dist/inference-service.d.ts +6 -1
  198. package/dist/inference-service.js +20 -9
  199. package/dist/knowledge/catalog-host.d.ts +9 -1
  200. package/dist/knowledge/catalog-host.js +2 -0
  201. package/dist/knowledge/manager.d.ts +21 -0
  202. package/dist/knowledge/manager.js +85 -7
  203. package/dist/knowledge/search-worker.d.ts +1 -1
  204. package/dist/knowledge/spatial-query.d.ts +2 -3
  205. package/dist/knowledge/spatial-query.js +12 -6
  206. package/dist/knowledge/worker-host.js +2 -0
  207. package/dist/memory/embed-core.d.ts +16 -0
  208. package/dist/memory/embed-core.js +31 -0
  209. package/dist/memory/embed-worker.d.ts +7 -3
  210. package/dist/memory/embed-worker.js +13 -4
  211. package/dist/memory/embeddings.d.ts +17 -2
  212. package/dist/memory/embeddings.js +36 -7
  213. package/dist/models/ensure.d.ts +2 -0
  214. package/dist/models/ensure.js +9 -0
  215. package/dist/perf/responsiveness.d.ts +14 -1
  216. package/dist/perf/responsiveness.js +81 -10
  217. package/dist/providers/audio/kokoro-frontend.js +1 -1
  218. package/dist/providers/ds4/build-provider.js +6 -0
  219. package/dist/providers/llama-cpp/backend-health.d.ts +30 -0
  220. package/dist/providers/llama-cpp/backend-health.js +64 -0
  221. package/dist/providers/llama-cpp/backend-quarantine.d.ts +40 -0
  222. package/dist/providers/llama-cpp/backend-quarantine.js +100 -0
  223. package/dist/providers/llama-cpp/build-provider.js +247 -199
  224. package/dist/providers/llama-cpp/cache-adapter.d.ts +7 -1
  225. package/dist/providers/llama-cpp/cache-adapter.js +11 -1
  226. package/dist/providers/llama-cpp/devices.d.ts +22 -2
  227. package/dist/providers/llama-cpp/devices.js +48 -5
  228. package/dist/providers/llama-cpp/engine-flags.d.ts +0 -18
  229. package/dist/providers/llama-cpp/engine-flags.js +12 -18
  230. package/dist/providers/llama-cpp/gguf-metadata.js +20 -3
  231. package/dist/providers/llama-cpp/gpu-startup-watch.d.ts +41 -0
  232. package/dist/providers/llama-cpp/gpu-startup-watch.js +58 -0
  233. package/dist/providers/llama-cpp/models.d.ts +6 -0
  234. package/dist/providers/llama-cpp/models.js +23 -2
  235. package/dist/providers/llama-cpp/offload-planner.d.ts +26 -18
  236. package/dist/providers/llama-cpp/offload-planner.js +30 -14
  237. package/dist/providers/llama-cpp/provider.d.ts +22 -3
  238. package/dist/providers/llama-cpp/provider.js +39 -3
  239. package/dist/providers/mlx/provider.js +5 -6
  240. package/dist/providers/mlx/python/qwen4_ple.py +9 -2
  241. package/dist/providers/mlx/reasoning-stream.d.ts +15 -0
  242. package/dist/providers/mlx/reasoning-stream.js +35 -0
  243. package/dist/providers/native/capacity-broker.d.ts +21 -15
  244. package/dist/providers/native/capacity-broker.js +25 -13
  245. package/dist/providers/native/capacity-budget.d.ts +35 -3
  246. package/dist/providers/native/capacity-budget.js +49 -5
  247. package/dist/providers/native/device-capacity.d.ts +1 -1
  248. package/dist/providers/native/device-capacity.js +23 -5
  249. package/dist/providers/native/local-engine-runtime.js +31 -31
  250. package/dist/providers/native/measured-budget.js +2 -0
  251. package/dist/providers/native/supervisor.d.ts +25 -0
  252. package/dist/providers/native/supervisor.js +62 -8
  253. package/dist/providers/queue.d.ts +9 -2
  254. package/dist/providers/queue.js +17 -2
  255. package/dist/providers/session-hooks.d.ts +4 -1
  256. package/dist/system/amdgpu-adapters.d.ts +18 -0
  257. package/dist/system/amdgpu-adapters.js +55 -0
  258. package/dist/system/gpu-carve-out.d.ts +84 -0
  259. package/dist/system/gpu-carve-out.js +111 -0
  260. package/dist/system/memory.d.ts +17 -1
  261. package/dist/system/memory.js +94 -13
  262. package/dist/transformers-cache.d.ts +2 -0
  263. package/dist/transformers-cache.js +6 -3
  264. package/package.json +5 -5
@@ -13,7 +13,7 @@ import { readFile, readdir, rm, stat } from 'node:fs/promises';
13
13
  import { dirname, join, resolve } from 'node:path';
14
14
  import { fileURLToPath } from 'node:url';
15
15
  import { KnowledgeMachineInventorySchema, KnowledgeNearbyRequestSchema, createLogger, formatKnowledgeUri, parseKnowledgeUri, resolveSecurityPolicy, sameVectorSpace, } from '@bendyline/gezel';
16
- import { compareCatalogVersions, knowledgeEmbeddingProfile, readGezkManifest, } from '@bendyline/gezel-knowledge';
16
+ import { compareCatalogVersions, knowledgeEmbeddingProfile, knowledgeProfileQueryModelBytes, missingPinnedFiles, readGezkManifest, } from '@bendyline/gezel-knowledge';
17
17
  import { knowledgeCatalogVersionDir, knowledgeCatalogsDir, knowledgeDownloadsDir, } from '@bendyline/gezel/paths';
18
18
  import { HANDBOEK_KNOWLEDGE_CATALOG, HANDBOEK_KNOWLEDGE_PUBLISHER } from './bundled-catalog.js';
19
19
  import { scoreResult } from '@bendyline/gezel';
@@ -21,10 +21,11 @@ import { sharedKnowledgeRoot, sharedKnowledgeVersionDir, } from '../machine-engi
21
21
  import { embedKnowledgeQuery, sharesDaemonEmbedder } from '../memory/embeddings.js';
22
22
  import { ChatModelInstallRegistry } from '../models/install-registry.js';
23
23
  import { beginPerfWork } from '../perf/responsiveness.js';
24
+ import { HF_CACHE_DIR_ENV, transformersCacheDir } from '../transformers-cache.js';
24
25
  import { resolveKnowledgeCatalogSource } from './catalog-source.js';
25
26
  import { KNOWLEDGE_DOWNLOAD_KEY_PATTERN, installKnowledgeCatalog, knowledgeDownloadKey, pruneOtherKnowledgeCatalogVersions, } from './install.js';
26
27
  import { KnowledgeRegistry } from './registry.js';
27
- import { KnowledgeSpatialCursorError, encodeNearbyCursor, nearbyCursorOffset, regionalDocumentIdentity, } from './spatial-query.js';
28
+ import { KnowledgeSpatialCursorError, encodeNearbyCursor, nearbyCursorOffset, spatialDocumentIdentity, } from './spatial-query.js';
28
29
  import { resolveKnowledgeVectorFloors } from './vector-floors.js';
29
30
  const log = createLogger('knowledge');
30
31
  /** Explicit-search shard budget (S) across every active catalog (§3.4). */
@@ -59,6 +60,9 @@ function semanticSearchModeFor(profile) {
59
60
  return 'profile';
60
61
  return 'keyword-only';
61
62
  }
63
+ function searchModelOf(profile) {
64
+ return { profileId: profile.id, repo: profile.model.repo };
65
+ }
62
66
  /**
63
67
  * Explicit search fuses three arms per catalog by reciprocal rank (k = 60):
64
68
  * the vector chunks (best chunk per document, by rerank cosine), the
@@ -409,10 +413,18 @@ export class KnowledgeManager {
409
413
  const shared = await this.sharedInventoryKeys();
410
414
  const activeIds = new Set(this.installs.active().map((a) => a.id));
411
415
  const partials = await this.partialDownloadKeys();
416
+ const modelBytes = new Map();
417
+ for (const item of gilde.values()) {
418
+ const profileId = item.embeddingProfile.id;
419
+ if (!modelBytes.has(profileId)) {
420
+ modelBytes.set(profileId, await this.searchModelDownloadBytes(profileId));
421
+ }
422
+ }
412
423
  return [...gilde.values()]
413
424
  .sort((a, b) => a.name.localeCompare(b.name))
414
425
  .map((item) => {
415
426
  const entry = registry.find((c) => c.ref.publisherId === item.publisherId && c.ref.catalogId === item.id);
427
+ const searchModelDownloadBytes = modelBytes.get(item.embeddingProfile.id);
416
428
  return {
417
429
  id: item.id,
418
430
  publisherId: item.publisherId,
@@ -451,9 +463,24 @@ export class KnowledgeManager {
451
463
  sharedOnDevice: shared.has(`${item.publisherId}/${item.id}/${item.version}/${item.sha256}`),
452
464
  installing: activeIds.has(item.id),
453
465
  incompleteDownload: partials.has(item.sha256.slice(0, 16)),
466
+ ...(searchModelDownloadBytes ? { searchModelDownloadBytes } : {}),
454
467
  };
455
468
  });
456
469
  }
470
+ /**
471
+ * The search model an install of a catalog with this profile would also
472
+ * download. None when the catalog searches with the daemon's own embedder
473
+ * (an install never fetches that) or the profile's pinned files are cached.
474
+ */
475
+ async searchModelDownloadBytes(profileId) {
476
+ const profile = knowledgeEmbeddingProfile(profileId);
477
+ const bytes = knowledgeProfileQueryModelBytes(profileId);
478
+ if (!profile || bytes === null || semanticSearchModeFor(profile) !== 'profile')
479
+ return undefined;
480
+ const cacheDir = process.env[HF_CACHE_DIR_ENV] ?? transformersCacheDir(this.opts.home);
481
+ const missing = await missingPinnedFiles(profile, { cacheDir });
482
+ return missing.length > 0 ? bytes : undefined;
483
+ }
457
484
  /** Resolve a catalogId (URI authority) to its mounted info, or null. */
458
485
  mountedCatalog(catalogId) {
459
486
  for (const info of this.mountedByKey.values()) {
@@ -676,7 +703,13 @@ export class KnowledgeManager {
676
703
  let warning;
677
704
  const mounted = this.mountedByKey.get(key);
678
705
  if (mounted?.semanticSearch === 'profile') {
679
- yield { type: 'progress', phase: 'embedder', bytesDone: 0, bytesTotal: 0 };
706
+ yield {
707
+ type: 'progress',
708
+ phase: 'embedder',
709
+ bytesDone: 0,
710
+ bytesTotal: 0,
711
+ searchModel: searchModelOf(mounted.embedding),
712
+ };
680
713
  warning = yield* this.prewarmProfile(mounted.embedding);
681
714
  }
682
715
  yield { ...event, ...(warning ? { warning } : {}) };
@@ -746,7 +779,12 @@ export class KnowledgeManager {
746
779
  (settled || Date.now() - lastReportAt >= EMBEDDER_PROGRESS_INTERVAL_MS)) {
747
780
  progress.reported = latest;
748
781
  lastReportAt = Date.now();
749
- yield { type: 'progress', phase: 'embedder', ...latest };
782
+ yield {
783
+ type: 'progress',
784
+ phase: 'embedder',
785
+ ...latest,
786
+ searchModel: searchModelOf(profile),
787
+ };
750
788
  continue;
751
789
  }
752
790
  if (settled)
@@ -802,6 +840,9 @@ export class KnowledgeManager {
802
840
  phase,
803
841
  bytesDone: lastEvent?.type === 'progress' ? lastEvent.bytesDone : 0,
804
842
  bytesTotal: lastEvent?.type === 'progress' ? lastEvent.bytesTotal : 0,
843
+ ...(lastEvent?.type === 'progress' && lastEvent.searchModel
844
+ ? { searchModel: lastEvent.searchModel }
845
+ : {}),
805
846
  };
806
847
  });
807
848
  }
@@ -1035,6 +1076,12 @@ export class KnowledgeManager {
1035
1076
  async documentsPage(catalogId, opts) {
1036
1077
  return this.opts.host.documentsPage(this.requireMounted(catalogId).key, opts);
1037
1078
  }
1079
+ async documentInitials(catalogId, opts) {
1080
+ return this.opts.host.documentInitials(this.requireMounted(catalogId).key, opts);
1081
+ }
1082
+ async documentPosition(catalogId, documentId, opts) {
1083
+ return this.opts.host.documentPosition(this.requireMounted(catalogId).key, documentId, opts);
1084
+ }
1038
1085
  async getDocument(catalogId, documentId) {
1039
1086
  return this.opts.host.getDocument(this.requireMounted(catalogId).key, documentId);
1040
1087
  }
@@ -1145,7 +1192,7 @@ export class KnowledgeManager {
1145
1192
  documentId: document.id,
1146
1193
  }),
1147
1194
  };
1148
- const identity = regionalDocumentIdentity(info.ref.publisherId, info.ref.catalogId, document.id);
1195
+ const identity = spatialDocumentIdentity(info.ref.publisherId, info.ref.catalogId, document.id);
1149
1196
  const previous = unique.get(identity);
1150
1197
  if (!previous || result.distanceMeters < previous.distanceMeters)
1151
1198
  unique.set(identity, result);
@@ -1268,6 +1315,20 @@ export class KnowledgeManager {
1268
1315
  }
1269
1316
  fused.set(key, entry);
1270
1317
  };
1318
+ // Every text passage the arms found, per document, best vector match first,
1319
+ // for `chunksPerDocument`. Fusion still ranks documents, not passages.
1320
+ const passagesByDocument = new Map();
1321
+ if ((opts.chunksPerDocument ?? 1) > 1) {
1322
+ for (const hit of response.chunks
1323
+ .filter((h) => !h.media)
1324
+ .sort((a, b) => (b.cosine ?? -1) - (a.cosine ?? -1))) {
1325
+ const key = `${hit.catalogKey}\u0000${hit.documentId}`;
1326
+ const passages = passagesByDocument.get(key) ?? [];
1327
+ if (!passages.some((known) => known.chunkUid === hit.chunkUid))
1328
+ passages.push(hit);
1329
+ passagesByDocument.set(key, passages);
1330
+ }
1331
+ }
1271
1332
  // Vector arm: one rank per document, in rerank-cosine order. A hit under
1272
1333
  // its profile's floor is not evidence at all — KNN always returns its
1273
1334
  // nearest rows, and a rank-0 row fuses to relevance 1.0 however far away
@@ -1383,7 +1444,7 @@ export class KnowledgeManager {
1383
1444
  const count = perCatalogCount.get(entry.catalogKey) ?? 0;
1384
1445
  if (capPerCatalog && count >= PER_CATALOG_CAP)
1385
1446
  continue;
1386
- const spatialIdentity = regionalDocumentIdentity(info.ref.publisherId, info.ref.catalogId, entry.documentId);
1447
+ const spatialIdentity = spatialDocumentIdentity(info.ref.publisherId, info.ref.catalogId, entry.documentId);
1387
1448
  if (opts.spatial && seenSpatial.has(spatialIdentity))
1388
1449
  continue;
1389
1450
  const relevance = fusedRankRelevance(out.length);
@@ -1399,7 +1460,24 @@ export class KnowledgeManager {
1399
1460
  continue;
1400
1461
  perCatalogCount.set(entry.catalogKey, count + 1);
1401
1462
  seenSpatial.add(spatialIdentity);
1402
- out.push({ ...result, ...matches.get(`${entry.catalogKey}\u0000${entry.documentId}`) });
1463
+ const docKey = `${entry.catalogKey}\u0000${entry.documentId}`;
1464
+ out.push({ ...result, ...matches.get(docKey) });
1465
+ if (out.length >= Math.max(10, opts.maxResults))
1466
+ break;
1467
+ // The document's other passages, each labelled by its own evidence: a
1468
+ // sibling is vector evidence only if it clears the floor by itself.
1469
+ const siblings = (passagesByDocument.get(docKey) ?? [])
1470
+ .filter((hit) => hit.chunkUid !== entry.chunk?.chunkUid)
1471
+ .slice(0, Math.max(0, (opts.chunksPerDocument ?? 1) - 1));
1472
+ const floor = this.vectorFloorFor(entry.catalogKey);
1473
+ for (const sibling of siblings) {
1474
+ const own = floor !== null && sibling.cosine !== undefined && sibling.cosine >= floor
1475
+ ? { arm: 'vector', similarity: sibling.cosine }
1476
+ : { arm: 'fts' };
1477
+ out.push(this.toResult(info, sibling, fusedRankRelevance(out.length), own));
1478
+ if (out.length >= Math.max(10, opts.maxResults))
1479
+ break;
1480
+ }
1403
1481
  if (out.length >= Math.max(10, opts.maxResults))
1404
1482
  break;
1405
1483
  }
@@ -12,6 +12,6 @@
12
12
  */
13
13
  export interface KnowledgeWorkerRequest {
14
14
  id: number;
15
- op: 'mount' | 'unmount' | 'mounted' | 'prewarm' | 'validate' | 'topics' | 'documentsPage' | 'nearbyDocuments' | 'getDocument' | 'getChunk' | 'assets' | 'readAsset' | 'assetFile' | 'search' | 'dispose';
15
+ op: 'mount' | 'unmount' | 'mounted' | 'prewarm' | 'validate' | 'topics' | 'documentsPage' | 'documentInitials' | 'documentPosition' | 'nearbyDocuments' | 'getDocument' | 'getChunk' | 'assets' | 'readAsset' | 'assetFile' | 'search' | 'dispose';
16
16
  args: unknown[];
17
17
  }
@@ -1,11 +1,10 @@
1
1
  /**
2
2
  * Radius pagination binds offsets to the authorized catalog snapshot and
3
3
  * query. Cursors confer no authority; the manager recalculates scope on
4
- * every request. Regional replicas share identity only under Qualla's
5
- * declared regional catalog and source-document naming convention.
4
+ * every request.
6
5
  */
7
6
  export declare class KnowledgeSpatialCursorError extends Error {
8
7
  }
9
8
  export declare function encodeNearbyCursor(snapshot: string, offset: number): string;
10
9
  export declare function nearbyCursorOffset(cursor: string | undefined, snapshot: string): number;
11
- export declare function regionalDocumentIdentity(publisherId: string, catalogId: string, documentId: string): string;
10
+ export declare function spatialDocumentIdentity(publisherId: string, catalogId: string, documentId: string): string;
@@ -1,8 +1,7 @@
1
1
  /**
2
2
  * Radius pagination binds offsets to the authorized catalog snapshot and
3
3
  * query. Cursors confer no authority; the manager recalculates scope on
4
- * every request. Regional replicas share identity only under Qualla's
5
- * declared regional catalog and source-document naming convention.
4
+ * every request.
6
5
  */
7
6
  export class KnowledgeSpatialCursorError extends Error {
8
7
  }
@@ -24,10 +23,17 @@ export function nearbyCursorOffset(cursor, snapshot) {
24
23
  throw new KnowledgeSpatialCursorError('Invalid or stale nearby cursor; restart the query.');
25
24
  }
26
25
  }
27
- export function regionalDocumentIdentity(publisherId, catalogId, documentId) {
28
- const regional = catalogId.startsWith('qualla-region-') &&
29
- /^qualla-(wikipedia|wikivoyage)-[0-9]+$/.test(documentId);
30
- return regional
26
+ /**
27
+ * Source-article ids (`wikipedia-<page-id>`) name the same article in every
28
+ * catalog of one publisher, and regional catalogs overlap heavily — Pacific
29
+ * Northwest and Canada & Alaska share 683 of the former's 1,106 articles — so
30
+ * those copies are one result. Any other id is unique only within its catalog.
31
+ * Keyed on the id, not the catalog name: an earlier catalog-name match never
32
+ * fired for the published `regional-*` catalogs, so every copy came through.
33
+ */
34
+ const SOURCE_ARTICLE_ID = /^(wikipedia|wikivoyage)-[0-9]+$/;
35
+ export function spatialDocumentIdentity(publisherId, catalogId, documentId) {
36
+ return SOURCE_ARTICLE_ID.test(documentId)
31
37
  ? `${publisherId}\u0000${documentId}`
32
38
  : `${publisherId}\u0000${catalogId}\u0000${documentId}`;
33
39
  }
@@ -141,6 +141,8 @@ export function createWorkerCatalogHost() {
141
141
  documentsPage: async (key, opts) =>
142
142
  // biome-ignore lint/suspicious/noExplicitAny: structured-clone round trip preserves the shape
143
143
  (await call('documentsPage', [key, opts])),
144
+ documentInitials: async (key, opts) => (await call('documentInitials', [key, opts])),
145
+ documentPosition: async (key, documentId, opts) => (await call('documentPosition', [key, documentId, opts])),
144
146
  nearbyDocuments: async (key, radius, opts) => (await call('nearbyDocuments', [key, radius, opts])),
145
147
  getDocument: async (key, documentId) =>
146
148
  // biome-ignore lint/suspicious/noExplicitAny: structured-clone round trip preserves the shape
@@ -106,6 +106,22 @@ export declare function isRetryablePipelineLoadFailure(error: unknown): boolean;
106
106
  * will not parse is a runtime problem instead, and retrying cannot fix it.
107
107
  */
108
108
  export declare function isFreshDownloadFailure(error: unknown, graphVerified: boolean): boolean;
109
+ /** Work abandoned because the host asked, at service shutdown. Not a model fault. */
110
+ export declare class ModelWorkCancelledError extends Error {
111
+ constructor();
112
+ }
113
+ /**
114
+ * Abandon the model download in flight and stop embedding runs at their next
115
+ * sub-batch, so the worker reaches idle and can be terminated safely.
116
+ *
117
+ * Shutdown cannot simply terminate a busy worker (that aborts the process
118
+ * inside onnxruntime), and a first-run model download outlasted every drain:
119
+ * quitting during it held the app for 30 s, until the quit timeout. A download
120
+ * is plain network I/O, outside onnxruntime, so stopping it is safe; session
121
+ * creation and a single sub-batch still finish. Work that starts afterwards
122
+ * gets a fresh signal.
123
+ */
124
+ export declare function cancelInFlightModelWork(): void;
109
125
  /** Lazily create the feature-extraction pipeline; cached for the process. */
110
126
  export declare function loadPipeline(): Promise<Pipeline>;
111
127
  /**
@@ -191,6 +191,29 @@ export function isFreshDownloadFailure(error, graphVerified) {
191
191
  /** Fresh starts after a failed download, per process, before vector memory is switched off. */
192
192
  const MAX_FRESH_DOWNLOAD_RETRIES = 3;
193
193
  let freshDownloadFailures = 0;
194
+ /** Work abandoned because the host asked, at service shutdown. Not a model fault. */
195
+ export class ModelWorkCancelledError extends Error {
196
+ constructor() {
197
+ super('model work was cancelled for shutdown');
198
+ this.name = 'ModelWorkCancelledError';
199
+ }
200
+ }
201
+ let cancelController = new AbortController();
202
+ /**
203
+ * Abandon the model download in flight and stop embedding runs at their next
204
+ * sub-batch, so the worker reaches idle and can be terminated safely.
205
+ *
206
+ * Shutdown cannot simply terminate a busy worker (that aborts the process
207
+ * inside onnxruntime), and a first-run model download outlasted every drain:
208
+ * quitting during it held the app for 30 s, until the quit timeout. A download
209
+ * is plain network I/O, outside onnxruntime, so stopping it is safe; session
210
+ * creation and a single sub-batch still finish. Work that starts afterwards
211
+ * gets a fresh signal.
212
+ */
213
+ export function cancelInFlightModelWork() {
214
+ cancelController.abort();
215
+ cancelController = new AbortController();
216
+ }
194
217
  /** The daemon pin's ONNX graph, when it names one with a digest. */
195
218
  function pinnedGraph() {
196
219
  const pin = daemonEmbedderPin();
@@ -203,6 +226,7 @@ let pipelinePromise = null;
203
226
  /** Lazily create the feature-extraction pipeline; cached for the process. */
204
227
  export async function loadPipeline() {
205
228
  if (!pipelinePromise) {
229
+ const signal = cancelController.signal;
206
230
  pipelinePromise = (async () => {
207
231
  let graphVerified = false;
208
232
  try {
@@ -230,6 +254,7 @@ export async function loadPipeline() {
230
254
  const outcome = await ensureVerifiedModelFile(cacheDir, modelId, graph, {
231
255
  remoteHost: env.remoteHost,
232
256
  remotePathTemplate: env.remotePathTemplate,
257
+ signal,
233
258
  });
234
259
  graphVerified = true;
235
260
  if (outcome === 'downloaded')
@@ -241,6 +266,9 @@ export async function loadPipeline() {
241
266
  });
242
267
  }
243
268
  catch (err) {
269
+ // Before the retry bookkeeping: a cancelled download is not a failed one.
270
+ if (signal.aborted)
271
+ throw new ModelWorkCancelledError();
244
272
  const missing = isMissingModule(err, TRANSFORMERS_MODULE);
245
273
  const message = missing
246
274
  ? 'Local memory embeddings are an optional npm feature. Install @huggingface/transformers@^4.3.1 alongside @bendyline/gezel-service (see the service README).'
@@ -272,9 +300,12 @@ export async function loadPipeline() {
272
300
  export async function runEmbed(texts) {
273
301
  if (texts.length === 0)
274
302
  return [];
303
+ const signal = cancelController.signal;
275
304
  const pipe = await loadPipeline();
276
305
  const out = [];
277
306
  for (let i = 0; i < texts.length; i += MAX_BATCH) {
307
+ if (signal.aborted)
308
+ throw new ModelWorkCancelledError();
278
309
  const slice = texts.slice(i, i + MAX_BATCH).map(capText);
279
310
  const result = await pipe(slice, { pooling: 'mean', normalize: true });
280
311
  for (const v of result.tolist())
@@ -11,13 +11,17 @@
11
11
  * with that profile;
12
12
  * reportProgress: send the
13
13
  * model download's bytes
14
+ * { cancel: true } — shutdown: stop the
15
+ * download and runs in flight
14
16
  * worker → host: { id, progress } — interim, zero or more
15
17
  * { id, vectors } — success
16
18
  * { id, error, fatal, — failure (fatal ⇒ model
17
- * retryable, optionalPeerMissing } unloadable, disable for
18
- * good; optionalPeerMissing
19
+ * retryable, optionalPeerMissing, unloadable, disable for
20
+ * cancelled } good; optionalPeerMissing
19
21
  * ⇒ the peer is just not
20
22
  * installed; retryable ⇒ a
21
- * short host-side cooldown)
23
+ * short host-side cooldown;
24
+ * cancelled ⇒ stopped by a
25
+ * cancel, not a fault)
22
26
  */
23
27
  export {};
@@ -11,22 +11,30 @@
11
11
  * with that profile;
12
12
  * reportProgress: send the
13
13
  * model download's bytes
14
+ * { cancel: true } — shutdown: stop the
15
+ * download and runs in flight
14
16
  * worker → host: { id, progress } — interim, zero or more
15
17
  * { id, vectors } — success
16
18
  * { id, error, fatal, — failure (fatal ⇒ model
17
- * retryable, optionalPeerMissing } unloadable, disable for
18
- * good; optionalPeerMissing
19
+ * retryable, optionalPeerMissing, unloadable, disable for
20
+ * cancelled } good; optionalPeerMissing
19
21
  * ⇒ the peer is just not
20
22
  * installed; retryable ⇒ a
21
- * short host-side cooldown)
23
+ * short host-side cooldown;
24
+ * cancelled ⇒ stopped by a
25
+ * cancel, not a fault)
22
26
  */
23
27
  import { parentPort } from 'node:worker_threads';
24
- import { PipelineLoadError, runEmbed, runProfileQueryEmbed } from './embed-core.js';
28
+ import { ModelWorkCancelledError, PipelineLoadError, cancelInFlightModelWork, runEmbed, runProfileQueryEmbed, } from './embed-core.js';
25
29
  if (!parentPort) {
26
30
  throw new Error('embed-worker must be run as a worker thread');
27
31
  }
28
32
  const port = parentPort;
29
33
  port.on('message', (msg) => {
34
+ if ('cancel' in msg) {
35
+ cancelInFlightModelWork();
36
+ return;
37
+ }
30
38
  void (async () => {
31
39
  try {
32
40
  const vectors = msg.profile
@@ -43,6 +51,7 @@ port.on('message', (msg) => {
43
51
  fatal: err instanceof PipelineLoadError && !err.retryable,
44
52
  retryable: err instanceof PipelineLoadError && err.retryable,
45
53
  optionalPeerMissing: err instanceof PipelineLoadError && err.optionalPeerMissing,
54
+ cancelled: err instanceof ModelWorkCancelledError,
46
55
  });
47
56
  }
48
57
  })();
@@ -35,6 +35,15 @@ export declare class EmbeddingsUnavailableError extends EmbeddingsDisabledError
35
35
  readonly retryable = true;
36
36
  constructor(reason: string);
37
37
  }
38
+ /**
39
+ * The service is stopping. Callers that keep per-item retry budgets check
40
+ * `shuttingDown` so a quit never counts against a file the way a real model
41
+ * failure does.
42
+ */
43
+ export declare class EmbeddingsShuttingDownError extends EmbeddingsUnavailableError {
44
+ readonly shuttingDown = true;
45
+ constructor();
46
+ }
38
47
  export type EmbeddingPipelineStatus = 'cold' | 'warming' | 'ready' | 'disabled' | 'unavailable';
39
48
  export declare function embeddingsDisabledReason(): string | null;
40
49
  export declare function embeddingPipelineStatus(): EmbeddingPipelineStatus;
@@ -49,8 +58,14 @@ export declare function embeddingsHealth(): {
49
58
  };
50
59
  /**
51
60
  * Close for service shutdown: refuse new work (it would start a worker, or
52
- * load the model in-process, as the process exits), then terminate the worker
53
- * once its in-flight work has finished, waiting at most `drainMs`.
61
+ * load the model in-process, as the process exits), cancel what is in flight,
62
+ * then terminate the worker once it is idle, waiting at most `drainMs`.
63
+ *
64
+ * The cancel is what makes the wait short: a first-run model download stops at
65
+ * once, and an embedding run stops at its next sub-batch. Without it the drain
66
+ * expired with the download still going, and every caller awaiting that
67
+ * embedding (index enrichment among them) held shutdown until the app's quit
68
+ * timeout.
54
69
  */
55
70
  export declare function shutdownEmbeddings(drainMs: number): Promise<void>;
56
71
  /** Reopen after shutdown — an embedded service can be started again in the same process. */
@@ -21,7 +21,7 @@ import { Worker } from 'node:worker_threads';
21
21
  import { createLogger, sameVectorSpace } from '@bendyline/gezel';
22
22
  import { retireModelWorker } from '../utils/retire-model-worker.js';
23
23
  import { findServiceWorkerEntry } from '../utils/service-worker-entry.js';
24
- import { PipelineLoadError, daemonEmbedderPin, daemonEmbedderVerified, passageInstruction, queryInstruction, runEmbed, runProfileQueryEmbed, } from './embed-core.js';
24
+ import { ModelWorkCancelledError, PipelineLoadError, cancelInFlightModelWork, daemonEmbedderPin, daemonEmbedderVerified, passageInstruction, queryInstruction, runEmbed, runProfileQueryEmbed, } from './embed-core.js';
25
25
  const log = createLogger('memory');
26
26
  /**
27
27
  * Marker class so callers can tell "the indexer is off on purpose" apart from a
@@ -46,6 +46,18 @@ export class EmbeddingsUnavailableError extends EmbeddingsDisabledError {
46
46
  this.message = `memory embeddings temporarily unavailable: ${reason}`;
47
47
  }
48
48
  }
49
+ /**
50
+ * The service is stopping. Callers that keep per-item retry budgets check
51
+ * `shuttingDown` so a quit never counts against a file the way a real model
52
+ * failure does.
53
+ */
54
+ export class EmbeddingsShuttingDownError extends EmbeddingsUnavailableError {
55
+ shuttingDown = true;
56
+ constructor() {
57
+ super('the service is shutting down');
58
+ this.name = 'EmbeddingsShuttingDownError';
59
+ }
60
+ }
49
61
  // Once the model fails to load, we permanently flip into a disabled state.
50
62
  // Every subsequent call throws the short marker error instead of re-attempting
51
63
  // the import and re-printing the full model-load error on every chat turn.
@@ -142,6 +154,11 @@ function onMessage(msg) {
142
154
  pending.delete(msg.id);
143
155
  if (pending.size === 0)
144
156
  worker?.unref();
157
+ if (msg.cancelled) {
158
+ // Our own shutdown cancel: no cooldown, no profile outage, nothing disabled.
159
+ p.reject(new EmbeddingsShuttingDownError());
160
+ return;
161
+ }
145
162
  if (msg.error) {
146
163
  if (p.profileId) {
147
164
  if (!p.localFilesOnly && (msg.fatal || msg.retryable))
@@ -214,6 +231,8 @@ async function embedInProcess(texts) {
214
231
  return await runEmbed(texts);
215
232
  }
216
233
  catch (err) {
234
+ if (err instanceof ModelWorkCancelledError)
235
+ throw new EmbeddingsShuttingDownError();
217
236
  if (err instanceof PipelineLoadError) {
218
237
  if (err.retryable) {
219
238
  markTemporarilyUnavailable(err.message);
@@ -256,17 +275,27 @@ function markDisabled(message, optionalPeerMissing = false) {
256
275
  log.error('[memory] failed to load embedding pipeline; vector memory is now disabled for this process.');
257
276
  log.error('[memory] underlying error:', message);
258
277
  }
259
- const SHUTTING_DOWN = 'the service is shutting down';
260
278
  /**
261
279
  * Close for service shutdown: refuse new work (it would start a worker, or
262
- * load the model in-process, as the process exits), then terminate the worker
263
- * once its in-flight work has finished, waiting at most `drainMs`.
280
+ * load the model in-process, as the process exits), cancel what is in flight,
281
+ * then terminate the worker once it is idle, waiting at most `drainMs`.
282
+ *
283
+ * The cancel is what makes the wait short: a first-run model download stops at
284
+ * once, and an embedding run stops at its next sub-batch. Without it the drain
285
+ * expired with the download still going, and every caller awaiting that
286
+ * embedding (index enrichment among them) held shutdown until the app's quit
287
+ * timeout.
264
288
  */
265
289
  export async function shutdownEmbeddings(drainMs) {
266
290
  closed = true;
267
291
  const w = worker;
268
- if (!w)
292
+ if (!w) {
293
+ // In-process fallback: any run in flight is on this thread.
294
+ cancelInFlightModelWork();
269
295
  return;
296
+ }
297
+ if (pending.size > 0)
298
+ w.postMessage({ cancel: true });
270
299
  if (await retireModelWorker(w, () => pending.size === 0, drainMs)) {
271
300
  if (worker === w)
272
301
  worker = null;
@@ -281,7 +310,7 @@ export function openEmbeddings() {
281
310
  /** Embed `texts`, preferring the worker and degrading to in-process. */
282
311
  async function embedMany(texts) {
283
312
  if (closed)
284
- throw new EmbeddingsUnavailableError(SHUTTING_DOWN);
313
+ throw new EmbeddingsShuttingDownError();
285
314
  if (disabledByEnv())
286
315
  throw new EmbeddingsDisabledError(ENV_DISABLED_REASON);
287
316
  if (disabledReason)
@@ -368,7 +397,7 @@ export function sharesDaemonEmbedder(profile) {
368
397
  export async function embedKnowledgeQuery(text, profile, opts = {}) {
369
398
  const { onDownloadProgress, localFilesOnly = false } = opts;
370
399
  if (closed)
371
- throw new EmbeddingsUnavailableError(SHUTTING_DOWN);
400
+ throw new EmbeddingsShuttingDownError();
372
401
  // The default daemon pipeline may fetch weights on a cold load.
373
402
  if (!localFilesOnly && sharesDaemonEmbedder(profile)) {
374
403
  const vector = await embedQuery(text);
@@ -111,6 +111,8 @@ export interface CreateEnsureModelOptions {
111
111
  backend: EnsureBackend;
112
112
  catalogId: string;
113
113
  }) => void;
114
+ /** Test seam for catalog backend exclusions — defaults to the process's own. */
115
+ resolveLlamaBackend?: () => string | undefined;
114
116
  }
115
117
  /**
116
118
  * Parse a qualified id (`<backend>:<catalogId>`) into its parts. Bare
@@ -1,4 +1,6 @@
1
1
  import { randomUUID } from 'node:crypto';
2
+ import { llamaCppBackendExclusion, llamaCppBackendLabel } from '@bendyline/gezel';
3
+ import { currentLlamaCppBackend } from '../providers/llama-cpp/backend-health.js';
2
4
  /**
3
5
  * Error class with a structured `code` so the route can pick the
4
6
  * right HTTP status. Avoids regex-matching error message strings.
@@ -160,6 +162,13 @@ export async function createEnsureModelOrchestrator(opts) {
160
162
  ].filter((id) => Boolean(id));
161
163
  throw new KnownEnsureError('no_source_for_backend', `chat-model "${catalogId}" has no ${backend} source${alternatives.length > 0 ? ` — try ${alternatives.map((id) => `"${id}"`).join(' or ')}` : ''}`);
162
164
  }
165
+ if (backend === 'llama-cpp') {
166
+ const engineBackend = (opts.resolveLlamaBackend ?? currentLlamaCppBackend)();
167
+ const excluded = llamaCppBackendExclusion(detail.manifest.llamaCpp, engineBackend);
168
+ if (excluded && engineBackend) {
169
+ throw new KnownEnsureError('no_source_for_backend', `chat-model "${catalogId}" is not available on the ${llamaCppBackendLabel(engineBackend)} llama.cpp engine: ${excluded}${detail.manifest.mlx ? ` Try "mlx:${catalogId}".` : ''}`);
170
+ }
171
+ }
163
172
  if (await isInstalled(backend, catalogId)) {
164
173
  return { status: 'ready', modelId: qualifiedModelId };
165
174
  }
@@ -2,7 +2,7 @@ import { type ClientPerfReport, type PerfSnapshot, type PerfWork } from '@bendyl
2
2
  /**
3
3
  * A gap is measured to within one beat, so this has to stay well inside
4
4
  * `STALL_RECORD_MS`. At the same period, a pause long enough to log leaves the
5
- * watchdog late by more than half the gap, which is what `isMainThreadBlock`
5
+ * watchdog late by more than half the heartbeat's lateness, which is what `isMainThreadBlock`
6
6
  * needs to rule the main thread out.
7
7
  */
8
8
  export declare const BEAT_MS = 250;
@@ -18,8 +18,21 @@ interface WatchdogReport {
18
18
  * A stale heartbeat is a main-thread block only when the watchdog itself kept
19
19
  * time. When the watchdog was late by a comparable amount, the process or the
20
20
  * host was paused, and blaming whatever was in flight would be a false lead.
21
+ *
22
+ * Lateness against lateness: both threads sleep a beat between checks, so a
23
+ * pause delays each by about its length. Comparing the watchdog's lateness
24
+ * with the whole gap, beat included, filed short pauses as blocks.
21
25
  */
22
26
  export declare function isMainThreadBlock(report: WatchdogReport): boolean;
27
+ /** What a gap cost: the main thread's CPU, and the process's major page faults. */
28
+ export interface StallCost {
29
+ /** Null where the runtime has no per-thread CPU accounting. */
30
+ cpuMs: number | null;
31
+ pageFaults: number;
32
+ }
33
+ /** Running code when it used at least half the stall; otherwise paging or waiting on something. */
34
+ export declare function stallVerdict(durationMs: number, cost: StallCost): 'running code' | 'paging' | 'blocked waiting' | undefined;
35
+ export declare function describeStallCost(durationMs: number, cost: StallCost): string;
23
36
  interface ActiveWork {
24
37
  label: string;
25
38
  startedAt: number;