dsh-plugin-local-infer 0.1.3 → 0.1.4

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 (250) hide show
  1. package/README.md +22 -4
  2. package/cordis.patch.yml +7 -0
  3. package/docs/zh/README.md +14 -2
  4. package/dsh/client.js +4195 -4522
  5. package/package.json +28 -11
  6. package/packages/core/lib/adapter.d.ts +133 -9
  7. package/packages/core/lib/adapter.js +74 -1
  8. package/packages/core/lib/config.d.ts +1 -1
  9. package/packages/core/lib/config.js +1 -1
  10. package/packages/core/lib/debug.d.ts +1 -1
  11. package/packages/core/lib/debug.js +1 -1
  12. package/packages/core/lib/defaults.d.ts +1 -1
  13. package/packages/core/lib/defaults.js +1 -1
  14. package/packages/core/lib/deps.d.ts +44 -17
  15. package/packages/core/lib/deps.js +24 -8
  16. package/packages/core/lib/estimator.d.ts +1 -1
  17. package/packages/core/lib/estimator.js +1 -1
  18. package/packages/core/lib/guideline.d.ts +1 -1
  19. package/packages/core/lib/guideline.js +1 -1
  20. package/packages/core/lib/hardware.d.ts +1 -1
  21. package/packages/core/lib/hardware.js +1 -1
  22. package/packages/core/lib/hf-cache.d.ts +70 -0
  23. package/packages/core/lib/hf-cache.js +83 -0
  24. package/packages/core/lib/index.d.ts +16 -9
  25. package/packages/core/lib/index.js +10 -6
  26. package/packages/core/lib/infer-cache.d.ts +39 -0
  27. package/packages/core/lib/infer-cache.js +53 -0
  28. package/packages/core/lib/launch-command.d.ts +8 -4
  29. package/packages/core/lib/launch-command.js +13 -6
  30. package/packages/core/lib/launch-record.d.ts +1 -1
  31. package/packages/core/lib/launch-record.js +1 -1
  32. package/packages/core/lib/logs.d.ts +8 -1
  33. package/packages/core/lib/logs.js +12 -4
  34. package/packages/core/lib/models.d.ts +120 -14
  35. package/packages/core/lib/models.js +209 -17
  36. package/packages/core/lib/parameter.d.ts +31 -3
  37. package/packages/core/lib/parameter.js +1 -1
  38. package/packages/core/lib/progress.d.ts +54 -0
  39. package/packages/core/lib/progress.js +120 -0
  40. package/packages/core/lib/registrar.d.ts +20 -9
  41. package/packages/core/lib/registrar.js +27 -7
  42. package/packages/core/lib/reserve.d.ts +11 -3
  43. package/packages/core/lib/reserve.js +6 -4
  44. package/packages/core/lib/status.d.ts +12 -6
  45. package/packages/core/lib/status.js +21 -10
  46. package/packages/core/lib/sudo.d.ts +80 -0
  47. package/packages/core/lib/sudo.js +78 -0
  48. package/packages/core/package.json +1 -1
  49. package/packages/orchestrator/lib/config.d.ts +63 -0
  50. package/packages/orchestrator/lib/config.js +111 -0
  51. package/packages/orchestrator/lib/index.d.ts +51 -0
  52. package/packages/orchestrator/lib/index.js +73 -0
  53. package/packages/orchestrator/lib/route.d.ts +60 -0
  54. package/packages/orchestrator/lib/route.js +159 -0
  55. package/packages/orchestrator/locale/en.json +6 -0
  56. package/packages/orchestrator/package.json +39 -0
  57. package/packages/orchestrator/prompt.md +87 -0
  58. package/packages/providers/lib/acquire.d.ts +92 -14
  59. package/packages/providers/lib/acquire.js +215 -37
  60. package/packages/providers/lib/adapters.d.ts +136 -12
  61. package/packages/providers/lib/adapters.js +597 -70
  62. package/packages/providers/lib/debug.d.ts +1 -1
  63. package/packages/providers/lib/debug.js +1 -1
  64. package/packages/providers/lib/deps.d.ts +27 -230
  65. package/packages/providers/lib/deps.js +61 -214
  66. package/packages/providers/lib/engines/basalt/basalt.d.ts +130 -0
  67. package/packages/providers/lib/engines/basalt/basalt.js +304 -0
  68. package/packages/providers/lib/engines/basalt/basaltAdapter.d.ts +85 -0
  69. package/packages/providers/lib/engines/basalt/basaltAdapter.js +439 -0
  70. package/packages/providers/lib/engines/basalt/basaltLinux.d.ts +23 -0
  71. package/packages/providers/lib/engines/basalt/basaltLinux.js +47 -0
  72. package/packages/providers/lib/engines/basalt/basaltMacOS.d.ts +17 -0
  73. package/packages/providers/lib/engines/basalt/basaltMacOS.js +19 -0
  74. package/packages/providers/lib/engines/basalt/basaltWindows.d.ts +24 -0
  75. package/packages/providers/lib/engines/basalt/basaltWindows.js +49 -0
  76. package/packages/providers/lib/engines/engineOs.d.ts +24 -0
  77. package/packages/providers/lib/engines/engineOs.js +17 -0
  78. package/packages/providers/lib/engines/{hyperqwen.d.ts → hyperqwen/hyperqwen.d.ts} +2 -2
  79. package/packages/providers/lib/engines/{hyperqwen.js → hyperqwen/hyperqwen.js} +14 -2
  80. package/packages/providers/lib/engines/{hyperqwenAdapter.d.ts → hyperqwen/hyperqwenAdapter.d.ts} +6 -6
  81. package/packages/providers/lib/engines/{hyperqwenAdapter.js → hyperqwen/hyperqwenAdapter.js} +24 -14
  82. package/packages/providers/lib/engines/hyperqwen/hyperqwenArch.d.ts +9 -0
  83. package/packages/providers/lib/engines/hyperqwen/hyperqwenArch.js +9 -0
  84. package/packages/providers/lib/engines/hyperqwen/hyperqwenDebian.d.ts +9 -0
  85. package/packages/providers/lib/engines/hyperqwen/hyperqwenDebian.js +9 -0
  86. package/packages/providers/lib/engines/hyperqwen/hyperqwenFedora.d.ts +9 -0
  87. package/packages/providers/lib/engines/hyperqwen/hyperqwenFedora.js +9 -0
  88. package/packages/providers/lib/engines/hyperqwen/hyperqwenLinux.d.ts +10 -0
  89. package/packages/providers/lib/engines/hyperqwen/hyperqwenLinux.js +14 -0
  90. package/packages/providers/lib/engines/hyperqwen/hyperqwenMacOS.d.ts +9 -0
  91. package/packages/providers/lib/engines/hyperqwen/hyperqwenMacOS.js +9 -0
  92. package/packages/providers/lib/engines/hyperqwen/hyperqwenWindows.d.ts +9 -0
  93. package/packages/providers/lib/engines/hyperqwen/hyperqwenWindows.js +9 -0
  94. package/packages/providers/lib/engines/{infernix.d.ts → infernix/infernix.d.ts} +7 -4
  95. package/packages/providers/lib/engines/{infernix.js → infernix/infernix.js} +52 -7
  96. package/packages/providers/lib/engines/{infernixAdapter.d.ts → infernix/infernixAdapter.d.ts} +5 -6
  97. package/packages/providers/lib/engines/{infernixAdapter.js → infernix/infernixAdapter.js} +75 -42
  98. package/packages/providers/lib/engines/infernix/infernixArch.d.ts +9 -0
  99. package/packages/providers/lib/engines/infernix/infernixArch.js +9 -0
  100. package/packages/providers/lib/engines/infernix/infernixDebian.d.ts +9 -0
  101. package/packages/providers/lib/engines/infernix/infernixDebian.js +9 -0
  102. package/packages/providers/lib/engines/infernix/infernixFedora.d.ts +9 -0
  103. package/packages/providers/lib/engines/infernix/infernixFedora.js +9 -0
  104. package/packages/providers/lib/engines/infernix/infernixLinux.d.ts +17 -0
  105. package/packages/providers/lib/engines/infernix/infernixLinux.js +35 -0
  106. package/packages/providers/lib/engines/infernix/infernixMacOS.d.ts +16 -0
  107. package/packages/providers/lib/engines/infernix/infernixMacOS.js +18 -0
  108. package/packages/providers/lib/engines/infernix/infernixWindows.d.ts +16 -0
  109. package/packages/providers/lib/engines/infernix/infernixWindows.js +34 -0
  110. package/packages/providers/lib/engines/{llamacpp.d.ts → llamacpp/llamacpp.d.ts} +2 -2
  111. package/packages/providers/lib/engines/{llamacpp.js → llamacpp/llamacpp.js} +12 -2
  112. package/packages/providers/lib/engines/{llamacppAdapter.d.ts → llamacpp/llamacppAdapter.d.ts} +5 -6
  113. package/packages/providers/lib/engines/{llamacppAdapter.js → llamacpp/llamacppAdapter.js} +87 -45
  114. package/packages/providers/lib/engines/llamacpp/llamacppArch.d.ts +9 -0
  115. package/packages/providers/lib/engines/llamacpp/llamacppArch.js +9 -0
  116. package/packages/providers/lib/engines/llamacpp/llamacppDebian.d.ts +9 -0
  117. package/packages/providers/lib/engines/llamacpp/llamacppDebian.js +9 -0
  118. package/packages/providers/lib/engines/llamacpp/llamacppFedora.d.ts +9 -0
  119. package/packages/providers/lib/engines/llamacpp/llamacppFedora.js +9 -0
  120. package/packages/providers/lib/engines/llamacpp/llamacppLinux.d.ts +17 -0
  121. package/packages/providers/lib/engines/llamacpp/llamacppLinux.js +28 -0
  122. package/packages/providers/lib/engines/llamacpp/llamacppMacOS.d.ts +15 -0
  123. package/packages/providers/lib/engines/llamacpp/llamacppMacOS.js +17 -0
  124. package/packages/providers/lib/engines/llamacpp/llamacppWindows.d.ts +15 -0
  125. package/packages/providers/lib/engines/llamacpp/llamacppWindows.js +26 -0
  126. package/packages/providers/lib/engines/{ninfer.d.ts → ninfer/ninfer.d.ts} +2 -2
  127. package/packages/providers/lib/engines/{ninfer.js → ninfer/ninfer.js} +24 -2
  128. package/packages/providers/lib/engines/{ninferAdapter.d.ts → ninfer/ninferAdapter.d.ts} +5 -6
  129. package/packages/providers/lib/engines/{ninferAdapter.js → ninfer/ninferAdapter.js} +123 -55
  130. package/packages/providers/lib/engines/ninfer/ninferArch.d.ts +9 -0
  131. package/packages/providers/lib/engines/ninfer/ninferArch.js +9 -0
  132. package/packages/providers/lib/engines/ninfer/ninferDebian.d.ts +9 -0
  133. package/packages/providers/lib/engines/ninfer/ninferDebian.js +9 -0
  134. package/packages/providers/lib/engines/ninfer/ninferFedora.d.ts +9 -0
  135. package/packages/providers/lib/engines/ninfer/ninferFedora.js +9 -0
  136. package/packages/providers/lib/engines/ninfer/ninferLinux.d.ts +17 -0
  137. package/packages/providers/lib/engines/ninfer/ninferLinux.js +37 -0
  138. package/packages/providers/lib/engines/ninfer/ninferMacOS.d.ts +15 -0
  139. package/packages/providers/lib/engines/ninfer/ninferMacOS.js +17 -0
  140. package/packages/providers/lib/engines/ninfer/ninferWindows.d.ts +29 -0
  141. package/packages/providers/lib/engines/ninfer/ninferWindows.js +59 -0
  142. package/packages/providers/lib/engines/strata/strata.d.ts +1 -0
  143. package/packages/providers/lib/engines/{strata.js → strata/strata.js} +70 -3
  144. package/packages/providers/lib/engines/{strataAdapter.d.ts → strata/strataAdapter.d.ts} +5 -6
  145. package/packages/providers/lib/engines/{strataAdapter.js → strata/strataAdapter.js} +147 -111
  146. package/packages/providers/lib/engines/strata/strataArch.d.ts +9 -0
  147. package/packages/providers/lib/engines/strata/strataArch.js +9 -0
  148. package/packages/providers/lib/engines/strata/strataDebian.d.ts +9 -0
  149. package/packages/providers/lib/engines/strata/strataDebian.js +9 -0
  150. package/packages/providers/lib/engines/strata/strataFedora.d.ts +9 -0
  151. package/packages/providers/lib/engines/strata/strataFedora.js +9 -0
  152. package/packages/providers/lib/engines/strata/strataLinux.d.ts +21 -0
  153. package/packages/providers/lib/engines/strata/strataLinux.js +30 -0
  154. package/packages/providers/lib/engines/strata/strataMacOS.d.ts +16 -0
  155. package/packages/providers/lib/engines/strata/strataMacOS.js +17 -0
  156. package/packages/providers/lib/engines/strata/strataWindows.d.ts +27 -0
  157. package/packages/providers/lib/engines/strata/strataWindows.js +37 -0
  158. package/packages/providers/lib/engines.d.ts +7 -5
  159. package/packages/providers/lib/engines.js +21 -10
  160. package/packages/providers/lib/guidelines.d.ts +5 -4
  161. package/packages/providers/lib/guidelines.js +18 -5
  162. package/packages/providers/lib/index.d.ts +33 -29
  163. package/packages/providers/lib/index.js +45 -33
  164. package/packages/providers/lib/model-defaults.d.ts +5 -1
  165. package/packages/providers/lib/model-defaults.js +25 -1
  166. package/packages/providers/lib/platform.d.ts +8 -5
  167. package/packages/providers/lib/platform.js +23 -9
  168. package/packages/providers/lib/presets.d.ts +6 -4
  169. package/packages/providers/lib/presets.js +53 -5
  170. package/packages/providers/lib/systemDeps.d.ts +313 -0
  171. package/packages/providers/lib/systemDeps.js +284 -0
  172. package/packages/providers/lib/vram.d.ts +34 -8
  173. package/packages/providers/lib/vram.js +81 -8
  174. package/packages/providers/package.json +2 -2
  175. package/packages/service/lib/index.d.ts +176 -21
  176. package/packages/service/lib/index.js +589 -86
  177. package/packages/service/lib/launch-target.d.ts +19 -0
  178. package/packages/service/lib/launch-target.js +28 -0
  179. package/packages/service/lib/run-logs.d.ts +72 -0
  180. package/packages/service/lib/run-logs.js +125 -0
  181. package/packages/service/lib/types.d.ts +163 -6
  182. package/packages/service/lib/types.js +1 -1
  183. package/packages/service/package.json +2 -2
  184. package/packages/tool/lib/index.d.ts +1 -1
  185. package/packages/tool/lib/index.js +1 -1
  186. package/packages/tool/package.json +2 -2
  187. package/packages/ui/lib/config.d.ts +12 -2
  188. package/packages/ui/lib/config.js +18 -2
  189. package/packages/ui/lib/deps.d.ts +1 -1
  190. package/packages/ui/lib/deps.js +1 -1
  191. package/packages/ui/lib/form.d.ts +1 -1
  192. package/packages/ui/lib/form.js +1 -1
  193. package/packages/ui/lib/index.d.ts +7 -1
  194. package/packages/ui/lib/index.js +12 -2
  195. package/packages/ui/lib/meter.d.ts +1 -1
  196. package/packages/ui/lib/meter.js +1 -1
  197. package/packages/ui/lib/ops/engine.d.ts +142 -34
  198. package/packages/ui/lib/ops/engine.js +469 -265
  199. package/packages/ui/lib/ops/get-steps.d.ts +98 -0
  200. package/packages/ui/lib/ops/get-steps.js +359 -0
  201. package/packages/ui/lib/ops/hf.d.ts +26 -11
  202. package/packages/ui/lib/ops/hf.js +115 -25
  203. package/packages/ui/lib/ops/logs.d.ts +70 -15
  204. package/packages/ui/lib/ops/logs.js +96 -35
  205. package/packages/ui/lib/ops/model.d.ts +2 -10
  206. package/packages/ui/lib/ops/model.js +9 -16
  207. package/packages/ui/lib/request.d.ts +1 -1
  208. package/packages/ui/lib/request.js +1 -1
  209. package/packages/ui/lib/route.d.ts +22 -6
  210. package/packages/ui/lib/route.js +32 -9
  211. package/packages/ui/lib/routes/chat-template.d.ts +1 -14
  212. package/packages/ui/lib/routes/chat-template.js +1 -37
  213. package/packages/ui/lib/routes/deps.d.ts +1 -16
  214. package/packages/ui/lib/routes/deps.js +1 -27
  215. package/packages/ui/lib/routes/download.d.ts +1 -16
  216. package/packages/ui/lib/routes/download.js +1 -48
  217. package/packages/ui/lib/routes/get.d.ts +1 -18
  218. package/packages/ui/lib/routes/get.js +66 -17
  219. package/packages/ui/lib/routes/install.d.ts +1 -1
  220. package/packages/ui/lib/routes/install.js +1 -1
  221. package/packages/ui/lib/routes/launch.d.ts +13 -7
  222. package/packages/ui/lib/routes/launch.js +33 -9
  223. package/packages/ui/lib/routes/logs.d.ts +57 -0
  224. package/packages/ui/lib/routes/logs.js +117 -0
  225. package/packages/ui/lib/routes/provider-delete.d.ts +1 -1
  226. package/packages/ui/lib/routes/provider-delete.js +1 -1
  227. package/packages/ui/lib/routes/register.d.ts +1 -1
  228. package/packages/ui/lib/routes/register.js +1 -1
  229. package/packages/ui/lib/routes/save.d.ts +1 -1
  230. package/packages/ui/lib/routes/save.js +22 -2
  231. package/packages/ui/lib/routes/stop.d.ts +7 -4
  232. package/packages/ui/lib/routes/stop.js +7 -4
  233. package/packages/ui/lib/routes/stream.d.ts +40 -0
  234. package/packages/ui/lib/routes/stream.js +68 -0
  235. package/packages/ui/lib/routes/summary.d.ts +1 -1
  236. package/packages/ui/lib/routes/summary.js +1 -1
  237. package/packages/ui/lib/routes/unregister.d.ts +1 -1
  238. package/packages/ui/lib/routes/unregister.js +1 -1
  239. package/packages/ui/lib/viewmodels/deps.d.ts +1 -31
  240. package/packages/ui/lib/viewmodels/deps.js +1 -89
  241. package/packages/ui/lib/viewmodels/depsHelpers.d.ts +1 -28
  242. package/packages/ui/lib/viewmodels/depsHelpers.js +72 -11
  243. package/packages/ui/lib/viewmodels/summary.d.ts +7 -2
  244. package/packages/ui/lib/viewmodels/summary.js +55 -35
  245. package/packages/ui/lib/viewmodels/summaryHelpers.d.ts +1 -1
  246. package/packages/ui/lib/viewmodels/summaryHelpers.js +1 -1
  247. package/packages/ui/lib/warnings.d.ts +1 -1
  248. package/packages/ui/lib/warnings.js +1 -1
  249. package/packages/ui/package.json +4 -4
  250. package/packages/providers/lib/engines/strata.d.ts +0 -53
@@ -1,3 +1,4 @@
1
+ import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
1
2
  import { join } from 'node:path';
2
3
  /**
3
4
  * repoIdFromUrl turns a Hugging Face repo URL into the `owner/name` repo id
@@ -21,26 +22,81 @@ export function repoNameFromUrl(url) {
21
22
  return slash >= 0 ? repo.slice(slash + 1) : repo;
22
23
  }
23
24
  /**
24
- * modelArtifactPath is the path the presence check looks for in the model
25
- * directory: the entry's artifact file when it names one, else the repo-name
26
- * directory a whole-repo download leaves behind.
25
+ * hubCacheModelDirectory is the per-model directory the hf CLI creates in the
26
+ * hub cache for one repo id: `models--{namespace}--{name}`. A repo id without
27
+ * an owner/name pair reads as an empty directory name, which the callers
28
+ * handle as an absent model.
27
29
  */
28
- export function modelArtifactPath(entry, directory) {
30
+ export function hubCacheModelDirectory(repoId) {
31
+ const slash = repoId.lastIndexOf('/');
32
+ if (slash <= 0)
33
+ return '';
34
+ return `models--${repoId.slice(0, slash)}--${repoId.slice(slash + 1)}`;
35
+ }
36
+ /**
37
+ * isModelDirectoryOverridden reports whether a model directory field (the
38
+ * config's or a download request's) names a path (a non-blank string), the
39
+ * one rule every download route and view model shares to decide the layout:
40
+ * a named directory targets that directory flat (`--local-dir`), an unnamed
41
+ * one takes the hub-cache layout (no `--local-dir`, the
42
+ * `models--{ns}--{name}` directories the hf CLI writes into the hub cache).
43
+ * The predicate form lets callers narrow the field to a string without
44
+ * restating the blank check.
45
+ */
46
+ export function isModelDirectoryOverridden(modelDirectory) {
47
+ return typeof modelDirectory === 'string' && modelDirectory.trim() !== '';
48
+ }
49
+ /**
50
+ * modelArtifactPath is the path a model download's artifact lands at in the
51
+ * model directory: the entry's artifact file when it names one, else the
52
+ * repo-name directory a whole-repo download leaves behind. With the hub-cache
53
+ * layout (`useHubCache`), it resolves into the per-model cache directory's
54
+ * `blobs` folder, the content-addressed store the hf CLI writes without
55
+ * `--local-dir`; the `directory` is then the hub cache root, not the per-model
56
+ * folder. Every presence decision (the card-facing checks and the engines'
57
+ * deploy re-fetch gates) resolves the deterministic snapshot instead
58
+ * (modelArtifactPresent); the hub-cache form here stays the blobs-store path.
59
+ */
60
+ export function modelArtifactPath(entry, directory, useHubCache = false) {
61
+ if (useHubCache) {
62
+ const modelDir = hubCacheModelDirectory(repoIdFromUrl(entry.url));
63
+ if (modelDir !== '')
64
+ return join(directory, modelDir, 'blobs');
65
+ }
29
66
  if (entry.file !== undefined && entry.file !== '') {
30
67
  return join(directory, entry.file);
31
68
  }
32
69
  return join(directory, repoNameFromUrl(entry.url));
33
70
  }
34
71
  /**
35
- * hfDownloadCommand renders the hf CLI download for one default model: the
36
- * repo, the artifact file when the entry names one, and the `--local-dir`
37
- * target. The string is the single source the download route runs and the
38
- * card shows beside its Download button.
72
+ * hfDownloadFileCommand renders the hf CLI download for one repo and file
73
+ * pair: the repo, the file (omitted when blank, a whole-repo download), the
74
+ * `--local-dir` target when the hub-cache layout does not apply, and the
75
+ * `--format human` flag. The flag overrides the non-TTY default (`auto` picks
76
+ * `agent`, which prints no progress) so the hf CLI prints tqdm progress
77
+ * lines to stderr, which the progress streaming tasks consume. The string is
78
+ * the single source every hf download command renders through: the model and
79
+ * chat-template builders below, and the Infernix extra-download commands the
80
+ * Get step streams.
81
+ */
82
+ export function hfDownloadFileCommand(repo, file, directory, useHubCache = false) {
83
+ const filePart = file !== '' ? ` ${file}` : '';
84
+ const localDir = useHubCache ? '' : ` --local-dir ${directory}`;
85
+ return `hf download ${repo}${filePart}${localDir} --format human`;
86
+ }
87
+ /**
88
+ * hfDownloadCommand renders the hf CLI download for one model download
89
+ * source: the repo, the artifact file when the entry names one, and the
90
+ * `--local-dir` target. With the hub-cache layout (`useHubCache`), the `--local-dir` is
91
+ * dropped so the hf CLI writes into the hub cache's `models--{ns}--{name}`
92
+ * directory (the content-addressed `blobs/` plus the `snapshots/`). The
93
+ * string is the single source the download route runs and the card shows
94
+ * beside its Download button, rendered through hfDownloadFileCommand.
39
95
  */
40
- export function hfDownloadCommand(entry, directory) {
96
+ export function hfDownloadCommand(entry, directory, useHubCache = false) {
41
97
  const repo = repoIdFromUrl(entry.url);
42
- const file = entry.file !== undefined && entry.file !== '' ? ` ${entry.file}` : '';
43
- return `hf download ${repo}${file} --local-dir ${directory}`;
98
+ const file = entry.file !== undefined && entry.file !== '' ? entry.file : '';
99
+ return hfDownloadFileCommand(repo, file, directory, useHubCache);
44
100
  }
45
101
  /**
46
102
  * The chat template file the hf CLI pulls into the model directory. The
@@ -51,12 +107,115 @@ export const CHAT_TEMPLATE_FILE = 'chat_template.jinja';
51
107
  /**
52
108
  * hfChatTemplateCommand renders the hf CLI download for one chat template repo
53
109
  * reference into the model directory: the repo, the template file, and the
54
- * `--local-dir` target. The string is the single source the template download
55
- * route runs and the card shows beside its Download button and in the System
56
- * dependencies box when the auto-download is unavailable.
110
+ * `--local-dir` target. With the hub-cache layout (`useHubCache`), the
111
+ * `--local-dir` is dropped so the template lands in the hub cache's
112
+ * `models--{ns}--{name}` directory. The string is the single source the
113
+ * template download route runs and the card shows beside its Download button
114
+ * and in the System dependencies box when the auto-download is unavailable,
115
+ * rendered through hfDownloadFileCommand.
116
+ */
117
+ export function hfChatTemplateCommand(repo, directory, useHubCache = false) {
118
+ return hfDownloadFileCommand(repo, CHAT_TEMPLATE_FILE, directory, useHubCache);
119
+ }
120
+ /**
121
+ * chatTemplateArtifactPath is the path the flat-layout chat-template presence
122
+ * check looks for: the template file joined onto the model directory. The
123
+ * hub-cache layout resolves through chatTemplatePresent instead (the
124
+ * deterministic snapshot pick), not through this join.
125
+ */
126
+ export function chatTemplateArtifactPath(directory) {
127
+ return join(directory, CHAT_TEMPLATE_FILE);
128
+ }
129
+ /**
130
+ * modelArtifactPresent reports whether a model download source's artifact is
131
+ * on disk. With the hub-cache layout it resolves the deterministic hub-cache
132
+ * snapshot holding the file (the same pick the model-directory display uses),
133
+ * so a `blobs` folder left by any download never reads as present on its
134
+ * own; a blank `file` (a whole-repo download) reads the repo's snapshot
135
+ * directories. With the flat layout it checks the artifact path joined onto
136
+ * the base directory. `baseDir` is the hub cache root under the cache layout,
137
+ * the flat override otherwise.
138
+ */
139
+ export function modelArtifactPresent(entry, baseDir, useHubCache = false) {
140
+ if (useHubCache) {
141
+ return hubCacheSnapshotFilePath(baseDir, repoIdFromUrl(entry.url), entry.file ?? '') !== null;
142
+ }
143
+ return existsSync(modelArtifactPath(entry, baseDir));
144
+ }
145
+ /**
146
+ * chatTemplatePresent reports whether a chat template repo's template file is
147
+ * on disk: the deterministic hub-cache snapshot pick under the hub-cache
148
+ * layout (a per-repo `snapshots` folder alone never reads as present), the
149
+ * flat file join otherwise. `baseDir` follows the modelArtifactPresent rule.
57
150
  */
58
- export function hfChatTemplateCommand(repo, directory) {
59
- return `hf download ${repo} ${CHAT_TEMPLATE_FILE} --local-dir ${directory}`;
151
+ export function chatTemplatePresent(repo, baseDir, useHubCache = false) {
152
+ if (useHubCache) {
153
+ return hubCacheSnapshotFilePath(baseDir, repo, CHAT_TEMPLATE_FILE) !== null;
154
+ }
155
+ return existsSync(chatTemplateArtifactPath(baseDir));
156
+ }
157
+ /**
158
+ * hubCacheSnapshotFilePath resolves the concrete file path of one file in the
159
+ * hub cache's snapshots, matching what the hf CLI treats as the active
160
+ * revision: the commit recorded in `refs/main` wins when its directory under
161
+ * `models--{ns}--{name}/snapshots/` holds the file; otherwise the snapshot
162
+ * with the newest mtime wins, covering a repo with no ref and a ref naming a
163
+ * commit that lacks the file. Ties break on readdir order. Returns null when
164
+ * no snapshot holds the file (the file is not downloaded), which the caller
165
+ * reads as absent. The `hubCache` is the hub cache root.
166
+ */
167
+ export function hubCacheSnapshotFilePath(hubCache, repoId, file) {
168
+ const modelDir = hubCacheModelDirectory(repoId);
169
+ if (modelDir === '')
170
+ return null;
171
+ const modelRoot = join(hubCache, modelDir);
172
+ const snapshots = join(modelRoot, 'snapshots');
173
+ // The hf CLI writes the revision it last downloaded into refs/main; that
174
+ // commit is the cache's active one, so it outranks mtime.
175
+ const active = readHubCacheRef(join(modelRoot, 'refs', 'main'));
176
+ if (active !== '') {
177
+ const activePath = join(snapshots, active, file);
178
+ if (existsSync(activePath))
179
+ return activePath;
180
+ }
181
+ let commits;
182
+ try {
183
+ commits = readdirSync(snapshots);
184
+ }
185
+ catch {
186
+ return null;
187
+ }
188
+ let newestPath = null;
189
+ let newestMtime = -Infinity;
190
+ for (const commit of commits) {
191
+ const path = join(snapshots, commit, file);
192
+ if (!existsSync(path))
193
+ continue;
194
+ const snapshotDir = join(snapshots, commit);
195
+ let mtime;
196
+ try {
197
+ mtime = statSync(snapshotDir).mtimeMs;
198
+ }
199
+ catch {
200
+ // A directory that cannot be stat'ed mid-scan cannot win the pick.
201
+ continue;
202
+ }
203
+ if (mtime > newestMtime) {
204
+ newestMtime = mtime;
205
+ newestPath = path;
206
+ }
207
+ }
208
+ return newestPath;
209
+ }
210
+ // readHubCacheRef returns the commit the hf CLI ref file names, or '' when
211
+ // the ref is absent or unreadable (a cache written by an older CLI).
212
+ function readHubCacheRef(refPath) {
213
+ try {
214
+ return readFileSync(refPath, 'utf8').trim();
215
+ }
216
+ catch {
217
+ return '';
218
+ }
60
219
  }
61
220
  /**
62
221
  * modelEntriesForEngine filters a model directory listing down to the model
@@ -98,4 +257,37 @@ export function modelEntriesForEngine(entries, engineId) {
98
257
  }
99
258
  return names.sort();
100
259
  }
101
- //# sourceMappingURL=models.js.map
260
+ /**
261
+ * modelCacheEntriesForEngine filters the hub cache's `models--*` directories
262
+ * down to the models the engine's Model dropdown offers, by the artifact types
263
+ * each engine serves: Basalt `.basalt`, NInfer `.ninfer`, Infernix
264
+ * `.ninfer`/`.infernix`, llama.cpp `.gguf`, Hyper-Qwen a repo holding a
265
+ * `.safetensors` weight file, and Strata `.gguf`/`.safetensors`. An engine with
266
+ * no matching cached model lists none. The result is the repo ids the
267
+ * dropdown shows, sorted for a stable order across summary fetches.
268
+ */
269
+ export function modelCacheEntriesForEngine(entries, engineId) {
270
+ const serves = (files) => {
271
+ switch (engineId) {
272
+ case 'basalt':
273
+ return files.some((file) => file.endsWith('.basalt'));
274
+ case 'ninfer':
275
+ return files.some((file) => file.endsWith('.ninfer'));
276
+ case 'infernix':
277
+ return files.some((file) => file.endsWith('.ninfer') || file.endsWith('.infernix'));
278
+ case 'llamacpp':
279
+ return files.some((file) => file.endsWith('.gguf'));
280
+ case 'hyperqwen':
281
+ return files.some((file) => file.endsWith('.safetensors'));
282
+ case 'strata':
283
+ return files.some((file) => file.endsWith('.gguf') || file.endsWith('.safetensors'));
284
+ default:
285
+ return false;
286
+ }
287
+ };
288
+ return entries
289
+ .filter((entry) => serves(entry.files))
290
+ .map((entry) => entry.repoId)
291
+ .sort();
292
+ }
293
+ //# sourceMappingURL=models.js.map
@@ -68,6 +68,29 @@ export type ParameterForm = {
68
68
  kind: 'combo';
69
69
  options: ComboOption[];
70
70
  };
71
+ /**
72
+ * ParameterDeploy declares where a parameter's value lands at deploy time:
73
+ * on the command line, in the process environment, or in a config file the
74
+ * deploy step writes. A `config` target names the file format and the
75
+ * nesting path: `block` is the top-level key, `key` the leaf. The config-file
76
+ * renderer nests `toConfig(value)` (or the raw value) under that path. A
77
+ * `block` ending in `[]` (e.g. `models[]`) is the array-entry convention: the
78
+ * key lands inside the single object of a one-element array named by the
79
+ * prefix, and specs sharing that block merge into that one entry. A spec
80
+ * declares one target, or a list of targets when the value lands in more than
81
+ * one place (a Strata shared spec lands on the CLI in the binary mode and in
82
+ * the env in the docker mode).
83
+ */
84
+ export type ParameterDeploy = {
85
+ target: 'cli';
86
+ } | {
87
+ target: 'env';
88
+ } | {
89
+ target: 'config';
90
+ format: 'json' | 'yaml' | 'env';
91
+ block?: string;
92
+ key?: string;
93
+ };
71
94
  /**
72
95
  * ParameterSpec is the unit of parameter management: a declarative description
73
96
  * of one engine parameter. It is the object that makes the parameter-to-
@@ -77,8 +100,11 @@ export type ParameterForm = {
77
100
  * of adding these specs, never rewiring the core.
78
101
  *
79
102
  * `description` is behavior-first: it says what the parameter does to the run,
80
- * not what it is. `toArgv` and `toEnv` are the engine-specific spellings that
81
- * turn a value into a CLI flag or an environment variable.
103
+ * not what it is. `toArgv`, `toEnv`, and `toConfig` are the engine-specific
104
+ * spellings that turn a value into a CLI flag, an environment variable, or a
105
+ * config-file entry. `deploy` declares which spelling the deploy step uses;
106
+ * a list declares several, so one spec can serve more than one build mode
107
+ * (the flags in the binary mode and the env vars in the docker mode).
82
108
  */
83
109
  export interface ParameterSpec {
84
110
  key: string;
@@ -92,5 +118,7 @@ export interface ParameterSpec {
92
118
  requires?: (config: Config) => string | null;
93
119
  toArgv?: (value: unknown) => string[];
94
120
  toEnv?: (value: unknown) => [string, string];
121
+ toConfig?: (value: unknown) => unknown;
122
+ deploy?: ParameterDeploy | readonly ParameterDeploy[];
95
123
  }
96
- //# sourceMappingURL=parameter.d.ts.map
124
+ //# sourceMappingURL=parameter.d.ts.map
@@ -1,2 +1,2 @@
1
1
  export {};
2
- //# sourceMappingURL=parameter.js.map
2
+ //# sourceMappingURL=parameter.js.map
@@ -0,0 +1,54 @@
1
+ /**
2
+ * The pure hf (huggingface_hub) tqdm progress parser the get and launch routes
3
+ * share: it turns the raw tqdm stderr lines into a structured HfProgress
4
+ * state, so a route's onChunk sink (the download runner's streamed stderr)
5
+ * feeds the card's progress events without a node import. The parser
6
+ * recognizes the two tqdm bar shapes the hf CLI emits with `--format human`:
7
+ * the file-count bar (`Fetching 102 files: 42%|...| 43/102 [00:01<00:02,
8
+ * 20.5it/s]`, where the done/total fields carry file counts, not bytes) and
9
+ * the byte bar (`Downloading x.safetensors: 12% | 5.2G/43.5G [01:23<10:11,
10
+ * 72.3MB/s]`, where the done/total fields carry human byte sizes). It
11
+ * converts the human byte sizes to bytes and the `[elapsed<remaining, rate]`
12
+ * tail to remaining seconds, and marks the unit the done/total amounts carry
13
+ * (`'files'` for the file-count bar, `'bytes'` for the byte bar), so a
14
+ * readout renders the amounts in the unit the bar measured. The client
15
+ * package mirrors this parser in
16
+ * `packages/client/src/store/progress.ts` (the TASK-006 browser-side copy):
17
+ * the core package is not browser-safe, so the client bundle keeps its own
18
+ * copy and the two shapes (`HfProgress` and the client's `HfProgressShape`)
19
+ * must stay in sync when a tqdm bar shape changes.
20
+ * @module dsh-plugin-local-infer/core/progress
21
+ */
22
+ /**
23
+ * HfProgress is the structured state one tqdm bar update folds to: the step
24
+ * label the bar names (the file, or the file-count fetch), the percent
25
+ * complete, the done and total amounts (bytes for the byte bar, file counts
26
+ * for the file-count bar), the remaining time in seconds (0 when the bar
27
+ * carries no tail), and the unit the done/total amounts carry ('bytes' for
28
+ * the byte bar, 'files' for the file-count bar), so a readout renders the
29
+ * amounts in the unit the bar measured.
30
+ */
31
+ export interface HfProgress {
32
+ step: string;
33
+ percent: number;
34
+ doneBytes: number;
35
+ totalBytes: number;
36
+ etaSeconds: number;
37
+ unit: 'bytes' | 'files';
38
+ }
39
+ /**
40
+ * parseHfProgressLine parses one tqdm bar line into an HfProgress state, or
41
+ * undefined when the line is not a bar update (a plain log line, a command
42
+ * echo). It recognizes the file-count and byte shapes above.
43
+ */
44
+ export declare function parseHfProgressLine(line: string): HfProgress | undefined;
45
+ /**
46
+ * foldHfProgressChunk folds a raw stderr chunk into the latest HfProgress
47
+ * state it carries: the chunk splits on the line separators tqdm writes
48
+ * (carriage returns between bar redraws, newlines between steps), each line
49
+ * parses through parseHfProgressLine, and the last parseable line wins. A
50
+ * chunk with no bar update returns the prior state, so a caller threads the
51
+ * fold across chunks and always holds the newest state.
52
+ */
53
+ export declare function foldHfProgressChunk(chunk: string, prior: HfProgress | undefined): HfProgress | undefined;
54
+ //# sourceMappingURL=progress.d.ts.map
@@ -0,0 +1,120 @@
1
+ /**
2
+ * The pure hf (huggingface_hub) tqdm progress parser the get and launch routes
3
+ * share: it turns the raw tqdm stderr lines into a structured HfProgress
4
+ * state, so a route's onChunk sink (the download runner's streamed stderr)
5
+ * feeds the card's progress events without a node import. The parser
6
+ * recognizes the two tqdm bar shapes the hf CLI emits with `--format human`:
7
+ * the file-count bar (`Fetching 102 files: 42%|...| 43/102 [00:01<00:02,
8
+ * 20.5it/s]`, where the done/total fields carry file counts, not bytes) and
9
+ * the byte bar (`Downloading x.safetensors: 12% | 5.2G/43.5G [01:23<10:11,
10
+ * 72.3MB/s]`, where the done/total fields carry human byte sizes). It
11
+ * converts the human byte sizes to bytes and the `[elapsed<remaining, rate]`
12
+ * tail to remaining seconds, and marks the unit the done/total amounts carry
13
+ * (`'files'` for the file-count bar, `'bytes'` for the byte bar), so a
14
+ * readout renders the amounts in the unit the bar measured. The client
15
+ * package mirrors this parser in
16
+ * `packages/client/src/store/progress.ts` (the TASK-006 browser-side copy):
17
+ * the core package is not browser-safe, so the client bundle keeps its own
18
+ * copy and the two shapes (`HfProgress` and the client's `HfProgressShape`)
19
+ * must stay in sync when a tqdm bar shape changes.
20
+ * @module dsh-plugin-local-infer/core/progress
21
+ */
22
+ // The file-count bar: `Fetching 102 files: 42%|...| 43/102 [00:01<00:02, ...]`.
23
+ // The bar glyph run between the pipes is skipped, and the counts are plain
24
+ // integers.
25
+ const FILES_LINE = /^Fetching (\d+) files:\s*(\d+(?:\.\d+)?)%\|[^|]*\|\s*(\d+)\s*\/\s*(\d+)(?:\s*\[([^\]]*)\])?/;
26
+ // The byte bar: `Downloading x.safetensors: 12% | 5.2G/43.5G [01:23<10:11, ...]`.
27
+ // The sizes carry an optional K/M/G/T/P unit (decimal, or binary with an
28
+ // `i`), and the tail is optional (a finished bar drops it).
29
+ const BYTES_LINE = /^(?:Downloading|Fetching)\s+([^:]+):\s*(\d+(?:\.\d+)?)%\s*\|\s*(\d+(?:\.\d+)?[KMGTP]?i?B?)\s*\/\s*(\d+(?:\.\d+)?[KMGTP]?i?B?)\s*(?:\[([^\]]*)\])?/;
30
+ // The unit exponents for the decimal (1000-based) sizes the hf CLI's tqdm
31
+ // renders; a binary `i` suffix (Gi, Mi, ...) switches the base to 1024.
32
+ const UNIT_EXPONENTS = { K: 1, M: 2, G: 3, T: 4, P: 5 };
33
+ // parseHumanSize converts one human byte size (`5.2G`) to bytes: the decimal
34
+ // base the hf CLI's tqdm uses, with a 1024 base when the unit carries the
35
+ // binary `i` suffix. A size with no unit is a plain byte count.
36
+ function parseHumanSize(text) {
37
+ const match = /^(\d+(?:\.\d+)?)([KMGTP]?)i?B?$/.exec(text);
38
+ if (match === null)
39
+ return 0;
40
+ const exponent = UNIT_EXPONENTS[match[2]] ?? 0;
41
+ const base = text.includes('i') ? 1024 : 1000;
42
+ return Number(match[1]) * base ** exponent;
43
+ }
44
+ // durationSeconds converts a `MM:SS`, `H:MM:SS`, or `D:H:MM:SS` clock string
45
+ // to seconds, the tqdm tail's remaining-time format.
46
+ function durationSeconds(text) {
47
+ let seconds = 0;
48
+ for (const part of text.split(':')) {
49
+ seconds = seconds * 60 + Number(part);
50
+ }
51
+ return seconds;
52
+ }
53
+ // etaSecondsFromTail reads the remaining time out of a tqdm tail
54
+ // (`01:23<10:11, 72.3MB/s`): the clock string after the `<`, 0 when the tail
55
+ // is absent or carries no remaining time.
56
+ function etaSecondsFromTail(tail) {
57
+ if (tail === undefined)
58
+ return 0;
59
+ const match = /<\s*(\d+(?::\d+)+)/.exec(tail);
60
+ if (match === null)
61
+ return 0;
62
+ return durationSeconds(match[1]);
63
+ }
64
+ /**
65
+ * parseHfProgressLine parses one tqdm bar line into an HfProgress state, or
66
+ * undefined when the line is not a bar update (a plain log line, a command
67
+ * echo). It recognizes the file-count and byte shapes above.
68
+ */
69
+ export function parseHfProgressLine(line) {
70
+ const trimmed = line.trim();
71
+ if (trimmed === '')
72
+ return undefined;
73
+ const files = FILES_LINE.exec(trimmed);
74
+ if (files !== null) {
75
+ // The file-count bar carries counts, not bytes: the done/total fields
76
+ // hold the file numbers, the step label names the fetch, and the unit
77
+ // marks the amounts as file counts, so a readout renders them as files.
78
+ return {
79
+ step: `Fetching ${files[1]} files`,
80
+ percent: Number(files[2]),
81
+ doneBytes: Number(files[3]),
82
+ totalBytes: Number(files[4]),
83
+ etaSeconds: etaSecondsFromTail(files[5]),
84
+ unit: 'files',
85
+ };
86
+ }
87
+ const bytes = BYTES_LINE.exec(trimmed);
88
+ if (bytes !== null) {
89
+ const verb = trimmed.startsWith('Fetching') ? 'Fetching' : 'Downloading';
90
+ // The byte bar carries human byte sizes: the done/total fields hold the
91
+ // parsed byte counts, and the unit marks the amounts as bytes.
92
+ return {
93
+ step: `${verb} ${bytes[1].trim()}`,
94
+ percent: Number(bytes[2]),
95
+ doneBytes: parseHumanSize(bytes[3]),
96
+ totalBytes: parseHumanSize(bytes[4]),
97
+ etaSeconds: etaSecondsFromTail(bytes[5]),
98
+ unit: 'bytes',
99
+ };
100
+ }
101
+ return undefined;
102
+ }
103
+ /**
104
+ * foldHfProgressChunk folds a raw stderr chunk into the latest HfProgress
105
+ * state it carries: the chunk splits on the line separators tqdm writes
106
+ * (carriage returns between bar redraws, newlines between steps), each line
107
+ * parses through parseHfProgressLine, and the last parseable line wins. A
108
+ * chunk with no bar update returns the prior state, so a caller threads the
109
+ * fold across chunks and always holds the newest state.
110
+ */
111
+ export function foldHfProgressChunk(chunk, prior) {
112
+ let folded = prior;
113
+ for (const line of chunk.split(/\r\n|\r|\n/)) {
114
+ const parsed = parseHfProgressLine(line);
115
+ if (parsed !== undefined)
116
+ folded = parsed;
117
+ }
118
+ return folded;
119
+ }
120
+ //# sourceMappingURL=progress.js.map
@@ -2,8 +2,9 @@ import type { BuildMode, LaunchPlan } from './adapter.ts';
2
2
  /**
3
3
  * DshModelEntry is the shape of the model row the registrar writes into the
4
4
  * dsh-llm-pi-ai provider: the provider id, the model id and display name, the
5
- * context window, the engine's base URL, and the env-var name the provider
6
- * reads its optional API key from.
5
+ * context window, the engine's base URL, the env-var name the provider reads
6
+ * its optional API key from, and the placeholder API key value the engine
7
+ * ships with so a model the user has not keyed still activates.
7
8
  */
8
9
  export interface DshModelEntry {
9
10
  providerId: string;
@@ -12,6 +13,7 @@ export interface DshModelEntry {
12
13
  contextWindow: number;
13
14
  baseURL: string;
14
15
  apiKeyEnv: string;
16
+ apiKey: string;
15
17
  input?: string[];
16
18
  }
17
19
  /**
@@ -94,13 +96,22 @@ export declare function providerIdFor(engineId: string, modelId: string, buildMo
94
96
  * underscores, plus _LOCAL_API_KEY.
95
97
  */
96
98
  export declare function apiKeyEnvFor(engineId: string): string;
99
+ /**
100
+ * defaultApiKeyFor derives the placeholder API key value a local engine ships
101
+ * with: the engine id, uppercased and non-alphanumerics mapped to underscores,
102
+ * wrapped in double braces plus _LOCAL_API_KEY. The double braces mark the
103
+ * value as a placeholder, not a real key, so a model the user has not keyed
104
+ * still activates the pi-ai OpenAI route. A user-added key replaces it in a
105
+ * future turn.
106
+ */
107
+ export declare function defaultApiKeyFor(engineId: string): string;
97
108
  /**
98
109
  * buildModelEntry turns a launch plan and a model row into the DshModelEntry
99
110
  * the registrar writes. The provider id is the engine, build mode, and model
100
111
  * slug; the display name is the engine, the build mode in parentheses, and
101
112
  * the model name (falling back to the slug when the row carries none). The
102
- * API-key env name derives from the engine id, and the base URL is the
103
- * engine's OpenAI endpoint on its port.
113
+ * API-key env name and the placeholder API key value derive from the engine
114
+ * id, and the base URL is the engine's OpenAI endpoint on its port.
104
115
  */
105
116
  export declare function buildModelEntry(plan: LaunchPlan, model: ModelRow): DshModelEntry;
106
117
  /**
@@ -118,10 +129,10 @@ export declare function upsertModelRow(rows: readonly ModelRow[], row: ModelRow)
118
129
  export declare function removeModelRow(rows: readonly ModelRow[], modelId: string): ModelRow[];
119
130
  /**
120
131
  * providerRegistrationOps computes the settings ops that upsert a provider
121
- * entry: the API-key env name, the openai-completions api, the base URL, and
122
- * the models list with the entry's row upserted. The ops write the provider
123
- * fields by path, so a provider the user edited by hand keeps every field
124
- * the registrar does not own.
132
+ * entry: the API-key env name, the openai-completions api, the base URL, the
133
+ * placeholder Authorization header, and the models list with the entry's row
134
+ * upserted. The ops write the provider fields by path, so a provider the user
135
+ * edited by hand keeps every field the registrar does not own.
125
136
  */
126
137
  export declare function providerRegistrationOps(entry: DshModelEntry, rows: readonly ModelRow[]): SettingsOp[];
127
138
  /**
@@ -154,4 +165,4 @@ export declare function modelRowsFrom(value: unknown, providerId: string): reado
154
165
  * when it names the removed pair.
155
166
  */
156
167
  export declare function agentDefaultPointsAt(value: unknown, providerId: string, modelId: string): boolean;
157
- //# sourceMappingURL=registrar.d.ts.map
168
+ //# sourceMappingURL=registrar.d.ts.map
@@ -42,6 +42,17 @@ export function providerIdFor(engineId, modelId, buildMode) {
42
42
  export function apiKeyEnvFor(engineId) {
43
43
  return `${engineId.toUpperCase().replace(/[^A-Z0-9]/g, '_')}_LOCAL_API_KEY`;
44
44
  }
45
+ /**
46
+ * defaultApiKeyFor derives the placeholder API key value a local engine ships
47
+ * with: the engine id, uppercased and non-alphanumerics mapped to underscores,
48
+ * wrapped in double braces plus _LOCAL_API_KEY. The double braces mark the
49
+ * value as a placeholder, not a real key, so a model the user has not keyed
50
+ * still activates the pi-ai OpenAI route. A user-added key replaces it in a
51
+ * future turn.
52
+ */
53
+ export function defaultApiKeyFor(engineId) {
54
+ return `{{${engineId.toUpperCase().replace(/[^A-Z0-9]/g, '_')}}}_LOCAL_API_KEY`;
55
+ }
45
56
  /**
46
57
  * engineDisplayName capitalizes the first letter of an engine id for the
47
58
  * display name: `ninfer` reads as `Ninfer`, `hyperqwen` as `Hyperqwen`.
@@ -54,8 +65,8 @@ function engineDisplayName(engineId) {
54
65
  * the registrar writes. The provider id is the engine, build mode, and model
55
66
  * slug; the display name is the engine, the build mode in parentheses, and
56
67
  * the model name (falling back to the slug when the row carries none). The
57
- * API-key env name derives from the engine id, and the base URL is the
58
- * engine's OpenAI endpoint on its port.
68
+ * API-key env name and the placeholder API key value derive from the engine
69
+ * id, and the base URL is the engine's OpenAI endpoint on its port.
59
70
  */
60
71
  export function buildModelEntry(plan, model) {
61
72
  const buildMode = plan.kind === 'docker' ? 'docker' : 'local';
@@ -68,6 +79,7 @@ export function buildModelEntry(plan, model) {
68
79
  contextWindow: model.contextWindow,
69
80
  baseURL: `http://127.0.0.1:${plan.port}/v1`,
70
81
  apiKeyEnv: apiKeyEnvFor(plan.engineId),
82
+ apiKey: defaultApiKeyFor(plan.engineId),
71
83
  };
72
84
  // exactOptionalPropertyTypes: carry the input modalities only when the row
73
85
  // names them, so a text-only registration never writes an empty list.
@@ -101,10 +113,10 @@ export function removeModelRow(rows, modelId) {
101
113
  }
102
114
  /**
103
115
  * providerRegistrationOps computes the settings ops that upsert a provider
104
- * entry: the API-key env name, the openai-completions api, the base URL, and
105
- * the models list with the entry's row upserted. The ops write the provider
106
- * fields by path, so a provider the user edited by hand keeps every field
107
- * the registrar does not own.
116
+ * entry: the API-key env name, the openai-completions api, the base URL, the
117
+ * placeholder Authorization header, and the models list with the entry's row
118
+ * upserted. The ops write the provider fields by path, so a provider the user
119
+ * edited by hand keeps every field the registrar does not own.
108
120
  */
109
121
  export function providerRegistrationOps(entry, rows) {
110
122
  const providerPath = ['providers', entry.providerId];
@@ -119,10 +131,18 @@ export function providerRegistrationOps(entry, rows) {
119
131
  if (entry.input !== undefined) {
120
132
  row.input = entry.input;
121
133
  }
134
+ // The pi-ai OpenAI route requires an API key or an Authorization header, so
135
+ // a keyless local server ships the placeholder key as a Bearer header. The
136
+ // profile schema has no literal apiKey field; headers carry the value.
122
137
  return [
123
138
  { op: 'set', path: [...providerPath, 'apiKeyEnv'], value: entry.apiKeyEnv },
124
139
  { op: 'set', path: [...providerPath, 'api'], value: 'openai-completions' },
125
140
  { op: 'set', path: [...providerPath, 'baseURL'], value: entry.baseURL },
141
+ {
142
+ op: 'set',
143
+ path: [...providerPath, 'headers', 'Authorization'],
144
+ value: `Bearer ${entry.apiKey}`,
145
+ },
126
146
  { op: 'set', path: [...providerPath, 'models'], value: upsertModelRow(rows, row) },
127
147
  ];
128
148
  }
@@ -205,4 +225,4 @@ export function agentDefaultPointsAt(value, providerId, modelId) {
205
225
  const section = value;
206
226
  return section.provider === providerId && section.model === modelId;
207
227
  }
208
- //# sourceMappingURL=registrar.js.map
228
+ //# sourceMappingURL=registrar.js.map
@@ -25,6 +25,13 @@ export declare function parseNvidiaSmiGpuUsage(output: string): GpuUsageProbe |
25
25
  export interface EngineLaunchSignature {
26
26
  binary: string;
27
27
  port?: number;
28
+ /**
29
+ * When false the engine binds its port from a file (Basalt's
30
+ * basalt_config.json server block), so the matcher never sees the number
31
+ * in the command line and must skip the port element. Undefined reads as
32
+ * true: the port must appear as a standalone number, the current rule.
33
+ */
34
+ portInArgv?: boolean;
28
35
  envKeys?: readonly string[];
29
36
  }
30
37
  /**
@@ -44,8 +51,9 @@ export interface ProcessLine {
44
51
  * signature. The binary marker must appear in the command line (the image
45
52
  * name for a docker plan, the engine binary for a local one); a declared port
46
53
  * must appear as a standalone number (the `--port N` flag or the docker
47
- * `-p host:port:port` mapping); a declared env key must be set when the line
48
- * carries env data, and is neutral when it does not.
54
+ * `-p host:port:port` mapping) unless `portInArgv` is false, which means the
55
+ * engine binds its port from a file; a declared env key must be set when the
56
+ * line carries env data, and is neutral when it does not.
49
57
  */
50
58
  export declare function matchLaunchSignature(line: ProcessLine, signature: EngineLaunchSignature): boolean;
51
59
  /**
@@ -86,4 +94,4 @@ export declare function resolveOsReserve(input: {
86
94
  foreignProcess?: boolean;
87
95
  gpuUsage?: GpuUsageProbe | null;
88
96
  }): ReserveResolution;
89
- //# sourceMappingURL=reserve.d.ts.map
97
+ //# sourceMappingURL=reserve.d.ts.map