@bendyline/gezel-inference 1.0.2 → 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 (215) 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 +182 -112
  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/routes/v1-chat.js +1 -1
  187. package/dist/http/routes/v1-knowledge.js +4 -0
  188. package/dist/http/routes/v1-responses.d.ts +1 -1
  189. package/dist/inference-service.d.ts +6 -1
  190. package/dist/inference-service.js +20 -9
  191. package/dist/knowledge/manager.d.ts +7 -0
  192. package/dist/knowledge/manager.js +32 -1
  193. package/dist/perf/responsiveness.js +4 -3
  194. package/dist/providers/audio/kokoro-frontend.js +1 -1
  195. package/dist/providers/ds4/build-provider.js +6 -0
  196. package/dist/providers/llama-cpp/build-provider.js +107 -56
  197. package/dist/providers/llama-cpp/cache-adapter.d.ts +7 -1
  198. package/dist/providers/llama-cpp/cache-adapter.js +9 -0
  199. package/dist/providers/llama-cpp/devices.d.ts +13 -2
  200. package/dist/providers/llama-cpp/devices.js +43 -6
  201. package/dist/providers/llama-cpp/models.js +4 -1
  202. package/dist/providers/llama-cpp/offload-planner.d.ts +23 -0
  203. package/dist/providers/llama-cpp/offload-planner.js +27 -0
  204. package/dist/providers/llama-cpp/provider.d.ts +14 -3
  205. package/dist/providers/llama-cpp/provider.js +35 -2
  206. package/dist/providers/mlx/provider.js +2 -2
  207. package/dist/providers/mlx/reasoning-stream.d.ts +15 -0
  208. package/dist/providers/mlx/reasoning-stream.js +35 -0
  209. package/dist/providers/native/capacity-broker.js +9 -3
  210. package/dist/providers/native/local-engine-runtime.js +15 -12
  211. package/dist/providers/native/supervisor.js +3 -1
  212. package/dist/providers/queue.d.ts +9 -2
  213. package/dist/providers/queue.js +17 -2
  214. package/dist/providers/session-hooks.d.ts +4 -1
  215. package/package.json +5 -5
@@ -123,7 +123,7 @@ export declare class LlamaCppCacheAdapter implements EngineCacheAdapter {
123
123
  */
124
124
  private allocationTail;
125
125
  private engineGeneration;
126
- private readonly slotCount;
126
+ private slotCount;
127
127
  private readonly resolveBaseUrl;
128
128
  private readonly resolveAuthToken;
129
129
  private readonly fetchImpl;
@@ -168,6 +168,12 @@ export declare class LlamaCppCacheAdapter implements EngineCacheAdapter {
168
168
  */
169
169
  private slotActionsUnsupported;
170
170
  constructor(opts: LlamaCppCacheAdapterOptions);
171
+ /**
172
+ * Follow an engine relaunched with a different `--parallel` count. Every
173
+ * binding goes with it: a session pinned to a slot the new engine lacks
174
+ * would send an `id_slot` the server rejects.
175
+ */
176
+ setSlotCount(slotCount: number): void;
171
177
  /** The provider survives engine restarts, but its resident slot contents do not. */
172
178
  resetEngineState(): void;
173
179
  /**
@@ -181,6 +181,15 @@ export class LlamaCppCacheAdapter {
181
181
  throw new Error('Invalid llama disk cache budget');
182
182
  this.diskCacheBudgetBytes = budget * 1024 * 1024;
183
183
  }
184
+ /**
185
+ * Follow an engine relaunched with a different `--parallel` count. Every
186
+ * binding goes with it: a session pinned to a slot the new engine lacks
187
+ * would send an `id_slot` the server rejects.
188
+ */
189
+ setSlotCount(slotCount) {
190
+ this.slotCount = Math.max(1, Math.floor(slotCount));
191
+ this.resetEngineState();
192
+ }
184
193
  /** The provider survives engine restarts, but its resident slot contents do not. */
185
194
  resetEngineState() {
186
195
  this.engineGeneration++;
@@ -37,6 +37,8 @@ export interface LlamaDeviceProbe {
37
37
  * means the cached devices describe a machine that no longer exists.
38
38
  */
39
39
  systemRamBytes?: number;
40
+ /** {@link llamaBinaryFingerprint} of the build that answered. */
41
+ binary?: string;
40
42
  }
41
43
  export interface NvidiaRuntimeDevice {
42
44
  index: number;
@@ -75,11 +77,20 @@ export declare function maxGpuVramBytes(devices: LlamaDevice[]): number;
75
77
  * (e.g. "AMD Radeon RX 7900 XTX") in a tier rationale. Pure — unit-tested.
76
78
  */
77
79
  export declare function pickBestGpuDevice(devices: LlamaDevice[]): LlamaDevice | null;
80
+ /**
81
+ * Which build a cached listing came from: its path (the backend) and its
82
+ * size and mtime (the release, since a native update rewrites the files).
83
+ * A listing taken from the CPU build, or from the build an update replaced,
84
+ * said "no GPU" about a 32 GB Radeon and was served for the rest of the day
85
+ * (2026-10-19).
86
+ */
87
+ export declare function llamaBinaryFingerprint(binaryPath: string): string;
78
88
  /**
79
89
  * Probe device memory by running `<binaryPath> --list-devices`, caching
80
90
  * the result. Reads the cache when present (VRAM capacity is stable per
81
- * machine) and the OS still reports the RAM it was probed with; pass
82
- * `force` to re-probe. Any failure (spawn error, no
91
+ * machine), the OS still reports the RAM it was probed with, and the same
92
+ * build answered it; an empty listing is re-asked hourly. Pass `force` to
93
+ * re-probe. Any failure (spawn error, no
83
94
  * cache, unparseable output) resolves to an empty device list — the
84
95
  * planner then simply does nothing and the engine's own `--fit`/`-ngl
85
96
  * auto` takes over. `nowIso` and `systemRamBytes` are injectable for
@@ -16,7 +16,7 @@
16
16
  * `backend.json` pattern in `core/native/llama-backend.ts`.
17
17
  */
18
18
  import { execFile } from 'node:child_process';
19
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
19
+ import { existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
20
20
  import { totalmem } from 'node:os';
21
21
  import { delimiter, dirname, join } from 'node:path';
22
22
  import { promisify } from 'node:util';
@@ -122,11 +122,34 @@ export function pickBestGpuDevice(devices) {
122
122
  function cachePath(home) {
123
123
  return join(home, 'engines', 'llama-cpp', 'devices.json');
124
124
  }
125
+ /**
126
+ * An empty listing is re-asked after this long. A CPU build always answers
127
+ * "(none)", but so can a GPU build whose driver was mid-update, and a cached
128
+ * "no GPU" sizes every model against system RAM.
129
+ */
130
+ const EMPTY_DEVICES_RETRY_MS = 60 * 60 * 1000;
131
+ /**
132
+ * Which build a cached listing came from: its path (the backend) and its
133
+ * size and mtime (the release, since a native update rewrites the files).
134
+ * A listing taken from the CPU build, or from the build an update replaced,
135
+ * said "no GPU" about a 32 GB Radeon and was served for the rest of the day
136
+ * (2026-10-19).
137
+ */
138
+ export function llamaBinaryFingerprint(binaryPath) {
139
+ try {
140
+ const { size, mtimeMs } = statSync(binaryPath);
141
+ return `${binaryPath}|${size}|${Math.floor(mtimeMs)}`;
142
+ }
143
+ catch {
144
+ return binaryPath;
145
+ }
146
+ }
125
147
  /**
126
148
  * Probe device memory by running `<binaryPath> --list-devices`, caching
127
149
  * the result. Reads the cache when present (VRAM capacity is stable per
128
- * machine) and the OS still reports the RAM it was probed with; pass
129
- * `force` to re-probe. Any failure (spawn error, no
150
+ * machine), the OS still reports the RAM it was probed with, and the same
151
+ * build answered it; an empty listing is re-asked hourly. Pass `force` to
152
+ * re-probe. Any failure (spawn error, no
130
153
  * cache, unparseable output) resolves to an empty device list — the
131
154
  * planner then simply does nothing and the engine's own `--fit`/`-ngl
132
155
  * auto` takes over. `nowIso` and `systemRamBytes` are injectable for
@@ -137,11 +160,19 @@ export async function probeLlamaDevices(opts) {
137
160
  const now = opts.nowIso ?? (() => new Date().toISOString());
138
161
  const systemRamBytes = opts.systemRamBytes ?? totalmem();
139
162
  const path = cachePath(home);
163
+ const binary = llamaBinaryFingerprint(binaryPath);
140
164
  if (!force && existsSync(path)) {
141
165
  try {
142
166
  const cached = JSON.parse(readFileSync(path, 'utf8'));
143
- // A cache without the RAM figure predates the check and re-probes once.
144
- if (Array.isArray(cached.devices) && cached.systemRamBytes === systemRamBytes) {
167
+ // A cache without the RAM figure or the binary predates that check and
168
+ // re-probes once.
169
+ const emptyExpired = Array.isArray(cached.devices) &&
170
+ cached.devices.length === 0 &&
171
+ !(Date.parse(now()) - Date.parse(cached.probedAt) < EMPTY_DEVICES_RETRY_MS);
172
+ if (Array.isArray(cached.devices) &&
173
+ cached.systemRamBytes === systemRamBytes &&
174
+ cached.binary === binary &&
175
+ !emptyExpired) {
145
176
  return { ...cached, cached: true };
146
177
  }
147
178
  }
@@ -171,7 +202,13 @@ export async function probeLlamaDevices(opts) {
171
202
  // Leave devices empty — caller treats "no devices" as "no offload target".
172
203
  return { devices, probedAt: now(), cached: false };
173
204
  }
174
- const probe = { devices, probedAt: now(), cached: false, systemRamBytes };
205
+ const probe = {
206
+ devices,
207
+ probedAt: now(),
208
+ cached: false,
209
+ systemRamBytes,
210
+ binary,
211
+ };
175
212
  try {
176
213
  mkdirSync(dirname(path), { recursive: true });
177
214
  writeFileSync(path, JSON.stringify(probe, null, 2), 'utf8');
@@ -748,7 +748,10 @@ export class LlamaCppModelManager {
748
748
  installed.fileSha256 = await hashModelPayloadFiles(itemDir, verifiedDigests);
749
749
  await writeFile(join(itemDir, 'manifest.json'), JSON.stringify(installed, null, 2), 'utf8');
750
750
  await makeSharedModelReadable(itemDir);
751
- const warning = summary.chatTemplateMissing
751
+ // ds4-server renders each family's prompt itself and never reads
752
+ // tokenizer.chat_template; DeepSeek V4.1 ships no Jinja template at all,
753
+ // and its first install warned that replies "may be incoherent".
754
+ const warning = summary.chatTemplateMissing && this.engine === 'llama-cpp'
752
755
  ? 'Model has no embedded chat template (tokenizer.chat_template is missing). llama-server will fall back to a generic template that may not match this model — replies may be incoherent. Consider a different quant of the same model.'
753
756
  : undefined;
754
757
  try {
@@ -166,6 +166,29 @@ export declare function planMoeOffload(input: MoeOffloadInput): MoeOffloadDecisi
166
166
  export declare function degradeMoeOffloadDecision(decision: MoeOffloadDecision | undefined): MoeOffloadDecision | null;
167
167
  /** Dense counterpart of {@link degradeMoeOffloadDecision}. */
168
168
  export declare function degradeDenseFfnOffloadDecision(decision: MoeOffloadDecision | undefined, blockCount: number | undefined): MoeOffloadDecision | null;
169
+ /**
170
+ * Room an extra engine slot must leave on the card. Other programs' graphics
171
+ * memory moves while a model runs (a browser, a video, the desktop), and a
172
+ * two-slot launch planned to within a gigabyte or two died with a Vulkan
173
+ * out-of-memory when they took more (2026-10-19, a 32 GB card).
174
+ */
175
+ export declare const EXTRA_SLOT_GPU_CUSHION_BYTES: number;
176
+ /**
177
+ * Fewer slots before system RAM. Keep `slots` only when the model stays
178
+ * fully on the GPU with {@link EXTRA_SLOT_GPU_CUSHION_BYTES} to spare;
179
+ * otherwise drop to one slot when that alone keeps it fully on the GPU. A
180
+ * model that needs system RAM even at one slot keeps its slots and the
181
+ * planner's split. `plan` prices one slot count against one VRAM pool.
182
+ */
183
+ export declare function shedSlotsForGpuResidency(input: {
184
+ slots: number;
185
+ vramBytes: number;
186
+ plan: (slots: number, vramBytes: number) => MoeOffloadDecision;
187
+ }): {
188
+ slots: number;
189
+ decision: MoeOffloadDecision;
190
+ shed: boolean;
191
+ };
169
192
  export interface KvReserveInput {
170
193
  blockCount?: number | undefined;
171
194
  embeddingLength?: number | undefined;
@@ -272,6 +272,33 @@ export function degradeDenseFfnOffloadDecision(decision, blockCount) {
272
272
  }
273
273
  return null;
274
274
  }
275
+ /**
276
+ * Room an extra engine slot must leave on the card. Other programs' graphics
277
+ * memory moves while a model runs (a browser, a video, the desktop), and a
278
+ * two-slot launch planned to within a gigabyte or two died with a Vulkan
279
+ * out-of-memory when they took more (2026-10-19, a 32 GB card).
280
+ */
281
+ export const EXTRA_SLOT_GPU_CUSHION_BYTES = 2 * 1024 ** 3;
282
+ /**
283
+ * Fewer slots before system RAM. Keep `slots` only when the model stays
284
+ * fully on the GPU with {@link EXTRA_SLOT_GPU_CUSHION_BYTES} to spare;
285
+ * otherwise drop to one slot when that alone keeps it fully on the GPU. A
286
+ * model that needs system RAM even at one slot keeps its slots and the
287
+ * planner's split. `plan` prices one slot count against one VRAM pool.
288
+ */
289
+ export function shedSlotsForGpuResidency(input) {
290
+ const decision = input.plan(input.slots, input.vramBytes);
291
+ const keep = { slots: input.slots, decision, shed: false };
292
+ if (input.slots <= 1 || input.vramBytes <= 0)
293
+ return keep;
294
+ const roomy = input.plan(input.slots, Math.max(0, input.vramBytes - EXTRA_SLOT_GPU_CUSHION_BYTES));
295
+ if (roomy.fullGpuResidency)
296
+ return keep;
297
+ const single = input.plan(1, input.vramBytes);
298
+ if (!single.fullGpuResidency)
299
+ return keep;
300
+ return { slots: 1, decision: single, shed: true };
301
+ }
275
302
  /** Bytes per KV element for llama-server's `--cache-type-k/v` values. */
276
303
  const KV_BYTES_PER_ELEMENT = {
277
304
  f32: 4,
@@ -143,7 +143,9 @@ export declare class LlamaCppProvider implements LLMProvider {
143
143
  /** Request-scoped effort shape for compatible wrappers such as ds4-server. */
144
144
  private readonly reasoningEffortRequestShape;
145
145
  /** Engine batch width; see the `batchMaxConcurrency` constructor opt. */
146
- private readonly batchMaxConcurrency;
146
+ private batchMaxConcurrency;
147
+ /** See the `reserveBackgroundSlot` constructor opt. */
148
+ private readonly reservesBackgroundLane;
147
149
  /**
148
150
  * Number of real llama-server request slots (`--parallel`). The provider
149
151
  * queue can intentionally be wider than this by one reserved background
@@ -152,7 +154,7 @@ export declare class LlamaCppProvider implements LLMProvider {
152
154
  * boundary: cache slot save/restore plus the streamed engine request must
153
155
  * never exceed the slots the native server actually owns.
154
156
  */
155
- private readonly engineRequestWidth;
157
+ private engineRequestWidth;
156
158
  private engineRequestsActive;
157
159
  private readonly engineRequestWaiters;
158
160
  /**
@@ -409,7 +411,7 @@ export declare class LlamaCppProvider implements LLMProvider {
409
411
  */
410
412
  private cacheAdapter;
411
413
  private slotSavePath?;
412
- private readonly launchedSlots;
414
+ private launchedSlots;
413
415
  setCacheAdapter(adapter: import('./cache-adapter.js').LlamaCppCacheAdapter): void;
414
416
  getCacheAdapter(): import('./cache-adapter.js').LlamaCppCacheAdapter | null;
415
417
  /**
@@ -430,6 +432,15 @@ export declare class LlamaCppProvider implements LLMProvider {
430
432
  * would 400 silently, and the debug probe's slot narration was fiction.
431
433
  */
432
434
  getLaunchedSlots(): number;
435
+ /**
436
+ * Follow a supervised engine that is relaunching with fewer `--parallel`
437
+ * slots because the wider launch ran the GPU out of memory. Runs between
438
+ * the failed start and the retry, while the request that asked for the
439
+ * engine still holds its lease. Refused when more requests hold leases
440
+ * than the narrower engine has slots: two turns on one slot would save and
441
+ * restore it under each other.
442
+ */
443
+ narrowEngineSlots(slots: number): boolean;
433
444
  /**
434
445
  * Claim one physical llama-server request slot.
435
446
  *
@@ -25,7 +25,7 @@ import * as os from 'node:os';
25
25
  import { createLogger } from '@bendyline/gezel';
26
26
  import { DEFAULT_NUM_CTX, LlamaCppSession, NativeEngineCrashedError, TOOL_GRAMMAR_FALLBACK_ORDER, constrainedToolNoSignalMsForModel, engineRequestAbortError, parseEnvMs, } from '@bendyline/gezel/local-loop';
27
27
  import { ProviderDisposedError } from '../provider-disposal.js';
28
- import { ProviderQueue, QUEUE_WAIT_NOTICE_DELAY_MS, QUEUE_WAIT_NOTICE_REPEAT_MS, backgroundLaneCap, defaultAmbientQuietMs, } from '../queue.js';
28
+ import { ProviderQueue, QUEUE_WAIT_NOTICE_DELAY_MS, QUEUE_WAIT_NOTICE_REPEAT_MS, backgroundLaneCap, defaultAmbientQuietMs, defaultQueueYieldQuantumMs, } from '../queue.js';
29
29
  import { createToolExecutor } from '../tool-executor.js';
30
30
  import { classifyStartupLine } from './stdout-parser.js';
31
31
  // The session and its helpers live in core so a phone runs the same loop.
@@ -147,6 +147,8 @@ export class LlamaCppProvider {
147
147
  reasoningEffortRequestShape;
148
148
  /** Engine batch width; see the `batchMaxConcurrency` constructor opt. */
149
149
  batchMaxConcurrency;
150
+ /** See the `reserveBackgroundSlot` constructor opt. */
151
+ reservesBackgroundLane;
150
152
  /**
151
153
  * Number of real llama-server request slots (`--parallel`). The provider
152
154
  * queue can intentionally be wider than this by one reserved background
@@ -341,7 +343,10 @@ export class LlamaCppProvider {
341
343
  // mid-turn compaction wedges the turn, the same failure the MLX
342
344
  // single-slot path hit. See the matching note in the MLX provider.
343
345
  const interactiveConcurrency = batchMax;
344
- const queueConcurrency = opts.reserveBackgroundSlot === false ? slots : Math.max(slots, interactiveConcurrency + 1);
346
+ this.reservesBackgroundLane = opts.reserveBackgroundSlot !== false;
347
+ const queueConcurrency = this.reservesBackgroundLane
348
+ ? Math.max(slots, interactiveConcurrency + 1)
349
+ : slots;
345
350
  this.queue = new ProviderQueue({
346
351
  // llama-server is launched with `--parallel ${slots}` (see
347
352
  // `buildLlamaCppProvider` in chat/manager.ts) so the engine has matching
@@ -364,6 +369,9 @@ export class LlamaCppProvider {
364
369
  // a multi-minute chore turn never lands right before their next
365
370
  // move. Applies to ds4 too — its provider wraps this class.
366
371
  ambientQuietMs: defaultAmbientQuietMs(),
372
+ // A tool loop hands the slot to a waiting conversation between
373
+ // iterations once it has held it this long (see `QueueLease`).
374
+ yieldQuantumMs: defaultQueueYieldQuantumMs(),
367
375
  });
368
376
  }
369
377
  /**
@@ -458,6 +466,31 @@ export class LlamaCppProvider {
458
466
  getLaunchedSlots() {
459
467
  return this.launchedSlots;
460
468
  }
469
+ /**
470
+ * Follow a supervised engine that is relaunching with fewer `--parallel`
471
+ * slots because the wider launch ran the GPU out of memory. Runs between
472
+ * the failed start and the retry, while the request that asked for the
473
+ * engine still holds its lease. Refused when more requests hold leases
474
+ * than the narrower engine has slots: two turns on one slot would save and
475
+ * restore it under each other.
476
+ */
477
+ narrowEngineSlots(slots) {
478
+ const next = Math.max(1, Math.floor(slots));
479
+ if (next >= this.launchedSlots || this.engineRequestsActive > next)
480
+ return false;
481
+ this.launchedSlots = next;
482
+ this.engineRequestWidth = next;
483
+ this.batchMaxConcurrency = Math.min(this.batchMaxConcurrency, next);
484
+ this.queue.narrow({
485
+ concurrency: this.reservesBackgroundLane
486
+ ? Math.max(next, this.batchMaxConcurrency + 1)
487
+ : next,
488
+ interactiveConcurrency: this.batchMaxConcurrency,
489
+ backgroundConcurrency: backgroundLaneCap(next),
490
+ });
491
+ this.cacheAdapter?.setSlotCount(next);
492
+ return true;
493
+ }
461
494
  /**
462
495
  * Claim one physical llama-server request slot.
463
496
  *
@@ -58,7 +58,7 @@ import { MlxEngineGate } from './engine-gate.js';
58
58
  import { EngineHold, engineHoldLabelFor } from './engine-hold.js';
59
59
  import { EngineLogRouter } from './engine-log-router.js';
60
60
  import { MlxEngineStatsSampler } from './engine-stats-sampler.js';
61
- import { REASONING_OPEN_HEADER, StreamingReasoningSplit, engineOpensReasoning, requestOpensReasoning, } from './reasoning-stream.js';
61
+ import { REASONING_OPEN_HEADER, StreamingReasoningSplit, engineOpensReasoning, reopenTemplateReasoning, requestOpensReasoning, } from './reasoning-stream.js';
62
62
  import { applyMlxRequestShape } from './request-shape.js';
63
63
  import { PRE_FIRST_BYTE_BASE_MS, buildMidStreamDropMessage, buildPreFirstByteAbortMessage, computePreFirstByteBudgetMs, estimatePromptTokens, formatTps, isMidStreamConnectionDrop, mlxHttpError, } from './runtime-diagnostics.js';
64
64
  import { classifyMlxFatalErrorLine, classifyMlxStartupLine, } from './stdout-parser.js';
@@ -2159,7 +2159,7 @@ class MlxSession extends StreamingSessionBase {
2159
2159
  // accumulated into `lastTurnReasoning` so the chat bubble can
2160
2160
  // render it behind a collapsed expander.
2161
2161
  {
2162
- const split = extractReasoningWithProfile(turnContent, this.deps.profile);
2162
+ const split = extractReasoningWithProfile(reopenTemplateReasoning(turnContent, opensInReasoning), this.deps.profile);
2163
2163
  turnContent = split.visible;
2164
2164
  if (split.reasoning) {
2165
2165
  this.lastTurnReasoning =
@@ -56,6 +56,21 @@ export declare function templateTextOpensReasoning(template: string): TemplateRe
56
56
  * into the reasoning pane. Absent means on, matching the engine's default.
57
57
  */
58
58
  export declare function requestOpensReasoning(templateOpen: TemplateReasoningOpen | undefined, body: Record<string, unknown>): boolean;
59
+ /**
60
+ * Restore the reasoning opener the chat template supplied, before the
61
+ * end-of-turn reasoning pass.
62
+ *
63
+ * When the rendered prompt left generation inside a reasoning block, the
64
+ * model's text starts mid-thought and carries only the closer. The live split
65
+ * knows that, but `extractReasoning` reads a lone `<channel|>` as stray and
66
+ * keeps the thoughts as the reply: Gemma 4 E4B, after a tool response, saved
67
+ * "The user wants to know…I need to answer in one sentence.The launch is on
68
+ * Friday…", and the stall check then asked it to answer again.
69
+ *
70
+ * Unchanged when the turn did not open in reasoning, when the model opened a
71
+ * block itself, or when no closer arrived.
72
+ */
73
+ export declare function reopenTemplateReasoning(text: string, opensInReasoning: boolean): string;
59
74
  export interface ReasoningSplitChunk {
60
75
  visible: string;
61
76
  reasoning: string;
@@ -112,6 +112,41 @@ export function requestOpensReasoning(templateOpen, body) {
112
112
  return true;
113
113
  return kwargs.enable_thinking !== false;
114
114
  }
115
+ /** The opener `extractReasoning` pairs with each closer a template-opened turn emits. */
116
+ const OPENER_FOR_CLOSER = [
117
+ ['</think>', '<think>'],
118
+ ['</reasoning>', '<reasoning>'],
119
+ ['[/THINK]', '[THINK]'],
120
+ ['<channel|>', '<|channel>thought\n'],
121
+ ['<|end|>', '<|channel|>analysis<|message|>'],
122
+ ];
123
+ /**
124
+ * Restore the reasoning opener the chat template supplied, before the
125
+ * end-of-turn reasoning pass.
126
+ *
127
+ * When the rendered prompt left generation inside a reasoning block, the
128
+ * model's text starts mid-thought and carries only the closer. The live split
129
+ * knows that, but `extractReasoning` reads a lone `<channel|>` as stray and
130
+ * keeps the thoughts as the reply: Gemma 4 E4B, after a tool response, saved
131
+ * "The user wants to know…I need to answer in one sentence.The launch is on
132
+ * Friday…", and the stall check then asked it to answer again.
133
+ *
134
+ * Unchanged when the turn did not open in reasoning, when the model opened a
135
+ * block itself, or when no closer arrived.
136
+ */
137
+ export function reopenTemplateReasoning(text, opensInReasoning) {
138
+ if (!opensInReasoning || /^\s*(?:<think>|<reasoning>|\[THINK\]|<\|?channel\|?>)/i.test(text)) {
139
+ return text;
140
+ }
141
+ const lower = text.toLowerCase();
142
+ let first = null;
143
+ for (const [closer, opener] of OPENER_FOR_CLOSER) {
144
+ const index = lower.indexOf(closer.toLowerCase());
145
+ if (index !== -1 && (first === null || index < first.index))
146
+ first = { index, opener };
147
+ }
148
+ return first ? `${first.opener}${text}` : text;
149
+ }
115
150
  /**
116
151
  * Incremental reasoning splitter for a streamed MLX turn.
117
152
  *
@@ -814,14 +814,20 @@ export function resolveLlamaCppContextRequirement(opts) {
814
814
  const strictModelMax = opts.contextSizing === 'model-max' && opts.explicitContextWindow === undefined;
815
815
  const requestedContextWindow = opts.explicitContextWindow ??
816
816
  (strictModelMax ? opts.modelContextWindow : opts.adaptiveContextWindow);
817
+ // Env, per-model and machine-wide numeric settings all express deliberate
818
+ // operator intent. The automatic host floor must not silently turn a 32K
819
+ // memory-saving request back into 64K. Resolve this here so launch and
820
+ // preview cannot apply different floors to the same explicit request.
821
+ const hostFloor = opts.minViableContextTokens ?? minViableLocalContextTokens();
822
+ const minimum = opts.explicitContextWindow !== undefined && opts.explicitContextWindow > 0
823
+ ? Math.min(hostFloor, opts.explicitContextWindow)
824
+ : hostFloor;
817
825
  const resolved = resolveLocalContextRequirement({
818
826
  ...(opts.modelContextWindow !== undefined
819
827
  ? { modelContextWindow: opts.modelContextWindow }
820
828
  : {}),
821
829
  ...(requestedContextWindow !== undefined ? { requestedContextWindow } : {}),
822
- ...(opts.minViableContextTokens !== undefined
823
- ? { minViableContextTokens: opts.minViableContextTokens }
824
- : {}),
830
+ minViableContextTokens: minimum,
825
831
  });
826
832
  if (strictModelMax) {
827
833
  return {
@@ -223,11 +223,19 @@ export class LocalEngineRuntime {
223
223
  return null;
224
224
  if (this.engineRouterInitPromise)
225
225
  return this.engineRouterInitPromise;
226
- this.engineRouterInitPromise = this.buildEngineRouter().then((r) => {
226
+ const pending = this.buildEngineRouter().then((r) => {
227
227
  this.engineRouterCache = r;
228
228
  return r;
229
+ }, (err) => {
230
+ // A failed build must not be cached. One that ran while a dev rebuild
231
+ // had emptied dist/ failed every later local turn with "Cannot find
232
+ // module …engine-router.js" until the app restarted (2026-10-19).
233
+ if (this.engineRouterInitPromise === pending)
234
+ this.engineRouterInitPromise = null;
235
+ throw err;
229
236
  });
230
- return this.engineRouterInitPromise;
237
+ this.engineRouterInitPromise = pending;
238
+ return pending;
231
239
  }
232
240
  async buildEngineRouter() {
233
241
  const { EngineRouter } = await import('./engine-router.js');
@@ -1136,17 +1144,12 @@ export class LocalEngineRuntime {
1136
1144
  const manifestEngineConfig = await resolveCatalogLlamaCppEngineConfig(this.catalog, modelId);
1137
1145
  const perModelCtxOverride = config.modelContextOverrides?.[`llama-cpp:${modelId}`];
1138
1146
  const explicitContextWindow = envNumCtx ?? perModelCtxOverride ?? config.llamaCppNumCtx;
1139
- // A per-model override below the host floor is deliberate user intent —
1140
- // lower the admission floor to the override instead of silently raising
1141
- // the request back to 64K. Env / machine-wide values keep their
1142
- // historical floor semantics. Mirrors buildLlamaCppProvider.
1147
+ // The shared resolver honors every explicit numeric context below the
1148
+ // host floor. Keep per-model override state for the Settings auto marker.
1143
1149
  const overrideActive = perModelCtxOverride !== undefined && explicitContextWindow === perModelCtxOverride;
1144
- const minViableTokens = overrideActive
1145
- ? Math.min(contextFloor, perModelCtxOverride)
1146
- : contextFloor;
1147
1150
  const contextRequirement = resolveLlamaCppContextRequirement({
1148
1151
  modelContextWindow: installed.contextWindow,
1149
- minViableContextTokens: minViableTokens,
1152
+ minViableContextTokens: contextFloor,
1150
1153
  ...(explicitContextWindow !== undefined ? { explicitContextWindow } : {}),
1151
1154
  ...(manifestEngineConfig?.contextSize !== undefined
1152
1155
  ? {
@@ -1162,7 +1165,7 @@ export class LocalEngineRuntime {
1162
1165
  // per-model override deliberately does NOT short-circuit on a resident
1163
1166
  // engine: the full plan must run so a stale resident window surfaces as
1164
1167
  // restart-required instead of masquerading as the applied setting.
1165
- const shortCircuit = (residentContextWindow !== undefined && !overrideActive) || envNumCtx !== undefined;
1168
+ const shortCircuit = residentContextWindow !== undefined && !overrideActive;
1166
1169
  const { defaultLocalEngineSlots, estimatePerSlotKvBytes, kvQuantScale, llamaCppSlotCeiling, planAdaptiveContextGrowth, planCtxTokensForMemory, plannedLocalEngineSlots, } = await import('./capacity-broker.js');
1167
1170
  const capacity = await this.previewCapacityInputs('llama-cpp', modelId, capacityOpts);
1168
1171
  const { budgetBytes: admissionBudgetBytes, fastBudgetBytes, committedOtherBytes } = capacity;
@@ -1587,7 +1590,7 @@ export class LocalEngineRuntime {
1587
1590
  }
1588
1591
  return { grantedCtx, slots, kvCacheType: kv, plannedFieldsOk: true };
1589
1592
  };
1590
- const plan = llamaPlanPass(explicitContextWindow, minViableTokens);
1593
+ const plan = llamaPlanPass(explicitContextWindow, contextFloor);
1591
1594
  let autoContextWindow;
1592
1595
  if (overrideActive) {
1593
1596
  try {
@@ -55,7 +55,9 @@ const RESTART_WINDOW_MS = 60_000;
55
55
  /**
56
56
  * Ceiling on `recoverStartup` retries within one start. Two covers the
57
57
  * full MoE degradation ladder (partial split → all experts to RAM →
58
- * engine-owned fit); a hook that keeps asking past that is looping.
58
+ * engine-owned fit), or one slot and one offload rung; a hook that keeps
59
+ * asking past that is looping. Every failed start also spends the restart
60
+ * budget, which refuses a fourth start inside its window anyway.
59
61
  */
60
62
  const MAX_STARTUP_RECOVERIES = 2;
61
63
  const HEALTH_FAIL_THRESHOLD = 3;
@@ -5,11 +5,18 @@
5
5
  * service's import point and keeps the one piece that reads the process
6
6
  * environment.
7
7
  */
8
- export { AbortedWhileQueuedError, DEFAULT_AMBIENT_QUIET_MS, ProviderQueue, QUEUE_WAIT_NOTICE_DELAY_MS, QUEUE_WAIT_NOTICE_REPEAT_MS, backgroundLaneCap, runInQueue, } from '@bendyline/gezel/runtime';
9
- export type { EnqueueRequest, Lane, ProviderQueueOptions, QueueSnapshot, QueueWaitOpts, } from '@bendyline/gezel/runtime';
8
+ export { AbortedWhileQueuedError, DEFAULT_AMBIENT_QUIET_MS, DEFAULT_LOCAL_YIELD_QUANTUM_MS, ProviderQueue, QUEUE_WAIT_NOTICE_DELAY_MS, QUEUE_WAIT_NOTICE_REPEAT_MS, backgroundLaneCap, runInQueue, } from '@bendyline/gezel/runtime';
9
+ export type { EnqueueRequest, Lane, ProviderQueueOptions, QueueLease, QueueSnapshot, QueueWaitOpts, } from '@bendyline/gezel/runtime';
10
10
  /**
11
11
  * Ambient quiet window for local engine queues. Override with
12
12
  * `GEZEL_AMBIENT_QUIET_MS` (milliseconds; `0` disables ambient gating
13
13
  * entirely).
14
14
  */
15
15
  export declare function defaultAmbientQuietMs(): number;
16
+ /**
17
+ * How long a local turn holds its queue slot before it hands the slot to
18
+ * another conversation that is waiting. Override with
19
+ * `GEZEL_QUEUE_YIELD_QUANTUM_MS` (milliseconds; `0` keeps a slot for the
20
+ * whole turn).
21
+ */
22
+ export declare function defaultQueueYieldQuantumMs(): number;
@@ -5,8 +5,8 @@
5
5
  * service's import point and keeps the one piece that reads the process
6
6
  * environment.
7
7
  */
8
- import { DEFAULT_AMBIENT_QUIET_MS } from '@bendyline/gezel/runtime';
9
- export { AbortedWhileQueuedError, DEFAULT_AMBIENT_QUIET_MS, ProviderQueue, QUEUE_WAIT_NOTICE_DELAY_MS, QUEUE_WAIT_NOTICE_REPEAT_MS, backgroundLaneCap, runInQueue, } from '@bendyline/gezel/runtime';
8
+ import { DEFAULT_AMBIENT_QUIET_MS, DEFAULT_LOCAL_YIELD_QUANTUM_MS } from '@bendyline/gezel/runtime';
9
+ export { AbortedWhileQueuedError, DEFAULT_AMBIENT_QUIET_MS, DEFAULT_LOCAL_YIELD_QUANTUM_MS, ProviderQueue, QUEUE_WAIT_NOTICE_DELAY_MS, QUEUE_WAIT_NOTICE_REPEAT_MS, backgroundLaneCap, runInQueue, } from '@bendyline/gezel/runtime';
10
10
  /**
11
11
  * Ambient quiet window for local engine queues. Override with
12
12
  * `GEZEL_AMBIENT_QUIET_MS` (milliseconds; `0` disables ambient gating
@@ -21,3 +21,18 @@ export function defaultAmbientQuietMs() {
21
21
  }
22
22
  return DEFAULT_AMBIENT_QUIET_MS;
23
23
  }
24
+ /**
25
+ * How long a local turn holds its queue slot before it hands the slot to
26
+ * another conversation that is waiting. Override with
27
+ * `GEZEL_QUEUE_YIELD_QUANTUM_MS` (milliseconds; `0` keeps a slot for the
28
+ * whole turn).
29
+ */
30
+ export function defaultQueueYieldQuantumMs() {
31
+ const raw = process.env.GEZEL_QUEUE_YIELD_QUANTUM_MS;
32
+ if (raw !== undefined) {
33
+ const parsed = Number.parseInt(raw, 10);
34
+ if (Number.isFinite(parsed) && parsed >= 0)
35
+ return parsed;
36
+ }
37
+ return DEFAULT_LOCAL_YIELD_QUANTUM_MS;
38
+ }
@@ -22,5 +22,8 @@ export interface ToolGroundingHooks {
22
22
  kind: 'allow';
23
23
  args: Record<string, unknown>;
24
24
  } | null;
25
- labelEvidence(toolName: string, args: Record<string, unknown>, text: string): string | null;
25
+ /** `source.thirdParty` is true for a tool served by any server but gezel's own. */
26
+ labelEvidence(toolName: string, args: Record<string, unknown>, text: string, source?: {
27
+ thirdParty: boolean;
28
+ }): string | null;
26
29
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bendyline/gezel-inference",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "description": "Gezel's on-device inference runtime: native engines, local model management, embeddings, knowledge search, and OpenAI-compatible routes.",
5
5
  "license": "MIT",
6
6
  "keywords": [
@@ -73,10 +73,10 @@
73
73
  "!dist/**/__pycache__"
74
74
  ],
75
75
  "dependencies": {
76
- "@bendyline/gezel": "1.2.7",
77
- "@bendyline/gezel-catalog": "1.2.7",
78
- "@bendyline/gezel-client": "1.2.7",
79
- "@bendyline/gezel-knowledge": "1.2.7",
76
+ "@bendyline/gezel": "1.2.8",
77
+ "@bendyline/gezel-catalog": "1.2.8",
78
+ "@bendyline/gezel-client": "1.2.8",
79
+ "@bendyline/gezel-knowledge": "1.2.8",
80
80
  "hono": "4.13.9",
81
81
  "tar": "7.5.21",
82
82
  "undici": "8.11.2",