@qvac/harness 0.7.0 → 1.0.0-rc.0
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/CHANGELOG.md +42 -1
- package/README.md +239 -2
- package/dist-lib/index.d.ts +58 -3
- package/dist-lib/index.js +736 -239
- package/dist-lib/lib/capabilities/completion-engine/abort-polyfill.d.ts +1 -0
- package/dist-lib/lib/capabilities/completion-engine/abort-polyfill.js +10 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/answering.d.ts +3 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/answering.js +17 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/approval.d.ts +4 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/approval.js +29 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/direct-search.d.ts +4 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/direct-search.js +73 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/execute.d.ts +41 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/execute.js +49 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/executing.d.ts +3 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/executing.js +102 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/execution-plan.d.ts +47 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/execution-plan.js +87 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/finishing.d.ts +2 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/finishing.js +32 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/inspect-preload.d.ts +3 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/inspect-preload.js +75 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/inspecting.d.ts +3 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/inspecting.js +152 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/loading-skill.d.ts +3 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/loading-skill.js +94 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/preload-failure.d.ts +9 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/preload-failure.js +11 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/preloading.d.ts +3 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/preloading.js +114 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/preparing.d.ts +3 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/preparing.js +198 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/rejecting.d.ts +3 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/rejecting.js +71 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/rejection.d.ts +18 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/rejection.js +70 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/resolving.d.ts +3 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/resolving.js +60 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/stream.d.ts +21 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/stream.js +48 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/streaming.d.ts +3 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/streaming.js +156 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/wrap-up.d.ts +7 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/wrap-up.js +61 -0
- package/dist-lib/lib/capabilities/completion-engine/driver.d.ts +25 -0
- package/dist-lib/lib/capabilities/completion-engine/driver.js +553 -0
- package/dist-lib/lib/capabilities/completion-engine/durable.d.ts +61 -0
- package/dist-lib/lib/capabilities/completion-engine/durable.js +150 -0
- package/dist-lib/lib/capabilities/completion-engine/operations.d.ts +6 -0
- package/dist-lib/lib/capabilities/completion-engine/operations.js +239 -0
- package/dist-lib/lib/capabilities/completion-engine/ports.d.ts +3 -0
- package/dist-lib/lib/capabilities/completion-engine/ports.js +267 -0
- package/dist-lib/lib/capabilities/completion-engine/session.d.ts +76 -0
- package/dist-lib/lib/capabilities/completion-engine/session.js +70 -0
- package/dist-lib/lib/capabilities/completion-loop/index.d.ts +80 -0
- package/dist-lib/lib/capabilities/completion-loop/index.js +158 -0
- package/dist-lib/lib/capabilities/completion-loop/metrics.d.ts +5 -0
- package/dist-lib/lib/capabilities/completion-loop/metrics.js +29 -0
- package/dist-lib/lib/capabilities/completion-loop/round.d.ts +33 -0
- package/dist-lib/lib/capabilities/completion-loop/round.js +353 -0
- package/dist-lib/lib/capabilities/completion-loop/tool-execution.d.ts +60 -0
- package/dist-lib/lib/capabilities/completion-loop/tool-execution.js +270 -0
- package/dist-lib/lib/capabilities/completion-loop/turns.d.ts +23 -0
- package/dist-lib/lib/capabilities/completion-loop/turns.js +112 -0
- package/dist-lib/lib/capabilities/completion-runtime.d.ts +46 -0
- package/dist-lib/lib/capabilities/completion-runtime.js +74 -0
- package/dist-lib/lib/capabilities/completion.d.ts +18 -335
- package/dist-lib/lib/capabilities/completion.js +20 -1301
- package/dist-lib/lib/capabilities/download.d.ts +1 -1
- package/dist-lib/lib/capabilities/download.js +7 -2
- package/dist-lib/lib/capabilities/invoke.js +5 -3
- package/dist-lib/lib/capabilities/tool-plan.d.ts +35 -0
- package/dist-lib/lib/capabilities/tool-plan.js +66 -0
- package/dist-lib/lib/capabilities/tool-turns.d.ts +11 -2
- package/dist-lib/lib/capabilities/tool-turns.js +30 -2
- package/dist-lib/lib/capabilities/types.d.ts +287 -0
- package/dist-lib/lib/capabilities/types.js +1 -0
- package/dist-lib/lib/completion-lifecycle.d.ts +14 -0
- package/dist-lib/lib/completion-lifecycle.js +126 -0
- package/dist-lib/lib/connect.d.ts +10 -3
- package/dist-lib/lib/connect.js +329 -23
- package/dist-lib/lib/contract-types.d.ts +19 -2
- package/dist-lib/lib/harness-lifecycle.d.ts +66 -0
- package/dist-lib/lib/harness-lifecycle.js +129 -0
- package/dist-lib/lib/helpers/detach-json.d.ts +1 -0
- package/dist-lib/lib/helpers/detach-json.js +146 -0
- package/dist-lib/lib/helpers/markup-scanner.d.ts +26 -0
- package/dist-lib/lib/helpers/markup-scanner.js +156 -0
- package/dist-lib/lib/helpers/markup-stream-filter.js +138 -35
- package/dist-lib/lib/helpers/race-abort.d.ts +9 -4
- package/dist-lib/lib/helpers/race-abort.js +2 -7
- package/dist-lib/lib/helpers/recovery-required.d.ts +1 -0
- package/dist-lib/lib/helpers/recovery-required.js +1 -0
- package/dist-lib/lib/helpers/run-outcome.d.ts +10 -2
- package/dist-lib/lib/helpers/run-outcome.js +8 -5
- package/dist-lib/lib/helpers/tool-call-markup.d.ts +2 -0
- package/dist-lib/lib/helpers/tool-call-markup.js +197 -82
- package/dist-lib/lib/hooks/built-in/arg-repair.d.ts +1 -0
- package/dist-lib/lib/hooks/built-in/arg-repair.js +1 -0
- package/dist-lib/lib/hooks/built-in/egress.d.ts +1 -0
- package/dist-lib/lib/hooks/built-in/egress.js +2 -1
- package/dist-lib/lib/hooks/built-in/sandbox.d.ts +1 -0
- package/dist-lib/lib/hooks/built-in/sandbox.js +6 -1
- package/dist-lib/lib/hooks/built-in/skill-catalog.d.ts +1 -0
- package/dist-lib/lib/hooks/built-in/skill-catalog.js +2 -1
- package/dist-lib/lib/hooks/pipeline.d.ts +4 -1
- package/dist-lib/lib/hooks/pipeline.js +24 -2
- package/dist-lib/lib/hooks/run.d.ts +2 -1
- package/dist-lib/lib/hooks/run.js +19 -7
- package/dist-lib/lib/hooks/types.d.ts +17 -6
- package/dist-lib/lib/machine/completion/actions/approval.d.ts +3 -0
- package/dist-lib/lib/machine/completion/actions/approval.js +16 -0
- package/dist-lib/lib/machine/completion/actions/completion.d.ts +23 -0
- package/dist-lib/lib/machine/completion/actions/completion.js +142 -0
- package/dist-lib/lib/machine/completion/actions/skills.d.ts +9 -0
- package/dist-lib/lib/machine/completion/actions/skills.js +83 -0
- package/dist-lib/lib/machine/completion/actions/tools.d.ts +16 -0
- package/dist-lib/lib/machine/completion/actions/tools.js +173 -0
- package/dist-lib/lib/machine/completion/context.d.ts +9 -0
- package/dist-lib/lib/machine/completion/context.js +44 -0
- package/dist-lib/lib/machine/completion/contract.d.ts +6 -0
- package/dist-lib/lib/machine/completion/contract.js +1 -0
- package/dist-lib/lib/machine/completion/guards/retry.d.ts +9 -0
- package/dist-lib/lib/machine/completion/guards/retry.js +25 -0
- package/dist-lib/lib/machine/completion/implementations.d.ts +81 -0
- package/dist-lib/lib/machine/completion/implementations.js +152 -0
- package/dist-lib/lib/machine/completion/machine.d.ts +375 -0
- package/dist-lib/lib/machine/completion/machine.js +511 -0
- package/dist-lib/lib/machine/completion/ports.d.ts +65 -0
- package/dist-lib/lib/machine/completion/ports.js +224 -0
- package/dist-lib/lib/machine/completion/rules.d.ts +39 -0
- package/dist-lib/lib/machine/completion/rules.js +115 -0
- package/dist-lib/lib/machine/completion/types.d.ts +493 -0
- package/dist-lib/lib/machine/completion/types.js +1 -0
- package/dist-lib/lib/machine/engine.d.ts +93 -0
- package/dist-lib/lib/machine/engine.js +483 -0
- package/dist-lib/lib/machine/errors.d.ts +44 -0
- package/dist-lib/lib/machine/errors.js +373 -0
- package/dist-lib/lib/machine/guards.d.ts +2 -0
- package/dist-lib/lib/machine/guards.js +6 -0
- package/dist-lib/lib/machine/index.d.ts +20 -0
- package/dist-lib/lib/machine/index.js +11 -0
- package/dist-lib/lib/machine/replay-output.d.ts +24 -0
- package/dist-lib/lib/machine/replay-output.js +50 -0
- package/dist-lib/lib/machine/stable-stringify.d.ts +1 -0
- package/dist-lib/lib/machine/stable-stringify.js +11 -0
- package/dist-lib/lib/models/model-handle.d.ts +5 -1
- package/dist-lib/lib/models/model-handle.js +8 -3
- package/dist-lib/lib/models/model-list.d.ts +5 -3
- package/dist-lib/lib/models/model-pool.d.ts +18 -7
- package/dist-lib/lib/models/model-pool.js +126 -38
- package/dist-lib/lib/run-log/history-codec.d.ts +23 -0
- package/dist-lib/lib/run-log/history-codec.js +973 -0
- package/dist-lib/lib/run-log/patch.d.ts +6 -0
- package/dist-lib/lib/run-log/patch.js +93 -0
- package/dist-lib/lib/run-log/persisted-actor.d.ts +18 -0
- package/dist-lib/lib/run-log/persisted-actor.js +218 -0
- package/dist-lib/lib/run-log/protocol-types.d.ts +153 -0
- package/dist-lib/lib/run-log/protocol-types.js +1 -0
- package/dist-lib/lib/run-log/protocol.d.ts +27 -0
- package/dist-lib/lib/run-log/protocol.js +1056 -0
- package/dist-lib/lib/run-log/replay.d.ts +25 -0
- package/dist-lib/lib/run-log/replay.js +129 -0
- package/dist-lib/lib/run-log/run-journal.d.ts +125 -0
- package/dist-lib/lib/run-log/run-journal.js +783 -0
- package/dist-lib/lib/run-log-inspect.d.ts +25 -0
- package/dist-lib/lib/run-log-inspect.js +627 -0
- package/dist-lib/lib/run-log-protocol.d.ts +2 -0
- package/dist-lib/lib/run-log-protocol.js +1 -0
- package/dist-lib/lib/run-log-redact.d.ts +27 -0
- package/dist-lib/lib/run-log-redact.js +1107 -0
- package/dist-lib/lib/serve.js +308 -12
- package/dist-lib/lib/spawn.js +3 -2
- package/dist-lib/lib/tool-call-display.d.ts +1 -21
- package/dist-lib/lib/tools/exec.d.ts +2 -1
- package/dist-lib/lib/tools/exec.js +57 -2
- package/dist-lib/lib/tools/generate-image.js +1 -1
- package/dist-lib/lib/tools/generate-music.js +1 -1
- package/dist-lib/lib/tools/http-request.d.ts +2 -1
- package/dist-lib/lib/tools/http-request.js +2 -1
- package/dist-lib/lib/tools/mcp-call.js +8 -1
- package/dist-lib/lib/tools/operations/builders.d.ts +1 -0
- package/dist-lib/lib/tools/operations/builders.js +1 -0
- package/dist-lib/lib/tools/operations/compile.js +11 -1
- package/dist-lib/lib/tools/operations/manifest.js +1 -0
- package/dist-lib/lib/tools/outbound-guard.d.ts +8 -6
- package/dist-lib/lib/tools/outbound-guard.js +25 -8
- package/dist-lib/lib/tools/skills/catalog.d.ts +1 -0
- package/dist-lib/lib/tools/skills/catalog.js +35 -0
- package/dist-lib/lib/tools/skills/cli-schema-types.d.ts +1 -0
- package/dist-lib/lib/tools/skills/cli-schema.js +25 -0
- package/dist-lib/lib/wire.d.ts +5 -0
- package/dist-lib/lib/wire.js +128 -5
- package/dist-lib/schema/harness.js +22 -6
- package/dist-lib/spec/hrpc/messages.js +100 -18
- package/dist-lib/spec/hyperschema/index.js +100 -18
- package/dist-lib/spec/hyperschema/schema.json +74 -1
- package/dist-lib/spec/hyperschema/types.d.ts +11 -3
- package/package.json +19 -1
- package/patches/@qvac%2Finference@0.20.0.patch +67 -0
- package/patches/bare-stow@0.1.5.patch +68 -0
- package/patches/hypercore-blob-server@1.15.0.patch +15 -0
- package/patches/localwatch@1.4.3.patch +13 -0
- package/patches/pyodide@314.0.2.patch +144 -0
package/dist-lib/index.js
CHANGED
|
@@ -5,6 +5,9 @@ import ReadyResource from 'ready-resource';
|
|
|
5
5
|
import { probeGpuStats } from './lib/hardware/gpu-probe.js';
|
|
6
6
|
import { probeCapabilityProfile } from './lib/hardware/capability-probe.js';
|
|
7
7
|
import { closePythonWorker } from './lib/tools/exec/adapters/python/worker/active.js';
|
|
8
|
+
import { trackCompletion } from './lib/completion-lifecycle.js';
|
|
9
|
+
import { createHarnessLifecycle } from './lib/harness-lifecycle.js';
|
|
10
|
+
import { didDrainTimeOut, raceDrain } from './lib/machine/index.js';
|
|
8
11
|
import { completion, imageSettingsFrom } from './lib/capabilities/completion.js';
|
|
9
12
|
import { detectLanguage } from './lib/capabilities/detect-language.js';
|
|
10
13
|
import { invoke } from './lib/capabilities/invoke.js';
|
|
@@ -24,7 +27,7 @@ import { AbortedError, raceAbort } from './lib/helpers/race-abort.js';
|
|
|
24
27
|
import { audioGenDownloadMetadata, customProjectionMap, defaultProjectionMap, defaultTtsFor, defaultVoiceModel, diffusionDownloadMetadata, curatedModelAsset, infraModelsFor, modelAddonOf, modelNameToCapabilityProfile, musicModelSet, residentSrc, setCustomModels } from './lib/models/model-catalog.js';
|
|
25
28
|
import { createModelStatusCache, customModelRows, invalidateModelStatusCache, mergeCustomRows, listModels } from './lib/models/model-list.js';
|
|
26
29
|
import { deleteModelFiles, summarizeDelete } from './lib/models/model-delete.js';
|
|
27
|
-
import { createEmbedPool, createLoadTracker, createModelPool, createVoicePool, awaitModelCapacity, evictIdle, evictIdleWhere, evictModel, evictModelWithin, isResident, leaseResidentModel, leaseCount, loadedModels, modelKey, modelOf, releaseModel, resolveLoadOptions, progressState } from './lib/models/model-pool.js';
|
|
30
|
+
import { createEmbedPool, createLoadTracker, createModelPool, createVoicePool, ModelUnloadError, awaitModelCapacity, evictIdle, evictIdleWhere, evictModel, evictModelWithin, isResident, leaseResidentModel, leaseCount, loadedModels, loadingModels, settledModels, modelKey, modelOf, releaseModel, resolveCtxSize, resolveLoadOptions, progressState } from './lib/models/model-pool.js';
|
|
28
31
|
import { defaultContextSize } from './lib/models/context.js';
|
|
29
32
|
import { resolveMainGpu } from './lib/models/gpu-selection.js';
|
|
30
33
|
import { createIdleReaper } from './lib/models/idle-unload.js';
|
|
@@ -32,6 +35,8 @@ import { openclCacheDir } from './lib/models/opencl-cache.js';
|
|
|
32
35
|
import { assertEmbedDevice, createEmbedConfig } from './lib/models/embed-config.js';
|
|
33
36
|
import { createGpuFaultState, isSdkGpuDeviceLostError, isSdkModelWedgedError } from './lib/models/sdk-faults.js';
|
|
34
37
|
import { createSdkLoader } from './lib/sdk-loader.js';
|
|
38
|
+
import { installAbortControllerPolyfill } from './lib/capabilities/completion-engine/abort-polyfill.js';
|
|
39
|
+
import { prepareCompletionRuntime } from './lib/capabilities/completion-runtime.js';
|
|
35
40
|
import { createEditImageTool } from './lib/tools/edit-image.js';
|
|
36
41
|
import { toolCatalog } from './lib/tools/catalog.js';
|
|
37
42
|
import { createDiffusionRuntime, createGenerateImageTool } from './lib/tools/generate-image.js';
|
|
@@ -41,9 +46,9 @@ import { ensureBundledSkillsDir } from './lib/tools/skills/bundled-dir.js';
|
|
|
41
46
|
import { ensureSkillsOverlayDir } from './lib/tools/skills/overlay-dir.js';
|
|
42
47
|
import { copyBaseForEdit, deleteOverlaySkill, listManagedSkills, readSkillFile, writeSkillFile } from './lib/tools/skills/overlay-crud.js';
|
|
43
48
|
import { watchSkillsOverlay } from './lib/tools/skills/overlay-watch.js';
|
|
44
|
-
import { errorMessage } from './lib/guards.js';
|
|
49
|
+
import { errorMessage, isRecord } from './lib/guards.js';
|
|
50
|
+
import { redactedRecoveryFields } from './lib/wire.js';
|
|
45
51
|
import { hlog } from './lib/log.js';
|
|
46
|
-
import { harness } from './spec/index.js';
|
|
47
52
|
import { installResolutionDetector, resolutionDetail } from './lib/resolution-detector.js';
|
|
48
53
|
import { probeBinVersions } from './lib/tools/skills/bin-check.js';
|
|
49
54
|
import { bareRuntime } from './lib/tools/exec/bare-runtime.js';
|
|
@@ -73,8 +78,8 @@ export { serveHarness } from './lib/serve.js';
|
|
|
73
78
|
export { hlog, setHarnessLog, setHarnessLogSink } from './lib/log.js';
|
|
74
79
|
export { harnessHooks } from './lib/hooks/registry.js';
|
|
75
80
|
export { EgressGuardHook, SkillCatalogGuardHook } from './lib/hooks/built-in/index.js';
|
|
76
|
-
const HarnessState = harness.getEnum('@harness/state');
|
|
77
81
|
const BIN_VERSION_PROBE_TTL_MS = 45_000;
|
|
82
|
+
const SUSPEND_SETTLE_MS = 500;
|
|
78
83
|
const TRANSCRIBE_STREAM_MIN_BYTES = 16_000 * 2 * 60;
|
|
79
84
|
const TRANSCRIPTION_MODEL_LABEL = 'transcription model';
|
|
80
85
|
const DIFFUSION_EVICT_WAIT_MS = 15_000;
|
|
@@ -101,6 +106,7 @@ export class Harness extends ReadyResource {
|
|
|
101
106
|
_models;
|
|
102
107
|
_embedModels;
|
|
103
108
|
_retiredEmbedModels;
|
|
109
|
+
_faultedEmbedKeys = new WeakMap();
|
|
104
110
|
_embedLane = createEmbedLane();
|
|
105
111
|
_embedFaults;
|
|
106
112
|
_embedGpuLayers;
|
|
@@ -109,7 +115,23 @@ export class Harness extends ReadyResource {
|
|
|
109
115
|
_ttsModels;
|
|
110
116
|
_transcriptionModels;
|
|
111
117
|
_voiceLane = createVoiceLane();
|
|
112
|
-
|
|
118
|
+
_lifecycle = createHarnessLifecycle({
|
|
119
|
+
suspend: () => this._suspendRuntimes(),
|
|
120
|
+
resume: () => this._resumeRuntimes()
|
|
121
|
+
});
|
|
122
|
+
_detachedRuns = new Set();
|
|
123
|
+
_effectDrainWaiters = new Set();
|
|
124
|
+
_activeWork = 0;
|
|
125
|
+
_workDrainWaiters = new Set();
|
|
126
|
+
_runtimeDraining = false;
|
|
127
|
+
_runtimeUnloadFailed = false;
|
|
128
|
+
_poolUnloadFailed = false;
|
|
129
|
+
_poolCleanupFailed = false;
|
|
130
|
+
_failedModelUnloads = new Set();
|
|
131
|
+
_sdkClosed = false;
|
|
132
|
+
_runtimesReleased = false;
|
|
133
|
+
_closeRetryNeeded = false;
|
|
134
|
+
_closeRequested = false;
|
|
113
135
|
_skillsDir;
|
|
114
136
|
_modelsDir;
|
|
115
137
|
_overlayWatcher = null;
|
|
@@ -134,6 +156,7 @@ export class Harness extends ReadyResource {
|
|
|
134
156
|
_musicSdk;
|
|
135
157
|
_translationSdk;
|
|
136
158
|
_musicRuntime;
|
|
159
|
+
_completionRuns;
|
|
137
160
|
_translationRuntime;
|
|
138
161
|
_capabilityTools;
|
|
139
162
|
_capability = null;
|
|
@@ -142,6 +165,7 @@ export class Harness extends ReadyResource {
|
|
|
142
165
|
_idle;
|
|
143
166
|
constructor({ tools = [], sdkLoader, skillsDir, modelsDir, projectionModel, imageModel, imageClamps, musicModel, musicClamps, llmDevice, llmGpuLayers, contextSize, kvCacheK, kvCacheV, embedDevice, embedGpuLayers, embedBatchSize, transcriptionDevice, completionModelCap, credentialStore, platform, gpuStats } = {}) {
|
|
144
167
|
super();
|
|
168
|
+
installAbortControllerPolyfill();
|
|
145
169
|
this._sdk = sdkLoader ?? createSdkLoader();
|
|
146
170
|
this._llmDevice = llmDevice;
|
|
147
171
|
this._gpuStats = gpuStats;
|
|
@@ -214,7 +238,10 @@ export class Harness extends ReadyResource {
|
|
|
214
238
|
...music,
|
|
215
239
|
loads: this._loads,
|
|
216
240
|
loadSdk: () => this._loadMusicSdk(),
|
|
217
|
-
onProgress: (model, progress) =>
|
|
241
|
+
onProgress: (model, progress) => {
|
|
242
|
+
const state = progressState(model, progress);
|
|
243
|
+
this.emit('model', state);
|
|
244
|
+
},
|
|
218
245
|
onBeforeLoad: async (signal) => {
|
|
219
246
|
if (this._models.destroyed)
|
|
220
247
|
return;
|
|
@@ -253,7 +280,6 @@ export class Harness extends ReadyResource {
|
|
|
253
280
|
...(this._musicRuntime ? [createGenerateMusicTool(this._musicRuntime, musicClamps)] : [])
|
|
254
281
|
];
|
|
255
282
|
this._projectionModel = projectionModel ?? defaultProjectionMap();
|
|
256
|
-
this._state = HarnessState.RESUME;
|
|
257
283
|
this._skillsDir = skillsDir;
|
|
258
284
|
this._modelsDir = modelsDir;
|
|
259
285
|
this._supportedSkills = [];
|
|
@@ -262,6 +288,7 @@ export class Harness extends ReadyResource {
|
|
|
262
288
|
this._platform = platform;
|
|
263
289
|
this._diffusionSdk = null;
|
|
264
290
|
this._musicSdk = null;
|
|
291
|
+
this._completionRuns = new Set();
|
|
265
292
|
this._translationSdk = null;
|
|
266
293
|
this._models = this._createPool();
|
|
267
294
|
this._embedModels = this._createEmbedPool();
|
|
@@ -274,13 +301,18 @@ export class Harness extends ReadyResource {
|
|
|
274
301
|
});
|
|
275
302
|
}
|
|
276
303
|
async _open() {
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
304
|
+
try {
|
|
305
|
+
installResolutionDetector(import.meta);
|
|
306
|
+
await this._reloadSkills();
|
|
307
|
+
await this._startOverlayWatch();
|
|
308
|
+
hlog('open', { skills: this._supportedSkills.length, tools: this._tools.length });
|
|
309
|
+
}
|
|
310
|
+
catch (error) {
|
|
311
|
+
throw error;
|
|
312
|
+
}
|
|
281
313
|
}
|
|
282
314
|
get suspended() {
|
|
283
|
-
return this.
|
|
315
|
+
return this._lifecycle.suspended;
|
|
284
316
|
}
|
|
285
317
|
get skillNames() {
|
|
286
318
|
return this._supportedSkills.map((skill) => skill.name);
|
|
@@ -393,6 +425,7 @@ export class Harness extends ReadyResource {
|
|
|
393
425
|
async skills(agentId, modelName, options) {
|
|
394
426
|
if (!this.opened)
|
|
395
427
|
await this.ready();
|
|
428
|
+
this._assertNotClosing();
|
|
396
429
|
const platform = this._platform ?? detectPlatform();
|
|
397
430
|
const refresh = options?.refresh === true;
|
|
398
431
|
return resolveSkillStatuses(this._allSkills, {
|
|
@@ -558,13 +591,22 @@ export class Harness extends ReadyResource {
|
|
|
558
591
|
};
|
|
559
592
|
}
|
|
560
593
|
_loadedModels() {
|
|
561
|
-
|
|
594
|
+
return this._pools().flatMap((pool) => loadedModels(pool));
|
|
595
|
+
}
|
|
596
|
+
_loadingModels() {
|
|
597
|
+
return new Set(this._pools().flatMap((pool) => loadingModels(pool)));
|
|
598
|
+
}
|
|
599
|
+
_settledModels() {
|
|
600
|
+
return new Set(this._pools().flatMap((pool) => settledModels(pool)));
|
|
601
|
+
}
|
|
602
|
+
_pools() {
|
|
603
|
+
return [
|
|
604
|
+
this._models,
|
|
562
605
|
this._embedModels,
|
|
563
606
|
...this._retiredEmbedModels,
|
|
564
607
|
this._ttsModels,
|
|
565
608
|
this._transcriptionModels
|
|
566
609
|
];
|
|
567
|
-
return [...loadedModels(this._models), ...pools.flatMap((pool) => loadedModels(pool))];
|
|
568
610
|
}
|
|
569
611
|
_loadPluginSdk(current, store, importPlugin) {
|
|
570
612
|
let pending = current();
|
|
@@ -632,19 +674,24 @@ export class Harness extends ReadyResource {
|
|
|
632
674
|
};
|
|
633
675
|
}
|
|
634
676
|
_createPool() {
|
|
635
|
-
const loadSdk = this.
|
|
677
|
+
const loadSdk = () => this._loadModelLifecycleSdk();
|
|
636
678
|
return createModelPool(loadSdk, (state) => this._notifyModel(state), {
|
|
637
679
|
deviceConfig: this._deviceConfig(),
|
|
638
|
-
loads: this._loads
|
|
680
|
+
loads: this._loads,
|
|
681
|
+
onUnloadFailure: (modelId, error) => this._recordModelUnloadFailure(modelId, error)
|
|
639
682
|
});
|
|
640
683
|
}
|
|
684
|
+
_loadModelLifecycleSdk() {
|
|
685
|
+
return this._sdk.load();
|
|
686
|
+
}
|
|
641
687
|
_createEmbedPool() {
|
|
642
688
|
const loadSdk = () => this._sdk.loadEmbed();
|
|
643
689
|
return createEmbedPool(loadSdk, (state) => this._notifyModel(state), {
|
|
644
690
|
faults: this._embedFaults,
|
|
645
691
|
batchSize: this._embedBatchSize,
|
|
646
692
|
openclCacheDir,
|
|
647
|
-
loads: this._loads
|
|
693
|
+
loads: this._loads,
|
|
694
|
+
onUnloadFailure: (modelId, error) => this._recordModelUnloadFailure(modelId, error)
|
|
648
695
|
});
|
|
649
696
|
}
|
|
650
697
|
_createTtsPool() {
|
|
@@ -653,7 +700,8 @@ export class Harness extends ReadyResource {
|
|
|
653
700
|
modelType: 'tts-ggml',
|
|
654
701
|
addon: 'tts',
|
|
655
702
|
modelConfig: TTS_MODEL_CONFIG,
|
|
656
|
-
loads: this._loads
|
|
703
|
+
loads: this._loads,
|
|
704
|
+
onUnloadFailure: (modelId, error) => this._recordModelUnloadFailure(modelId, error)
|
|
657
705
|
});
|
|
658
706
|
}
|
|
659
707
|
_createTranscriptionPool() {
|
|
@@ -663,12 +711,18 @@ export class Harness extends ReadyResource {
|
|
|
663
711
|
addon: 'parakeet',
|
|
664
712
|
modelConfig: { useGPU: this._transcriptionDevice !== 'cpu' },
|
|
665
713
|
openclCacheDir,
|
|
666
|
-
loads: this._loads
|
|
714
|
+
loads: this._loads,
|
|
715
|
+
onUnloadFailure: (modelId, error) => this._recordModelUnloadFailure(modelId, error)
|
|
667
716
|
});
|
|
668
717
|
}
|
|
718
|
+
_recordModelUnloadFailure(modelId, error) {
|
|
719
|
+
this._failedModelUnloads.add(modelId);
|
|
720
|
+
this._poolUnloadFailed = true;
|
|
721
|
+
this._runtimeUnloadFailed = true;
|
|
722
|
+
hlog('model unload failed', { modelId, err: errorMessage(error) });
|
|
723
|
+
}
|
|
669
724
|
_notifyModel(state) {
|
|
670
|
-
|
|
671
|
-
if (catalogChanged) {
|
|
725
|
+
if (state.status === 'ready' || state.status === 'unloaded' || state.status === 'error') {
|
|
672
726
|
invalidateModelStatusCache(this._modelStatus);
|
|
673
727
|
}
|
|
674
728
|
if (state.status === 'unloaded') {
|
|
@@ -678,33 +732,175 @@ export class Harness extends ReadyResource {
|
|
|
678
732
|
}
|
|
679
733
|
}
|
|
680
734
|
}
|
|
681
|
-
this.emit('model', {
|
|
735
|
+
this.emit('model', {
|
|
736
|
+
...state,
|
|
737
|
+
catalogChanged: state.catalogChanged === true,
|
|
738
|
+
loaded: this._loadedModels()
|
|
739
|
+
});
|
|
682
740
|
}
|
|
683
741
|
async _collectRetiredEmbedPool(pool) {
|
|
684
742
|
if (!this._retiredEmbedModels.has(pool) || pool.idle !== pool.size)
|
|
685
743
|
return;
|
|
744
|
+
try {
|
|
745
|
+
await pool.destroy();
|
|
746
|
+
}
|
|
747
|
+
catch (error) {
|
|
748
|
+
if (!(error instanceof ModelUnloadError)) {
|
|
749
|
+
this._poolCleanupFailed = true;
|
|
750
|
+
this._poolUnloadFailed = true;
|
|
751
|
+
this._runtimeUnloadFailed = true;
|
|
752
|
+
}
|
|
753
|
+
throw error;
|
|
754
|
+
}
|
|
686
755
|
this._retiredEmbedModels.delete(pool);
|
|
687
|
-
await pool.destroy();
|
|
688
756
|
}
|
|
689
757
|
async _releaseEmbedResource(pool, key) {
|
|
690
758
|
releaseModel(pool, key);
|
|
759
|
+
const faulted = this._faultedEmbedKeys.get(pool);
|
|
760
|
+
if (faulted?.has(key)) {
|
|
761
|
+
await evictIdle(pool);
|
|
762
|
+
if (!isResident(pool, key)) {
|
|
763
|
+
faulted.delete(key);
|
|
764
|
+
}
|
|
765
|
+
}
|
|
691
766
|
await this._collectRetiredEmbedPool(pool);
|
|
692
767
|
}
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
768
|
+
async _releaseEmbedResourceBeforeWork(releasing, releaseWork) {
|
|
769
|
+
try {
|
|
770
|
+
await releasing;
|
|
771
|
+
}
|
|
772
|
+
finally {
|
|
773
|
+
releaseWork();
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
_markEmbedFault({ pool, key, faults, generation }) {
|
|
777
|
+
faults.recordFault(generation);
|
|
778
|
+
let faulted = this._faultedEmbedKeys.get(pool);
|
|
779
|
+
if (!faulted) {
|
|
780
|
+
faulted = new Set();
|
|
781
|
+
this._faultedEmbedKeys.set(pool, faulted);
|
|
782
|
+
}
|
|
783
|
+
faulted.add(key);
|
|
784
|
+
}
|
|
785
|
+
_releaseEmbedResourceAfter(settled, fault) {
|
|
786
|
+
return settled
|
|
787
|
+
.then(() => this._releaseEmbedResource(fault.pool, fault.key), (error) => {
|
|
788
|
+
if (isSdkGpuDeviceLostError(error)) {
|
|
789
|
+
this._markEmbedFault(fault);
|
|
790
|
+
}
|
|
791
|
+
return this._releaseEmbedResource(fault.pool, fault.key);
|
|
792
|
+
})
|
|
696
793
|
.catch(noop);
|
|
697
794
|
}
|
|
698
795
|
async _destroyEmbedPools() {
|
|
699
|
-
const pools = [this._embedModels, ...this._retiredEmbedModels];
|
|
700
|
-
|
|
701
|
-
|
|
796
|
+
const pools = [this._embedModels, ...this._retiredEmbedModels].filter((pool) => Boolean(pool));
|
|
797
|
+
const outcomes = await Promise.allSettled(pools.map(async (pool) => {
|
|
798
|
+
await pool.destroy();
|
|
799
|
+
this._retiredEmbedModels.delete(pool);
|
|
800
|
+
}));
|
|
801
|
+
const generic = outcomes.find((outcome) => outcome.status === 'rejected' && !(outcome.reason instanceof ModelUnloadError));
|
|
802
|
+
if (generic?.status === 'rejected') {
|
|
803
|
+
throw generic.reason;
|
|
804
|
+
}
|
|
805
|
+
const failed = outcomes.find((outcome) => outcome.status === 'rejected');
|
|
806
|
+
if (failed?.status === 'rejected') {
|
|
807
|
+
throw failed.reason;
|
|
808
|
+
}
|
|
702
809
|
}
|
|
703
810
|
_enqueueEmbedControl(run) {
|
|
704
811
|
const next = this._embedControl.then(run, run);
|
|
705
812
|
this._embedControl = next.catch(noop);
|
|
706
813
|
return next;
|
|
707
814
|
}
|
|
815
|
+
async _runLifecycle(type, { open = true } = {}) {
|
|
816
|
+
if (!this.opened) {
|
|
817
|
+
if (open) {
|
|
818
|
+
await this.ready();
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
if (this._closeRequested || this.closing) {
|
|
822
|
+
return;
|
|
823
|
+
}
|
|
824
|
+
await this._lifecycle.send(type);
|
|
825
|
+
}
|
|
826
|
+
_abortCompletions() {
|
|
827
|
+
for (const run of this._completionRuns) {
|
|
828
|
+
run.abort();
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
_assertNotClosing() {
|
|
832
|
+
if (this._closeRequested || this.closing || this.closed) {
|
|
833
|
+
throw new Error('harness is closing');
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
_enterWork() {
|
|
837
|
+
this._assertNotClosing();
|
|
838
|
+
if (this._runtimeDraining) {
|
|
839
|
+
throw new Error('harness is suspended');
|
|
840
|
+
}
|
|
841
|
+
if (this._runtimeUnloadFailed) {
|
|
842
|
+
throw new Error('Runtime unload failed; close the harness before loading models again');
|
|
843
|
+
}
|
|
844
|
+
this._activeWork++;
|
|
845
|
+
let released = false;
|
|
846
|
+
return () => {
|
|
847
|
+
if (released)
|
|
848
|
+
return;
|
|
849
|
+
released = true;
|
|
850
|
+
if (--this._activeWork !== 0) {
|
|
851
|
+
return;
|
|
852
|
+
}
|
|
853
|
+
for (const resolve of this._workDrainWaiters) {
|
|
854
|
+
resolve();
|
|
855
|
+
}
|
|
856
|
+
this._workDrainWaiters.clear();
|
|
857
|
+
};
|
|
858
|
+
}
|
|
859
|
+
_waitForWorkDrain() {
|
|
860
|
+
if (!this._activeWork) {
|
|
861
|
+
return Promise.resolve();
|
|
862
|
+
}
|
|
863
|
+
return new Promise((resolve) => this._workDrainWaiters.add(resolve));
|
|
864
|
+
}
|
|
865
|
+
_effectsSettled() {
|
|
866
|
+
return !this._detachedRuns.size && ![...this._completionRuns].some((run) => run.started);
|
|
867
|
+
}
|
|
868
|
+
_notifyEffectDrain() {
|
|
869
|
+
if (!this._effectsSettled())
|
|
870
|
+
return;
|
|
871
|
+
for (const resolve of this._effectDrainWaiters) {
|
|
872
|
+
resolve();
|
|
873
|
+
}
|
|
874
|
+
this._effectDrainWaiters.clear();
|
|
875
|
+
}
|
|
876
|
+
_waitForEffectDrain() {
|
|
877
|
+
if (this._effectsSettled()) {
|
|
878
|
+
return Promise.resolve();
|
|
879
|
+
}
|
|
880
|
+
return new Promise((resolve) => this._effectDrainWaiters.add(resolve));
|
|
881
|
+
}
|
|
882
|
+
_trackDetachedRun(pending, runId) {
|
|
883
|
+
const tracked = pending.catch((error) => {
|
|
884
|
+
hlog('detached completion effect failed', { runId, err: errorMessage(error) });
|
|
885
|
+
});
|
|
886
|
+
this._detachedRuns.add(tracked);
|
|
887
|
+
void tracked
|
|
888
|
+
.finally(() => {
|
|
889
|
+
this._detachedRuns.delete(tracked);
|
|
890
|
+
this._notifyEffectDrain();
|
|
891
|
+
})
|
|
892
|
+
.catch(noop);
|
|
893
|
+
}
|
|
894
|
+
async _closeCompletions(runs = this._completionRuns) {
|
|
895
|
+
const closed = await Promise.allSettled([...runs].map((run) => run.close()));
|
|
896
|
+
for (const outcome of closed) {
|
|
897
|
+
if (outcome.status === 'rejected') {
|
|
898
|
+
hlog('completion close failed', {
|
|
899
|
+
err: errorMessage(outcome.reason)
|
|
900
|
+
});
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
}
|
|
708
904
|
getEmbeddingDevice() {
|
|
709
905
|
return this._embedFaults.device;
|
|
710
906
|
}
|
|
@@ -715,43 +911,61 @@ export class Harness extends ReadyResource {
|
|
|
715
911
|
async _setEmbeddingDevice(device) {
|
|
716
912
|
if (!this.opened)
|
|
717
913
|
await this.ready();
|
|
718
|
-
if (this.closing)
|
|
719
|
-
throw new Error('harness is closing');
|
|
720
|
-
if (this._embedFaults.device === device) {
|
|
721
|
-
return;
|
|
722
|
-
}
|
|
723
|
-
this._embedFaults = createGpuFaultState({ device, gpuLayers: this._embedGpuLayers });
|
|
724
914
|
if (this.suspended) {
|
|
915
|
+
this._assertNotClosing();
|
|
916
|
+
if (this._embedFaults.device === device) {
|
|
917
|
+
return;
|
|
918
|
+
}
|
|
919
|
+
this._embedFaults = createGpuFaultState({ device, gpuLayers: this._embedGpuLayers });
|
|
725
920
|
return;
|
|
726
921
|
}
|
|
727
|
-
const
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
922
|
+
const releaseWork = this._enterWork();
|
|
923
|
+
try {
|
|
924
|
+
if (this._embedFaults.device === device) {
|
|
925
|
+
return;
|
|
926
|
+
}
|
|
927
|
+
this._embedFaults = createGpuFaultState({ device, gpuLayers: this._embedGpuLayers });
|
|
928
|
+
if (this.suspended) {
|
|
929
|
+
return;
|
|
930
|
+
}
|
|
931
|
+
const retired = this._embedModels;
|
|
932
|
+
this._retiredEmbedModels.add(retired);
|
|
933
|
+
this._embedModels = this._createEmbedPool();
|
|
934
|
+
await this._collectRetiredEmbedPool(retired);
|
|
935
|
+
}
|
|
936
|
+
finally {
|
|
937
|
+
releaseWork();
|
|
938
|
+
}
|
|
731
939
|
}
|
|
732
940
|
async presuspend() {
|
|
733
|
-
|
|
734
|
-
return;
|
|
735
|
-
this._state = HarnessState.PRESUSPEND;
|
|
736
|
-
hlog('presuspend');
|
|
941
|
+
await this._runLifecycle('PRESUSPEND', { open: false });
|
|
737
942
|
}
|
|
738
943
|
async suspend() {
|
|
739
|
-
|
|
740
|
-
if (
|
|
741
|
-
|
|
742
|
-
}
|
|
743
|
-
if (this.closing) {
|
|
744
|
-
return;
|
|
944
|
+
const result = await raceDrain(this._runLifecycle('SUSPEND'), SUSPEND_SETTLE_MS);
|
|
945
|
+
if (didDrainTimeOut(result)) {
|
|
946
|
+
throw new Error('Harness suspend is still waiting for active work to settle');
|
|
745
947
|
}
|
|
746
|
-
|
|
948
|
+
}
|
|
949
|
+
async _suspendRuntimes() {
|
|
950
|
+
this._runtimeDraining = true;
|
|
747
951
|
hlog('suspending', { models: loadedModels(this._models).map((entry) => entry.model) });
|
|
952
|
+
this._abortCompletions();
|
|
953
|
+
this._loads.drain();
|
|
954
|
+
await this._closeCompletions([...this._completionRuns].filter((run) => run.started));
|
|
955
|
+
await this._waitForEffectDrain();
|
|
748
956
|
const pending = this._sdk.peek();
|
|
749
957
|
if (pending) {
|
|
750
|
-
|
|
958
|
+
const sdk = await pending.catch(noop);
|
|
959
|
+
if (sdk) {
|
|
960
|
+
this._loads.drain(sdk);
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
await this._waitForWorkDrain();
|
|
964
|
+
if (!(await this._releaseRuntimes())) {
|
|
965
|
+
throw new Error('Runtime unload failed; close the harness before loading models again');
|
|
751
966
|
}
|
|
752
|
-
await this._releaseRuntimes();
|
|
753
|
-
await this._sdkLifecycle('suspend');
|
|
754
967
|
invalidateModelStatusCache(this._modelStatus);
|
|
968
|
+
await this._sdkLifecycle('suspend');
|
|
755
969
|
hlog('suspended');
|
|
756
970
|
}
|
|
757
971
|
async _sdkLifecycle(op) {
|
|
@@ -765,35 +979,60 @@ export class Harness extends ReadyResource {
|
|
|
765
979
|
catch (err) {
|
|
766
980
|
hlog(`sdk ${op} failed`, { err: errorMessage(err) });
|
|
767
981
|
if (op === 'suspend') {
|
|
768
|
-
|
|
982
|
+
throw err;
|
|
769
983
|
}
|
|
770
984
|
await sdk.resume().catch((retryErr) => {
|
|
771
985
|
hlog('sdk resume retry failed', {
|
|
772
986
|
err: errorMessage(retryErr)
|
|
773
987
|
});
|
|
988
|
+
throw retryErr;
|
|
774
989
|
});
|
|
775
990
|
}
|
|
776
991
|
}
|
|
777
992
|
async _releaseRuntimes() {
|
|
778
993
|
const released = await Promise.allSettled([
|
|
779
|
-
this._models
|
|
994
|
+
this._models?.destroy(),
|
|
780
995
|
this._destroyEmbedPools(),
|
|
781
|
-
this._ttsModels
|
|
782
|
-
this._transcriptionModels
|
|
996
|
+
this._ttsModels?.destroy(),
|
|
997
|
+
this._transcriptionModels?.destroy(),
|
|
783
998
|
this._imageRuntime?.unload(),
|
|
784
999
|
this._musicRuntime?.unload(),
|
|
785
1000
|
this._translationRuntime.unload()
|
|
786
1001
|
]);
|
|
787
|
-
for (const outcome of released) {
|
|
1002
|
+
for (const [index, outcome] of released.entries()) {
|
|
788
1003
|
if (outcome.status !== 'rejected')
|
|
789
1004
|
continue;
|
|
1005
|
+
if (index < 4 && !(outcome.reason instanceof ModelUnloadError)) {
|
|
1006
|
+
this._poolCleanupFailed = true;
|
|
1007
|
+
}
|
|
790
1008
|
const err = outcome.reason;
|
|
791
1009
|
hlog('unload failed', { err: errorMessage(err) });
|
|
792
1010
|
}
|
|
1011
|
+
const sdk = await this._sdk.peek()?.catch(noop);
|
|
1012
|
+
if (sdk) {
|
|
1013
|
+
for (const modelId of this._failedModelUnloads) {
|
|
1014
|
+
try {
|
|
1015
|
+
await sdk.unloadModel({ modelId });
|
|
1016
|
+
this._failedModelUnloads.delete(modelId);
|
|
1017
|
+
invalidateModelStatusCache(this._modelStatus);
|
|
1018
|
+
}
|
|
1019
|
+
catch (error) {
|
|
1020
|
+
hlog('model unload retry failed', {
|
|
1021
|
+
modelId,
|
|
1022
|
+
err: errorMessage(error)
|
|
1023
|
+
});
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
this._poolUnloadFailed = this._poolCleanupFailed || this._failedModelUnloads.size > 0;
|
|
1028
|
+
this._runtimeUnloadFailed =
|
|
1029
|
+
this._poolUnloadFailed || released.slice(4).some((outcome) => outcome.status === 'rejected');
|
|
1030
|
+
return !this._runtimeUnloadFailed;
|
|
793
1031
|
}
|
|
794
1032
|
async _unloadIdleModels() {
|
|
795
|
-
if (!this.opened || this.closing || this.suspended)
|
|
1033
|
+
if (!this.opened || this.closing || this.suspended || this._runtimeDraining) {
|
|
796
1034
|
return;
|
|
1035
|
+
}
|
|
797
1036
|
hlog('idle unload', { models: this._loadedModels().map((entry) => entry.model) });
|
|
798
1037
|
const released = await Promise.allSettled([
|
|
799
1038
|
evictIdle(this._models),
|
|
@@ -813,62 +1052,164 @@ export class Harness extends ReadyResource {
|
|
|
813
1052
|
invalidateModelStatusCache(this._modelStatus);
|
|
814
1053
|
}
|
|
815
1054
|
async resume() {
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
if (this.
|
|
821
|
-
|
|
822
|
-
}
|
|
823
|
-
if (this._state === HarnessState.PRESUSPEND) {
|
|
824
|
-
this._state = HarnessState.RESUME;
|
|
825
|
-
return;
|
|
1055
|
+
await this._runLifecycle('RESUME');
|
|
1056
|
+
}
|
|
1057
|
+
async _resumeRuntimes() {
|
|
1058
|
+
this._assertNotClosing();
|
|
1059
|
+
if (this._runtimeUnloadFailed) {
|
|
1060
|
+
throw new Error('Runtime unload failed; close the harness before loading models again');
|
|
826
1061
|
}
|
|
827
|
-
this._state = HarnessState.RESUME;
|
|
828
1062
|
hlog('resuming', { pools: 'recreated' });
|
|
829
1063
|
this._loads.undrain();
|
|
830
1064
|
this._models = this._createPool();
|
|
831
1065
|
this._embedModels = this._createEmbedPool();
|
|
832
1066
|
this._ttsModels = this._createTtsPool();
|
|
833
1067
|
this._transcriptionModels = this._createTranscriptionPool();
|
|
834
|
-
|
|
1068
|
+
try {
|
|
1069
|
+
await this._sdkLifecycle('resume');
|
|
1070
|
+
this._runtimeDraining = false;
|
|
1071
|
+
}
|
|
1072
|
+
catch (error) {
|
|
1073
|
+
const sdk = await this._sdk.peek()?.catch(noop);
|
|
1074
|
+
if (sdk) {
|
|
1075
|
+
this._loads.drain(sdk);
|
|
1076
|
+
}
|
|
1077
|
+
await this._releaseRuntimes();
|
|
1078
|
+
throw error;
|
|
1079
|
+
}
|
|
835
1080
|
}
|
|
836
1081
|
async wakeup() {
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
1082
|
+
await this._runLifecycle('WAKEUP');
|
|
1083
|
+
}
|
|
1084
|
+
ready() {
|
|
1085
|
+
if (this._closeRequested || this.closing || this.closed) {
|
|
1086
|
+
return Promise.reject(new Error('harness is closing'));
|
|
1087
|
+
}
|
|
1088
|
+
return super.ready();
|
|
842
1089
|
}
|
|
843
1090
|
async _close() {
|
|
844
1091
|
hlog('closing');
|
|
845
1092
|
this._idle.close();
|
|
846
|
-
this.
|
|
847
|
-
this.
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
1093
|
+
this._lifecycle.cancelPending();
|
|
1094
|
+
this._abortCompletions();
|
|
1095
|
+
this._loads.drain();
|
|
1096
|
+
await this._lifecycle.settled();
|
|
1097
|
+
await this._finishClose();
|
|
1098
|
+
}
|
|
1099
|
+
close() {
|
|
1100
|
+
this._closeRequested = true;
|
|
1101
|
+
if (this._closeRetryNeeded) {
|
|
1102
|
+
this.closing = null;
|
|
1103
|
+
this._closeRetryNeeded = false;
|
|
1104
|
+
}
|
|
1105
|
+
const pending = !this.opened && this.opening !== null ? this._closeWhileOpening() : super.close();
|
|
1106
|
+
void pending.catch(() => {
|
|
1107
|
+
if (this.closing === pending) {
|
|
1108
|
+
this._closeRetryNeeded = true;
|
|
1109
|
+
}
|
|
1110
|
+
});
|
|
1111
|
+
const bounded = raceDrain(pending).then((result) => {
|
|
1112
|
+
if (didDrainTimeOut(result)) {
|
|
1113
|
+
throw new Error('Harness close is still waiting for active work to settle');
|
|
1114
|
+
}
|
|
1115
|
+
});
|
|
1116
|
+
void bounded.catch(noop);
|
|
1117
|
+
return bounded;
|
|
1118
|
+
}
|
|
1119
|
+
_closeWhileOpening() {
|
|
1120
|
+
if (this.closing)
|
|
1121
|
+
return this.closing;
|
|
1122
|
+
const opening = this.opening;
|
|
1123
|
+
const pending = (async () => {
|
|
1124
|
+
await opening?.catch(noop);
|
|
1125
|
+
this._abortCompletions();
|
|
1126
|
+
this._loads.drain();
|
|
1127
|
+
await this._finishClose();
|
|
1128
|
+
this.closed = true;
|
|
1129
|
+
this.emit('close');
|
|
1130
|
+
})();
|
|
1131
|
+
this.closing = pending;
|
|
1132
|
+
return pending;
|
|
1133
|
+
}
|
|
1134
|
+
async _finishClose() {
|
|
1135
|
+
let completed = false;
|
|
1136
|
+
try {
|
|
1137
|
+
const pending = this._sdk.peek();
|
|
1138
|
+
if (pending) {
|
|
1139
|
+
const sdk = await pending.catch(noop);
|
|
1140
|
+
if (sdk) {
|
|
1141
|
+
this._loads.drain(sdk);
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
await this._closeCompletions();
|
|
1145
|
+
await this._waitForEffectDrain();
|
|
1146
|
+
await this._waitForWorkDrain();
|
|
1147
|
+
this._overlayWatcher?.stop();
|
|
1148
|
+
this._overlayWatcher = null;
|
|
1149
|
+
if (!this._runtimesReleased) {
|
|
1150
|
+
let [released] = await Promise.all([this._releaseRuntimes(), closePythonWorker()]);
|
|
1151
|
+
if (!released && !this._poolUnloadFailed) {
|
|
1152
|
+
released = await this._releaseRuntimes();
|
|
1153
|
+
}
|
|
1154
|
+
this._runtimesReleased = released;
|
|
1155
|
+
}
|
|
1156
|
+
let sdkCloseFailed = false;
|
|
1157
|
+
let sdkCloseError;
|
|
1158
|
+
const sdk = !this._sdkClosed && !this._runtimeUnloadFailed ? await this._sdk.peek()?.catch(noop) : null;
|
|
1159
|
+
if (sdk) {
|
|
1160
|
+
try {
|
|
1161
|
+
await sdk.close();
|
|
1162
|
+
this._sdkClosed = true;
|
|
1163
|
+
}
|
|
1164
|
+
catch (error) {
|
|
1165
|
+
sdkCloseFailed = true;
|
|
1166
|
+
sdkCloseError = error;
|
|
1167
|
+
hlog('sdk close failed', { err: errorMessage(error) });
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
if (sdkCloseFailed) {
|
|
1171
|
+
throw sdkCloseError;
|
|
1172
|
+
}
|
|
1173
|
+
if (this._runtimeUnloadFailed) {
|
|
1174
|
+
throw new Error('Runtime unload failed; model cleanup could not be confirmed');
|
|
1175
|
+
}
|
|
1176
|
+
completed = true;
|
|
851
1177
|
}
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
1178
|
+
finally {
|
|
1179
|
+
if (completed) {
|
|
1180
|
+
this._lifecycle.stop();
|
|
1181
|
+
}
|
|
856
1182
|
}
|
|
857
1183
|
}
|
|
858
1184
|
download(args) {
|
|
1185
|
+
this._assertNotClosing();
|
|
1186
|
+
let releaseWork = null;
|
|
859
1187
|
const projection = (args.addon ?? 'llm') === 'llm' ? this._projectionFor(args.model) : undefined;
|
|
860
1188
|
const multiFile = diffusionDownloadMetadata(args.model, args.addon) ??
|
|
861
1189
|
audioGenDownloadMetadata(args.model, args.addon);
|
|
862
1190
|
return download(async () => {
|
|
863
1191
|
if (!this.opened)
|
|
864
1192
|
await this.ready();
|
|
865
|
-
|
|
1193
|
+
releaseWork = this._enterWork();
|
|
1194
|
+
try {
|
|
1195
|
+
const sdk = await this._sdk.load();
|
|
1196
|
+
this._assertNotClosing();
|
|
1197
|
+
return sdk;
|
|
1198
|
+
}
|
|
1199
|
+
catch (error) {
|
|
1200
|
+
releaseWork?.();
|
|
1201
|
+
releaseWork = null;
|
|
1202
|
+
throw error;
|
|
1203
|
+
}
|
|
866
1204
|
}, {
|
|
867
1205
|
...args,
|
|
868
1206
|
projection,
|
|
869
1207
|
companions: multiFile?.companions,
|
|
870
1208
|
assetSizes: multiFile?.assetSizes
|
|
871
|
-
}, () => invalidateModelStatusCache(this._modelStatus))
|
|
1209
|
+
}, () => invalidateModelStatusCache(this._modelStatus), () => {
|
|
1210
|
+
releaseWork?.();
|
|
1211
|
+
releaseWork = null;
|
|
1212
|
+
});
|
|
872
1213
|
}
|
|
873
1214
|
embed(context) {
|
|
874
1215
|
return this._busy(() => this._embed(context));
|
|
@@ -876,8 +1217,10 @@ export class Harness extends ReadyResource {
|
|
|
876
1217
|
async _embed({ model, texts, signal }) {
|
|
877
1218
|
if (!this.opened)
|
|
878
1219
|
await this.ready();
|
|
879
|
-
|
|
1220
|
+
this._assertNotClosing();
|
|
1221
|
+
if (this.suspended) {
|
|
880
1222
|
throw new Error('harness is suspended');
|
|
1223
|
+
}
|
|
881
1224
|
if (texts.length === 0) {
|
|
882
1225
|
return [];
|
|
883
1226
|
}
|
|
@@ -885,13 +1228,23 @@ export class Harness extends ReadyResource {
|
|
|
885
1228
|
const faults = this._embedFaults;
|
|
886
1229
|
const generation = faults.generation;
|
|
887
1230
|
const key = faults.key(residentSrc(model, 'embeddings'));
|
|
888
|
-
const
|
|
1231
|
+
const releaseWork = this._enterWork();
|
|
1232
|
+
let handle;
|
|
1233
|
+
try {
|
|
1234
|
+
handle = pool.open(key, model);
|
|
1235
|
+
}
|
|
1236
|
+
catch (error) {
|
|
1237
|
+
releaseWork();
|
|
1238
|
+
throw error;
|
|
1239
|
+
}
|
|
889
1240
|
let loaded = null;
|
|
890
1241
|
let job = null;
|
|
891
1242
|
let releaseAfter = null;
|
|
1243
|
+
let drainWorkAfter = null;
|
|
892
1244
|
try {
|
|
893
1245
|
loaded = await raceAbort(handle.load(), signal);
|
|
894
1246
|
const sdk = await this._sdk.loadEmbed();
|
|
1247
|
+
this._assertNotClosing();
|
|
895
1248
|
job = this._embedLane(sdk, { modelId: loaded.modelId, texts });
|
|
896
1249
|
const vectors = await raceAbort(job, signal);
|
|
897
1250
|
faults.reset();
|
|
@@ -899,26 +1252,35 @@ export class Harness extends ReadyResource {
|
|
|
899
1252
|
}
|
|
900
1253
|
catch (err) {
|
|
901
1254
|
if (err instanceof AbortedError) {
|
|
902
|
-
|
|
1255
|
+
if (job || this._retiredEmbedModels.has(pool)) {
|
|
1256
|
+
releaseAfter = job ?? handle.peek();
|
|
1257
|
+
}
|
|
1258
|
+
else {
|
|
1259
|
+
drainWorkAfter = handle.peek();
|
|
1260
|
+
}
|
|
903
1261
|
}
|
|
904
1262
|
if (isSdkGpuDeviceLostError(err)) {
|
|
905
|
-
|
|
906
|
-
if (loaded) {
|
|
907
|
-
const dead = loaded.modelId;
|
|
908
|
-
await this._sdk
|
|
909
|
-
.loadEmbed()
|
|
910
|
-
.then((sdk) => sdk.unloadModel({ modelId: dead }))
|
|
911
|
-
.catch(noop);
|
|
912
|
-
}
|
|
1263
|
+
this._markEmbedFault({ pool, key, faults, generation });
|
|
913
1264
|
}
|
|
914
1265
|
throw err;
|
|
915
1266
|
}
|
|
916
1267
|
finally {
|
|
917
1268
|
if (releaseAfter) {
|
|
918
|
-
this._releaseEmbedResourceAfter(releaseAfter,
|
|
1269
|
+
void this._releaseEmbedResourceAfter(releaseAfter, {
|
|
1270
|
+
pool,
|
|
1271
|
+
key,
|
|
1272
|
+
faults,
|
|
1273
|
+
generation
|
|
1274
|
+
}).finally(releaseWork);
|
|
919
1275
|
}
|
|
920
1276
|
else {
|
|
921
|
-
|
|
1277
|
+
const releasing = this._releaseEmbedResource(pool, key);
|
|
1278
|
+
if (drainWorkAfter) {
|
|
1279
|
+
void Promise.allSettled([releasing, drainWorkAfter]).then(releaseWork);
|
|
1280
|
+
}
|
|
1281
|
+
else {
|
|
1282
|
+
await this._releaseEmbedResourceBeforeWork(releasing, releaseWork);
|
|
1283
|
+
}
|
|
922
1284
|
}
|
|
923
1285
|
}
|
|
924
1286
|
}
|
|
@@ -941,7 +1303,16 @@ export class Harness extends ReadyResource {
|
|
|
941
1303
|
throw new Error('harness is suspended');
|
|
942
1304
|
const addon = category === 'tts' ? 'tts' : 'parakeet';
|
|
943
1305
|
const name = residentSrc(model || (await this._defaultVoiceSrc(category)), addon);
|
|
944
|
-
|
|
1306
|
+
this._assertNotClosing();
|
|
1307
|
+
const releaseWork = this._enterWork();
|
|
1308
|
+
let handle;
|
|
1309
|
+
try {
|
|
1310
|
+
handle = pool.open(name, name);
|
|
1311
|
+
}
|
|
1312
|
+
catch (error) {
|
|
1313
|
+
releaseWork();
|
|
1314
|
+
throw error;
|
|
1315
|
+
}
|
|
945
1316
|
let job = null;
|
|
946
1317
|
let releaseAfter = null;
|
|
947
1318
|
let sdk = null;
|
|
@@ -949,6 +1320,7 @@ export class Harness extends ReadyResource {
|
|
|
949
1320
|
try {
|
|
950
1321
|
modelId = (await raceAbort(handle.load(), signal)).modelId;
|
|
951
1322
|
sdk = (await this._sdk.loadVoice());
|
|
1323
|
+
this._assertNotClosing();
|
|
952
1324
|
const boundSdk = sdk;
|
|
953
1325
|
const boundId = modelId;
|
|
954
1326
|
job = this._voiceLane(() => run(boundSdk, boundId));
|
|
@@ -959,7 +1331,7 @@ export class Harness extends ReadyResource {
|
|
|
959
1331
|
if (sdk && modelId) {
|
|
960
1332
|
void sdk.cancel({ modelId, operation: 'inference' }).catch(noop);
|
|
961
1333
|
}
|
|
962
|
-
releaseAfter = job;
|
|
1334
|
+
releaseAfter = job ?? handle.peek();
|
|
963
1335
|
}
|
|
964
1336
|
throw err;
|
|
965
1337
|
}
|
|
@@ -968,10 +1340,15 @@ export class Harness extends ReadyResource {
|
|
|
968
1340
|
releaseModel(pool, name);
|
|
969
1341
|
};
|
|
970
1342
|
if (releaseAfter) {
|
|
971
|
-
void releaseAfter.then(release, release);
|
|
1343
|
+
void releaseAfter.then(release, release).finally(releaseWork).catch(noop);
|
|
972
1344
|
}
|
|
973
1345
|
else {
|
|
974
|
-
|
|
1346
|
+
try {
|
|
1347
|
+
release();
|
|
1348
|
+
}
|
|
1349
|
+
finally {
|
|
1350
|
+
releaseWork();
|
|
1351
|
+
}
|
|
975
1352
|
}
|
|
976
1353
|
}
|
|
977
1354
|
}
|
|
@@ -1009,9 +1386,18 @@ export class Harness extends ReadyResource {
|
|
|
1009
1386
|
if (this.suspended)
|
|
1010
1387
|
throw new Error('harness is suspended');
|
|
1011
1388
|
const name = residentSrc(model || (await this._defaultVoiceSrc('tts')), 'tts');
|
|
1389
|
+
this._assertNotClosing();
|
|
1012
1390
|
const pool = this._ttsModels;
|
|
1013
|
-
const handle = pool.open(name, name);
|
|
1014
1391
|
const chunks = pushQueue();
|
|
1392
|
+
const releaseWork = this._enterWork();
|
|
1393
|
+
let handle;
|
|
1394
|
+
try {
|
|
1395
|
+
handle = pool.open(name, name);
|
|
1396
|
+
}
|
|
1397
|
+
catch (error) {
|
|
1398
|
+
releaseWork();
|
|
1399
|
+
throw error;
|
|
1400
|
+
}
|
|
1015
1401
|
let sdk = null;
|
|
1016
1402
|
let modelId = null;
|
|
1017
1403
|
const cancel = () => {
|
|
@@ -1024,6 +1410,7 @@ export class Harness extends ReadyResource {
|
|
|
1024
1410
|
try {
|
|
1025
1411
|
modelId = (await handle.load()).modelId;
|
|
1026
1412
|
sdk = (await this._sdk.loadVoice());
|
|
1413
|
+
this._assertNotClosing();
|
|
1027
1414
|
if (signal?.aborted) {
|
|
1028
1415
|
return;
|
|
1029
1416
|
}
|
|
@@ -1046,53 +1433,98 @@ export class Harness extends ReadyResource {
|
|
|
1046
1433
|
if (!settled) {
|
|
1047
1434
|
cancel();
|
|
1048
1435
|
}
|
|
1049
|
-
void job
|
|
1050
|
-
|
|
1051
|
-
|
|
1436
|
+
void job
|
|
1437
|
+
.catch(noop)
|
|
1438
|
+
.finally(() => {
|
|
1439
|
+
try {
|
|
1440
|
+
releaseModel(pool, name);
|
|
1441
|
+
}
|
|
1442
|
+
finally {
|
|
1443
|
+
releaseWork();
|
|
1444
|
+
}
|
|
1445
|
+
})
|
|
1446
|
+
.catch(noop);
|
|
1052
1447
|
}
|
|
1053
1448
|
}
|
|
1054
1449
|
async list(options = {}) {
|
|
1055
1450
|
if (!this.opened)
|
|
1056
1451
|
await this.ready();
|
|
1057
|
-
const
|
|
1058
|
-
|
|
1059
|
-
|
|
1452
|
+
const releaseWork = this._enterWork();
|
|
1453
|
+
try {
|
|
1454
|
+
const sdk = await this._sdk.load();
|
|
1455
|
+
this._assertNotClosing();
|
|
1456
|
+
const rows = await listModels(sdk, options, {
|
|
1060
1457
|
cache: this._modelStatus,
|
|
1061
1458
|
projectionFor: (name) => this._projectionFor(name)
|
|
1062
|
-
})
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1459
|
+
});
|
|
1460
|
+
const loaded = this._settledModels();
|
|
1461
|
+
const loading = this._loadingModels();
|
|
1462
|
+
const custom = await customModelRows(options, { modelsDir: this._modelsDir, loaded });
|
|
1463
|
+
return mergeCustomRows(rows, custom).map((row) => ({
|
|
1464
|
+
...row,
|
|
1465
|
+
isLoading: loading.has(row.name)
|
|
1466
|
+
}));
|
|
1467
|
+
}
|
|
1468
|
+
finally {
|
|
1469
|
+
releaseWork();
|
|
1470
|
+
}
|
|
1069
1471
|
}
|
|
1070
1472
|
async deleteModel(name) {
|
|
1071
1473
|
if (!this.opened)
|
|
1072
1474
|
await this.ready();
|
|
1073
|
-
const
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1475
|
+
const releaseWork = this._enterWork();
|
|
1476
|
+
try {
|
|
1477
|
+
const sdk = await this._sdk.load();
|
|
1478
|
+
this._assertNotClosing();
|
|
1479
|
+
const result = await deleteModelFiles(sdk, name, { modelsDir: this._modelsDir });
|
|
1480
|
+
invalidateModelStatusCache(this._modelStatus);
|
|
1481
|
+
return summarizeDelete(result);
|
|
1482
|
+
}
|
|
1483
|
+
finally {
|
|
1484
|
+
releaseWork();
|
|
1485
|
+
}
|
|
1077
1486
|
}
|
|
1078
1487
|
async clearKvCache() {
|
|
1079
1488
|
if (!this.opened)
|
|
1080
1489
|
await this.ready();
|
|
1081
|
-
|
|
1490
|
+
const releaseWork = this._enterWork();
|
|
1491
|
+
try {
|
|
1492
|
+
return await deleteAllKvCaches(() => {
|
|
1493
|
+
this._assertNotClosing();
|
|
1494
|
+
return this._sdk.load();
|
|
1495
|
+
});
|
|
1496
|
+
}
|
|
1497
|
+
finally {
|
|
1498
|
+
releaseWork();
|
|
1499
|
+
}
|
|
1082
1500
|
}
|
|
1083
1501
|
async deleteKvCache(kvCacheKey) {
|
|
1084
1502
|
if (!this.opened)
|
|
1085
1503
|
await this.ready();
|
|
1086
|
-
|
|
1504
|
+
const releaseWork = this._enterWork();
|
|
1505
|
+
try {
|
|
1506
|
+
return await deleteKvCacheKey(() => {
|
|
1507
|
+
this._assertNotClosing();
|
|
1508
|
+
return this._sdk.load();
|
|
1509
|
+
}, kvCacheKey);
|
|
1510
|
+
}
|
|
1511
|
+
finally {
|
|
1512
|
+
releaseWork();
|
|
1513
|
+
}
|
|
1087
1514
|
}
|
|
1088
1515
|
async infraModels(profile) {
|
|
1089
1516
|
return infraModelsFor(profile);
|
|
1090
1517
|
}
|
|
1091
1518
|
async capability() {
|
|
1092
|
-
if (this.
|
|
1519
|
+
if (!this.opened)
|
|
1520
|
+
await this.ready();
|
|
1521
|
+
this._assertNotClosing();
|
|
1522
|
+
if (this._capability) {
|
|
1093
1523
|
return this._capability;
|
|
1094
|
-
|
|
1524
|
+
}
|
|
1525
|
+
if (this._capabilityInflight) {
|
|
1095
1526
|
return this._capabilityInflight;
|
|
1527
|
+
}
|
|
1096
1528
|
const reads = {
|
|
1097
1529
|
totalmem,
|
|
1098
1530
|
platform,
|
|
@@ -1118,6 +1550,7 @@ export class Harness extends ReadyResource {
|
|
|
1118
1550
|
};
|
|
1119
1551
|
this._capabilityInflight = probeCapabilityProfile(reads)
|
|
1120
1552
|
.then((capability) => {
|
|
1553
|
+
capability = { ...capability, hostRunLog: true, hostRunLogVersion: 1 };
|
|
1121
1554
|
this._capability = capability;
|
|
1122
1555
|
return capability;
|
|
1123
1556
|
})
|
|
@@ -1132,23 +1565,26 @@ export class Harness extends ReadyResource {
|
|
|
1132
1565
|
async _warmModel({ model, agent, signal }) {
|
|
1133
1566
|
if (!this.opened)
|
|
1134
1567
|
await this.ready();
|
|
1135
|
-
|
|
1568
|
+
this._assertNotClosing();
|
|
1569
|
+
if (this.suspended) {
|
|
1136
1570
|
return;
|
|
1571
|
+
}
|
|
1137
1572
|
const target = this._residency(model, agent);
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1573
|
+
hlog('warm model', { model, resident: isResident(target.pool, target.key) });
|
|
1574
|
+
await this._loadAndRelease(target, signal, async () => {
|
|
1575
|
+
if (target.pool !== this._models || isResident(target.pool, target.key)) {
|
|
1576
|
+
return true;
|
|
1577
|
+
}
|
|
1141
1578
|
const roomed = await this._makeRoomForCompletion(model, {
|
|
1142
1579
|
ctxSize: target.ctxSize,
|
|
1143
1580
|
signal,
|
|
1144
1581
|
deadlineMs: WARM_ROOM_WAIT_MS
|
|
1145
1582
|
});
|
|
1146
|
-
if (
|
|
1147
|
-
|
|
1583
|
+
if (roomed) {
|
|
1584
|
+
await this._unloadIdleGenerators();
|
|
1148
1585
|
}
|
|
1149
|
-
|
|
1150
|
-
}
|
|
1151
|
-
await this._loadAndRelease(target, signal);
|
|
1586
|
+
return roomed;
|
|
1587
|
+
});
|
|
1152
1588
|
}
|
|
1153
1589
|
loadModel(context) {
|
|
1154
1590
|
return this._busy(() => this._loadModel(context));
|
|
@@ -1202,13 +1638,9 @@ export class Harness extends ReadyResource {
|
|
|
1202
1638
|
}
|
|
1203
1639
|
return { unloaded };
|
|
1204
1640
|
}
|
|
1205
|
-
async _loadAndRelease(
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
}
|
|
1209
|
-
finally {
|
|
1210
|
-
releaseModel(target.pool, target.key);
|
|
1211
|
-
}
|
|
1641
|
+
async _loadAndRelease({ pool, key, open }, signal, prepare = async () => true) {
|
|
1642
|
+
const lease = await this._acquireModelLease({ pool, key, open, signal, prepare });
|
|
1643
|
+
lease?.release();
|
|
1212
1644
|
}
|
|
1213
1645
|
async _unloadIdleGenerators() {
|
|
1214
1646
|
await this._imageRuntime?.unloadIdle().catch(noop);
|
|
@@ -1271,6 +1703,57 @@ export class Harness extends ReadyResource {
|
|
|
1271
1703
|
projectionModel: this._projectionFor(model)
|
|
1272
1704
|
}, this._deviceConfig());
|
|
1273
1705
|
}
|
|
1706
|
+
async _acquireModelLease({ pool, key, open, signal, prepare }) {
|
|
1707
|
+
const releaseWork = this._enterWork();
|
|
1708
|
+
let pendingLoad = null;
|
|
1709
|
+
let opened = false;
|
|
1710
|
+
let released = false;
|
|
1711
|
+
const release = () => {
|
|
1712
|
+
if (released) {
|
|
1713
|
+
return;
|
|
1714
|
+
}
|
|
1715
|
+
released = true;
|
|
1716
|
+
try {
|
|
1717
|
+
if (opened) {
|
|
1718
|
+
releaseModel(pool, key);
|
|
1719
|
+
}
|
|
1720
|
+
}
|
|
1721
|
+
finally {
|
|
1722
|
+
releaseWork();
|
|
1723
|
+
}
|
|
1724
|
+
};
|
|
1725
|
+
try {
|
|
1726
|
+
if (!(await prepare())) {
|
|
1727
|
+
release();
|
|
1728
|
+
return null;
|
|
1729
|
+
}
|
|
1730
|
+
if (signal?.aborted) {
|
|
1731
|
+
throw new AbortedError();
|
|
1732
|
+
}
|
|
1733
|
+
if (this.suspended || this._runtimeDraining) {
|
|
1734
|
+
throw new Error('harness is suspended');
|
|
1735
|
+
}
|
|
1736
|
+
this._assertNotClosing();
|
|
1737
|
+
if (this._runtimeUnloadFailed) {
|
|
1738
|
+
throw new Error('Runtime unload failed; close the harness before loading models again');
|
|
1739
|
+
}
|
|
1740
|
+
const handle = open();
|
|
1741
|
+
opened = true;
|
|
1742
|
+
const loading = handle.load();
|
|
1743
|
+
pendingLoad = loading;
|
|
1744
|
+
const loaded = await raceAbort(loading, signal);
|
|
1745
|
+
return { modelId: loaded.modelId, release };
|
|
1746
|
+
}
|
|
1747
|
+
catch (error) {
|
|
1748
|
+
if (pendingLoad) {
|
|
1749
|
+
void pendingLoad.then(release, release).catch(noop);
|
|
1750
|
+
}
|
|
1751
|
+
else {
|
|
1752
|
+
release();
|
|
1753
|
+
}
|
|
1754
|
+
throw error;
|
|
1755
|
+
}
|
|
1756
|
+
}
|
|
1274
1757
|
invoke(context) {
|
|
1275
1758
|
return this._busyStream(() => this._invoke(context));
|
|
1276
1759
|
}
|
|
@@ -1278,8 +1761,10 @@ export class Harness extends ReadyResource {
|
|
|
1278
1761
|
if (!this.opened)
|
|
1279
1762
|
await this.ready();
|
|
1280
1763
|
if (this.suspended) {
|
|
1764
|
+
this._assertNotClosing();
|
|
1281
1765
|
return yield { type: 'aborted' };
|
|
1282
1766
|
}
|
|
1767
|
+
const releaseWork = this._enterWork();
|
|
1283
1768
|
hlog('invoke start', { capability, enhance });
|
|
1284
1769
|
try {
|
|
1285
1770
|
yield* invoke({
|
|
@@ -1299,117 +1784,101 @@ export class Harness extends ReadyResource {
|
|
|
1299
1784
|
}
|
|
1300
1785
|
finally {
|
|
1301
1786
|
hlog('invoke end', { capability });
|
|
1787
|
+
releaseWork();
|
|
1302
1788
|
}
|
|
1303
1789
|
}
|
|
1304
1790
|
completion(context) {
|
|
1305
|
-
|
|
1791
|
+
this._assertNotClosing();
|
|
1792
|
+
if (this._runtimeDraining) {
|
|
1793
|
+
throw new Error('harness is suspended');
|
|
1794
|
+
}
|
|
1795
|
+
if (this._runtimeUnloadFailed) {
|
|
1796
|
+
throw new Error('Runtime unload failed; close the harness before loading models again');
|
|
1797
|
+
}
|
|
1798
|
+
const tracked = trackCompletion((signal) => this._busyStream(() => this._runCompletion({ ...context, signal })), context.signal, (run) => {
|
|
1799
|
+
this._completionRuns.delete(run);
|
|
1800
|
+
this._notifyEffectDrain();
|
|
1801
|
+
});
|
|
1802
|
+
const { iterator, run } = tracked;
|
|
1803
|
+
this._completionRuns.add(run);
|
|
1804
|
+
return iterator;
|
|
1306
1805
|
}
|
|
1307
|
-
async *
|
|
1806
|
+
async *_runCompletion(request) {
|
|
1308
1807
|
if (!this.opened)
|
|
1309
1808
|
await this.ready();
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
const key = modelKey(model, loadOpts);
|
|
1315
|
-
const pool = this._models;
|
|
1316
|
-
const resident = isResident(pool, key);
|
|
1317
|
-
hlog('completion start', { model, resident, projection: Boolean(projectionModel) });
|
|
1318
|
-
if (!resident) {
|
|
1319
|
-
await this._makeRoomForCompletion(model, { ctxSize: loadOpts.ctxSize, signal });
|
|
1320
|
-
await this._unloadIdleGenerators();
|
|
1809
|
+
this._assertNotClosing();
|
|
1810
|
+
if (this.suspended || this._runtimeDraining) {
|
|
1811
|
+
yield { type: 'aborted' };
|
|
1812
|
+
return;
|
|
1321
1813
|
}
|
|
1322
|
-
const
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
const
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1814
|
+
const { boot, onStep, hostAppendTimeoutMs, requestDigestKey, ...provided } = request;
|
|
1815
|
+
let modelId = request.model ?? '';
|
|
1816
|
+
let key = null;
|
|
1817
|
+
try {
|
|
1818
|
+
const runtime = async (run) => {
|
|
1819
|
+
modelId = run.model ?? run.modelId;
|
|
1820
|
+
const runContext = { ...run, model: run.model ?? run.modelId };
|
|
1821
|
+
const lease = await this._completionRuntime(runContext);
|
|
1822
|
+
if (!key) {
|
|
1823
|
+
this._replying.push(lease.key);
|
|
1824
|
+
}
|
|
1825
|
+
key = lease.key;
|
|
1826
|
+
return lease;
|
|
1827
|
+
};
|
|
1828
|
+
const args = { ...provided, lockOwner: this };
|
|
1829
|
+
for await (const event of completion(runtime, args, {
|
|
1830
|
+
boot,
|
|
1831
|
+
onStep,
|
|
1832
|
+
hostAppendTimeoutMs,
|
|
1833
|
+
requestDigestKey,
|
|
1834
|
+
resumeRequest: provided,
|
|
1835
|
+
onDetached: (pending, runId) => this._trackDetachedRun(pending, runId)
|
|
1836
|
+
})) {
|
|
1837
|
+
yield event;
|
|
1335
1838
|
}
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
throw err;
|
|
1839
|
+
if (key) {
|
|
1840
|
+
this._modelFaults.delete(modelId);
|
|
1339
1841
|
}
|
|
1340
|
-
};
|
|
1341
|
-
const handle = pool.open(key, model, loadOpts);
|
|
1342
|
-
let held = true;
|
|
1343
|
-
const releaseHeld = () => {
|
|
1344
|
-
if (!held)
|
|
1345
|
-
return;
|
|
1346
|
-
held = false;
|
|
1347
|
-
releaseModel(pool, key);
|
|
1348
|
-
};
|
|
1349
|
-
this._replying.push(key);
|
|
1350
|
-
try {
|
|
1351
|
-
const { modelId, ctxSize } = await raceAbort(handle.load(), signal);
|
|
1352
|
-
hlog('model ready', { model });
|
|
1353
|
-
const sdk = await this._sdk.load();
|
|
1354
|
-
const turns = projectionModel ? messages : messages.map(({ attachments, ...turn }) => turn);
|
|
1355
|
-
const remoteRoots = driveOp && grantedDrives?.length ? createRemoteRoots(grantedDrives, driveOp) : [];
|
|
1356
|
-
const fsRoots = [
|
|
1357
|
-
...(grantedDirs?.length ? await createLocalRoots(grantedDirs) : []),
|
|
1358
|
-
...remoteRoots
|
|
1359
|
-
];
|
|
1360
|
-
const fsWriteRoots = [
|
|
1361
|
-
...(grantedWriteDirs?.length ? await createLocalRoots(grantedWriteDirs) : []),
|
|
1362
|
-
...remoteRoots.filter((root) => root.writable)
|
|
1363
|
-
];
|
|
1364
|
-
releaseHeld();
|
|
1365
|
-
yield* completion(sdk, {
|
|
1366
|
-
modelId,
|
|
1367
|
-
acquireRound,
|
|
1368
|
-
model,
|
|
1369
|
-
agent,
|
|
1370
|
-
ctxSize,
|
|
1371
|
-
cacheKey,
|
|
1372
|
-
thinkingDefault,
|
|
1373
|
-
generationParams,
|
|
1374
|
-
responseFormat,
|
|
1375
|
-
captureInferenceCalls,
|
|
1376
|
-
messages: turns,
|
|
1377
|
-
tools: this._tools,
|
|
1378
|
-
skillCatalog: await this._completionSkills(agent?.id ?? null, model),
|
|
1379
|
-
toolConfigs,
|
|
1380
|
-
fsRoots,
|
|
1381
|
-
fsWriteRoots,
|
|
1382
|
-
approve,
|
|
1383
|
-
attach,
|
|
1384
|
-
fetchAttachment,
|
|
1385
|
-
ragSearch,
|
|
1386
|
-
progress,
|
|
1387
|
-
askUser,
|
|
1388
|
-
signal
|
|
1389
|
-
});
|
|
1390
|
-
this._modelFaults.delete(model);
|
|
1391
1842
|
}
|
|
1392
|
-
catch (
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1843
|
+
catch (error) {
|
|
1844
|
+
yield* this._completionFailed(error, modelId, key);
|
|
1845
|
+
}
|
|
1846
|
+
finally {
|
|
1847
|
+
if (key) {
|
|
1848
|
+
hlog('completion end', { model: modelId });
|
|
1849
|
+
this._replying.splice(this._replying.indexOf(key), 1);
|
|
1850
|
+
void this._imageRuntime?.unloadIdle().catch(noop);
|
|
1851
|
+
void this._musicRuntime?.unloadIdle().catch(noop);
|
|
1396
1852
|
}
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1853
|
+
}
|
|
1854
|
+
}
|
|
1855
|
+
async *_completionFailed(err, model, key) {
|
|
1856
|
+
if (err instanceof AbortedError || this.suspended || this._runtimeDraining) {
|
|
1857
|
+
hlog('completion aborted', { model, suspended: this.suspended || this._runtimeDraining });
|
|
1858
|
+
yield { type: 'aborted' };
|
|
1859
|
+
return;
|
|
1860
|
+
}
|
|
1861
|
+
const message = errorMessage(err);
|
|
1862
|
+
const name = err instanceof Error ? err.name : undefined;
|
|
1863
|
+
hlog('completion failed', { model, ...resolutionDetail(err), err: message });
|
|
1864
|
+
if (key && isSdkModelWedgedError(err)) {
|
|
1865
|
+
const wedged = await this._evictWedgedModel(model, key);
|
|
1866
|
+
yield completionErrorEvent(wedged.message, name, err);
|
|
1867
|
+
if (wedged.fatal) {
|
|
1868
|
+
process.exit(1);
|
|
1405
1869
|
}
|
|
1406
1870
|
}
|
|
1871
|
+
else {
|
|
1872
|
+
yield completionErrorEvent(message, name, err);
|
|
1873
|
+
}
|
|
1874
|
+
}
|
|
1875
|
+
async _completionRuntime(request) {
|
|
1876
|
+
const releaseWork = this._enterWork();
|
|
1877
|
+
try {
|
|
1878
|
+
return await prepareCompletionRuntime(this, request);
|
|
1879
|
+
}
|
|
1407
1880
|
finally {
|
|
1408
|
-
|
|
1409
|
-
this._replying.splice(this._replying.indexOf(key), 1);
|
|
1410
|
-
releaseHeld();
|
|
1411
|
-
void this._imageRuntime?.unloadIdle().catch(noop);
|
|
1412
|
-
void this._musicRuntime?.unloadIdle().catch(noop);
|
|
1881
|
+
releaseWork();
|
|
1413
1882
|
}
|
|
1414
1883
|
}
|
|
1415
1884
|
_residentEnhancer() {
|
|
@@ -1499,4 +1968,32 @@ export class Harness extends ReadyResource {
|
|
|
1499
1968
|
}
|
|
1500
1969
|
}
|
|
1501
1970
|
}
|
|
1971
|
+
function completionErrorEvent(message, name, error) {
|
|
1972
|
+
const event = { type: 'error', message, ...completionErrorDetails(error) };
|
|
1973
|
+
if (name) {
|
|
1974
|
+
event.name = name;
|
|
1975
|
+
}
|
|
1976
|
+
return event;
|
|
1977
|
+
}
|
|
1978
|
+
function completionErrorDetails(error) {
|
|
1979
|
+
if (!isRecord(error))
|
|
1980
|
+
return {};
|
|
1981
|
+
if (error.code === 'PENDING_ARGS_REDACTED') {
|
|
1982
|
+
return { code: 'PENDING_ARGS_REDACTED', ...redactedRecoveryFields(error) };
|
|
1983
|
+
}
|
|
1984
|
+
if (error.name !== 'RunIncompatible') {
|
|
1985
|
+
return {};
|
|
1986
|
+
}
|
|
1987
|
+
const match = errorMessage(error).match(/^Incompatible run log: (machine|log|xstate) version (\d{1,10}(?:\.\d{1,10}){0,3}(?:-[A-Za-z0-9.-]{1,16})?) \(expected (\d{1,10}(?:\.\d{1,10}){0,3}(?:-[A-Za-z0-9.-]{1,16})?)\)/);
|
|
1988
|
+
if (!match) {
|
|
1989
|
+
return {};
|
|
1990
|
+
}
|
|
1991
|
+
const kind = match[1];
|
|
1992
|
+
const found = match[2];
|
|
1993
|
+
const expected = match[3];
|
|
1994
|
+
if ((kind !== 'machine' && kind !== 'log' && kind !== 'xstate') || !found || !expected) {
|
|
1995
|
+
return {};
|
|
1996
|
+
}
|
|
1997
|
+
return { kind, found, expected };
|
|
1998
|
+
}
|
|
1502
1999
|
function noop() { }
|