dsh-plugin-local-infer 0.1.1 → 0.1.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 (108) hide show
  1. package/README.md +14 -3
  2. package/docs/zh/README.md +14 -3
  3. package/dsh/client.js +1214 -242
  4. package/package.json +16 -10
  5. package/packages/core/lib/adapter.d.ts +119 -6
  6. package/packages/core/lib/deps.d.ts +4 -3
  7. package/packages/core/lib/index.d.ts +2 -1
  8. package/packages/core/lib/index.js +1 -0
  9. package/packages/core/lib/launch-command.d.ts +13 -0
  10. package/packages/core/lib/launch-command.js +64 -0
  11. package/packages/core/lib/models.d.ts +2 -1
  12. package/packages/core/lib/models.js +10 -1
  13. package/packages/core/lib/registrar.d.ts +5 -5
  14. package/packages/core/lib/registrar.js +3 -3
  15. package/packages/core/lib/status.d.ts +9 -1
  16. package/packages/core/lib/status.js +6 -1
  17. package/packages/core/package.json +1 -1
  18. package/packages/providers/lib/acquire.d.ts +127 -0
  19. package/packages/providers/lib/acquire.js +346 -0
  20. package/packages/providers/lib/adapters.d.ts +19 -172
  21. package/packages/providers/lib/adapters.js +114 -659
  22. package/packages/providers/lib/deps.d.ts +26 -2
  23. package/packages/providers/lib/deps.js +37 -4
  24. package/packages/providers/lib/engines/hyperqwenAdapter.d.ts +38 -0
  25. package/packages/providers/lib/engines/hyperqwenAdapter.js +105 -0
  26. package/packages/providers/lib/engines/infernix.d.ts +170 -0
  27. package/packages/providers/lib/engines/infernix.js +671 -0
  28. package/packages/providers/lib/engines/infernixAdapter.d.ts +53 -0
  29. package/packages/providers/lib/engines/infernixAdapter.js +202 -0
  30. package/packages/providers/lib/engines/llamacppAdapter.d.ts +51 -0
  31. package/packages/providers/lib/engines/llamacppAdapter.js +232 -0
  32. package/packages/providers/lib/{ninfer.d.ts → engines/ninfer.d.ts} +7 -0
  33. package/packages/providers/lib/{ninfer.js → engines/ninfer.js} +8 -1
  34. package/packages/providers/lib/engines/ninferAdapter.d.ts +82 -0
  35. package/packages/providers/lib/engines/ninferAdapter.js +324 -0
  36. package/packages/providers/lib/engines/strata.d.ts +53 -0
  37. package/packages/providers/lib/engines/strata.js +197 -0
  38. package/packages/providers/lib/engines/strataAdapter.d.ts +98 -0
  39. package/packages/providers/lib/engines/strataAdapter.js +427 -0
  40. package/packages/providers/lib/engines.d.ts +5 -3
  41. package/packages/providers/lib/engines.js +16 -8
  42. package/packages/providers/lib/guidelines.js +16 -0
  43. package/packages/providers/lib/index.d.ts +21 -10
  44. package/packages/providers/lib/index.js +23 -9
  45. package/packages/providers/lib/model-defaults.d.ts +8 -5
  46. package/packages/providers/lib/model-defaults.js +47 -5
  47. package/packages/providers/lib/platform.d.ts +31 -0
  48. package/packages/providers/lib/platform.js +64 -0
  49. package/packages/providers/package.json +1 -1
  50. package/packages/service/lib/index.d.ts +74 -22
  51. package/packages/service/lib/index.js +354 -44
  52. package/packages/service/lib/types.d.ts +48 -5
  53. package/packages/service/package.json +1 -1
  54. package/packages/tool/package.json +1 -1
  55. package/packages/ui/lib/config.d.ts +79 -0
  56. package/packages/ui/lib/config.js +235 -0
  57. package/packages/ui/lib/deps.d.ts +3 -3
  58. package/packages/ui/lib/ops/engine.d.ts +108 -0
  59. package/packages/ui/lib/ops/engine.js +493 -0
  60. package/packages/ui/lib/ops/hf.d.ts +27 -0
  61. package/packages/ui/lib/ops/hf.js +60 -0
  62. package/packages/ui/lib/ops/logs.d.ts +23 -0
  63. package/packages/ui/lib/ops/logs.js +58 -0
  64. package/packages/ui/lib/ops/model.d.ts +79 -0
  65. package/packages/ui/lib/ops/model.js +130 -0
  66. package/packages/ui/lib/request.d.ts +106 -0
  67. package/packages/ui/lib/request.js +80 -0
  68. package/packages/ui/lib/route.d.ts +96 -357
  69. package/packages/ui/lib/route.js +123 -1415
  70. package/packages/ui/lib/routes/chat-template.d.ts +14 -0
  71. package/packages/ui/lib/routes/chat-template.js +37 -0
  72. package/packages/ui/lib/routes/deps.d.ts +16 -0
  73. package/packages/ui/lib/routes/deps.js +27 -0
  74. package/packages/ui/lib/routes/download.d.ts +16 -0
  75. package/packages/ui/lib/routes/download.js +48 -0
  76. package/packages/ui/lib/routes/get.d.ts +18 -0
  77. package/packages/ui/lib/routes/get.js +57 -0
  78. package/packages/ui/lib/routes/install.d.ts +16 -0
  79. package/packages/ui/lib/routes/install.js +36 -0
  80. package/packages/ui/lib/routes/launch.d.ts +18 -0
  81. package/packages/ui/lib/routes/launch.js +50 -0
  82. package/packages/ui/lib/routes/provider-delete.d.ts +15 -0
  83. package/packages/ui/lib/routes/provider-delete.js +37 -0
  84. package/packages/ui/lib/routes/register.d.ts +16 -0
  85. package/packages/ui/lib/routes/register.js +28 -0
  86. package/packages/ui/lib/routes/save.d.ts +18 -0
  87. package/packages/ui/lib/routes/save.js +139 -0
  88. package/packages/ui/lib/routes/stop.d.ts +16 -0
  89. package/packages/ui/lib/routes/stop.js +29 -0
  90. package/packages/ui/lib/routes/summary.d.ts +15 -0
  91. package/packages/ui/lib/routes/summary.js +26 -0
  92. package/packages/ui/lib/routes/unregister.d.ts +14 -0
  93. package/packages/ui/lib/routes/unregister.js +26 -0
  94. package/packages/ui/lib/viewmodels/deps.d.ts +31 -0
  95. package/packages/ui/lib/viewmodels/deps.js +89 -0
  96. package/packages/ui/lib/viewmodels/depsHelpers.d.ts +28 -0
  97. package/packages/ui/lib/viewmodels/depsHelpers.js +33 -0
  98. package/packages/ui/lib/viewmodels/summary.d.ts +140 -0
  99. package/packages/ui/lib/viewmodels/summary.js +225 -0
  100. package/packages/ui/lib/viewmodels/summaryHelpers.d.ts +30 -0
  101. package/packages/ui/lib/viewmodels/summaryHelpers.js +42 -0
  102. package/packages/ui/package.json +1 -1
  103. package/packages/providers/lib/strata.d.ts +0 -15
  104. package/packages/providers/lib/strata.js +0 -14
  105. /package/packages/providers/lib/{hyperqwen.d.ts → engines/hyperqwen.d.ts} +0 -0
  106. /package/packages/providers/lib/{hyperqwen.js → engines/hyperqwen.js} +0 -0
  107. /package/packages/providers/lib/{llamacpp.d.ts → engines/llamacpp.d.ts} +0 -0
  108. /package/packages/providers/lib/{llamacpp.js → engines/llamacpp.js} +0 -0
@@ -0,0 +1,53 @@
1
+ /**
2
+ * The Infernix engine adapter. InfernixAdapter is the local-only engine for
3
+ * Infernix, the C++/CUDA fork of NInfer (https://github.com/wallawalla47/infernix)
4
+ * tuned for Qwen models on a single RTX 50-series GPU. `renderLaunch` renders a
5
+ * local `infernix-serve` process from the `INFERNIX_PARAMETERS` surface (the
6
+ * Infernix-only fields, from `--ngram-volume` to the quality routes, spell
7
+ * onto the argv only for this engine); `deploy` builds the engine in the
8
+ * engine directory (the engine's own cmake preset), downloads the model
9
+ * artifact (and the n-gram volume, for the multi-file Flash-Next repo) into
10
+ * the model directory, and starts the engine; `healthCheck` polls the health
11
+ * URL; `stop` signals the tracked process. The engine ships no docker image,
12
+ * so the adapter supports the local build mode only.
13
+ * @module dsh-plugin-local-infer/providers/engines/infernixAdapter
14
+ */
15
+ import { EngineAdapter } from '../../../core/lib/index.js';
16
+ import type { Config, DeployOptions, DeployResult, EngineLaunchSignature, EnginePlatformConfig, HealthCheckOptions, HealthResult, HardwareProfile, LaunchPlan, LaunchProgress, LifecycleHost, ParameterSpec } from '../../../core/lib/index.js';
17
+ /**
18
+ * InfernixAdapter is the local-only engine for Infernix, the C++/CUDA fork
19
+ * of NInfer (https://github.com/wallawalla47/infernix) tuned for Qwen models
20
+ * on a single RTX 50-series GPU. `renderLaunch` renders a local
21
+ * `infernix-serve` process from the `INFERNIX_PARAMETERS` surface (the
22
+ * Infernix-only fields, from `--ngram-volume` to the quality routes, spell
23
+ * onto the argv only for this engine); `deploy` builds the engine in the
24
+ * engine directory (the engine's own cmake preset), downloads the model
25
+ * artifact (and the n-gram volume, for the multi-file Flash-Next repo) into
26
+ * the model directory, and starts the engine; `healthCheck` polls the health
27
+ * URL; `stop` signals the tracked process. The engine ships no docker image,
28
+ * so the adapter supports the local build mode only.
29
+ */
30
+ export declare class InfernixAdapter extends EngineAdapter {
31
+ readonly id = "infernix";
32
+ readonly supports: {
33
+ docker: boolean;
34
+ local: boolean;
35
+ };
36
+ private readonly host;
37
+ private readonly children;
38
+ constructor(host?: LifecycleHost);
39
+ parameters(): readonly ParameterSpec[];
40
+ modelRef(config: Config): {
41
+ id: string;
42
+ name: string;
43
+ };
44
+ launchSignature(config?: Config): EngineLaunchSignature;
45
+ renderLaunch(config: Config, _hw: HardwareProfile): LaunchPlan;
46
+ deploy(plan: LaunchPlan, platform?: EnginePlatformConfig, onProgress?: LaunchProgress, options?: DeployOptions): Promise<DeployResult>;
47
+ healthCheck(plan: LaunchPlan, options?: HealthCheckOptions): Promise<HealthResult>;
48
+ stop(plan: LaunchPlan): Promise<void>;
49
+ private port;
50
+ private planKey;
51
+ private downloadModel;
52
+ }
53
+ //# sourceMappingURL=infernixAdapter.d.ts.map
@@ -0,0 +1,202 @@
1
+ /**
2
+ * The Infernix engine adapter. InfernixAdapter is the local-only engine for
3
+ * Infernix, the C++/CUDA fork of NInfer (https://github.com/wallawalla47/infernix)
4
+ * tuned for Qwen models on a single RTX 50-series GPU. `renderLaunch` renders a
5
+ * local `infernix-serve` process from the `INFERNIX_PARAMETERS` surface (the
6
+ * Infernix-only fields, from `--ngram-volume` to the quality routes, spell
7
+ * onto the argv only for this engine); `deploy` builds the engine in the
8
+ * engine directory (the engine's own cmake preset), downloads the model
9
+ * artifact (and the n-gram volume, for the multi-file Flash-Next repo) into
10
+ * the model directory, and starts the engine; `healthCheck` polls the health
11
+ * URL; `stop` signals the tracked process. The engine ships no docker image,
12
+ * so the adapter supports the local build mode only.
13
+ * @module dsh-plugin-local-infer/providers/engines/infernixAdapter
14
+ */
15
+ import { join } from 'node:path';
16
+ import { EngineAdapter } from '../../../core/lib/index.js';
17
+ import { customArgFlags, flagsFromSpecs, nodeLifecycleHost, pollHealth, } from "../adapters.js";
18
+ import { syncAndRebuildEngine } from "../acquire.js";
19
+ import { INFERNIX_MODEL_ARTIFACTS, INFERNIX_PARAMETERS, infernixArtifactFor, infernixDraftModelPath, infernixModelKey, } from "./infernix.js";
20
+ /**
21
+ * InfernixAdapter is the local-only engine for Infernix, the C++/CUDA fork
22
+ * of NInfer (https://github.com/wallawalla47/infernix) tuned for Qwen models
23
+ * on a single RTX 50-series GPU. `renderLaunch` renders a local
24
+ * `infernix-serve` process from the `INFERNIX_PARAMETERS` surface (the
25
+ * Infernix-only fields, from `--ngram-volume` to the quality routes, spell
26
+ * onto the argv only for this engine); `deploy` builds the engine in the
27
+ * engine directory (the engine's own cmake preset), downloads the model
28
+ * artifact (and the n-gram volume, for the multi-file Flash-Next repo) into
29
+ * the model directory, and starts the engine; `healthCheck` polls the health
30
+ * URL; `stop` signals the tracked process. The engine ships no docker image,
31
+ * so the adapter supports the local build mode only.
32
+ */
33
+ export class InfernixAdapter extends EngineAdapter {
34
+ id = 'infernix';
35
+ supports = { docker: false, local: true };
36
+ host;
37
+ children = new Map();
38
+ constructor(host = nodeLifecycleHost) {
39
+ super();
40
+ this.host = host;
41
+ }
42
+ parameters() {
43
+ return INFERNIX_PARAMETERS;
44
+ }
45
+ modelRef(config) {
46
+ // The engine serves the model under the artifact's advertised id, so the
47
+ // registrar publishes that alias and a chat turn routes to it. A model
48
+ // key that names a local artifact file (a directory-scanned model)
49
+ // reuses the 27B alias, the same fallback the NInfer adapter applies.
50
+ const artifact = infernixArtifactFor(infernixModelKey(config));
51
+ return { id: artifact.modelId, name: artifact.modelId };
52
+ }
53
+ launchSignature(config = {}) {
54
+ // The local plan runs the infernix-serve binary (the .exe on Windows),
55
+ // so the binary name is the command-line marker the reserve probe
56
+ // matches on, and the port the plan binds rides along.
57
+ return { binary: 'infernix-serve', port: this.port(config) };
58
+ }
59
+ renderLaunch(config, _hw) {
60
+ const port = this.port(config);
61
+ const modelKey = infernixModelKey(config);
62
+ const artifact = infernixArtifactFor(modelKey);
63
+ // A model key that names a local artifact file (a directory-scanned
64
+ // model, a .ninfer or .infernix name) overrides the artifact map's file,
65
+ // so a file the user has on disk launches as-is.
66
+ const file = modelKey.endsWith('.ninfer') || modelKey.endsWith('.infernix')
67
+ ? modelKey
68
+ : artifact.file;
69
+ const modelDirectory = typeof config.modelDirectory === 'string' ? config.modelDirectory : '';
70
+ const modelPath = modelDirectory === '' ? file : join(modelDirectory, file);
71
+ // The engine binary is the .exe on a Windows host, where the published
72
+ // release zip ships the server and its DLLs.
73
+ const binary = process.platform === 'win32' ? 'infernix-serve.exe' : 'infernix-serve';
74
+ const flags = flagsFromSpecs(INFERNIX_PARAMETERS, config);
75
+ // The dflash2 backend always loads a drafter through --draft-model, so the
76
+ // flag is spelled here from the resolved path (the selected DFlash2 preset,
77
+ // or the Custom Draft model field) rather than by a spec toArgv. A config
78
+ // that names no drafter path is the misconfiguration the dflash2
79
+ // guideline names, so the flag is spelled only when a path resolves.
80
+ if (config.spec === 'dflash2') {
81
+ const draftPath = infernixDraftModelPath(config);
82
+ if (draftPath !== null) {
83
+ flags.push('--draft-model', draftPath);
84
+ }
85
+ }
86
+ const customArgs = customArgFlags(config);
87
+ return {
88
+ engineId: this.id,
89
+ kind: 'process',
90
+ argv: [
91
+ binary,
92
+ modelPath,
93
+ '--host',
94
+ '127.0.0.1',
95
+ '--port',
96
+ String(port),
97
+ '--model-id',
98
+ artifact.modelId,
99
+ ...flags,
100
+ ...customArgs,
101
+ ],
102
+ env: {},
103
+ modelId: modelKey,
104
+ ...(modelDirectory === '' ? {} : { modelDirectory }),
105
+ port,
106
+ healthUrl: `http://127.0.0.1:${port}/health`,
107
+ };
108
+ }
109
+ async deploy(plan, platform, onProgress, options) {
110
+ if (plan.kind !== 'process') {
111
+ // Infernix renders process plans only; a docker or compose plan is a
112
+ // no-op.
113
+ return { ok: true };
114
+ }
115
+ const engineDir = platform?.engineDirectory;
116
+ let buildLogs = '';
117
+ // Auto Rebuild off (the card's checkbox): the git sync (fetch, rebase,
118
+ // rebuild) is skipped and the checkout launches as-is.
119
+ if (engineDir !== undefined && options?.autoRebuild !== false) {
120
+ // The git sync is the checkout download: the card's button reads
121
+ // "Downloading..." for it, not "Building...".
122
+ onProgress?.('download');
123
+ // The conditional rebuild fetches the checkout, prices it against the
124
+ // remote branch, and rebuilds only when it is behind (or a fresh
125
+ // clone). The engine's own quick start (the cmake preset, then the
126
+ // preset's build) rides the acquisition-data build steps. The decision,
127
+ // the build command, and its output are captured into buildLogs and
128
+ // threaded to the card's Logs section.
129
+ const sync = await syncAndRebuildEngine(this.host, {
130
+ engineId: this.id,
131
+ directory: engineDir,
132
+ ...(platform?.engineFork !== undefined ? { fork: platform.engineFork } : {}),
133
+ ...(plan.argv?.[0] !== undefined ? { binary: plan.argv[0] } : {}),
134
+ ...(options?.allowRunDeps !== undefined ? { allowRunDeps: options.allowRunDeps } : {}),
135
+ ...(platform?.buildCommands !== undefined
136
+ ? { buildCommands: platform.buildCommands }
137
+ : {}),
138
+ // Report the build phase the moment the build steps start, so the
139
+ // card's button reads "Building..." while the build is in flight.
140
+ onBuildStart: () => onProgress?.('build'),
141
+ }, () => { });
142
+ buildLogs = sync.logs;
143
+ if (!sync.ok) {
144
+ return { ok: false, detail: sync.detail ?? 'unknown', logs: buildLogs };
145
+ }
146
+ // The build output reads as a fresh Logs section: a "Building..."
147
+ // separator leads the build decision and output, the same line the
148
+ // card writes when the build step event lands.
149
+ if (sync.rebuilt && buildLogs !== '') {
150
+ buildLogs = `--- Building...\n${buildLogs}`;
151
+ }
152
+ }
153
+ const withLogs = (result) => buildLogs !== '' ? { ...result, logs: buildLogs } : result;
154
+ onProgress?.('download');
155
+ const download = await this.downloadModel(plan);
156
+ if (!download.ok)
157
+ return withLogs(download);
158
+ const handle = this.host.spawn(plan.argv ?? [], plan.env ?? {}, engineDir);
159
+ this.children.set(this.planKey(plan), handle);
160
+ return withLogs({ ok: true, handle });
161
+ }
162
+ async healthCheck(plan, options) {
163
+ return pollHealth(this.host, plan, options);
164
+ }
165
+ async stop(plan) {
166
+ const handle = this.children.get(this.planKey(plan));
167
+ if (handle !== undefined) {
168
+ handle.kill();
169
+ this.children.delete(this.planKey(plan));
170
+ }
171
+ }
172
+ port(config) {
173
+ // 8080 is the infernix-serve CLI default, so the form seeds it and the
174
+ // adapter falls back to it when a config names no port.
175
+ return typeof config.port === 'number' ? config.port : 8080;
176
+ }
177
+ planKey(plan) {
178
+ return `${plan.engineId}:${plan.port}`;
179
+ }
180
+ async downloadModel(plan) {
181
+ const artifact = plan.modelId !== undefined ? INFERNIX_MODEL_ARTIFACTS[plan.modelId] : undefined;
182
+ // A model key that names a local artifact file carries no artifact entry,
183
+ // so the file the user has on disk launches as-is without a download.
184
+ if (artifact === undefined)
185
+ return { ok: true };
186
+ const target = plan.modelDirectory ?? '.';
187
+ // A multi-file artifact (the Flash-Next shards) downloads the whole repo,
188
+ // which carries the n-gram volume beside the shards, so the
189
+ // --ngram-volume flag points at a file the same download left on disk.
190
+ // A single-file artifact downloads the file alone.
191
+ const repo = artifact.repo;
192
+ const files = artifact.files ?? [];
193
+ const download = files.length > 1
194
+ ? await this.host.run(`hf download ${repo} --local-dir ${target}`)
195
+ : await this.host.run(`hf download ${repo} ${artifact.file} --local-dir ${target}`);
196
+ if (!download.ok) {
197
+ return { ok: false, detail: `model download failed: ${download.detail ?? 'unknown'}` };
198
+ }
199
+ return { ok: true };
200
+ }
201
+ }
202
+ //# sourceMappingURL=infernixAdapter.js.map
@@ -0,0 +1,51 @@
1
+ /**
2
+ * The llama.cpp engine adapter. LlamaCppAdapter is the llama.cpp engine,
3
+ * both-capable like NInfer. `renderLaunch` renders a local `llama-server`
4
+ * process or a `docker run` of the ggml image from the same
5
+ * `LLAMACPP_PARAMETERS` surface; `deploy` builds the engine in the engine
6
+ * directory (or pulls the image), downloads the GGUF model into the model
7
+ * directory, and starts the engine; `healthCheck` polls the health URL;
8
+ * `stop` signals the tracked process and stops the named container.
9
+ * @module dsh-plugin-local-infer/providers/engines/llamacppAdapter
10
+ */
11
+ import { EngineAdapter } from '../../../core/lib/index.js';
12
+ import type { Config, DeployOptions, DeployResult, EngineLaunchSignature, EnginePlatformConfig, HealthCheckOptions, HealthResult, HardwareProfile, LaunchPlan, LaunchProgress, LifecycleHost, ParameterSpec } from '../../../core/lib/index.js';
13
+ /**
14
+ * The llama.cpp docker image the adapter pulls and runs. The engineFork
15
+ * platform config overrides the git source for a local build; the image stays
16
+ * the upstream ggml artifact.
17
+ */
18
+ export declare const LLAMACPP_DOCKER_IMAGE = "ghcr.io/ggml-org/llama.cpp";
19
+ /**
20
+ * LlamaCppAdapter is the llama.cpp engine, both-capable like NInfer.
21
+ * `renderLaunch` renders a local `llama-server` process or a `docker run` of the
22
+ * ggml image from the same `LLAMACPP_PARAMETERS` surface; `deploy` builds the
23
+ * engine in the engine directory (or pulls the image), downloads the GGUF model
24
+ * into the model directory, and starts the engine; `healthCheck` polls the health
25
+ * URL; `stop` signals the tracked process and stops the named container.
26
+ */
27
+ export declare class LlamaCppAdapter extends EngineAdapter {
28
+ readonly id = "llamacpp";
29
+ readonly supports: {
30
+ docker: boolean;
31
+ local: boolean;
32
+ };
33
+ private readonly host;
34
+ private readonly children;
35
+ constructor(host?: LifecycleHost);
36
+ parameters(): readonly ParameterSpec[];
37
+ modelRef(config: Config): {
38
+ id: string;
39
+ name: string;
40
+ };
41
+ launchSignature(config?: Config): EngineLaunchSignature;
42
+ renderLaunch(config: Config, _hw: HardwareProfile): LaunchPlan;
43
+ deploy(plan: LaunchPlan, platform?: EnginePlatformConfig, onProgress?: LaunchProgress, options?: DeployOptions): Promise<DeployResult>;
44
+ healthCheck(plan: LaunchPlan, options?: HealthCheckOptions): Promise<HealthResult>;
45
+ stop(plan: LaunchPlan): Promise<void>;
46
+ private buildMode;
47
+ private port;
48
+ private planKey;
49
+ private downloadModel;
50
+ }
51
+ //# sourceMappingURL=llamacppAdapter.d.ts.map
@@ -0,0 +1,232 @@
1
+ /**
2
+ * The llama.cpp engine adapter. LlamaCppAdapter is the llama.cpp engine,
3
+ * both-capable like NInfer. `renderLaunch` renders a local `llama-server`
4
+ * process or a `docker run` of the ggml image from the same
5
+ * `LLAMACPP_PARAMETERS` surface; `deploy` builds the engine in the engine
6
+ * directory (or pulls the image), downloads the GGUF model into the model
7
+ * directory, and starts the engine; `healthCheck` polls the health URL;
8
+ * `stop` signals the tracked process and stops the named container.
9
+ * @module dsh-plugin-local-infer/providers/engines/llamacppAdapter
10
+ */
11
+ import { basename, dirname, join } from 'node:path';
12
+ import { EngineAdapter } from '../../../core/lib/index.js';
13
+ import { customArgFlags, flagsFromSpecs, nodeLifecycleHost, pollHealth, trackChild, } from "../adapters.js";
14
+ import { syncAndRebuildEngine } from "../acquire.js";
15
+ import { LLAMACPP_MODEL_ARTIFACTS, LLAMACPP_PARAMETERS, llamaCppModelKey, } from "./llamacpp.js";
16
+ /**
17
+ * The llama.cpp docker image the adapter pulls and runs. The engineFork
18
+ * platform config overrides the git source for a local build; the image stays
19
+ * the upstream ggml artifact.
20
+ */
21
+ export const LLAMACPP_DOCKER_IMAGE = 'ghcr.io/ggml-org/llama.cpp';
22
+ /**
23
+ * llamaCppArtifact resolves the artifact a model key deploys. A key no entry
24
+ * names falls back to the 27B artifact, so a bare config always renders a plan.
25
+ */
26
+ function llamaCppArtifact(modelKey) {
27
+ const artifact = modelKey !== undefined ? LLAMACPP_MODEL_ARTIFACTS[modelKey] : undefined;
28
+ return artifact ?? LLAMACPP_MODEL_ARTIFACTS['qwen3-27b'];
29
+ }
30
+ /**
31
+ * LlamaCppAdapter is the llama.cpp engine, both-capable like NInfer.
32
+ * `renderLaunch` renders a local `llama-server` process or a `docker run` of the
33
+ * ggml image from the same `LLAMACPP_PARAMETERS` surface; `deploy` builds the
34
+ * engine in the engine directory (or pulls the image), downloads the GGUF model
35
+ * into the model directory, and starts the engine; `healthCheck` polls the health
36
+ * URL; `stop` signals the tracked process and stops the named container.
37
+ */
38
+ export class LlamaCppAdapter extends EngineAdapter {
39
+ id = 'llamacpp';
40
+ supports = { docker: true, local: true };
41
+ host;
42
+ children = new Map();
43
+ constructor(host = nodeLifecycleHost) {
44
+ super();
45
+ this.host = host;
46
+ }
47
+ parameters() {
48
+ return LLAMACPP_PARAMETERS;
49
+ }
50
+ modelRef(config) {
51
+ // The engine serves the model under the artifact's advertised id, so the
52
+ // registrar publishes that alias and a chat turn routes to it.
53
+ const artifact = llamaCppArtifact(llamaCppModelKey(config));
54
+ return { id: artifact.modelId, name: artifact.modelId };
55
+ }
56
+ launchSignature(config = {}) {
57
+ // The local plan runs the llama-server binary; the docker plan's host
58
+ // process is docker run of the ggml image, so the image name is the
59
+ // command-line marker the reserve probe matches on.
60
+ const port = this.port(config);
61
+ if (this.buildMode(config) === 'docker') {
62
+ return { binary: LLAMACPP_DOCKER_IMAGE, port };
63
+ }
64
+ return { binary: 'llama-server', port };
65
+ }
66
+ renderLaunch(config, _hw) {
67
+ const port = this.port(config);
68
+ const modelId = llamaCppModelKey(config);
69
+ const artifact = llamaCppArtifact(modelId);
70
+ // A model key that names a local GGUF file (a directory-scanned model)
71
+ // overrides the artifact map's file, so a file the user has on disk
72
+ // launches as-is.
73
+ const file = modelId.endsWith('.gguf') ? modelId : artifact.file;
74
+ const modelDirectory = typeof config.modelDirectory === 'string' ? config.modelDirectory : '';
75
+ const flags = flagsFromSpecs(LLAMACPP_PARAMETERS, config);
76
+ if (this.buildMode(config) === 'docker') {
77
+ const modelPath = modelDirectory === '' ? file : join(modelDirectory, file);
78
+ const containerModelPath = `/models/${basename(file)}`;
79
+ const modelMount = modelDirectory === ''
80
+ ? `${file}:${containerModelPath}`
81
+ : `${dirname(modelPath)}:/models`;
82
+ const containerName = `llamacpp-${port}`;
83
+ const customArgs = customArgFlags(config);
84
+ return {
85
+ engineId: this.id,
86
+ kind: 'docker',
87
+ argv: [
88
+ 'docker',
89
+ 'run',
90
+ '--rm',
91
+ '--gpus',
92
+ 'all',
93
+ '--name',
94
+ containerName,
95
+ '-p',
96
+ `127.0.0.1:${port}:8080`,
97
+ '-v',
98
+ modelMount,
99
+ LLAMACPP_DOCKER_IMAGE,
100
+ '--model',
101
+ containerModelPath,
102
+ ...flags,
103
+ ...customArgs,
104
+ ],
105
+ env: {},
106
+ containerName,
107
+ modelId,
108
+ ...(modelDirectory === '' ? {} : { modelDirectory }),
109
+ port,
110
+ healthUrl: `http://127.0.0.1:${port}/health`,
111
+ };
112
+ }
113
+ const modelPath = modelDirectory === '' ? file : join(modelDirectory, file);
114
+ const customArgs = customArgFlags(config);
115
+ return {
116
+ engineId: this.id,
117
+ kind: 'process',
118
+ argv: [
119
+ 'llama-server',
120
+ '--model',
121
+ modelPath,
122
+ '--host',
123
+ '127.0.0.1',
124
+ '--port',
125
+ String(port),
126
+ ...flags,
127
+ ...customArgs,
128
+ ],
129
+ env: {},
130
+ modelId,
131
+ ...(modelDirectory === '' ? {} : { modelDirectory }),
132
+ port,
133
+ healthUrl: `http://127.0.0.1:${port}/health`,
134
+ };
135
+ }
136
+ async deploy(plan, platform, onProgress, options) {
137
+ if (plan.kind === 'process') {
138
+ const engineDir = platform?.engineDirectory;
139
+ let buildLogs = '';
140
+ // Auto Rebuild off (the card's checkbox): the git sync (fetch, rebase,
141
+ // rebuild) is skipped and the checkout launches as-is.
142
+ if (engineDir !== undefined && options?.autoRebuild !== false) {
143
+ // The git sync is the checkout download: the card's button reads
144
+ // "Downloading..." for it, not "Building...".
145
+ onProgress?.('download');
146
+ // The conditional rebuild fetches the checkout, prices it against the
147
+ // remote branch, and rebuilds only when it is behind (or a fresh
148
+ // clone). The decision, the build command, and its output are captured
149
+ // into buildLogs and threaded to the card's Logs section.
150
+ const sync = await syncAndRebuildEngine(this.host, {
151
+ engineId: this.id,
152
+ directory: engineDir,
153
+ ...(platform?.engineFork !== undefined ? { fork: platform.engineFork } : {}),
154
+ ...(plan.argv?.[0] !== undefined ? { binary: plan.argv[0] } : {}),
155
+ ...(options?.allowRunDeps !== undefined ? { allowRunDeps: options.allowRunDeps } : {}),
156
+ ...(platform?.buildCommands !== undefined
157
+ ? { buildCommands: platform.buildCommands }
158
+ : {}),
159
+ // Report the build phase the moment the build steps start, so the
160
+ // card's button reads "Building..." while the build is in flight.
161
+ onBuildStart: () => onProgress?.('build'),
162
+ }, () => { });
163
+ buildLogs = sync.logs;
164
+ if (!sync.ok) {
165
+ return { ok: false, detail: sync.detail ?? 'unknown', logs: buildLogs };
166
+ }
167
+ // The build output reads as a fresh Logs section: a "Building..."
168
+ // separator leads the build decision and output, the same line the
169
+ // card writes when the build step event lands.
170
+ if (sync.rebuilt && buildLogs !== '') {
171
+ buildLogs = `--- Building...\n${buildLogs}`;
172
+ }
173
+ }
174
+ const withLogs = (result) => buildLogs !== '' ? { ...result, logs: buildLogs } : result;
175
+ onProgress?.('download');
176
+ const download = await this.downloadModel(plan);
177
+ if (!download.ok)
178
+ return withLogs(download);
179
+ const handle = this.host.spawn(plan.argv ?? [], plan.env ?? {}, engineDir);
180
+ trackChild(this.children, this.planKey(plan), handle);
181
+ return withLogs({ ok: true, handle });
182
+ }
183
+ if (plan.kind === 'docker') {
184
+ // The image pull is the docker build step: the card's button reads
185
+ // "Building..." for it.
186
+ onProgress?.('build');
187
+ const pull = await this.host.run(`docker pull ${LLAMACPP_DOCKER_IMAGE}`);
188
+ if (!pull.ok) {
189
+ return { ok: false, detail: `docker pull failed: ${pull.detail ?? 'unknown'}` };
190
+ }
191
+ const handle = this.host.spawn(plan.argv ?? [], plan.env ?? {});
192
+ trackChild(this.children, this.planKey(plan), handle);
193
+ return { ok: true, handle };
194
+ }
195
+ // llama.cpp renders process and docker plans; a compose plan is a no-op.
196
+ return { ok: true };
197
+ }
198
+ async healthCheck(plan, options) {
199
+ return pollHealth(this.host, plan, options);
200
+ }
201
+ async stop(plan) {
202
+ if (plan.kind === 'docker' && plan.containerName !== undefined) {
203
+ await this.host.run(`docker stop ${plan.containerName}`);
204
+ }
205
+ const handle = this.children.get(this.planKey(plan));
206
+ if (handle !== undefined) {
207
+ handle.kill();
208
+ this.children.delete(this.planKey(plan));
209
+ }
210
+ }
211
+ buildMode(config) {
212
+ return config.buildMode === 'docker' ? 'docker' : 'local';
213
+ }
214
+ port(config) {
215
+ return typeof config.port === 'number' ? config.port : 8080;
216
+ }
217
+ planKey(plan) {
218
+ return `${plan.engineId}:${plan.port}`;
219
+ }
220
+ async downloadModel(plan) {
221
+ const artifact = plan.modelId !== undefined ? LLAMACPP_MODEL_ARTIFACTS[plan.modelId] : undefined;
222
+ if (artifact === undefined)
223
+ return { ok: true };
224
+ const target = plan.modelDirectory ?? '.';
225
+ const download = await this.host.run(`hf download ${artifact.repo} ${artifact.file} --local-dir ${target}`);
226
+ if (!download.ok) {
227
+ return { ok: false, detail: `model download failed: ${download.detail ?? 'unknown'}` };
228
+ }
229
+ return { ok: true };
230
+ }
231
+ }
232
+ //# sourceMappingURL=llamacppAdapter.js.map
@@ -39,6 +39,13 @@ export declare const CHAT_TEMPLATE_OPTIONS: readonly ComboOption[];
39
39
  * click, not just the constraint.
40
40
  */
41
41
  export declare const SPEC_GATE_MESSAGE = "Pick a speculation backend first (mtp, dflash, or dflash2)";
42
+ /**
43
+ * The gate message the form shows while the LM-head draft field is greyed out.
44
+ * The LM-head drafter is an MTP-backend feature, so the field unlocks only
45
+ * while the MTP speculation backend is selected, for every provider that
46
+ * spells the flag.
47
+ */
48
+ export declare const LM_HEAD_DRAFT_GATE_MESSAGE = "LM-head draft is only available with the MTP speculation backend";
42
49
  /**
43
50
  * The NInfer `ParameterSpec` surface, in form order. The `group` field is the
44
51
  * form section; specs without a group render under `General`. `requires`
@@ -66,6 +66,13 @@ export const CHAT_TEMPLATE_OPTIONS = [
66
66
  * click, not just the constraint.
67
67
  */
68
68
  export const SPEC_GATE_MESSAGE = 'Pick a speculation backend first (mtp, dflash, or dflash2)';
69
+ /**
70
+ * The gate message the form shows while the LM-head draft field is greyed out.
71
+ * The LM-head drafter is an MTP-backend feature, so the field unlocks only
72
+ * while the MTP speculation backend is selected, for every provider that
73
+ * spells the flag.
74
+ */
75
+ export const LM_HEAD_DRAFT_GATE_MESSAGE = 'LM-head draft is only available with the MTP speculation backend';
69
76
  /**
70
77
  * The NInfer `ParameterSpec` surface, in form order. The `group` field is the
71
78
  * form section; specs without a group render under `General`. `requires`
@@ -213,7 +220,7 @@ export const NINFER_PARAMETERS = [
213
220
  form: { kind: 'boolean' },
214
221
  defaultValue: false,
215
222
  group: 'Speculation',
216
- requires: (config) => config.spec && config.spec !== 'none' ? null : SPEC_GATE_MESSAGE,
223
+ requires: (config) => config.spec === 'mtp' ? null : LM_HEAD_DRAFT_GATE_MESSAGE,
217
224
  toArgv: (value) => (value === true ? ['--lm-head-draft'] : []),
218
225
  },
219
226
  {
@@ -0,0 +1,82 @@
1
+ /**
2
+ * The NInfer engine adapter. NInferModelArtifact is the per-model deploy data:
3
+ * the `.ninfer` artifact file, the Hugging Face repo the model download pulls
4
+ * it from, and the `--model-id` alias the server advertises. The data is the
5
+ * surface; adding a model is a data edit here, never an adapter rewire.
6
+ * NInferAdapter is the canonical both-capable engine. `renderLaunch` renders a
7
+ * local `ninfer-serve` process or a `docker run` of the ghcr image from the
8
+ * same spec surface; `deploy` builds the engine in the engine directory (or
9
+ * pulls the image), downloads the model into the model directory, and starts
10
+ * the engine; `healthCheck` polls the health URL; `stop` signals the tracked
11
+ * process and stops the named container.
12
+ * @module dsh-plugin-local-infer/providers/engines/ninferAdapter
13
+ */
14
+ import { EngineAdapter } from '../../../core/lib/index.js';
15
+ import type { Config, DeployOptions, DeployResult, EngineLaunchSignature, EnginePlatformConfig, HealthCheckOptions, HealthResult, HardwareProfile, LaunchPlan, LaunchProgress, LifecycleHost, ParameterSpec } from '../../../core/lib/index.js';
16
+ /**
17
+ * NInferModelArtifact is the per-model deploy data: the `.ninfer` artifact
18
+ * file, the Hugging Face repo the model download pulls it from, and the
19
+ * `--model-id` alias the server advertises. The data is the surface; adding a
20
+ * model is a data edit here, never an adapter rewire.
21
+ */
22
+ export interface NInferModelArtifact {
23
+ file: string;
24
+ repo: string;
25
+ modelId: string;
26
+ }
27
+ /**
28
+ * The NInfer model artifacts, keyed by the model registry id. The 27B entry
29
+ * is the default the DEFAULT_MODELS list names; the 35B-A3B entries are the
30
+ * Qwen3.8 measured artifact (`qwen3-35b`) and the Qwen3.6 default
31
+ * (`qwen36-35b`).
32
+ */
33
+ export declare const NINFER_MODEL_ARTIFACTS: Record<string, NInferModelArtifact>;
34
+ /**
35
+ * The NInfer docker image the adapter pulls and runs. The engineFork platform
36
+ * config overrides the git source for a local build; the image stays the
37
+ * upstream ghcr artifact.
38
+ */
39
+ export declare const NINFER_DOCKER_IMAGE = "ghcr.io/iamwavecut/ninfer-all";
40
+ /**
41
+ * NInferAdapter is the canonical both-capable engine. `renderLaunch` renders a
42
+ * local `ninfer-serve` process or a `docker run` of the ghcr image from the
43
+ * same spec surface; `deploy` builds the engine in the engine directory (or
44
+ * pulls the image), downloads the model into the model directory, and starts
45
+ * the engine; `healthCheck` polls the health URL; `stop` signals the tracked
46
+ * process and stops the named container.
47
+ */
48
+ export declare class NInferAdapter extends EngineAdapter {
49
+ readonly id = "ninfer";
50
+ readonly supports: {
51
+ docker: boolean;
52
+ local: boolean;
53
+ };
54
+ private readonly host;
55
+ private readonly children;
56
+ constructor(host?: LifecycleHost);
57
+ parameters(): readonly ParameterSpec[];
58
+ modelRef(config: Config): {
59
+ id: string;
60
+ name: string;
61
+ };
62
+ launchSignature(config?: Config): EngineLaunchSignature;
63
+ renderLaunch(config: Config, _hw: HardwareProfile): LaunchPlan;
64
+ deploy(plan: LaunchPlan, platform?: EnginePlatformConfig, onProgress?: LaunchProgress, options?: DeployOptions): Promise<DeployResult>;
65
+ healthCheck(plan: LaunchPlan, options?: HealthCheckOptions): Promise<HealthResult>;
66
+ stop(plan: LaunchPlan): Promise<void>;
67
+ private buildMode;
68
+ private port;
69
+ private planKey;
70
+ private downloadModel;
71
+ /**
72
+ * resolveChatTemplate rewrites a --chat-template flag that carries an HF repo
73
+ * reference into the local file the engine reads. A value with a slash that
74
+ * is not an absolute path (the combo control's known choices) is a repo the
75
+ * deploy step downloads into the model directory; an already-local path is
76
+ * left as-is. The download is best-effort: a template that is already on
77
+ * disk is used without a re-fetch, and a missing hf CLI leaves the flag
78
+ * untouched so the engine reports the missing template itself.
79
+ */
80
+ private resolveChatTemplate;
81
+ }
82
+ //# sourceMappingURL=ninferAdapter.d.ts.map