dsh-plugin-local-infer 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/README.md +4 -1
  2. package/docs/zh/README.md +4 -1
  3. package/dsh/client.js +627 -133
  4. package/package.json +11 -10
  5. package/packages/core/lib/adapter.d.ts +75 -4
  6. package/packages/core/lib/index.d.ts +2 -1
  7. package/packages/core/lib/index.js +1 -0
  8. package/packages/core/lib/launch-command.d.ts +13 -0
  9. package/packages/core/lib/launch-command.js +64 -0
  10. package/packages/core/lib/models.d.ts +2 -1
  11. package/packages/core/lib/models.js +10 -1
  12. package/packages/core/lib/status.d.ts +9 -1
  13. package/packages/core/lib/status.js +6 -1
  14. package/packages/providers/lib/acquire.d.ts +125 -0
  15. package/packages/providers/lib/acquire.js +325 -0
  16. package/packages/providers/lib/adapters.d.ts +42 -5
  17. package/packages/providers/lib/adapters.js +385 -57
  18. package/packages/providers/lib/deps.d.ts +4 -2
  19. package/packages/providers/lib/deps.js +16 -2
  20. package/packages/providers/lib/engines.d.ts +4 -3
  21. package/packages/providers/lib/engines.js +11 -3
  22. package/packages/providers/lib/guidelines.js +16 -0
  23. package/packages/providers/lib/index.d.ts +9 -3
  24. package/packages/providers/lib/index.js +8 -4
  25. package/packages/providers/lib/infernix.d.ts +170 -0
  26. package/packages/providers/lib/infernix.js +671 -0
  27. package/packages/providers/lib/model-defaults.d.ts +5 -3
  28. package/packages/providers/lib/model-defaults.js +27 -3
  29. package/packages/providers/lib/ninfer.d.ts +7 -0
  30. package/packages/providers/lib/ninfer.js +9 -2
  31. package/packages/providers/lib/platform.d.ts +31 -0
  32. package/packages/providers/lib/platform.js +58 -0
  33. package/packages/service/lib/index.d.ts +61 -18
  34. package/packages/service/lib/index.js +278 -42
  35. package/packages/service/lib/types.d.ts +44 -2
  36. package/packages/ui/lib/route.d.ts +83 -12
  37. package/packages/ui/lib/route.js +495 -55
  38. package/packages/ui/package.json +1 -1
@@ -0,0 +1,325 @@
1
+ /**
2
+ * The per-engine acquisition data and the Get step. Each engine declares its
3
+ * git source (the upstream repo, the branch the rebase targets, and the build
4
+ * steps the checkout runs) as data here; the adapters' deploy steps and the
5
+ * /local-infer/get route both consume the same constants, so a build command
6
+ * has one home. The sync step clones a missing checkout, and a clean existing
7
+ * checkout is fetched and rebased onto the upstream branch; a checkout with
8
+ * local changes aborts, because a rebase would eat the user's work.
9
+ * @module dsh-plugin-local-infer/providers/acquire
10
+ */
11
+ /**
12
+ * The remote name a user-provided fork is added under, so the checkout's own
13
+ * origin (the upstream it was cloned from) survives the fork override.
14
+ */
15
+ export const FORK_REMOTE = 'dsh-fork';
16
+ /**
17
+ * The per-engine git sources, keyed by engine id. NInfer and Infernix build
18
+ * through cmake (Infernix through its own preset); llama.cpp builds through
19
+ * the same cmake shape as NInfer; Hyper-Qwen is the compose-driven vLLM
20
+ * engine, so it carries no local build steps (the compose up pulls the
21
+ * image); Strata is the placeholder engine, and its repo and pull step land
22
+ * with the engine, the same way its docker image constant did.
23
+ */
24
+ export const ENGINE_SOURCES = {
25
+ ninfer: {
26
+ repo: 'https://github.com/Neroued/ninfer',
27
+ branch: 'master',
28
+ buildSteps: [
29
+ 'cmake -S {dir} -B {dir}/build -G Ninja -DCMAKE_BUILD_TYPE=Release && cmake --build {dir}/build -j',
30
+ ],
31
+ },
32
+ hyperqwen: {
33
+ repo: 'https://github.com/vllm-project/vllm',
34
+ branch: 'main',
35
+ buildSteps: [],
36
+ },
37
+ llamacpp: {
38
+ repo: 'https://github.com/ggml-org/llama.cpp',
39
+ branch: 'master',
40
+ buildSteps: [
41
+ 'cmake -S {dir} -B {dir}/build -G Ninja -DCMAKE_BUILD_TYPE=Release && cmake --build {dir}/build -j',
42
+ ],
43
+ },
44
+ infernix: {
45
+ repo: 'https://github.com/Wallawalla47/Infernix',
46
+ branch: 'main',
47
+ buildSteps: ['cd {dir} && cmake --preset release && cmake --build build -j'],
48
+ },
49
+ strata: {
50
+ repo: 'https://github.com/strata/strata',
51
+ branch: 'main',
52
+ buildSteps: ['docker pull ghcr.io/strata/strata'],
53
+ },
54
+ };
55
+ /**
56
+ * engineSource reads the registered acquisition data for an engine id, or
57
+ * undefined when none is registered.
58
+ */
59
+ export function engineSource(engineId) {
60
+ return ENGINE_SOURCES[engineId];
61
+ }
62
+ /**
63
+ * engineBuildSteps reads the build steps an engine runs in its checkout, so
64
+ * the adapter's deploy step and the Get build step share one list.
65
+ */
66
+ export function engineBuildSteps(engineId) {
67
+ return ENGINE_SOURCES[engineId]?.buildSteps ?? [];
68
+ }
69
+ /**
70
+ * runLogged runs one shell command through the host's streaming surface when
71
+ * it has one (echoing the command and streaming its output through log), and
72
+ * through plain run otherwise. The streamed copy is also collected, so a
73
+ * caller can price the command's stdout (the dirty-check, for one).
74
+ */
75
+ async function runLogged(host, command, log, options) {
76
+ let output = '';
77
+ const collect = (chunk) => {
78
+ output += chunk;
79
+ log(chunk);
80
+ };
81
+ if (options?.echo !== false) {
82
+ log(`$ ${command}\n`);
83
+ }
84
+ if (host.runStream !== undefined) {
85
+ const outcome = await host.runStream(command, collect);
86
+ return { ...outcome, output };
87
+ }
88
+ const outcome = await host.run(command);
89
+ return { ...outcome, output };
90
+ }
91
+ /**
92
+ * syncEngineRepo brings the engine checkout to the listed source. A missing
93
+ * checkout is a fresh clone of the fork (or the engine's upstream repo when
94
+ * no fork is provided). An existing checkout with local changes aborts, so
95
+ * the user's work is never rebased away; a clean checkout fetches the listed
96
+ * fork (added as the named fork remote when provided, origin when not) and
97
+ * rebases onto its branch. Every command echoes into log as it runs.
98
+ */
99
+ export async function syncEngineRepo(host, request, log) {
100
+ const source = engineSource(request.engineId);
101
+ if (source === undefined) {
102
+ return {
103
+ ok: false,
104
+ detail: `no engine source is registered for "${request.engineId}"`,
105
+ };
106
+ }
107
+ const directory = request.directory;
108
+ const fork = request.fork !== undefined && request.fork.trim() !== '' ? request.fork : undefined;
109
+ const present = await host.run(`[ -d ${directory}/.git ]`);
110
+ if (present.ok) {
111
+ const status = await runLogged(host, `git -C ${directory} status --short`, log);
112
+ if (!status.ok) {
113
+ return { ok: false, detail: `git status failed: ${status.detail ?? 'unknown'}` };
114
+ }
115
+ if (status.output.trim() !== '') {
116
+ log(`!! ${directory} has local changes. Get aborted; commit or revert them first.\n`);
117
+ return {
118
+ ok: false,
119
+ aborted: true,
120
+ detail: 'the engine repository has local changes',
121
+ };
122
+ }
123
+ if (fork !== undefined) {
124
+ // Add the fork under its own remote name; re-Get updates the URL in
125
+ // place so an edited Engine Fork field takes effect on the next run.
126
+ const add = await runLogged(host, `git -C ${directory} remote add ${FORK_REMOTE} ${fork}`, log);
127
+ if (!add.ok) {
128
+ await runLogged(host, `git -C ${directory} remote set-url ${FORK_REMOTE} ${fork}`, log);
129
+ }
130
+ }
131
+ const remote = fork !== undefined ? FORK_REMOTE : 'origin';
132
+ const fetch = await runLogged(host, `git -C ${directory} fetch ${remote}`, log);
133
+ if (!fetch.ok) {
134
+ return { ok: false, detail: `git fetch ${remote} failed: ${fetch.detail ?? 'unknown'}` };
135
+ }
136
+ const rebase = await runLogged(host, `git -C ${directory} rebase ${remote}/${source.branch} || ` +
137
+ `{ git -C ${directory} rebase --abort; exit 1; }`, log);
138
+ if (!rebase.ok) {
139
+ return {
140
+ ok: false,
141
+ detail: `git rebase ${remote}/${source.branch} failed: ${rebase.detail ?? 'unknown'}`,
142
+ };
143
+ }
144
+ return { ok: true };
145
+ }
146
+ const clone = await runLogged(host, `git clone ${fork ?? source.repo} ${directory}`, log);
147
+ if (!clone.ok) {
148
+ return { ok: false, detail: `git clone failed: ${clone.detail ?? 'unknown'}` };
149
+ }
150
+ return { ok: true };
151
+ }
152
+ /**
153
+ * syncAndRebuildEngine brings the engine checkout to the listed source and
154
+ * rebuilds it only when it needs to. A missing checkout is a fresh clone of
155
+ * the fork (or the upstream repo) and always builds. An existing checkout is
156
+ * fetched and priced against the remote branch: when it is behind and clean,
157
+ * the primary branch is checked out, rebased onto the remote branch, and the
158
+ * working tree is cleaned (git clean -fxd) before the build; when it is not
159
+ * behind, or it carries local changes, the build is skipped so a launch does
160
+ * not rebuild a checkout that is current. The decision, the build command,
161
+ * and its output stream through log and are returned as the result's logs.
162
+ * After a rebuild, the engine binary is checked against the path; when a build
163
+ * that did not install it leaves it out, a symlink into /usr/local/bin is
164
+ * added (run when the trust gate is on, written to the logs when it is off).
165
+ */
166
+ export async function syncAndRebuildEngine(host, request, log) {
167
+ const source = engineSource(request.engineId);
168
+ if (source === undefined) {
169
+ return {
170
+ ok: false,
171
+ rebuilt: false,
172
+ detail: `no engine source is registered for "${request.engineId}"`,
173
+ logs: '',
174
+ };
175
+ }
176
+ const directory = request.directory;
177
+ const fork = request.fork !== undefined && request.fork.trim() !== '' ? request.fork : undefined;
178
+ const remote = fork !== undefined ? FORK_REMOTE : 'origin';
179
+ const chunks = [];
180
+ const emit = (chunk) => {
181
+ chunks.push(chunk);
182
+ log(chunk);
183
+ };
184
+ const present = await host.run(`[ -d ${directory}/.git ]`);
185
+ let rebuilt = false;
186
+ if (!present.ok) {
187
+ // A missing checkout is a fresh clone of the fork (or the upstream repo),
188
+ // which always needs a build.
189
+ emit(`>> cloning ${fork ?? source.repo} into ${directory}\n`);
190
+ const clone = await runLogged(host, `git clone ${fork ?? source.repo} ${directory}`, emit);
191
+ if (!clone.ok) {
192
+ return {
193
+ ok: false,
194
+ rebuilt: false,
195
+ detail: `git clone failed: ${clone.detail ?? 'unknown'}`,
196
+ logs: chunks.join(''),
197
+ };
198
+ }
199
+ rebuilt = true;
200
+ }
201
+ else {
202
+ if (fork !== undefined) {
203
+ // Add the fork under its own remote name; a re-launch updates the URL in
204
+ // place so an edited fork field takes effect on the next run.
205
+ const add = await runLogged(host, `git -C ${directory} remote add ${FORK_REMOTE} ${fork}`, emit, { echo: false });
206
+ if (!add.ok) {
207
+ await runLogged(host, `git -C ${directory} remote set-url ${FORK_REMOTE} ${fork}`, emit, { echo: false });
208
+ }
209
+ }
210
+ const status = await runLogged(host, `git -C ${directory} status --short`, emit, { echo: false });
211
+ if (!status.ok) {
212
+ return {
213
+ ok: false,
214
+ rebuilt: false,
215
+ detail: `git status failed: ${status.detail ?? 'unknown'}`,
216
+ logs: chunks.join(''),
217
+ };
218
+ }
219
+ if (status.output.trim() !== '') {
220
+ emit(`!! ${directory} has local changes; the rebase and rebuild are skipped.\n`);
221
+ return { ok: true, rebuilt: false, logs: chunks.join('') };
222
+ }
223
+ // The fetch is best-effort: an offline host prices the checkout it has.
224
+ await runLogged(host, `git -C ${directory} fetch ${remote} || true`, emit, { echo: false });
225
+ // Price whether the current branch is behind the remote branch. A fetch
226
+ // that failed (offline) or a missing remote branch reads as not-behind, so
227
+ // the checkout builds as-is.
228
+ const count = await runLogged(host, `git -C ${directory} rev-list --count HEAD..${remote}/${source.branch} 2>/dev/null || echo 0`, emit, { echo: false });
229
+ const behind = (count.output.trim() || '0') !== '0';
230
+ if (behind) {
231
+ emit(`>> ${directory} is behind ${remote}/${source.branch}; rebasing and rebuilding.\n`);
232
+ const checkout = await runLogged(host, `git -C ${directory} checkout ${source.branch}`, emit);
233
+ if (!checkout.ok) {
234
+ return {
235
+ ok: false,
236
+ rebuilt: false,
237
+ detail: `git checkout ${source.branch} failed: ${checkout.detail ?? 'unknown'}`,
238
+ logs: chunks.join(''),
239
+ };
240
+ }
241
+ const rebase = await runLogged(host, `git -C ${directory} rebase ${remote}/${source.branch} || ` +
242
+ `{ git -C ${directory} rebase --abort; exit 1; }`, emit);
243
+ if (!rebase.ok) {
244
+ return {
245
+ ok: false,
246
+ rebuilt: false,
247
+ detail: `git rebase ${remote}/${source.branch} failed: ${rebase.detail ?? 'unknown'}`,
248
+ logs: chunks.join(''),
249
+ };
250
+ }
251
+ const clean = await runLogged(host, `git -C ${directory} clean -fxd`, emit);
252
+ if (!clean.ok) {
253
+ return {
254
+ ok: false,
255
+ rebuilt: false,
256
+ detail: `git clean -fxd failed: ${clean.detail ?? 'unknown'}`,
257
+ logs: chunks.join(''),
258
+ };
259
+ }
260
+ rebuilt = true;
261
+ }
262
+ else {
263
+ emit(`>> ${directory} is up to date with ${remote}/${source.branch}; the rebuild is skipped.\n`);
264
+ }
265
+ }
266
+ if (rebuilt) {
267
+ // Run the engine's build steps, capturing the command and its output.
268
+ for (const step of source.buildSteps) {
269
+ const command = step.replaceAll('{dir}', directory);
270
+ const outcome = await runLogged(host, command, emit);
271
+ if (!outcome.ok) {
272
+ return {
273
+ ok: false,
274
+ rebuilt: true,
275
+ detail: `build failed: ${outcome.detail ?? 'unknown'}`,
276
+ logs: chunks.join(''),
277
+ };
278
+ }
279
+ }
280
+ // A rebuild that did not install the binary (no make install) leaves it
281
+ // out of the path. Check, and add a symlink when the trust gate allows.
282
+ const binary = request.binary;
283
+ if (binary !== undefined) {
284
+ const which = await host.run(`command -v ${binary}`);
285
+ if (!which.ok) {
286
+ const link = `sudo ln -sf ${directory}/build/${binary} /usr/local/bin/${binary}`;
287
+ if (request.allowRunDeps === true) {
288
+ const result = await runLogged(host, link, emit);
289
+ if (!result.ok) {
290
+ emit(`!! ${link} failed: ${result.detail ?? 'unknown'}\n`);
291
+ }
292
+ }
293
+ else {
294
+ emit(`!! ${binary} is not on the path; run ${link} (the provider command must be on the path to launch)\n`);
295
+ }
296
+ }
297
+ }
298
+ }
299
+ return { ok: true, rebuilt, logs: chunks.join('') };
300
+ }
301
+ /**
302
+ * buildEngineRepo runs the engine's build steps in the checkout, streaming
303
+ * each step's output through log. An engine with no build steps (the compose
304
+ * engines) passes through with a note; a failing step stops the build with
305
+ * the step's detail.
306
+ */
307
+ export async function buildEngineRepo(host, engineId, directory, log) {
308
+ const source = engineSource(engineId);
309
+ if (source === undefined) {
310
+ return { ok: false, detail: `no engine source is registered for "${engineId}"` };
311
+ }
312
+ if (source.buildSteps.length === 0) {
313
+ log(`no local build steps for ${engineId}\n`);
314
+ return { ok: true };
315
+ }
316
+ for (const step of source.buildSteps) {
317
+ const command = step.replaceAll('{dir}', directory);
318
+ const outcome = await runLogged(host, command, log);
319
+ if (!outcome.ok) {
320
+ return { ok: false, detail: `build failed: ${outcome.detail ?? 'unknown'}` };
321
+ }
322
+ }
323
+ return { ok: true };
324
+ }
325
+ //# sourceMappingURL=acquire.js.map
@@ -8,7 +8,8 @@
8
8
  * @module dsh-plugin-local-infer/providers/adapters
9
9
  */
10
10
  import { EngineAdapter } from '../../core/lib/index.js';
11
- import type { Config, DebugLogger, DeployResult, EngineLaunchSignature, EnginePlatformConfig, HealthCheckOptions, HealthResult, HardwareProfile, LaunchPlan, LifecycleHost, ParameterSpec } from '../../core/lib/index.js';
11
+ import type { Config, DebugLogger, DeployOptions, DeployResult, EngineLaunchSignature, EnginePlatformConfig, HealthCheckOptions, HealthResult, HardwareProfile, LaunchPlan, LaunchProgress, LifecycleHost, ParameterSpec } from '../../core/lib/index.js';
12
+ export { INFERNIX_MODEL_ARTIFACTS } from './infernix.ts';
12
13
  /**
13
14
  * NInferModelArtifact is the per-model deploy data: the `.ninfer` artifact
14
15
  * file, the Hugging Face repo the model download pulls it from, and the
@@ -111,7 +112,7 @@ export declare class NInferAdapter extends EngineAdapter {
111
112
  };
112
113
  launchSignature(config?: Config): EngineLaunchSignature;
113
114
  renderLaunch(config: Config, _hw: HardwareProfile): LaunchPlan;
114
- deploy(plan: LaunchPlan, platform?: EnginePlatformConfig): Promise<DeployResult>;
115
+ deploy(plan: LaunchPlan, platform?: EnginePlatformConfig, onProgress?: LaunchProgress, options?: DeployOptions): Promise<DeployResult>;
115
116
  healthCheck(plan: LaunchPlan, options?: HealthCheckOptions): Promise<HealthResult>;
116
117
  stop(plan: LaunchPlan): Promise<void>;
117
118
  private buildMode;
@@ -150,7 +151,7 @@ export declare class HyperQwenAdapter extends EngineAdapter {
150
151
  };
151
152
  launchSignature(config?: Config): EngineLaunchSignature;
152
153
  renderLaunch(config: Config, _hw: HardwareProfile): LaunchPlan;
153
- deploy(plan: LaunchPlan): Promise<DeployResult>;
154
+ deploy(plan: LaunchPlan, platform?: EnginePlatformConfig, onProgress?: LaunchProgress, _options?: DeployOptions): Promise<DeployResult>;
154
155
  healthCheck(plan: LaunchPlan, options?: HealthCheckOptions): Promise<HealthResult>;
155
156
  stop(plan: LaunchPlan): Promise<void>;
156
157
  private port;
@@ -186,7 +187,7 @@ export declare class LlamaCppAdapter extends EngineAdapter {
186
187
  };
187
188
  launchSignature(config?: Config): EngineLaunchSignature;
188
189
  renderLaunch(config: Config, _hw: HardwareProfile): LaunchPlan;
189
- deploy(plan: LaunchPlan, platform?: EnginePlatformConfig): Promise<DeployResult>;
190
+ deploy(plan: LaunchPlan, platform?: EnginePlatformConfig, onProgress?: LaunchProgress, options?: DeployOptions): Promise<DeployResult>;
190
191
  healthCheck(plan: LaunchPlan, options?: HealthCheckOptions): Promise<HealthResult>;
191
192
  stop(plan: LaunchPlan): Promise<void>;
192
193
  private buildMode;
@@ -223,10 +224,46 @@ export declare class StrataAdapter extends EngineAdapter {
223
224
  };
224
225
  launchSignature(config?: Config): EngineLaunchSignature;
225
226
  renderLaunch(config: Config, _hw: HardwareProfile): LaunchPlan;
226
- deploy(plan: LaunchPlan): Promise<DeployResult>;
227
+ deploy(plan: LaunchPlan, _platform?: EnginePlatformConfig, onProgress?: LaunchProgress, _options?: DeployOptions): Promise<DeployResult>;
227
228
  healthCheck(plan: LaunchPlan, options?: HealthCheckOptions): Promise<HealthResult>;
228
229
  stop(plan: LaunchPlan): Promise<void>;
229
230
  private port;
230
231
  private planKey;
231
232
  }
233
+ /**
234
+ * InfernixAdapter is the local-only engine for Infernix, the C++/CUDA fork
235
+ * of NInfer (https://github.com/wallawalla47/infernix) tuned for Qwen models
236
+ * on a single RTX 50-series GPU. `renderLaunch` renders a local
237
+ * `infernix-serve` process from the `INFERNIX_PARAMETERS` surface (the
238
+ * Infernix-only fields, from `--ngram-volume` to the quality routes, spell
239
+ * onto the argv only for this engine); `deploy` builds the engine in the
240
+ * engine directory (the engine's own cmake preset), downloads the model
241
+ * artifact (and the n-gram volume, for the multi-file Flash-Next repo) into
242
+ * the model directory, and starts the engine; `healthCheck` polls the health
243
+ * URL; `stop` signals the tracked process. The engine ships no docker image,
244
+ * so the adapter supports the local build mode only.
245
+ */
246
+ export declare class InfernixAdapter extends EngineAdapter {
247
+ readonly id = "infernix";
248
+ readonly supports: {
249
+ docker: boolean;
250
+ local: boolean;
251
+ };
252
+ private readonly host;
253
+ private readonly children;
254
+ constructor(host?: LifecycleHost);
255
+ parameters(): readonly ParameterSpec[];
256
+ modelRef(config: Config): {
257
+ id: string;
258
+ name: string;
259
+ };
260
+ launchSignature(config?: Config): EngineLaunchSignature;
261
+ renderLaunch(config: Config, _hw: HardwareProfile): LaunchPlan;
262
+ deploy(plan: LaunchPlan, platform?: EnginePlatformConfig, onProgress?: LaunchProgress, options?: DeployOptions): Promise<DeployResult>;
263
+ healthCheck(plan: LaunchPlan, options?: HealthCheckOptions): Promise<HealthResult>;
264
+ stop(plan: LaunchPlan): Promise<void>;
265
+ private port;
266
+ private planKey;
267
+ private downloadModel;
268
+ }
232
269
  //# sourceMappingURL=adapters.d.ts.map