dsh-plugin-local-infer 0.1.2 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +32 -6
- package/cordis.patch.yml +7 -0
- package/docs/zh/README.md +24 -4
- package/dsh/client.js +4200 -4049
- package/package.json +33 -11
- package/packages/core/lib/adapter.d.ts +177 -11
- package/packages/core/lib/adapter.js +74 -1
- package/packages/core/lib/config.d.ts +1 -1
- package/packages/core/lib/config.js +1 -1
- package/packages/core/lib/debug.d.ts +1 -1
- package/packages/core/lib/debug.js +1 -1
- package/packages/core/lib/defaults.d.ts +1 -1
- package/packages/core/lib/defaults.js +1 -1
- package/packages/core/lib/deps.d.ts +48 -20
- package/packages/core/lib/deps.js +24 -8
- package/packages/core/lib/estimator.d.ts +1 -1
- package/packages/core/lib/estimator.js +1 -1
- package/packages/core/lib/guideline.d.ts +1 -1
- package/packages/core/lib/guideline.js +1 -1
- package/packages/core/lib/hardware.d.ts +1 -1
- package/packages/core/lib/hardware.js +1 -1
- package/packages/core/lib/hf-cache.d.ts +70 -0
- package/packages/core/lib/hf-cache.js +83 -0
- package/packages/core/lib/index.d.ts +16 -9
- package/packages/core/lib/index.js +10 -6
- package/packages/core/lib/infer-cache.d.ts +39 -0
- package/packages/core/lib/infer-cache.js +53 -0
- package/packages/core/lib/launch-command.d.ts +8 -4
- package/packages/core/lib/launch-command.js +13 -6
- package/packages/core/lib/launch-record.d.ts +1 -1
- package/packages/core/lib/launch-record.js +1 -1
- package/packages/core/lib/logs.d.ts +8 -1
- package/packages/core/lib/logs.js +12 -4
- package/packages/core/lib/models.d.ts +120 -14
- package/packages/core/lib/models.js +209 -17
- package/packages/core/lib/parameter.d.ts +31 -3
- package/packages/core/lib/parameter.js +1 -1
- package/packages/core/lib/progress.d.ts +54 -0
- package/packages/core/lib/progress.js +120 -0
- package/packages/core/lib/registrar.d.ts +25 -14
- package/packages/core/lib/registrar.js +30 -10
- package/packages/core/lib/reserve.d.ts +11 -3
- package/packages/core/lib/reserve.js +6 -4
- package/packages/core/lib/status.d.ts +12 -6
- package/packages/core/lib/status.js +21 -10
- package/packages/core/lib/sudo.d.ts +80 -0
- package/packages/core/lib/sudo.js +78 -0
- package/packages/core/package.json +1 -1
- package/packages/orchestrator/lib/config.d.ts +63 -0
- package/packages/orchestrator/lib/config.js +111 -0
- package/packages/orchestrator/lib/index.d.ts +51 -0
- package/packages/orchestrator/lib/index.js +73 -0
- package/packages/orchestrator/lib/route.d.ts +60 -0
- package/packages/orchestrator/lib/route.js +159 -0
- package/packages/orchestrator/locale/en.json +6 -0
- package/packages/orchestrator/package.json +39 -0
- package/packages/orchestrator/prompt.md +87 -0
- package/packages/providers/lib/acquire.d.ts +95 -15
- package/packages/providers/lib/acquire.js +239 -40
- package/packages/providers/lib/adapters.d.ts +155 -221
- package/packages/providers/lib/adapters.js +589 -935
- package/packages/providers/lib/debug.d.ts +1 -1
- package/packages/providers/lib/debug.js +1 -1
- package/packages/providers/lib/deps.d.ts +31 -212
- package/packages/providers/lib/deps.js +71 -205
- package/packages/providers/lib/engines/basalt/basalt.d.ts +130 -0
- package/packages/providers/lib/engines/basalt/basalt.js +304 -0
- package/packages/providers/lib/engines/basalt/basaltAdapter.d.ts +85 -0
- package/packages/providers/lib/engines/basalt/basaltAdapter.js +439 -0
- package/packages/providers/lib/engines/basalt/basaltLinux.d.ts +23 -0
- package/packages/providers/lib/engines/basalt/basaltLinux.js +47 -0
- package/packages/providers/lib/engines/basalt/basaltMacOS.d.ts +17 -0
- package/packages/providers/lib/engines/basalt/basaltMacOS.js +19 -0
- package/packages/providers/lib/engines/basalt/basaltWindows.d.ts +24 -0
- package/packages/providers/lib/engines/basalt/basaltWindows.js +49 -0
- package/packages/providers/lib/engines/engineOs.d.ts +24 -0
- package/packages/providers/lib/engines/engineOs.js +17 -0
- package/packages/providers/lib/{hyperqwen.d.ts → engines/hyperqwen/hyperqwen.d.ts} +2 -2
- package/packages/providers/lib/{hyperqwen.js → engines/hyperqwen/hyperqwen.js} +14 -2
- package/packages/providers/lib/engines/hyperqwen/hyperqwenAdapter.d.ts +38 -0
- package/packages/providers/lib/engines/hyperqwen/hyperqwenAdapter.js +115 -0
- package/packages/providers/lib/engines/hyperqwen/hyperqwenArch.d.ts +9 -0
- package/packages/providers/lib/engines/hyperqwen/hyperqwenArch.js +9 -0
- package/packages/providers/lib/engines/hyperqwen/hyperqwenDebian.d.ts +9 -0
- package/packages/providers/lib/engines/hyperqwen/hyperqwenDebian.js +9 -0
- package/packages/providers/lib/engines/hyperqwen/hyperqwenFedora.d.ts +9 -0
- package/packages/providers/lib/engines/hyperqwen/hyperqwenFedora.js +9 -0
- package/packages/providers/lib/engines/hyperqwen/hyperqwenLinux.d.ts +10 -0
- package/packages/providers/lib/engines/hyperqwen/hyperqwenLinux.js +14 -0
- package/packages/providers/lib/engines/hyperqwen/hyperqwenMacOS.d.ts +9 -0
- package/packages/providers/lib/engines/hyperqwen/hyperqwenMacOS.js +9 -0
- package/packages/providers/lib/engines/hyperqwen/hyperqwenWindows.d.ts +9 -0
- package/packages/providers/lib/engines/hyperqwen/hyperqwenWindows.js +9 -0
- package/packages/providers/lib/{infernix.d.ts → engines/infernix/infernix.d.ts} +7 -4
- package/packages/providers/lib/{infernix.js → engines/infernix/infernix.js} +53 -8
- package/packages/providers/lib/engines/infernix/infernixAdapter.d.ts +52 -0
- package/packages/providers/lib/engines/infernix/infernixAdapter.js +235 -0
- package/packages/providers/lib/engines/infernix/infernixArch.d.ts +9 -0
- package/packages/providers/lib/engines/infernix/infernixArch.js +9 -0
- package/packages/providers/lib/engines/infernix/infernixDebian.d.ts +9 -0
- package/packages/providers/lib/engines/infernix/infernixDebian.js +9 -0
- package/packages/providers/lib/engines/infernix/infernixFedora.d.ts +9 -0
- package/packages/providers/lib/engines/infernix/infernixFedora.js +9 -0
- package/packages/providers/lib/engines/infernix/infernixLinux.d.ts +17 -0
- package/packages/providers/lib/engines/infernix/infernixLinux.js +35 -0
- package/packages/providers/lib/engines/infernix/infernixMacOS.d.ts +16 -0
- package/packages/providers/lib/engines/infernix/infernixMacOS.js +18 -0
- package/packages/providers/lib/engines/infernix/infernixWindows.d.ts +16 -0
- package/packages/providers/lib/engines/infernix/infernixWindows.js +34 -0
- package/packages/providers/lib/{llamacpp.d.ts → engines/llamacpp/llamacpp.d.ts} +2 -2
- package/packages/providers/lib/{llamacpp.js → engines/llamacpp/llamacpp.js} +12 -2
- package/packages/providers/lib/engines/llamacpp/llamacppAdapter.d.ts +50 -0
- package/packages/providers/lib/engines/llamacpp/llamacppAdapter.js +274 -0
- package/packages/providers/lib/engines/llamacpp/llamacppArch.d.ts +9 -0
- package/packages/providers/lib/engines/llamacpp/llamacppArch.js +9 -0
- package/packages/providers/lib/engines/llamacpp/llamacppDebian.d.ts +9 -0
- package/packages/providers/lib/engines/llamacpp/llamacppDebian.js +9 -0
- package/packages/providers/lib/engines/llamacpp/llamacppFedora.d.ts +9 -0
- package/packages/providers/lib/engines/llamacpp/llamacppFedora.js +9 -0
- package/packages/providers/lib/engines/llamacpp/llamacppLinux.d.ts +17 -0
- package/packages/providers/lib/engines/llamacpp/llamacppLinux.js +28 -0
- package/packages/providers/lib/engines/llamacpp/llamacppMacOS.d.ts +15 -0
- package/packages/providers/lib/engines/llamacpp/llamacppMacOS.js +17 -0
- package/packages/providers/lib/engines/llamacpp/llamacppWindows.d.ts +15 -0
- package/packages/providers/lib/engines/llamacpp/llamacppWindows.js +26 -0
- package/packages/providers/lib/{ninfer.d.ts → engines/ninfer/ninfer.d.ts} +2 -2
- package/packages/providers/lib/{ninfer.js → engines/ninfer/ninfer.js} +25 -3
- package/packages/providers/lib/engines/ninfer/ninferAdapter.d.ts +81 -0
- package/packages/providers/lib/engines/ninfer/ninferAdapter.js +392 -0
- package/packages/providers/lib/engines/ninfer/ninferArch.d.ts +9 -0
- package/packages/providers/lib/engines/ninfer/ninferArch.js +9 -0
- package/packages/providers/lib/engines/ninfer/ninferDebian.d.ts +9 -0
- package/packages/providers/lib/engines/ninfer/ninferDebian.js +9 -0
- package/packages/providers/lib/engines/ninfer/ninferFedora.d.ts +9 -0
- package/packages/providers/lib/engines/ninfer/ninferFedora.js +9 -0
- package/packages/providers/lib/engines/ninfer/ninferLinux.d.ts +17 -0
- package/packages/providers/lib/engines/ninfer/ninferLinux.js +37 -0
- package/packages/providers/lib/engines/ninfer/ninferMacOS.d.ts +15 -0
- package/packages/providers/lib/engines/ninfer/ninferMacOS.js +17 -0
- package/packages/providers/lib/engines/ninfer/ninferWindows.d.ts +29 -0
- package/packages/providers/lib/engines/ninfer/ninferWindows.js +59 -0
- package/packages/providers/lib/engines/strata/strata.d.ts +1 -0
- package/packages/providers/lib/engines/strata/strata.js +264 -0
- package/packages/providers/lib/engines/strata/strataAdapter.d.ts +97 -0
- package/packages/providers/lib/engines/strata/strataAdapter.js +463 -0
- package/packages/providers/lib/engines/strata/strataArch.d.ts +9 -0
- package/packages/providers/lib/engines/strata/strataArch.js +9 -0
- package/packages/providers/lib/engines/strata/strataDebian.d.ts +9 -0
- package/packages/providers/lib/engines/strata/strataDebian.js +9 -0
- package/packages/providers/lib/engines/strata/strataFedora.d.ts +9 -0
- package/packages/providers/lib/engines/strata/strataFedora.js +9 -0
- package/packages/providers/lib/engines/strata/strataLinux.d.ts +21 -0
- package/packages/providers/lib/engines/strata/strataLinux.js +30 -0
- package/packages/providers/lib/engines/strata/strataMacOS.d.ts +16 -0
- package/packages/providers/lib/engines/strata/strataMacOS.js +17 -0
- package/packages/providers/lib/engines/strata/strataWindows.d.ts +27 -0
- package/packages/providers/lib/engines/strata/strataWindows.js +37 -0
- package/packages/providers/lib/engines.d.ts +8 -5
- package/packages/providers/lib/engines.js +22 -11
- package/packages/providers/lib/guidelines.d.ts +5 -4
- package/packages/providers/lib/guidelines.js +18 -5
- package/packages/providers/lib/index.d.ts +33 -24
- package/packages/providers/lib/index.js +46 -24
- package/packages/providers/lib/model-defaults.d.ts +10 -5
- package/packages/providers/lib/model-defaults.js +47 -5
- package/packages/providers/lib/platform.d.ts +8 -5
- package/packages/providers/lib/platform.js +27 -7
- package/packages/providers/lib/presets.d.ts +6 -4
- package/packages/providers/lib/presets.js +53 -5
- package/packages/providers/lib/systemDeps.d.ts +313 -0
- package/packages/providers/lib/systemDeps.js +284 -0
- package/packages/providers/lib/vram.d.ts +34 -8
- package/packages/providers/lib/vram.js +81 -8
- package/packages/providers/package.json +2 -2
- package/packages/service/lib/index.d.ts +187 -23
- package/packages/service/lib/index.js +667 -90
- package/packages/service/lib/launch-target.d.ts +19 -0
- package/packages/service/lib/launch-target.js +28 -0
- package/packages/service/lib/run-logs.d.ts +72 -0
- package/packages/service/lib/run-logs.js +125 -0
- package/packages/service/lib/types.d.ts +166 -8
- package/packages/service/lib/types.js +1 -1
- package/packages/service/package.json +2 -2
- package/packages/tool/lib/index.d.ts +1 -1
- package/packages/tool/lib/index.js +1 -1
- package/packages/tool/package.json +2 -2
- package/packages/ui/lib/config.d.ts +89 -0
- package/packages/ui/lib/config.js +251 -0
- package/packages/ui/lib/deps.d.ts +4 -4
- package/packages/ui/lib/deps.js +1 -1
- package/packages/ui/lib/form.d.ts +1 -1
- package/packages/ui/lib/form.js +1 -1
- package/packages/ui/lib/index.d.ts +7 -1
- package/packages/ui/lib/index.js +12 -2
- package/packages/ui/lib/meter.d.ts +1 -1
- package/packages/ui/lib/meter.js +1 -1
- package/packages/ui/lib/ops/engine.d.ts +216 -0
- package/packages/ui/lib/ops/engine.js +697 -0
- package/packages/ui/lib/ops/get-steps.d.ts +98 -0
- package/packages/ui/lib/ops/get-steps.js +359 -0
- package/packages/ui/lib/ops/hf.d.ts +42 -0
- package/packages/ui/lib/ops/hf.js +150 -0
- package/packages/ui/lib/ops/logs.d.ts +78 -0
- package/packages/ui/lib/ops/logs.js +119 -0
- package/packages/ui/lib/ops/model.d.ts +71 -0
- package/packages/ui/lib/ops/model.js +123 -0
- package/packages/ui/lib/request.d.ts +106 -0
- package/packages/ui/lib/request.js +80 -0
- package/packages/ui/lib/route.d.ts +106 -422
- package/packages/ui/lib/route.js +136 -1845
- package/packages/ui/lib/routes/chat-template.d.ts +1 -0
- package/packages/ui/lib/routes/chat-template.js +1 -0
- package/packages/ui/lib/routes/deps.d.ts +1 -0
- package/packages/ui/lib/routes/deps.js +1 -0
- package/packages/ui/lib/routes/download.d.ts +1 -0
- package/packages/ui/lib/routes/download.js +1 -0
- package/packages/ui/lib/routes/get.d.ts +1 -0
- package/packages/ui/lib/routes/get.js +106 -0
- package/packages/ui/lib/routes/install.d.ts +16 -0
- package/packages/ui/lib/routes/install.js +36 -0
- package/packages/ui/lib/routes/launch.d.ts +24 -0
- package/packages/ui/lib/routes/launch.js +74 -0
- package/packages/ui/lib/routes/logs.d.ts +57 -0
- package/packages/ui/lib/routes/logs.js +117 -0
- package/packages/ui/lib/routes/provider-delete.d.ts +15 -0
- package/packages/ui/lib/routes/provider-delete.js +37 -0
- package/packages/ui/lib/routes/register.d.ts +16 -0
- package/packages/ui/lib/routes/register.js +28 -0
- package/packages/ui/lib/routes/save.d.ts +18 -0
- package/packages/ui/lib/routes/save.js +159 -0
- package/packages/ui/lib/routes/stop.d.ts +19 -0
- package/packages/ui/lib/routes/stop.js +32 -0
- package/packages/ui/lib/routes/stream.d.ts +40 -0
- package/packages/ui/lib/routes/stream.js +68 -0
- package/packages/ui/lib/routes/summary.d.ts +15 -0
- package/packages/ui/lib/routes/summary.js +26 -0
- package/packages/ui/lib/routes/unregister.d.ts +14 -0
- package/packages/ui/lib/routes/unregister.js +26 -0
- package/packages/ui/lib/viewmodels/deps.d.ts +1 -0
- package/packages/ui/lib/viewmodels/deps.js +1 -0
- package/packages/ui/lib/viewmodels/depsHelpers.d.ts +1 -0
- package/packages/ui/lib/viewmodels/depsHelpers.js +94 -0
- package/packages/ui/lib/viewmodels/summary.d.ts +145 -0
- package/packages/ui/lib/viewmodels/summary.js +245 -0
- package/packages/ui/lib/viewmodels/summaryHelpers.d.ts +30 -0
- package/packages/ui/lib/viewmodels/summaryHelpers.js +42 -0
- package/packages/ui/lib/warnings.d.ts +1 -1
- package/packages/ui/lib/warnings.js +1 -1
- package/packages/ui/package.json +4 -4
- package/packages/providers/lib/strata.d.ts +0 -15
- package/packages/providers/lib/strata.js +0 -14
|
@@ -0,0 +1,697 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The engine lifecycle operations: launchEngine runs the shared Get-steps
|
|
3
|
+
* routine (idempotent) and then renders the plan the registered adapter
|
|
4
|
+
* produces and runs the service's launch entry point (deploy, then the health
|
|
5
|
+
* poll), preserving the Get-step logs in the persisted last-run logs;
|
|
6
|
+
* stopEngine stops the exact container or process the launch started (a
|
|
7
|
+
* re-adopted engine through the record's pid), and a failed stop persists a
|
|
8
|
+
* note to the last-run logs and the log file; getEngine is a thin wrapper
|
|
9
|
+
* over the shared Get-steps routine (deps, git sync, build, model download,
|
|
10
|
+
* chat-template download). The launch record (the engine pid, start time,
|
|
11
|
+
* launch arguments, and the DSH process pid that launched it) is what a DSH
|
|
12
|
+
* restart re-links a surviving engine to its config:
|
|
13
|
+
* relinkStoredProviders re-adopts surviving engines on mount and per summary
|
|
14
|
+
* read, handleEngineDeath settles the stored state the moment an engine dies
|
|
15
|
+
* outside the stop path, and persistLaunchLogs stores the captured output.
|
|
16
|
+
* The platform-config registry sync keeps the service registry in step with
|
|
17
|
+
* the stored config.
|
|
18
|
+
* @module dsh-plugin-local-infer/ui/ops/engine
|
|
19
|
+
*/
|
|
20
|
+
import { SUDO_CLI_NOTE, formatLaunchCommand, launchRecordFromPlan, } from '../../../core/lib/index.js';
|
|
21
|
+
import { findLaunchTarget, runLogKey } from '../../../service/lib/index.js';
|
|
22
|
+
import { nodeLifecycleHost } from '../../../providers/lib/index.js';
|
|
23
|
+
import { readConfig, writeStored } from "../config.js";
|
|
24
|
+
import { CANCELED_LINE, LAUNCH_RUN_SEED, STOP_RUN_SEED, capRunLogs, persistProcessLog, writeRunFile, } from "./logs.js";
|
|
25
|
+
import { resolveEngineDirectory, runGetSteps } from "./get-steps.js";
|
|
26
|
+
import { RouteError } from "../route.js";
|
|
27
|
+
/**
|
|
28
|
+
* CANCELED_LINE names the shared "Canceled" run line; it lives with the other
|
|
29
|
+
* run-line constants in ops/logs.ts and is re-exported here for the launch
|
|
30
|
+
* persisters' import surface.
|
|
31
|
+
*/
|
|
32
|
+
export { CANCELED_LINE } from "./logs.js";
|
|
33
|
+
/**
|
|
34
|
+
* GET_EVENT_PREFIX leads a Get run's event lines in the streamed and
|
|
35
|
+
* persisted run (the route's `get failed` line and getEngine's sudo CLI
|
|
36
|
+
* note), so both read alike and the `!!` marker has one home (routes/get.ts
|
|
37
|
+
* and getEngine both build it from here).
|
|
38
|
+
*/
|
|
39
|
+
export const GET_EVENT_PREFIX = '!! ';
|
|
40
|
+
/**
|
|
41
|
+
* resolveAdapter picks the registered engine adapter an engine id routes to. A
|
|
42
|
+
* card that names an engine no adapter serves gets a 404, not a blank panel.
|
|
43
|
+
*/
|
|
44
|
+
export function resolveAdapter(localInfer, engineId) {
|
|
45
|
+
const adapter = localInfer.registeredEngines().find((engine) => engine.id === engineId);
|
|
46
|
+
if (adapter === undefined) {
|
|
47
|
+
throw new RouteError(404, `no engine adapter is registered for "${engineId}"`);
|
|
48
|
+
}
|
|
49
|
+
return adapter;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* launchCommandNote renders the plan's child-process launch line for the
|
|
53
|
+
* card's Logs section: the core formatter's command under a [local-infer]
|
|
54
|
+
* launch prefix, newline-terminated. The optional autoRebuild is the
|
|
55
|
+
* effective deploy flag (the syncFailed override included), so a compose
|
|
56
|
+
* plan's note carries the same pull policy the deploy's up command runs and
|
|
57
|
+
* the printed line stays the exact string the host executed. A plan that
|
|
58
|
+
* names no command (a bare docker plan with no argv) has no note, and the
|
|
59
|
+
* run logs stay as they are.
|
|
60
|
+
*/
|
|
61
|
+
function launchCommandNote(plan, autoRebuild) {
|
|
62
|
+
const command = formatLaunchCommand(plan, autoRebuild);
|
|
63
|
+
if (command === undefined)
|
|
64
|
+
return undefined;
|
|
65
|
+
return `[local-infer] launch: ${command}\n`;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* applyComposeArgs seeds a compose plan's compose arguments from the stored
|
|
69
|
+
* platform config (the card's docker-tag field for a compose engine, e.g.
|
|
70
|
+
* `--profile single`), so the up line the deploy runs and the down line the
|
|
71
|
+
* stop runs both select the profile the user named. A compose plan with no
|
|
72
|
+
* stored docker-tag keeps the engine's default profile; a non-compose plan is
|
|
73
|
+
* untouched.
|
|
74
|
+
*/
|
|
75
|
+
function applyComposeArgs(plan, localInfer, engineId) {
|
|
76
|
+
if (plan.kind !== 'compose')
|
|
77
|
+
return;
|
|
78
|
+
const platform = localInfer.enginePlatformConfig(engineId);
|
|
79
|
+
if (platform?.dockerTag !== undefined) {
|
|
80
|
+
plan.composeArgs = platform.dockerTag;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* persistStoredLogs overwrites the stored last-run logs (the card's Logs
|
|
85
|
+
* section shows them after a reload) and leaves every other stored field as
|
|
86
|
+
* the launch reads it.
|
|
87
|
+
*/
|
|
88
|
+
function persistStoredLogs(logs) {
|
|
89
|
+
const stored = readConfig();
|
|
90
|
+
writeStored(stored, { lastLogs: capRunLogs(logs) });
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* resolveStoredRequest resolves a route request's engine and config against
|
|
94
|
+
* the stored config: the request's fields win, and an absent field falls back
|
|
95
|
+
* to the stored value, the same rule every route applies. The Get route
|
|
96
|
+
* reads it for its run-log key before delegating to getEngine.
|
|
97
|
+
*/
|
|
98
|
+
export function resolveStoredRequest(request) {
|
|
99
|
+
const stored = readConfig();
|
|
100
|
+
return {
|
|
101
|
+
stored,
|
|
102
|
+
engineId: request.engine ?? stored.engine,
|
|
103
|
+
config: request.config ?? stored.config,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* registerLaunchPlatform registers the request's platform override (the
|
|
108
|
+
* card's unsaved Engine Directory / Engine Fork edits) with the service
|
|
109
|
+
* registry, and returns the effective platform entry the Get steps and the
|
|
110
|
+
* deploy step read. A request with no Engine Directory override falls back
|
|
111
|
+
* to the engine's default checkout home (`~/.cache/dsh/infer/{engineId}`),
|
|
112
|
+
* so the deploy step resolves a relative launch path (the NInfer Windows
|
|
113
|
+
* build tree) against a real checkout; the fallback is registered in the
|
|
114
|
+
* service registry only (memory, never the stored platform map), and a
|
|
115
|
+
* stored entry that already names a directory is left as-is. An absent
|
|
116
|
+
* request platform keeps the stored entry, which the save route and route
|
|
117
|
+
* registration already synced.
|
|
118
|
+
*/
|
|
119
|
+
function registerLaunchPlatform(localInfer, request, stored, engineId) {
|
|
120
|
+
if (request.platform !== undefined) {
|
|
121
|
+
setPlatformConfig(localInfer, engineId, request.platform);
|
|
122
|
+
}
|
|
123
|
+
const platform = request.platform ?? stored.platform[engineId];
|
|
124
|
+
// resolveEngineDirectory is the one home of the override-or-default rule,
|
|
125
|
+
// so the registration decision compares against its answer instead of
|
|
126
|
+
// restating the blank check: a platform that already names the effective
|
|
127
|
+
// directory needs no registration.
|
|
128
|
+
const engineDirectory = resolveEngineDirectory(engineId, platform);
|
|
129
|
+
if (platform?.engineDirectory !== engineDirectory) {
|
|
130
|
+
setPlatformConfig(localInfer, engineId, { ...platform, engineDirectory });
|
|
131
|
+
}
|
|
132
|
+
return platform;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* getStepsRequest builds the shared Get-steps request both getEngine and
|
|
136
|
+
* launchEngine run: the engine and config, the platform entry the checkout
|
|
137
|
+
* resolves against, the Play/Run trust gate, and the lifecycle host plus the
|
|
138
|
+
* optional cancel signal and download-progress sink. The exactOptionalProperty-
|
|
139
|
+
* types-safe spreads keep an absent field from writing `undefined`, so the
|
|
140
|
+
* host, signal, and sink travel as one group built in one place. Each caller
|
|
141
|
+
* layers its own extras on the result: the launch adds autoRebuild, the step
|
|
142
|
+
* reporter, and the sync override, a standalone Get adds none (its control
|
|
143
|
+
* keeps its throbber, so no reporter rides).
|
|
144
|
+
*/
|
|
145
|
+
function getStepsRequest(engineId, config, stored, platform, host, signal, onChunk) {
|
|
146
|
+
return {
|
|
147
|
+
engineId,
|
|
148
|
+
config,
|
|
149
|
+
...(platform !== undefined ? { platform } : {}),
|
|
150
|
+
allowRunDeps: stored.allowRunDeps,
|
|
151
|
+
host,
|
|
152
|
+
...(signal !== undefined ? { signal } : {}),
|
|
153
|
+
...(onChunk !== undefined ? { onChunk } : {}),
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* registerSudoObserver registers the run's sudo hook on the service under
|
|
158
|
+
* the run's key (the hub's own keying) and returns the disposer that removes
|
|
159
|
+
* only this hook: concurrent runs each keep their own slot, a re-launch
|
|
160
|
+
* under the same key takes the slot from the older run, and the first settle
|
|
161
|
+
* never clears another run's note routing. The service hands every guarded
|
|
162
|
+
* announcement to every registered hook (the shared CLI password wait can
|
|
163
|
+
* block any run, and the announce seam does not name the triggering run), so
|
|
164
|
+
* one wait notes every run in flight. launchEngine and getEngine share the
|
|
165
|
+
* helper so the sudo-note rule has one home: the hook appends noteLine to
|
|
166
|
+
* the run's own sink (the note rides the streamed run and the run text
|
|
167
|
+
* persisted when the step settles) and through persistProcessLog (so a run
|
|
168
|
+
* still hung on the prompt reads the note in the Logs section and the log
|
|
169
|
+
* file before it settles), then calls onSudo, the route's stream event; a
|
|
170
|
+
* Get passes none because its control keeps its throbber. The caller invokes
|
|
171
|
+
* the disposer in a finally so a failed run leaves no hook behind.
|
|
172
|
+
*/
|
|
173
|
+
export function registerSudoObserver(localInfer, runKey, noteLine, noteTo, onSudo) {
|
|
174
|
+
return localInfer.registerSudoObserver(runKey, () => {
|
|
175
|
+
noteTo(noteLine);
|
|
176
|
+
persistProcessLog(noteLine.replace(/\n$/, ''));
|
|
177
|
+
onSudo?.();
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* openRunLog begins the run under runKey (the seed resets the key's buffer,
|
|
182
|
+
* so a mid-run re-launch replaces the previous run's text) and hands back the
|
|
183
|
+
* bracket. The caller closes it in a finally so every settle path clears the
|
|
184
|
+
* running flag.
|
|
185
|
+
*/
|
|
186
|
+
function openRunLog(localInfer, runKey, seed) {
|
|
187
|
+
localInfer.beginRun(runKey, seed);
|
|
188
|
+
let text = '';
|
|
189
|
+
return {
|
|
190
|
+
append(chunk) {
|
|
191
|
+
text += chunk;
|
|
192
|
+
localInfer.appendRunLog(runKey, chunk);
|
|
193
|
+
},
|
|
194
|
+
text() {
|
|
195
|
+
return text;
|
|
196
|
+
},
|
|
197
|
+
end() {
|
|
198
|
+
localInfer.endRun(runKey);
|
|
199
|
+
},
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* launchEngine runs the shared Get-steps routine (idempotent: deps, git sync,
|
|
204
|
+
* build, model download, chat-template download, each skipped when already
|
|
205
|
+
* satisfied) and then renders the plan the registered adapter produces for
|
|
206
|
+
* the config and the detected hardware, and runs the service's launch entry
|
|
207
|
+
* point (deploy, then the health poll). A git sync of an existing checkout
|
|
208
|
+
* that fails under the continueOnSyncFailure override (always on for a
|
|
209
|
+
* launch) keeps the Get run going with the checkout's current version and
|
|
210
|
+
* reports syncFailed on the result; the launch then threads autoRebuild:
|
|
211
|
+
* false into the deploy step, which skips its own git sync (it would hit the
|
|
212
|
+
* same rebase conflict) and launches the current version, while the Get
|
|
213
|
+
* error line persists in the last-run logs and the log file. The detail the
|
|
214
|
+
* launch returns names the broken step when the engine does not come up.
|
|
215
|
+
* The launch line (the
|
|
216
|
+
* engine binary and its arguments) leads the engine output in the run logs
|
|
217
|
+
* the card's Logs section and the log file carry, and the Get-step output the
|
|
218
|
+
* launch ran leads it, so a broken setup diagnoses against the exact steps
|
|
219
|
+
* and child process the launch started, and the Get logs are preserved, not
|
|
220
|
+
* wiped, by the launch's persistence. The launch step persists the
|
|
221
|
+
* launch record (the engine pid, start time, launch arguments, and the DSH
|
|
222
|
+
* process pid that launched it) on the request's provider entry, so a DSH
|
|
223
|
+
* restart can re-link a surviving engine to its config. The optional signal
|
|
224
|
+
* is the card's cancel: an abort interrupts the in-flight Get-step download
|
|
225
|
+
* child, and the persisted run carries a "Canceled" line. The optional
|
|
226
|
+
* onChunk is the hf download's raw stderr progress sink: the download runner
|
|
227
|
+
* hands each streamed chunk to it as it lands, so a launch that runs the Get
|
|
228
|
+
* steps streams the download progress the same way a Get does. The optional
|
|
229
|
+
* onSudo is the route's sudo-event writer: while the run is in flight, the
|
|
230
|
+
* service's sudo observer answers a guarded command waiting for a password on
|
|
231
|
+
* the launching CLI by landing the core CLI note in the run logs and the log
|
|
232
|
+
* file and calling onSudo; the observer is unregistered when the run settles,
|
|
233
|
+
* failed or not, so a failing run leaves none behind. The whole run brackets
|
|
234
|
+
* one live hub run under the runLogKey rule (the "--- Launching..." seed,
|
|
235
|
+
* the Get-steps sink, the command note, the deploy and engine output at
|
|
236
|
+
* settle, end in a finally), so the streaming logs route reads the launch.
|
|
237
|
+
*/
|
|
238
|
+
export async function launchEngine(localInfer, request, onProgress, host = nodeLifecycleHost, signal, onChunk, onSudo) {
|
|
239
|
+
const { stored, engineId, config } = resolveStoredRequest(request);
|
|
240
|
+
const adapter = resolveAdapter(localInfer, engineId);
|
|
241
|
+
// The run key this launch's live buffer rides, the shared runLogKey rule
|
|
242
|
+
// the streaming logs route resolves the same way. The seed resets the
|
|
243
|
+
// key's buffer, so a mid-run re-launch replaces the previous run's text.
|
|
244
|
+
const runKey = runLogKey(stored.providers, engineId, request.providerId, config);
|
|
245
|
+
// The run's live-buffer bracket (see openRunLog): the Get-step sink and the
|
|
246
|
+
// sudo note append through it (one chunk, two readers: the persisted run
|
|
247
|
+
// text and the live hub), and the finally closes it on every settle path.
|
|
248
|
+
const run = openRunLog(localInfer, runKey, LAUNCH_RUN_SEED);
|
|
249
|
+
// The run owns the sudo observer, not the route, so the note lands in the
|
|
250
|
+
// persisted run and in the stream from one place (see
|
|
251
|
+
// registerSudoObserver): the CLI note appends to the Get-step log sink
|
|
252
|
+
// (composeLaunchLogs and persistLaunchOutcome carry it into the last-run
|
|
253
|
+
// logs and the log file), persists to disk while the run is still hung
|
|
254
|
+
// waiting on the prompt, and tells the route's stream writer.
|
|
255
|
+
const clearSudoObserver = registerSudoObserver(localInfer, runKey, `${SUDO_CLI_NOTE}\n`, run.append, onSudo);
|
|
256
|
+
try {
|
|
257
|
+
const hardware = await localInfer.detectHardware();
|
|
258
|
+
const platform = registerLaunchPlatform(localInfer, request, stored, engineId);
|
|
259
|
+
// The Get steps (deps, git sync, build, model download, chat-template
|
|
260
|
+
// download) run idempotently before the launch plan renders, so a launch
|
|
261
|
+
// never reads a missing checkout, binary, model, or template. The Auto
|
|
262
|
+
// Rebuild checkbox off skips the sync and build steps, the same rule the
|
|
263
|
+
// adapter's deploy step applies. The launch always sets
|
|
264
|
+
// continueOnSyncFailure: a failed git sync of an existing checkout
|
|
265
|
+
// reports syncFailed on the result (the run keeps going with the
|
|
266
|
+
// checkout's current version) instead of halting the launch. The
|
|
267
|
+
// progress sink rides into the steps as onStep: each step announces its
|
|
268
|
+
// phase as its work actually starts (the prereqs announcement belongs to
|
|
269
|
+
// the dep install, not to the launch's start), so the card's button
|
|
270
|
+
// tracks the phase actually running.
|
|
271
|
+
const get = await runGetSteps(localInfer, {
|
|
272
|
+
...getStepsRequest(engineId, config, stored, platform, host, signal, onChunk),
|
|
273
|
+
...(request.autoRebuild !== undefined ? { autoRebuild: request.autoRebuild } : {}),
|
|
274
|
+
...(onProgress !== undefined ? { onStep: onProgress } : {}),
|
|
275
|
+
continueOnSyncFailure: true,
|
|
276
|
+
}, run.append);
|
|
277
|
+
const plan = adapter.renderLaunch(config, hardware);
|
|
278
|
+
if (!get.ok) {
|
|
279
|
+
if (signal?.aborted === true) {
|
|
280
|
+
localInfer.appendRunLog(runKey, CANCELED_LINE);
|
|
281
|
+
}
|
|
282
|
+
return settleGetFailure(plan, get, engineId, config, request.providerId, run.text(), signal?.aborted === true);
|
|
283
|
+
}
|
|
284
|
+
applyComposeArgs(plan, localInfer, engineId);
|
|
285
|
+
// The effective auto-rebuild flag for the deploy step. A Get sync that
|
|
286
|
+
// failed under the override threads autoRebuild: false into the deploy,
|
|
287
|
+
// which skips its own git sync (it would hit the same rebase conflict)
|
|
288
|
+
// and launches the checkout's current version; a Get run whose sync
|
|
289
|
+
// passed keeps the request's autoRebuild. It is computed before the
|
|
290
|
+
// command note so the persisted Logs line renders the same pull policy
|
|
291
|
+
// the deploy's compose up runs, and the deploy reads the flag to decide
|
|
292
|
+
// whether a rebuild's missing-binary symlink into /usr/local/bin runs
|
|
293
|
+
// (on) or is written to the card's Logs section (off), and whether its
|
|
294
|
+
// acquisition steps (git sync, image pull, installer) run at all.
|
|
295
|
+
const launchAutoRebuild = get.syncFailed === true ? false : request.autoRebuild;
|
|
296
|
+
const commandNote = launchCommandNote(plan, launchAutoRebuild);
|
|
297
|
+
if (commandNote !== undefined) {
|
|
298
|
+
// Persist before the deploy starts: a slow or hung launch keeps the Get
|
|
299
|
+
// steps and the command readable in the Logs section across a card
|
|
300
|
+
// reload, where the engine output has not landed yet.
|
|
301
|
+
persistStoredLogs(`${run.text()}${commandNote}`);
|
|
302
|
+
localInfer.appendRunLog(runKey, commandNote);
|
|
303
|
+
}
|
|
304
|
+
// The trust gate and the auto-rebuild flag ride into the deploy step.
|
|
305
|
+
// onLog is the deploy's run-log sink: the deploy's mutating commands
|
|
306
|
+
// (image pull, model download) write their `$ <command>` lines through
|
|
307
|
+
// it before their children spawn, so the live launch run reads each
|
|
308
|
+
// command ahead of its output.
|
|
309
|
+
const result = await localInfer.launch(plan, {
|
|
310
|
+
...(onProgress !== undefined ? { onProgress } : {}),
|
|
311
|
+
allowRunDeps: stored.allowRunDeps,
|
|
312
|
+
...(launchAutoRebuild !== undefined ? { autoRebuild: launchAutoRebuild } : {}),
|
|
313
|
+
onLog: run.append,
|
|
314
|
+
});
|
|
315
|
+
// The deploy step's build output and the engine's runtime output reach
|
|
316
|
+
// the live buffer at settle (they only exist once the launch returns;
|
|
317
|
+
// the Get-step sink and the command note already streamed through the
|
|
318
|
+
// run bracket). composeLaunchLogs then folds every part into the
|
|
319
|
+
// persisted order (Get steps, build, command, engine), where the hub
|
|
320
|
+
// carries them in arrival order (Get steps, command, build, engine).
|
|
321
|
+
const deployLogs = result.buildLogs ?? '';
|
|
322
|
+
const engineLogs = result.logs ?? '';
|
|
323
|
+
if (deployLogs + engineLogs !== '') {
|
|
324
|
+
localInfer.appendRunLog(runKey, deployLogs + engineLogs);
|
|
325
|
+
}
|
|
326
|
+
// A cancel that landed after the Get steps (the card's provider-form Cancel
|
|
327
|
+
// closed the stream during the deploy or health poll) adds the "Canceled"
|
|
328
|
+
// line, so the persisted run reads as canceled.
|
|
329
|
+
const canceled = signal?.aborted === true;
|
|
330
|
+
composeLaunchLogs(result, run.text(), commandNote, canceled);
|
|
331
|
+
if (canceled) {
|
|
332
|
+
localInfer.appendRunLog(runKey, CANCELED_LINE);
|
|
333
|
+
}
|
|
334
|
+
// The result's logs field already carries the Get-step output via
|
|
335
|
+
// composeLaunchLogs, so the persisted run leads with no runText. Passing
|
|
336
|
+
// runText again would include the Get steps twice in the persisted run.
|
|
337
|
+
persistLaunchOutcome(result, engineId, config, request.providerId, '');
|
|
338
|
+
return result;
|
|
339
|
+
}
|
|
340
|
+
finally {
|
|
341
|
+
clearSudoObserver();
|
|
342
|
+
// The bracket closes on every settle path: healthy, failed, and canceled
|
|
343
|
+
// all clear the key's running flag.
|
|
344
|
+
run.end();
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* composeLaunchLogs orders a launch's run logs: the Get-step output the
|
|
349
|
+
* launch ran leads, the deploy step's build output follows it, the launch
|
|
350
|
+
* command note follows that (after a successful build, or a direct launch
|
|
351
|
+
* with no build), and the engine's runtime output comes last. The deploy
|
|
352
|
+
* step's buildLogs field is folded into the ordered text and dropped, so the
|
|
353
|
+
* result carries the ordered run in its logs field. A cancel that landed
|
|
354
|
+
* after the Get steps appends the "Canceled" line, so the persisted run reads
|
|
355
|
+
* as canceled.
|
|
356
|
+
*/
|
|
357
|
+
function composeLaunchLogs(result, runText, commandNote, canceled) {
|
|
358
|
+
const buildLogs = result.buildLogs ?? '';
|
|
359
|
+
const engineLogs = result.logs ?? '';
|
|
360
|
+
result.logs = runText + buildLogs + (commandNote ?? '') + engineLogs;
|
|
361
|
+
if (result.buildLogs !== undefined) {
|
|
362
|
+
delete result.buildLogs;
|
|
363
|
+
}
|
|
364
|
+
if (canceled) {
|
|
365
|
+
result.logs += CANCELED_LINE;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* persistLaunchOutcome persists a settled launch: the run logs to the stored
|
|
370
|
+
* last-run logs and the log file, and the launch record on the target
|
|
371
|
+
* provider entry, so a DSH restart can re-link a surviving engine to its
|
|
372
|
+
* config. The runText argument leads the persisted run only when the result
|
|
373
|
+
* carries no logs of its own (the get-failed path, where the deploy never
|
|
374
|
+
* ran). The success path passes an empty string, because composeLaunchLogs
|
|
375
|
+
* already folded the Get-step output into the result's logs, and leading it
|
|
376
|
+
* again would include the Get steps twice.
|
|
377
|
+
*/
|
|
378
|
+
function persistLaunchOutcome(result, engineId, config, providerId, runText) {
|
|
379
|
+
persistLaunchLogs(result, engineId, config, runText);
|
|
380
|
+
persistLaunchRecord(result, engineId, config, providerId);
|
|
381
|
+
}
|
|
382
|
+
/**
|
|
383
|
+
* settleGetFailure settles a launch whose Get steps failed before the deploy
|
|
384
|
+
* starts: it builds the unhealthy result (naming the broken step), persists
|
|
385
|
+
* the run (the Get-step output leads, and a cancel that landed adds the
|
|
386
|
+
* "Canceled" line, so the run reads as canceled), and returns the result so
|
|
387
|
+
* the route answers with it.
|
|
388
|
+
*/
|
|
389
|
+
function settleGetFailure(plan, get, engineId, config, providerId, runText, canceled) {
|
|
390
|
+
const result = {
|
|
391
|
+
plan,
|
|
392
|
+
healthy: false,
|
|
393
|
+
detail: get.detail ?? 'a Get step failed',
|
|
394
|
+
};
|
|
395
|
+
persistLaunchOutcome(result, engineId, config, providerId, canceled ? runText + CANCELED_LINE : runText);
|
|
396
|
+
return result;
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* withoutLaunchRecord returns a provider entry with its launch record (the
|
|
400
|
+
* engine pid and the previous-pid list) removed, so the record is gone from
|
|
401
|
+
* the file, not just ignored, and a recycled pid cannot re-link to a config
|
|
402
|
+
* whose engine is stopped or dead. The stop, death, and re-link paths share
|
|
403
|
+
* it, so the rebuilt entry shape lives in one place.
|
|
404
|
+
*/
|
|
405
|
+
function withoutLaunchRecord(entry) {
|
|
406
|
+
return { id: entry.id, engine: entry.engine, config: entry.config, platform: entry.platform };
|
|
407
|
+
}
|
|
408
|
+
/**
|
|
409
|
+
* persistLaunchRecord stores a launch's record on the target provider entry:
|
|
410
|
+
* the engine pid the deploy step started, the start time, the plan's argv,
|
|
411
|
+
* the DSH process pid the launch ran under (only when it identified as DSH),
|
|
412
|
+
* and the previous-pid list the re-link rule prices (the entry's own list
|
|
413
|
+
* plus the current DSH pid, capped by the core builder). A launch whose
|
|
414
|
+
* deploy started no tracked process (a compose up) persists nothing, and a
|
|
415
|
+
* launch with no resolvable entry persists nothing.
|
|
416
|
+
*/
|
|
417
|
+
function persistLaunchRecord(result, engineId, config, providerId) {
|
|
418
|
+
if (result.pid === undefined)
|
|
419
|
+
return;
|
|
420
|
+
const stored = readConfig();
|
|
421
|
+
const target = findLaunchTarget(stored.providers, engineId, providerId, config);
|
|
422
|
+
if (target === undefined)
|
|
423
|
+
return;
|
|
424
|
+
const record = launchRecordFromPlan(result.plan, result.pid, result.parentPid, target.launch?.previousPids ?? []);
|
|
425
|
+
const providers = stored.providers.map((entry) => entry.id === target.id ? { ...entry, launch: record } : entry);
|
|
426
|
+
writeStored(stored, { providers });
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* clearLaunchRecord removes the target provider entry's launch record (the
|
|
430
|
+
* engine pid and the previous-pid list with it), so a stopped engine's pid
|
|
431
|
+
* cannot be re-linked to its config by a recycled pid. A stop with no
|
|
432
|
+
* resolvable entry, or an entry that carries no record, is a no-op.
|
|
433
|
+
*/
|
|
434
|
+
function clearLaunchRecord(engineId, config, providerId) {
|
|
435
|
+
const stored = readConfig();
|
|
436
|
+
const target = findLaunchTarget(stored.providers, engineId, providerId, config);
|
|
437
|
+
if (target === undefined || target.launch === undefined)
|
|
438
|
+
return;
|
|
439
|
+
const providers = stored.providers.map((entry) => entry.id === target.id ? withoutLaunchRecord(entry) : entry);
|
|
440
|
+
writeStored(stored, { providers });
|
|
441
|
+
}
|
|
442
|
+
/**
|
|
443
|
+
* handleEngineDeath persists a provider entry's state when its engine process
|
|
444
|
+
* dies outside the plugin's stop path: a crash, a pkill of a plugin-launched
|
|
445
|
+
* child seen through the service's exit watch, or the poll-detected exit of a
|
|
446
|
+
* process adopted after a DSH restart. The entry whose launch record names the
|
|
447
|
+
* dead pid (or the dead engine-and-port, when the report carries no pid match)
|
|
448
|
+
* has its record cleared, so a restart never re-links a dead pid, and the exit
|
|
449
|
+
* line lands in the card's last-run logs beside the engine's own output. An
|
|
450
|
+
* event that matches no entry carrying a record (a stop the route already
|
|
451
|
+
* cleared, an engine the plugin never tracked) is a no-op, which keeps an
|
|
452
|
+
* expected stop quiet. With a run-log hub (the route's observer closure
|
|
453
|
+
* captures the service), the same note line also lands in the dead entry's
|
|
454
|
+
* live buffer: the snapshot is read first, and if the run was NOT already
|
|
455
|
+
* running, the append's lazy reopen is settled with endRun right after, so a
|
|
456
|
+
* late note streams without stranding the key as running.
|
|
457
|
+
*/
|
|
458
|
+
export function handleEngineDeath(event, runLogs) {
|
|
459
|
+
const stored = readConfig();
|
|
460
|
+
let matched;
|
|
461
|
+
const providers = stored.providers.map((entry) => {
|
|
462
|
+
const record = entry.launch;
|
|
463
|
+
if (record === undefined)
|
|
464
|
+
return entry;
|
|
465
|
+
const byPid = event.pid !== undefined && record.pid === event.pid;
|
|
466
|
+
const byEnginePort = event.port !== undefined && record.engineId === entry.engine && record.port === event.port;
|
|
467
|
+
if (!byPid && !byEnginePort)
|
|
468
|
+
return entry;
|
|
469
|
+
matched = matched ?? entry;
|
|
470
|
+
return withoutLaunchRecord(entry);
|
|
471
|
+
});
|
|
472
|
+
if (matched === undefined)
|
|
473
|
+
return;
|
|
474
|
+
const signalNote = event.signal !== null && event.signal !== undefined
|
|
475
|
+
? ` on signal ${event.signal}`
|
|
476
|
+
: ` with exit code ${event.code}`;
|
|
477
|
+
const note = `[local-infer] engine ${event.engineId}` +
|
|
478
|
+
`${event.port !== undefined ? ` (port ${event.port})` : ''}` +
|
|
479
|
+
` pid ${event.pid ?? 'unknown'} exited${signalNote}` +
|
|
480
|
+
`${event.detail !== undefined ? `: ${event.detail}` : ''}\n`;
|
|
481
|
+
// The note persists through the shared fold (persistProcessLog: the
|
|
482
|
+
// join + capRunLogs tail every appender shares, the per-run log file
|
|
483
|
+
// included) instead of a bespoke lastLogs tail-join; the entry clear rides
|
|
484
|
+
// its own write before it.
|
|
485
|
+
writeStored(stored, { providers });
|
|
486
|
+
persistProcessLog(note);
|
|
487
|
+
if (runLogs === undefined)
|
|
488
|
+
return;
|
|
489
|
+
// The death's run key follows the runLogKey rule over the matched entry
|
|
490
|
+
// (the same resolution the launch wrote the buffer under: the entry id
|
|
491
|
+
// when its config names the model, else the engine id).
|
|
492
|
+
const runKey = runLogKey(stored.providers, event.engineId, undefined, matched.config);
|
|
493
|
+
const wasRunning = runLogs.readRunLog(runKey).running;
|
|
494
|
+
runLogs.appendRunLog(runKey, note);
|
|
495
|
+
if (!wasRunning) {
|
|
496
|
+
runLogs.endRun(runKey);
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
/**
|
|
500
|
+
* persistLaunchLogs stores a launch's captured output as the last run and
|
|
501
|
+
* writes it to the configured log file. The Get-step output the launch ran
|
|
502
|
+
* (the optional runText) leads the persisted run, so the Logs section and the
|
|
503
|
+
* log file show the Get steps followed by the launch, not the launch wiping
|
|
504
|
+
* the Get logs. The stored last-run logs ride the shared capRunLogs tail rule
|
|
505
|
+
* (ops/logs.ts), the same cap the append paths and the live hub apply. The
|
|
506
|
+
* log file is only written when the run produced output and the log location
|
|
507
|
+
* is set; the last-run logs are always persisted (an empty string clears a
|
|
508
|
+
* stale run) so the card's Logs section shows the previous run after a
|
|
509
|
+
* reload.
|
|
510
|
+
*/
|
|
511
|
+
export function persistLaunchLogs(result, engineId, config, runText = '') {
|
|
512
|
+
const stored = readConfig();
|
|
513
|
+
// The "Launching..." separator leads the persisted run, so the Logs
|
|
514
|
+
// section and the log file read the launch as a fresh process section, the
|
|
515
|
+
// same line the launch's live hub run seeds with (LAUNCH_RUN_SEED).
|
|
516
|
+
const body = runText + (result.logs === undefined ? '' : result.logs);
|
|
517
|
+
const logs = body === '' ? '' : `${LAUNCH_RUN_SEED}${body}`;
|
|
518
|
+
writeStored(stored, { lastLogs: capRunLogs(logs) });
|
|
519
|
+
if (logs === '') {
|
|
520
|
+
return;
|
|
521
|
+
}
|
|
522
|
+
const modelId = typeof config.model === 'string' ? config.model : undefined;
|
|
523
|
+
writeRunFile(stored, logs, engineId, modelId);
|
|
524
|
+
}
|
|
525
|
+
/**
|
|
526
|
+
* stopEngine renders the same plan and runs the service's stop entry point, so
|
|
527
|
+
* the stop step targets the exact container or process the launch started. The
|
|
528
|
+
* stop then clears the target entry's launch record (the engine pid and the
|
|
529
|
+
* previous-pid list with it), so a recycled pid cannot re-link an engine the
|
|
530
|
+
* user just stopped. An engine a DSH restart re-adopted carries no tracked
|
|
531
|
+
* child handle in the adapter, so the stop stamps the record's pid on the
|
|
532
|
+
* plan and the adapter signals it through the host; a stop that fails
|
|
533
|
+
* persists a note (the engine, the model, and the host's message) to the
|
|
534
|
+
* stored last-run logs and the log file, so the card's Logs section names the
|
|
535
|
+
* broken step, and re-throws, so the route answers 500 and the card's stop
|
|
536
|
+
* deadline settles the entry to the red Logs state.
|
|
537
|
+
*/
|
|
538
|
+
export async function stopEngine(localInfer, request) {
|
|
539
|
+
const { stored, engineId, config } = resolveStoredRequest(request);
|
|
540
|
+
const adapter = resolveAdapter(localInfer, engineId);
|
|
541
|
+
const hardware = await localInfer.detectHardware();
|
|
542
|
+
const plan = adapter.renderLaunch(config, hardware);
|
|
543
|
+
applyComposeArgs(plan, localInfer, engineId);
|
|
544
|
+
// The re-adopted engine's pid: without it the adapter's stop is a no-op for
|
|
545
|
+
// a process it did not spawn, and the engine the user just clicked Stop on
|
|
546
|
+
// keeps running.
|
|
547
|
+
const target = findLaunchTarget(stored.providers, engineId, request.providerId, config);
|
|
548
|
+
if (target?.launch?.pid !== undefined) {
|
|
549
|
+
plan.pid = target.launch.pid;
|
|
550
|
+
}
|
|
551
|
+
const runKey = runLogKey(stored.providers, engineId, request.providerId, config);
|
|
552
|
+
localInfer.beginRun(runKey, STOP_RUN_SEED);
|
|
553
|
+
try {
|
|
554
|
+
// The stop's log sink is the live run buffer: the adapter writes the
|
|
555
|
+
// `$ <command>` line of every command it runs (compose down, docker stop,
|
|
556
|
+
// a pid signal) through it before the child runs, so the stop's live run
|
|
557
|
+
// reads the command ahead of any note.
|
|
558
|
+
await localInfer.stop(plan, (line) => localInfer.appendRunLog(runKey, line));
|
|
559
|
+
clearLaunchRecord(engineId, config, request.providerId);
|
|
560
|
+
}
|
|
561
|
+
catch (error) {
|
|
562
|
+
persistStopFailure(engineId, config, error, localInfer, runKey);
|
|
563
|
+
throw error;
|
|
564
|
+
}
|
|
565
|
+
finally {
|
|
566
|
+
localInfer.endRun(runKey);
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
/**
|
|
570
|
+
* persistStopFailure appends a stop's failure note to the stored last-run logs
|
|
571
|
+
* (the card's Logs section shows it after the summary re-fetch) and writes it
|
|
572
|
+
* to the configured log file, the same per-run file rule the launch step
|
|
573
|
+
* applies, and to the stop's live run-log buffer under runKey (one note text,
|
|
574
|
+
* no third copy). A host that threw without a message reads as "unknown".
|
|
575
|
+
*/
|
|
576
|
+
function persistStopFailure(engineId, config, error, localInfer, runKey) {
|
|
577
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
578
|
+
const modelId = typeof config.model === 'string' ? config.model : undefined;
|
|
579
|
+
const note = `[local-infer] stop failed for ${engineId}` +
|
|
580
|
+
`${modelId !== undefined ? ` (${modelId})` : ''}: ${detail}\n`;
|
|
581
|
+
persistProcessLog(note);
|
|
582
|
+
localInfer.appendRunLog(runKey, note);
|
|
583
|
+
}
|
|
584
|
+
/**
|
|
585
|
+
* relinkStoredProviders prices every stored provider launch record against the
|
|
586
|
+
* live process table through the service's re-link rule and returns the
|
|
587
|
+
* provider list the card should read: an adopted engine is re-marked plugin-
|
|
588
|
+
* launched and tied to its model by the service, and a liveness watcher starts
|
|
589
|
+
* on its port so a later pkill settles the stored state through the death
|
|
590
|
+
* observer without another summary read; a dead engine's record is cleared on
|
|
591
|
+
* disk here (the previous-pid list with it), so a recycled pid cannot re-link
|
|
592
|
+
* to a config whose engine is down. The route calls it on mount and the
|
|
593
|
+
* summary calls it per read, making re-attach independent of the card opening.
|
|
594
|
+
*/
|
|
595
|
+
export async function relinkStoredProviders(localInfer, stored) {
|
|
596
|
+
const recorded = stored.providers.filter((provider) => provider.launch !== undefined);
|
|
597
|
+
if (recorded.length === 0)
|
|
598
|
+
return stored.providers;
|
|
599
|
+
const outcomes = await localInfer.reLinkProviders(recorded.map((provider) => ({ id: provider.id, record: provider.launch })));
|
|
600
|
+
if (!recorded.some((provider) => outcomes[provider.id] === 'dead')) {
|
|
601
|
+
return stored.providers;
|
|
602
|
+
}
|
|
603
|
+
const providers = stored.providers.map((provider) => provider.launch !== undefined && outcomes[provider.id] === 'dead'
|
|
604
|
+
? withoutLaunchRecord(provider)
|
|
605
|
+
: provider);
|
|
606
|
+
writeStored(stored, { providers });
|
|
607
|
+
return providers;
|
|
608
|
+
}
|
|
609
|
+
/**
|
|
610
|
+
* getEngine is the Get step: a thin wrapper over the shared Get-steps routine
|
|
611
|
+
* (the system dependencies, the git sync, the build, the model download, and
|
|
612
|
+
* the chat-template download), streaming every step through log as it lands,
|
|
613
|
+
* so the card appends it to the Logs section live. The dependency install
|
|
614
|
+
* only runs while the Play/Run trust gate is on; with the gate off the deps
|
|
615
|
+
* are re-checked and a missing one aborts the step with the copy/paste note.
|
|
616
|
+
* The optional signal is the card's cancel: an abort interrupts the in-flight
|
|
617
|
+
* hf download child (SIGINT, then SIGTERM). Resume is within-invocation only:
|
|
618
|
+
* the hf CLI (huggingface_hub 2.x) resumes a network blip via HTTP Range inside
|
|
619
|
+
* one `hf download` run, but a killed run re-downloads from zero on the re-run
|
|
620
|
+
* (each run writes a process-unique `.incomplete` temp blob and unlinks it on
|
|
621
|
+
* failure), so an orphaned partial blob lingers until `hf cache prune`. The
|
|
622
|
+
* optional onChunk is the hf download's raw stderr progress sink: the
|
|
623
|
+
* download runner hands each streamed chunk to it as it lands, separate from
|
|
624
|
+
* the log sink, so the route parses the tqdm lines into progress events. The
|
|
625
|
+
* run registers the same sudo observer a launch does: a guarded command
|
|
626
|
+
* waiting for the user's password on the launching CLI writes the core CLI
|
|
627
|
+
* note through log as a GET_EVENT_PREFIX line, the same marker the route's
|
|
628
|
+
* own failure line uses, so the note rides the streamed run and the run text
|
|
629
|
+
* the route persists, and through persistProcessLog so a run still hung on
|
|
630
|
+
* the prompt reads it before it settles. The Get control keeps its throbber,
|
|
631
|
+
* so the observer announces nothing beyond the note line.
|
|
632
|
+
*/
|
|
633
|
+
export async function getEngine(localInfer, request, log, host = nodeLifecycleHost, signal, onChunk) {
|
|
634
|
+
const { stored, engineId, config } = resolveStoredRequest(request);
|
|
635
|
+
// Register the request's platform override the same way launchEngine does,
|
|
636
|
+
// so the next launch reads the Engine Directory / Engine Fork the Get used.
|
|
637
|
+
const platform = registerLaunchPlatform(localInfer, request, stored, engineId);
|
|
638
|
+
// The Get run owns the sudo hook for the run's duration, registered under
|
|
639
|
+
// the same runLogKey rule the route brackets the live hub run with (see
|
|
640
|
+
// registerSudoObserver); the finally leaves only this run's slot cleared.
|
|
641
|
+
const clearSudoObserver = registerSudoObserver(localInfer, runLogKey(stored.providers, engineId, undefined, config), `${GET_EVENT_PREFIX}${SUDO_CLI_NOTE}\n`, log);
|
|
642
|
+
try {
|
|
643
|
+
const result = await runGetSteps(localInfer, getStepsRequest(engineId, config, stored, platform, host, signal, onChunk), log);
|
|
644
|
+
return { ok: result.ok, ...(result.detail !== undefined ? { detail: result.detail } : {}) };
|
|
645
|
+
}
|
|
646
|
+
finally {
|
|
647
|
+
clearSudoObserver();
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
/**
|
|
651
|
+
* The route's view of which engine platform configs are registered with the
|
|
652
|
+
* service, keyed by engine id. The service registry is the single source the
|
|
653
|
+
* launch step reads, so the route keeps it in sync with the stored config:
|
|
654
|
+
* on route registration, after every save, and when a launch request carries
|
|
655
|
+
* a platform override. A disposer is tracked per engine, so an engine whose
|
|
656
|
+
* stored config is cleared is unregistered rather than left stale.
|
|
657
|
+
*/
|
|
658
|
+
const platformDisposers = new Map();
|
|
659
|
+
/**
|
|
660
|
+
* setPlatformConfig registers one engine's platform config with the service,
|
|
661
|
+
* replacing any stored entry. A config with no fields is read as "no platform
|
|
662
|
+
* config", which unregisters the engine so the deploy step falls back to the
|
|
663
|
+
* adapter's default source and cache directory.
|
|
664
|
+
*/
|
|
665
|
+
function setPlatformConfig(localInfer, engineId, config) {
|
|
666
|
+
platformDisposers.get(engineId)?.();
|
|
667
|
+
platformDisposers.delete(engineId);
|
|
668
|
+
if (config === undefined) {
|
|
669
|
+
return;
|
|
670
|
+
}
|
|
671
|
+
if (config.engineDirectory === undefined &&
|
|
672
|
+
config.engineFork === undefined &&
|
|
673
|
+
config.buildCommands === undefined &&
|
|
674
|
+
config.dockerTag === undefined) {
|
|
675
|
+
return;
|
|
676
|
+
}
|
|
677
|
+
const platform = { engineId, ...config };
|
|
678
|
+
platformDisposers.set(engineId, localInfer.registerEnginePlatformConfig(platform));
|
|
679
|
+
}
|
|
680
|
+
/**
|
|
681
|
+
* syncPlatformConfigs re-syncs the service registry with the stored platform
|
|
682
|
+
* map: it registers (or re-registers) every engine that carries a config, and
|
|
683
|
+
* unregisters every engine that no longer does, so a launch always reads the
|
|
684
|
+
* platform config the user last saved.
|
|
685
|
+
*/
|
|
686
|
+
export function syncPlatformConfigs(localInfer, stored) {
|
|
687
|
+
const wanted = stored.platform;
|
|
688
|
+
for (const engineId of [...platformDisposers.keys()]) {
|
|
689
|
+
if (wanted[engineId] === undefined) {
|
|
690
|
+
setPlatformConfig(localInfer, engineId, undefined);
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
for (const [engineId, config] of Object.entries(wanted)) {
|
|
694
|
+
setPlatformConfig(localInfer, engineId, config);
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
//# sourceMappingURL=engine.js.map
|