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,127 @@
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
+ import type { LifecycleHost } from '../../core/lib/index.js';
12
+ /**
13
+ * EngineSource is the per-engine acquisition data: the upstream git repo the
14
+ * clone step uses when no fork is provided, the branch the fetch/rebase step
15
+ * rebases onto, and the build steps run in the checkout after a clone or a
16
+ * rebase. A build step is a shell command line; the `{dir}` placeholder is
17
+ * replaced with the checkout directory before the step runs.
18
+ */
19
+ export interface EngineSource {
20
+ repo: string;
21
+ branch: string;
22
+ buildSteps: readonly string[];
23
+ }
24
+ /**
25
+ * The remote name a user-provided fork is added under, so the checkout's own
26
+ * origin (the upstream it was cloned from) survives the fork override.
27
+ */
28
+ export declare const FORK_REMOTE = "dsh-fork";
29
+ /**
30
+ * The per-engine git sources, keyed by engine id. NInfer and Infernix build
31
+ * through cmake (Infernix through its own preset); llama.cpp builds through
32
+ * the same cmake shape as NInfer; Hyper-Qwen is the compose-driven vLLM
33
+ * engine, so it carries no local build steps (the compose up pulls the
34
+ * image); Strata builds through its own setup.sh (the README's
35
+ * Build-from-source route), which the source-build mode runs in the checkout.
36
+ */
37
+ export declare const ENGINE_SOURCES: Record<string, EngineSource>;
38
+ /**
39
+ * AcquireLog receives one line (or chunk) of Get output as it lands, so the
40
+ * route can stream it to the card's Logs section.
41
+ */
42
+ export type AcquireLog = (chunk: string) => void;
43
+ /**
44
+ * EngineStepResult reports one Get step (sync or build): whether it passed,
45
+ * whether it aborted on a rule (a dirty checkout), and a detail the Logs
46
+ * section shows when it did not pass.
47
+ */
48
+ export interface EngineStepResult {
49
+ ok: boolean;
50
+ aborted?: boolean;
51
+ detail?: string;
52
+ }
53
+ /**
54
+ * engineSource reads the registered acquisition data for an engine id, or
55
+ * undefined when none is registered.
56
+ */
57
+ export declare function engineSource(engineId: string): EngineSource | undefined;
58
+ /**
59
+ * engineBuildSteps reads the build steps an engine runs in its checkout, so
60
+ * the adapter's deploy step and the Get build step share one list.
61
+ */
62
+ export declare function engineBuildSteps(engineId: string): readonly string[];
63
+ /**
64
+ * syncEngineRepo brings the engine checkout to the listed source. A missing
65
+ * checkout is a fresh clone of the fork (or the engine's upstream repo when
66
+ * no fork is provided). An existing checkout with local changes aborts, so
67
+ * the user's work is never rebased away; a clean checkout fetches the listed
68
+ * fork (added as the named fork remote when provided, origin when not) and
69
+ * rebases onto its branch. Every command echoes into log as it runs.
70
+ */
71
+ export declare function syncEngineRepo(host: LifecycleHost, request: {
72
+ engineId: string;
73
+ directory: string;
74
+ fork?: string;
75
+ }, log: AcquireLog): Promise<EngineStepResult>;
76
+ /**
77
+ * SyncRebuildRequest names the checkout the conditional rebuild prices: the
78
+ * engine, its directory, the optional fork remote, the engine binary the
79
+ * symlink step links when a rebuild leaves it out of the path, and the
80
+ * Play/Run trust gate that decides whether that symlink runs (on) or is
81
+ * written to the logs (off).
82
+ */
83
+ export interface SyncRebuildRequest {
84
+ engineId: string;
85
+ directory: string;
86
+ fork?: string;
87
+ binary?: string;
88
+ allowRunDeps?: boolean;
89
+ buildCommands?: string;
90
+ onBuildStart?: () => void;
91
+ }
92
+ /**
93
+ * SyncRebuildResult reports the conditional rebuild: whether it passed,
94
+ * whether a build actually ran (a fresh clone, or a rebase of a checkout that
95
+ * was behind), a detail for a failure, and the captured log text (the decision,
96
+ * the build command, and its output) the deploy step threads to the card's
97
+ * Logs section.
98
+ */
99
+ export interface SyncRebuildResult {
100
+ ok: boolean;
101
+ rebuilt: boolean;
102
+ detail?: string;
103
+ logs: string;
104
+ }
105
+ /**
106
+ * syncAndRebuildEngine brings the engine checkout to the listed source and
107
+ * rebuilds it only when it needs to. A missing checkout is a fresh clone of
108
+ * the fork (or the upstream repo) and always builds. An existing checkout is
109
+ * fetched and priced against the remote branch: when it is behind and clean,
110
+ * the primary branch is checked out, rebased onto the remote branch, and the
111
+ * working tree is cleaned (git clean -fxd) before the build; when it is not
112
+ * behind, or it carries local changes, the build is skipped so a launch does
113
+ * not rebuild a checkout that is current. The decision, the build command,
114
+ * and its output stream through log and are returned as the result's logs.
115
+ * After a rebuild, the engine binary is checked against the path; when a build
116
+ * that did not install it leaves it out, a symlink into /usr/local/bin is
117
+ * added (run when the trust gate is on, written to the logs when it is off).
118
+ */
119
+ export declare function syncAndRebuildEngine(host: LifecycleHost, request: SyncRebuildRequest, log: AcquireLog): Promise<SyncRebuildResult>;
120
+ /**
121
+ * buildEngineRepo runs the engine's build steps in the checkout, streaming
122
+ * each step's output through log. An engine with no build steps (the compose
123
+ * engines) passes through with a note; a failing step stops the build with
124
+ * the step's detail.
125
+ */
126
+ export declare function buildEngineRepo(host: LifecycleHost, engineId: string, directory: string, log: AcquireLog, buildCommands?: string): Promise<EngineStepResult>;
127
+ //# sourceMappingURL=acquire.d.ts.map
@@ -0,0 +1,346 @@
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 builds through its own setup.sh (the README's
22
+ * Build-from-source route), which the source-build mode runs in the checkout.
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
+ // The README's Build-from-source route: git clone the repo, then run the
51
+ // engine's own setup.sh. `--yes` takes the recommended answer to every
52
+ // question (no prompts) and `--no-start` builds and installs the engine
53
+ // without starting the server, which the adapter's launch step then does.
54
+ repo: 'https://github.com/Niko1221/Strata',
55
+ branch: 'main',
56
+ buildSteps: ['cd {dir} && ./setup.sh --yes --no-start'],
57
+ },
58
+ };
59
+ /**
60
+ * engineSource reads the registered acquisition data for an engine id, or
61
+ * undefined when none is registered.
62
+ */
63
+ export function engineSource(engineId) {
64
+ return ENGINE_SOURCES[engineId];
65
+ }
66
+ /**
67
+ * engineBuildSteps reads the build steps an engine runs in its checkout, so
68
+ * the adapter's deploy step and the Get build step share one list.
69
+ */
70
+ export function engineBuildSteps(engineId) {
71
+ return ENGINE_SOURCES[engineId]?.buildSteps ?? [];
72
+ }
73
+ /**
74
+ * runLogged runs one shell command through the host's streaming surface when
75
+ * it has one (echoing the command and streaming its output through log), and
76
+ * through plain run otherwise. The streamed copy is also collected, so a
77
+ * caller can price the command's stdout (the dirty-check, for one).
78
+ */
79
+ async function runLogged(host, command, log, options) {
80
+ let output = '';
81
+ const collect = (chunk) => {
82
+ output += chunk;
83
+ log(chunk);
84
+ };
85
+ if (options?.echo !== false) {
86
+ log(`$ ${command}\n`);
87
+ }
88
+ if (host.runStream !== undefined) {
89
+ const outcome = await host.runStream(command, collect);
90
+ return { ...outcome, output };
91
+ }
92
+ const outcome = await host.run(command);
93
+ return { ...outcome, output };
94
+ }
95
+ /**
96
+ * syncEngineRepo brings the engine checkout to the listed source. A missing
97
+ * checkout is a fresh clone of the fork (or the engine's upstream repo when
98
+ * no fork is provided). An existing checkout with local changes aborts, so
99
+ * the user's work is never rebased away; a clean checkout fetches the listed
100
+ * fork (added as the named fork remote when provided, origin when not) and
101
+ * rebases onto its branch. Every command echoes into log as it runs.
102
+ */
103
+ export async function syncEngineRepo(host, request, log) {
104
+ const source = engineSource(request.engineId);
105
+ if (source === undefined) {
106
+ return {
107
+ ok: false,
108
+ detail: `no engine source is registered for "${request.engineId}"`,
109
+ };
110
+ }
111
+ const directory = request.directory;
112
+ const fork = request.fork !== undefined && request.fork.trim() !== '' ? request.fork : undefined;
113
+ const present = await host.run(`[ -d ${directory}/.git ]`);
114
+ if (present.ok) {
115
+ const status = await runLogged(host, `git -C ${directory} status --short`, log);
116
+ if (!status.ok) {
117
+ return { ok: false, detail: `git status failed: ${status.detail ?? 'unknown'}` };
118
+ }
119
+ if (status.output.trim() !== '') {
120
+ log(`!! ${directory} has local changes. Get aborted; commit or revert them first.\n`);
121
+ return {
122
+ ok: false,
123
+ aborted: true,
124
+ detail: 'the engine repository has local changes',
125
+ };
126
+ }
127
+ if (fork !== undefined) {
128
+ // Add the fork under its own remote name; re-Get updates the URL in
129
+ // place so an edited Engine Fork field takes effect on the next run.
130
+ const add = await runLogged(host, `git -C ${directory} remote add ${FORK_REMOTE} ${fork}`, log);
131
+ if (!add.ok) {
132
+ await runLogged(host, `git -C ${directory} remote set-url ${FORK_REMOTE} ${fork}`, log);
133
+ }
134
+ }
135
+ const remote = fork !== undefined ? FORK_REMOTE : 'origin';
136
+ const fetch = await runLogged(host, `git -C ${directory} fetch ${remote}`, log);
137
+ if (!fetch.ok) {
138
+ return { ok: false, detail: `git fetch ${remote} failed: ${fetch.detail ?? 'unknown'}` };
139
+ }
140
+ const rebase = await runLogged(host, `git -C ${directory} rebase ${remote}/${source.branch} || ` +
141
+ `{ git -C ${directory} rebase --abort; exit 1; }`, log);
142
+ if (!rebase.ok) {
143
+ return {
144
+ ok: false,
145
+ detail: `git rebase ${remote}/${source.branch} failed: ${rebase.detail ?? 'unknown'}`,
146
+ };
147
+ }
148
+ return { ok: true };
149
+ }
150
+ const clone = await runLogged(host, `git clone ${fork ?? source.repo} ${directory}`, log);
151
+ if (!clone.ok) {
152
+ return { ok: false, detail: `git clone failed: ${clone.detail ?? 'unknown'}` };
153
+ }
154
+ return { ok: true };
155
+ }
156
+ /**
157
+ * syncAndRebuildEngine brings the engine checkout to the listed source and
158
+ * rebuilds it only when it needs to. A missing checkout is a fresh clone of
159
+ * the fork (or the upstream repo) and always builds. An existing checkout is
160
+ * fetched and priced against the remote branch: when it is behind and clean,
161
+ * the primary branch is checked out, rebased onto the remote branch, and the
162
+ * working tree is cleaned (git clean -fxd) before the build; when it is not
163
+ * behind, or it carries local changes, the build is skipped so a launch does
164
+ * not rebuild a checkout that is current. The decision, the build command,
165
+ * and its output stream through log and are returned as the result's logs.
166
+ * After a rebuild, the engine binary is checked against the path; when a build
167
+ * that did not install it leaves it out, a symlink into /usr/local/bin is
168
+ * added (run when the trust gate is on, written to the logs when it is off).
169
+ */
170
+ export async function syncAndRebuildEngine(host, request, log) {
171
+ const source = engineSource(request.engineId);
172
+ if (source === undefined) {
173
+ return {
174
+ ok: false,
175
+ rebuilt: false,
176
+ detail: `no engine source is registered for "${request.engineId}"`,
177
+ logs: '',
178
+ };
179
+ }
180
+ const directory = request.directory;
181
+ const fork = request.fork !== undefined && request.fork.trim() !== '' ? request.fork : undefined;
182
+ const remote = fork !== undefined ? FORK_REMOTE : 'origin';
183
+ const chunks = [];
184
+ const emit = (chunk) => {
185
+ chunks.push(chunk);
186
+ log(chunk);
187
+ };
188
+ const present = await host.run(`[ -d ${directory}/.git ]`);
189
+ let rebuilt = false;
190
+ if (!present.ok) {
191
+ // A missing checkout is a fresh clone of the fork (or the upstream repo),
192
+ // which always needs a build.
193
+ emit(`>> cloning ${fork ?? source.repo} into ${directory}\n`);
194
+ const clone = await runLogged(host, `git clone ${fork ?? source.repo} ${directory}`, emit);
195
+ if (!clone.ok) {
196
+ return {
197
+ ok: false,
198
+ rebuilt: false,
199
+ detail: `git clone failed: ${clone.detail ?? 'unknown'}`,
200
+ logs: chunks.join(''),
201
+ };
202
+ }
203
+ rebuilt = true;
204
+ }
205
+ else {
206
+ if (fork !== undefined) {
207
+ // Add the fork under its own remote name; a re-launch updates the URL in
208
+ // place so an edited fork field takes effect on the next run.
209
+ const add = await runLogged(host, `git -C ${directory} remote add ${FORK_REMOTE} ${fork}`, emit, { echo: false });
210
+ if (!add.ok) {
211
+ await runLogged(host, `git -C ${directory} remote set-url ${FORK_REMOTE} ${fork}`, emit, { echo: false });
212
+ }
213
+ }
214
+ const status = await runLogged(host, `git -C ${directory} status --short`, emit, { echo: false });
215
+ if (!status.ok) {
216
+ return {
217
+ ok: false,
218
+ rebuilt: false,
219
+ detail: `git status failed: ${status.detail ?? 'unknown'}`,
220
+ logs: chunks.join(''),
221
+ };
222
+ }
223
+ if (status.output.trim() !== '') {
224
+ emit(`!! ${directory} has local changes; the rebase and rebuild are skipped.\n`);
225
+ return { ok: true, rebuilt: false, logs: chunks.join('') };
226
+ }
227
+ // The fetch is best-effort: an offline host prices the checkout it has.
228
+ await runLogged(host, `git -C ${directory} fetch ${remote} || true`, emit, { echo: false });
229
+ // Price whether the current branch is behind the remote branch. A fetch
230
+ // that failed (offline) or a missing remote branch reads as not-behind, so
231
+ // the checkout builds as-is.
232
+ const count = await runLogged(host, `git -C ${directory} rev-list --count HEAD..${remote}/${source.branch} 2>/dev/null || echo 0`, emit, { echo: false });
233
+ const behind = (count.output.trim() || '0') !== '0';
234
+ if (behind) {
235
+ emit(`>> ${directory} is behind ${remote}/${source.branch}; rebasing and rebuilding.\n`);
236
+ const checkout = await runLogged(host, `git -C ${directory} checkout ${source.branch}`, emit);
237
+ if (!checkout.ok) {
238
+ return {
239
+ ok: false,
240
+ rebuilt: false,
241
+ detail: `git checkout ${source.branch} failed: ${checkout.detail ?? 'unknown'}`,
242
+ logs: chunks.join(''),
243
+ };
244
+ }
245
+ const rebase = await runLogged(host, `git -C ${directory} rebase ${remote}/${source.branch} || ` +
246
+ `{ git -C ${directory} rebase --abort; exit 1; }`, emit);
247
+ if (!rebase.ok) {
248
+ return {
249
+ ok: false,
250
+ rebuilt: false,
251
+ detail: `git rebase ${remote}/${source.branch} failed: ${rebase.detail ?? 'unknown'}`,
252
+ logs: chunks.join(''),
253
+ };
254
+ }
255
+ const clean = await runLogged(host, `git -C ${directory} clean -fxd`, emit);
256
+ if (!clean.ok) {
257
+ return {
258
+ ok: false,
259
+ rebuilt: false,
260
+ detail: `git clean -fxd failed: ${clean.detail ?? 'unknown'}`,
261
+ logs: chunks.join(''),
262
+ };
263
+ }
264
+ rebuilt = true;
265
+ }
266
+ else {
267
+ emit(`>> ${directory} is up to date with ${remote}/${source.branch}; the rebuild is skipped.\n`);
268
+ }
269
+ }
270
+ if (rebuilt) {
271
+ // The user's local build command (the card's Build commands field)
272
+ // overrides the engine's default build steps: it runs in the checkout, so
273
+ // it is wrapped with a cd into the directory. Absent falls back to the
274
+ // engine's acquisition build steps.
275
+ const buildCommands = request.buildCommands !== undefined && request.buildCommands.trim() !== ''
276
+ ? request.buildCommands
277
+ : undefined;
278
+ const steps = buildCommands !== undefined
279
+ ? [`cd ${directory} && ${buildCommands}`]
280
+ : source.buildSteps;
281
+ // Report the build phase before the first step runs, so the card's button
282
+ // reads "Building..." while the build is in flight, not after it completes.
283
+ request.onBuildStart?.();
284
+ for (const step of steps) {
285
+ const command = step.replaceAll('{dir}', directory);
286
+ const outcome = await runLogged(host, command, emit);
287
+ if (!outcome.ok) {
288
+ return {
289
+ ok: false,
290
+ rebuilt: true,
291
+ detail: `build failed: ${outcome.detail ?? 'unknown'}`,
292
+ logs: chunks.join(''),
293
+ };
294
+ }
295
+ }
296
+ // A rebuild that did not install the binary (no make install) leaves it
297
+ // out of the path. Check, and add a symlink when the trust gate allows.
298
+ const binary = request.binary;
299
+ if (binary !== undefined) {
300
+ const which = await host.run(`command -v ${binary}`);
301
+ if (!which.ok) {
302
+ const link = `sudo ln -sf ${directory}/build/${binary} /usr/local/bin/${binary}`;
303
+ if (request.allowRunDeps === true) {
304
+ const result = await runLogged(host, link, emit);
305
+ if (!result.ok) {
306
+ emit(`!! ${link} failed: ${result.detail ?? 'unknown'}\n`);
307
+ }
308
+ }
309
+ else {
310
+ emit(`!! ${binary} is not on the path; run ${link} (the provider command must be on the path to launch)\n`);
311
+ }
312
+ }
313
+ }
314
+ }
315
+ return { ok: true, rebuilt, logs: chunks.join('') };
316
+ }
317
+ /**
318
+ * buildEngineRepo runs the engine's build steps in the checkout, streaming
319
+ * each step's output through log. An engine with no build steps (the compose
320
+ * engines) passes through with a note; a failing step stops the build with
321
+ * the step's detail.
322
+ */
323
+ export async function buildEngineRepo(host, engineId, directory, log, buildCommands) {
324
+ const source = engineSource(engineId);
325
+ if (source === undefined) {
326
+ return { ok: false, detail: `no engine source is registered for "${engineId}"` };
327
+ }
328
+ // The user's local build command (the card's Build commands field) overrides
329
+ // the engine's default build steps: it runs in the checkout, so it is wrapped
330
+ // with a cd into the directory. Absent falls back to the engine's steps.
331
+ const commands = buildCommands !== undefined && buildCommands.trim() !== '' ? buildCommands : undefined;
332
+ const steps = commands !== undefined ? [`cd ${directory} && ${commands}`] : source.buildSteps;
333
+ if (steps.length === 0) {
334
+ log(`no local build steps for ${engineId}\n`);
335
+ return { ok: true };
336
+ }
337
+ for (const step of steps) {
338
+ const command = step.replaceAll('{dir}', directory);
339
+ const outcome = await runLogged(host, command, log);
340
+ if (!outcome.ok) {
341
+ return { ok: false, detail: `build failed: ${outcome.detail ?? 'unknown'}` };
342
+ }
343
+ }
344
+ return { ok: true };
345
+ }
346
+ //# sourceMappingURL=acquire.js.map
@@ -1,32 +1,13 @@
1
1
  /**
2
- * The concrete engine adapters: NInfer (docker and local) and Hyper-Qwen
3
- * (compose). Each composes the provider `ParameterSpec` data into a
4
- * `LaunchPlan` and drives its deploy, health-check, and stop steps through an
5
- * injectable `LifecycleHost`, so the tests stub the host and never run a real
6
- * build, docker, or network in CI. The node implementation
7
- * (`nodeLifecycleHost`) is what the service wires in on a live host.
2
+ * The shared engine-adapter helpers: the spec-to-argv and spec-to-env
3
+ * translators the concrete adapters (the `engines/*Adapter.ts` files) compose
4
+ * into a `LaunchPlan`, the card's Custom Arguments translators, the
5
+ * `LifecycleHost` the deploy, health-check, and stop steps drive, and the
6
+ * health poll. The node implementation (`nodeLifecycleHost`) is what the
7
+ * service wires in on a live host.
8
8
  * @module dsh-plugin-local-infer/providers/adapters
9
9
  */
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';
12
- /**
13
- * NInferModelArtifact is the per-model deploy data: the `.ninfer` artifact
14
- * file, the Hugging Face repo the model download pulls it from, and the
15
- * `--model-id` alias the server advertises. The data is the surface; adding a
16
- * model is a data edit here, never an adapter rewire.
17
- */
18
- export interface NInferModelArtifact {
19
- file: string;
20
- repo: string;
21
- modelId: string;
22
- }
23
- /**
24
- * The NInfer model artifacts, keyed by the model registry id. The 27B entry
25
- * is the default the DEFAULT_MODELS list names; the 35B-A3B entries are the
26
- * Qwen3.8 measured artifact (`qwen3-35b`) and the Qwen3.6 default
27
- * (`qwen36-35b`).
28
- */
29
- export declare const NINFER_MODEL_ARTIFACTS: Record<string, NInferModelArtifact>;
10
+ import type { Config, DebugLogger, HealthCheckOptions, HealthResult, LaunchPlan, LifecycleHost, ParameterSpec, ProcessHandle } from '../../core/lib/index.js';
30
11
  /**
31
12
  * flagsFromSpecs turns a spec surface and a config into the CLI flag list: each
32
13
  * spec's `toArgv` runs over the config value (falling back to the spec
@@ -75,6 +56,14 @@ export declare function createNodeLifecycleHost(debug?: DebugLogger): LifecycleH
75
56
  * in debug mode.
76
57
  */
77
58
  export declare const nodeLifecycleHost: LifecycleHost;
59
+ /**
60
+ * trackChild stores a spawned handle in the adapter's children map and ties
61
+ * the entry's lifetime to the child's: when the child ends (a crash, a pkill,
62
+ * or the adapter's own stop kill) the entry is dropped, so a later stop never
63
+ * signals a pid a recycled process has taken over. A handle whose host cannot
64
+ * watch (onExit absent) stays tracked until stop removes it.
65
+ */
66
+ export declare function trackChild(children: Map<string, ProcessHandle>, key: string, handle: ProcessHandle): void;
78
67
  /**
79
68
  * pollHealth polls a plan's health URL until a 200 or the attempt cap, the
80
69
  * same loop the qwen-ninfer launcher runs. A poll interval of 0 skips the
@@ -82,151 +71,9 @@ export declare const nodeLifecycleHost: LifecycleHost;
82
71
  */
83
72
  export declare function pollHealth(host: LifecycleHost, plan: LaunchPlan, options?: HealthCheckOptions): Promise<HealthResult>;
84
73
  /**
85
- * The NInfer docker image the adapter pulls and runs. The engineFork platform
86
- * config overrides the git source for a local build; the image stays the
87
- * upstream ghcr artifact.
88
- */
89
- export declare const NINFER_DOCKER_IMAGE = "ghcr.io/iamwavecut/ninfer-all";
90
- /**
91
- * NInferAdapter is the canonical both-capable engine. `renderLaunch` renders a
92
- * local `ninfer-serve` process or a `docker run` of the ghcr image from the
93
- * same spec surface; `deploy` builds the engine in the engine directory (or
94
- * pulls the image), downloads the model into the model directory, and starts
95
- * the engine; `healthCheck` polls the health URL; `stop` signals the tracked
96
- * process and stops the named container.
97
- */
98
- export declare class NInferAdapter extends EngineAdapter {
99
- readonly id = "ninfer";
100
- readonly supports: {
101
- docker: boolean;
102
- local: boolean;
103
- };
104
- private readonly host;
105
- private readonly children;
106
- constructor(host?: LifecycleHost);
107
- parameters(): readonly ParameterSpec[];
108
- modelRef(config: Config): {
109
- id: string;
110
- name: string;
111
- };
112
- launchSignature(config?: Config): EngineLaunchSignature;
113
- renderLaunch(config: Config, _hw: HardwareProfile): LaunchPlan;
114
- deploy(plan: LaunchPlan, platform?: EnginePlatformConfig): Promise<DeployResult>;
115
- healthCheck(plan: LaunchPlan, options?: HealthCheckOptions): Promise<HealthResult>;
116
- stop(plan: LaunchPlan): Promise<void>;
117
- private buildMode;
118
- private port;
119
- private planKey;
120
- private downloadModel;
121
- /**
122
- * resolveChatTemplate rewrites a --chat-template flag that carries an HF repo
123
- * reference into the local file the engine reads. A value with a slash that
124
- * is not an absolute path (the combo control's known choices) is a repo the
125
- * deploy step downloads into the model directory; an already-local path is
126
- * left as-is. The download is best-effort: a template that is already on
127
- * disk is used without a re-fetch, and a missing hf CLI leaves the flag
128
- * untouched so the engine reports the missing template itself.
129
- */
130
- private resolveChatTemplate;
131
- }
132
- /**
133
- * HyperQwenAdapter is the docker-compose engine. `renderLaunch` renders a
134
- * compose plan (the compose file, the profile, and the `.env` the spec surface
135
- * maps to); `deploy` runs `docker compose up`; `healthCheck` polls the health
136
- * URL; `stop` runs `docker compose down`.
137
- */
138
- export declare class HyperQwenAdapter extends EngineAdapter {
139
- readonly id = "hyperqwen";
140
- readonly supports: {
141
- docker: boolean;
142
- local: boolean;
143
- };
144
- private readonly host;
145
- constructor(host?: LifecycleHost);
146
- parameters(): readonly ParameterSpec[];
147
- modelRef(_config: Config): {
148
- id: string;
149
- name: string;
150
- };
151
- launchSignature(config?: Config): EngineLaunchSignature;
152
- renderLaunch(config: Config, _hw: HardwareProfile): LaunchPlan;
153
- deploy(plan: LaunchPlan): Promise<DeployResult>;
154
- healthCheck(plan: LaunchPlan, options?: HealthCheckOptions): Promise<HealthResult>;
155
- stop(plan: LaunchPlan): Promise<void>;
156
- private port;
157
- private profile;
158
- }
159
- /**
160
- * The llama.cpp docker image the adapter pulls and runs. The engineFork
161
- * platform config overrides the git source for a local build; the image stays
162
- * the upstream ggml artifact.
163
- */
164
- export declare const LLAMACPP_DOCKER_IMAGE = "ghcr.io/ggml-org/llama.cpp";
165
- /**
166
- * LlamaCppAdapter is the llama.cpp engine, both-capable like NInfer.
167
- * `renderLaunch` renders a local `llama-server` process or a `docker run` of the
168
- * ggml image from the same `LLAMACPP_PARAMETERS` surface; `deploy` builds the
169
- * engine in the engine directory (or pulls the image), downloads the GGUF model
170
- * into the model directory, and starts the engine; `healthCheck` polls the health
171
- * URL; `stop` signals the tracked process and stops the named container.
172
- */
173
- export declare class LlamaCppAdapter extends EngineAdapter {
174
- readonly id = "llamacpp";
175
- readonly supports: {
176
- docker: boolean;
177
- local: boolean;
178
- };
179
- private readonly host;
180
- private readonly children;
181
- constructor(host?: LifecycleHost);
182
- parameters(): readonly ParameterSpec[];
183
- modelRef(config: Config): {
184
- id: string;
185
- name: string;
186
- };
187
- launchSignature(config?: Config): EngineLaunchSignature;
188
- renderLaunch(config: Config, _hw: HardwareProfile): LaunchPlan;
189
- deploy(plan: LaunchPlan, platform?: EnginePlatformConfig): Promise<DeployResult>;
190
- healthCheck(plan: LaunchPlan, options?: HealthCheckOptions): Promise<HealthResult>;
191
- stop(plan: LaunchPlan): Promise<void>;
192
- private buildMode;
193
- private port;
194
- private planKey;
195
- private downloadModel;
196
- }
197
- /**
198
- * The Strata docker image the adapter pulls and runs. Strata is the
199
- * placeholder engine: the image and the parameter data land when the engine
200
- * is available, and the adapter renders a bare docker plan until then.
201
- */
202
- export declare const STRATA_DOCKER_IMAGE = "ghcr.io/strata/strata";
203
- /**
204
- * StrataAdapter is the placeholder docker-only engine. `renderLaunch` renders
205
- * a `docker run` of the Strata image (no spec surface yet, so no flags);
206
- * `deploy` pulls the image and starts the container; `healthCheck` polls the
207
- * health URL; `stop` stops the named container. The parameter surface and the
208
- * per-model data land with the engine, as data edits, never an adapter rewire.
74
+ * delay waits `ms` milliseconds. The health polls use it between attempts; a
75
+ * poll interval of 0 skips the wait, which the tests use to run the loop
76
+ * without a real sleep.
209
77
  */
210
- export declare class StrataAdapter extends EngineAdapter {
211
- readonly id = "strata";
212
- readonly supports: {
213
- docker: boolean;
214
- local: boolean;
215
- };
216
- private readonly host;
217
- private readonly children;
218
- constructor(host?: LifecycleHost);
219
- parameters(): readonly ParameterSpec[];
220
- modelRef(_config: Config): {
221
- id: string;
222
- name: string;
223
- };
224
- launchSignature(config?: Config): EngineLaunchSignature;
225
- renderLaunch(config: Config, _hw: HardwareProfile): LaunchPlan;
226
- deploy(plan: LaunchPlan): Promise<DeployResult>;
227
- healthCheck(plan: LaunchPlan, options?: HealthCheckOptions): Promise<HealthResult>;
228
- stop(plan: LaunchPlan): Promise<void>;
229
- private port;
230
- private planKey;
231
- }
78
+ export declare function delay(ms: number): Promise<void>;
232
79
  //# sourceMappingURL=adapters.d.ts.map