@qvac/harness 0.7.1 → 1.0.0-rc.1
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 +224 -0
- package/dist-lib/index.d.ts +55 -3
- package/dist-lib/index.js +721 -236
- 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-pool.d.ts +14 -6
- package/dist-lib/lib/models/model-pool.js +95 -30
- 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 +307 -11
- 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/adapters/python/wheels.js +2 -1
- 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 +20 -5
- package/dist-lib/spec/hrpc/messages.js +95 -15
- package/dist-lib/spec/hyperschema/index.js +95 -15
- package/dist-lib/spec/hyperschema/schema.json +69 -1
- package/dist-lib/spec/hyperschema/types.d.ts +10 -3
- package/package.json +20 -2
- 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, loadingModels, settledModels, 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, {
|
|
@@ -641,19 +674,24 @@ export class Harness extends ReadyResource {
|
|
|
641
674
|
};
|
|
642
675
|
}
|
|
643
676
|
_createPool() {
|
|
644
|
-
const loadSdk = this.
|
|
677
|
+
const loadSdk = () => this._loadModelLifecycleSdk();
|
|
645
678
|
return createModelPool(loadSdk, (state) => this._notifyModel(state), {
|
|
646
679
|
deviceConfig: this._deviceConfig(),
|
|
647
|
-
loads: this._loads
|
|
680
|
+
loads: this._loads,
|
|
681
|
+
onUnloadFailure: (modelId, error) => this._recordModelUnloadFailure(modelId, error)
|
|
648
682
|
});
|
|
649
683
|
}
|
|
684
|
+
_loadModelLifecycleSdk() {
|
|
685
|
+
return this._sdk.load();
|
|
686
|
+
}
|
|
650
687
|
_createEmbedPool() {
|
|
651
688
|
const loadSdk = () => this._sdk.loadEmbed();
|
|
652
689
|
return createEmbedPool(loadSdk, (state) => this._notifyModel(state), {
|
|
653
690
|
faults: this._embedFaults,
|
|
654
691
|
batchSize: this._embedBatchSize,
|
|
655
692
|
openclCacheDir,
|
|
656
|
-
loads: this._loads
|
|
693
|
+
loads: this._loads,
|
|
694
|
+
onUnloadFailure: (modelId, error) => this._recordModelUnloadFailure(modelId, error)
|
|
657
695
|
});
|
|
658
696
|
}
|
|
659
697
|
_createTtsPool() {
|
|
@@ -662,7 +700,8 @@ export class Harness extends ReadyResource {
|
|
|
662
700
|
modelType: 'tts-ggml',
|
|
663
701
|
addon: 'tts',
|
|
664
702
|
modelConfig: TTS_MODEL_CONFIG,
|
|
665
|
-
loads: this._loads
|
|
703
|
+
loads: this._loads,
|
|
704
|
+
onUnloadFailure: (modelId, error) => this._recordModelUnloadFailure(modelId, error)
|
|
666
705
|
});
|
|
667
706
|
}
|
|
668
707
|
_createTranscriptionPool() {
|
|
@@ -672,9 +711,16 @@ export class Harness extends ReadyResource {
|
|
|
672
711
|
addon: 'parakeet',
|
|
673
712
|
modelConfig: { useGPU: this._transcriptionDevice !== 'cpu' },
|
|
674
713
|
openclCacheDir,
|
|
675
|
-
loads: this._loads
|
|
714
|
+
loads: this._loads,
|
|
715
|
+
onUnloadFailure: (modelId, error) => this._recordModelUnloadFailure(modelId, error)
|
|
676
716
|
});
|
|
677
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
|
+
}
|
|
678
724
|
_notifyModel(state) {
|
|
679
725
|
if (state.status === 'ready' || state.status === 'unloaded' || state.status === 'error') {
|
|
680
726
|
invalidateModelStatusCache(this._modelStatus);
|
|
@@ -695,28 +741,166 @@ export class Harness extends ReadyResource {
|
|
|
695
741
|
async _collectRetiredEmbedPool(pool) {
|
|
696
742
|
if (!this._retiredEmbedModels.has(pool) || pool.idle !== pool.size)
|
|
697
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
|
+
}
|
|
698
755
|
this._retiredEmbedModels.delete(pool);
|
|
699
|
-
await pool.destroy();
|
|
700
756
|
}
|
|
701
757
|
async _releaseEmbedResource(pool, key) {
|
|
702
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
|
+
}
|
|
703
766
|
await this._collectRetiredEmbedPool(pool);
|
|
704
767
|
}
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
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
|
+
})
|
|
708
793
|
.catch(noop);
|
|
709
794
|
}
|
|
710
795
|
async _destroyEmbedPools() {
|
|
711
|
-
const pools = [this._embedModels, ...this._retiredEmbedModels];
|
|
712
|
-
|
|
713
|
-
|
|
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
|
+
}
|
|
714
809
|
}
|
|
715
810
|
_enqueueEmbedControl(run) {
|
|
716
811
|
const next = this._embedControl.then(run, run);
|
|
717
812
|
this._embedControl = next.catch(noop);
|
|
718
813
|
return next;
|
|
719
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
|
+
}
|
|
720
904
|
getEmbeddingDevice() {
|
|
721
905
|
return this._embedFaults.device;
|
|
722
906
|
}
|
|
@@ -727,43 +911,61 @@ export class Harness extends ReadyResource {
|
|
|
727
911
|
async _setEmbeddingDevice(device) {
|
|
728
912
|
if (!this.opened)
|
|
729
913
|
await this.ready();
|
|
730
|
-
if (this.closing)
|
|
731
|
-
throw new Error('harness is closing');
|
|
732
|
-
if (this._embedFaults.device === device) {
|
|
733
|
-
return;
|
|
734
|
-
}
|
|
735
|
-
this._embedFaults = createGpuFaultState({ device, gpuLayers: this._embedGpuLayers });
|
|
736
914
|
if (this.suspended) {
|
|
915
|
+
this._assertNotClosing();
|
|
916
|
+
if (this._embedFaults.device === device) {
|
|
917
|
+
return;
|
|
918
|
+
}
|
|
919
|
+
this._embedFaults = createGpuFaultState({ device, gpuLayers: this._embedGpuLayers });
|
|
737
920
|
return;
|
|
738
921
|
}
|
|
739
|
-
const
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
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
|
+
}
|
|
743
939
|
}
|
|
744
940
|
async presuspend() {
|
|
745
|
-
|
|
746
|
-
return;
|
|
747
|
-
this._state = HarnessState.PRESUSPEND;
|
|
748
|
-
hlog('presuspend');
|
|
941
|
+
await this._runLifecycle('PRESUSPEND', { open: false });
|
|
749
942
|
}
|
|
750
943
|
async suspend() {
|
|
751
|
-
|
|
752
|
-
if (
|
|
753
|
-
|
|
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');
|
|
754
947
|
}
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
this._state = HarnessState.SUSPEND;
|
|
948
|
+
}
|
|
949
|
+
async _suspendRuntimes() {
|
|
950
|
+
this._runtimeDraining = true;
|
|
759
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();
|
|
760
956
|
const pending = this._sdk.peek();
|
|
761
957
|
if (pending) {
|
|
762
|
-
|
|
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');
|
|
763
966
|
}
|
|
764
|
-
await this._releaseRuntimes();
|
|
765
|
-
await this._sdkLifecycle('suspend');
|
|
766
967
|
invalidateModelStatusCache(this._modelStatus);
|
|
968
|
+
await this._sdkLifecycle('suspend');
|
|
767
969
|
hlog('suspended');
|
|
768
970
|
}
|
|
769
971
|
async _sdkLifecycle(op) {
|
|
@@ -777,35 +979,60 @@ export class Harness extends ReadyResource {
|
|
|
777
979
|
catch (err) {
|
|
778
980
|
hlog(`sdk ${op} failed`, { err: errorMessage(err) });
|
|
779
981
|
if (op === 'suspend') {
|
|
780
|
-
|
|
982
|
+
throw err;
|
|
781
983
|
}
|
|
782
984
|
await sdk.resume().catch((retryErr) => {
|
|
783
985
|
hlog('sdk resume retry failed', {
|
|
784
986
|
err: errorMessage(retryErr)
|
|
785
987
|
});
|
|
988
|
+
throw retryErr;
|
|
786
989
|
});
|
|
787
990
|
}
|
|
788
991
|
}
|
|
789
992
|
async _releaseRuntimes() {
|
|
790
993
|
const released = await Promise.allSettled([
|
|
791
|
-
this._models
|
|
994
|
+
this._models?.destroy(),
|
|
792
995
|
this._destroyEmbedPools(),
|
|
793
|
-
this._ttsModels
|
|
794
|
-
this._transcriptionModels
|
|
996
|
+
this._ttsModels?.destroy(),
|
|
997
|
+
this._transcriptionModels?.destroy(),
|
|
795
998
|
this._imageRuntime?.unload(),
|
|
796
999
|
this._musicRuntime?.unload(),
|
|
797
1000
|
this._translationRuntime.unload()
|
|
798
1001
|
]);
|
|
799
|
-
for (const outcome of released) {
|
|
1002
|
+
for (const [index, outcome] of released.entries()) {
|
|
800
1003
|
if (outcome.status !== 'rejected')
|
|
801
1004
|
continue;
|
|
1005
|
+
if (index < 4 && !(outcome.reason instanceof ModelUnloadError)) {
|
|
1006
|
+
this._poolCleanupFailed = true;
|
|
1007
|
+
}
|
|
802
1008
|
const err = outcome.reason;
|
|
803
1009
|
hlog('unload failed', { err: errorMessage(err) });
|
|
804
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;
|
|
805
1031
|
}
|
|
806
1032
|
async _unloadIdleModels() {
|
|
807
|
-
if (!this.opened || this.closing || this.suspended)
|
|
1033
|
+
if (!this.opened || this.closing || this.suspended || this._runtimeDraining) {
|
|
808
1034
|
return;
|
|
1035
|
+
}
|
|
809
1036
|
hlog('idle unload', { models: this._loadedModels().map((entry) => entry.model) });
|
|
810
1037
|
const released = await Promise.allSettled([
|
|
811
1038
|
evictIdle(this._models),
|
|
@@ -825,62 +1052,164 @@ export class Harness extends ReadyResource {
|
|
|
825
1052
|
invalidateModelStatusCache(this._modelStatus);
|
|
826
1053
|
}
|
|
827
1054
|
async resume() {
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
if (this.
|
|
833
|
-
|
|
834
|
-
}
|
|
835
|
-
if (this._state === HarnessState.PRESUSPEND) {
|
|
836
|
-
this._state = HarnessState.RESUME;
|
|
837
|
-
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');
|
|
838
1061
|
}
|
|
839
|
-
this._state = HarnessState.RESUME;
|
|
840
1062
|
hlog('resuming', { pools: 'recreated' });
|
|
841
1063
|
this._loads.undrain();
|
|
842
1064
|
this._models = this._createPool();
|
|
843
1065
|
this._embedModels = this._createEmbedPool();
|
|
844
1066
|
this._ttsModels = this._createTtsPool();
|
|
845
1067
|
this._transcriptionModels = this._createTranscriptionPool();
|
|
846
|
-
|
|
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
|
+
}
|
|
847
1080
|
}
|
|
848
1081
|
async wakeup() {
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
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();
|
|
854
1089
|
}
|
|
855
1090
|
async _close() {
|
|
856
1091
|
hlog('closing');
|
|
857
1092
|
this._idle.close();
|
|
858
|
-
this.
|
|
859
|
-
this.
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
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;
|
|
863
1177
|
}
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
1178
|
+
finally {
|
|
1179
|
+
if (completed) {
|
|
1180
|
+
this._lifecycle.stop();
|
|
1181
|
+
}
|
|
868
1182
|
}
|
|
869
1183
|
}
|
|
870
1184
|
download(args) {
|
|
1185
|
+
this._assertNotClosing();
|
|
1186
|
+
let releaseWork = null;
|
|
871
1187
|
const projection = (args.addon ?? 'llm') === 'llm' ? this._projectionFor(args.model) : undefined;
|
|
872
1188
|
const multiFile = diffusionDownloadMetadata(args.model, args.addon) ??
|
|
873
1189
|
audioGenDownloadMetadata(args.model, args.addon);
|
|
874
1190
|
return download(async () => {
|
|
875
1191
|
if (!this.opened)
|
|
876
1192
|
await this.ready();
|
|
877
|
-
|
|
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
|
+
}
|
|
878
1204
|
}, {
|
|
879
1205
|
...args,
|
|
880
1206
|
projection,
|
|
881
1207
|
companions: multiFile?.companions,
|
|
882
1208
|
assetSizes: multiFile?.assetSizes
|
|
883
|
-
}, () => invalidateModelStatusCache(this._modelStatus))
|
|
1209
|
+
}, () => invalidateModelStatusCache(this._modelStatus), () => {
|
|
1210
|
+
releaseWork?.();
|
|
1211
|
+
releaseWork = null;
|
|
1212
|
+
});
|
|
884
1213
|
}
|
|
885
1214
|
embed(context) {
|
|
886
1215
|
return this._busy(() => this._embed(context));
|
|
@@ -888,8 +1217,10 @@ export class Harness extends ReadyResource {
|
|
|
888
1217
|
async _embed({ model, texts, signal }) {
|
|
889
1218
|
if (!this.opened)
|
|
890
1219
|
await this.ready();
|
|
891
|
-
|
|
1220
|
+
this._assertNotClosing();
|
|
1221
|
+
if (this.suspended) {
|
|
892
1222
|
throw new Error('harness is suspended');
|
|
1223
|
+
}
|
|
893
1224
|
if (texts.length === 0) {
|
|
894
1225
|
return [];
|
|
895
1226
|
}
|
|
@@ -897,13 +1228,23 @@ export class Harness extends ReadyResource {
|
|
|
897
1228
|
const faults = this._embedFaults;
|
|
898
1229
|
const generation = faults.generation;
|
|
899
1230
|
const key = faults.key(residentSrc(model, 'embeddings'));
|
|
900
|
-
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
|
+
}
|
|
901
1240
|
let loaded = null;
|
|
902
1241
|
let job = null;
|
|
903
1242
|
let releaseAfter = null;
|
|
1243
|
+
let drainWorkAfter = null;
|
|
904
1244
|
try {
|
|
905
1245
|
loaded = await raceAbort(handle.load(), signal);
|
|
906
1246
|
const sdk = await this._sdk.loadEmbed();
|
|
1247
|
+
this._assertNotClosing();
|
|
907
1248
|
job = this._embedLane(sdk, { modelId: loaded.modelId, texts });
|
|
908
1249
|
const vectors = await raceAbort(job, signal);
|
|
909
1250
|
faults.reset();
|
|
@@ -911,26 +1252,35 @@ export class Harness extends ReadyResource {
|
|
|
911
1252
|
}
|
|
912
1253
|
catch (err) {
|
|
913
1254
|
if (err instanceof AbortedError) {
|
|
914
|
-
|
|
1255
|
+
if (job || this._retiredEmbedModels.has(pool)) {
|
|
1256
|
+
releaseAfter = job ?? handle.peek();
|
|
1257
|
+
}
|
|
1258
|
+
else {
|
|
1259
|
+
drainWorkAfter = handle.peek();
|
|
1260
|
+
}
|
|
915
1261
|
}
|
|
916
1262
|
if (isSdkGpuDeviceLostError(err)) {
|
|
917
|
-
|
|
918
|
-
if (loaded) {
|
|
919
|
-
const dead = loaded.modelId;
|
|
920
|
-
await this._sdk
|
|
921
|
-
.loadEmbed()
|
|
922
|
-
.then((sdk) => sdk.unloadModel({ modelId: dead }))
|
|
923
|
-
.catch(noop);
|
|
924
|
-
}
|
|
1263
|
+
this._markEmbedFault({ pool, key, faults, generation });
|
|
925
1264
|
}
|
|
926
1265
|
throw err;
|
|
927
1266
|
}
|
|
928
1267
|
finally {
|
|
929
1268
|
if (releaseAfter) {
|
|
930
|
-
this._releaseEmbedResourceAfter(releaseAfter,
|
|
1269
|
+
void this._releaseEmbedResourceAfter(releaseAfter, {
|
|
1270
|
+
pool,
|
|
1271
|
+
key,
|
|
1272
|
+
faults,
|
|
1273
|
+
generation
|
|
1274
|
+
}).finally(releaseWork);
|
|
931
1275
|
}
|
|
932
1276
|
else {
|
|
933
|
-
|
|
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
|
+
}
|
|
934
1284
|
}
|
|
935
1285
|
}
|
|
936
1286
|
}
|
|
@@ -953,7 +1303,16 @@ export class Harness extends ReadyResource {
|
|
|
953
1303
|
throw new Error('harness is suspended');
|
|
954
1304
|
const addon = category === 'tts' ? 'tts' : 'parakeet';
|
|
955
1305
|
const name = residentSrc(model || (await this._defaultVoiceSrc(category)), addon);
|
|
956
|
-
|
|
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
|
+
}
|
|
957
1316
|
let job = null;
|
|
958
1317
|
let releaseAfter = null;
|
|
959
1318
|
let sdk = null;
|
|
@@ -961,6 +1320,7 @@ export class Harness extends ReadyResource {
|
|
|
961
1320
|
try {
|
|
962
1321
|
modelId = (await raceAbort(handle.load(), signal)).modelId;
|
|
963
1322
|
sdk = (await this._sdk.loadVoice());
|
|
1323
|
+
this._assertNotClosing();
|
|
964
1324
|
const boundSdk = sdk;
|
|
965
1325
|
const boundId = modelId;
|
|
966
1326
|
job = this._voiceLane(() => run(boundSdk, boundId));
|
|
@@ -971,7 +1331,7 @@ export class Harness extends ReadyResource {
|
|
|
971
1331
|
if (sdk && modelId) {
|
|
972
1332
|
void sdk.cancel({ modelId, operation: 'inference' }).catch(noop);
|
|
973
1333
|
}
|
|
974
|
-
releaseAfter = job;
|
|
1334
|
+
releaseAfter = job ?? handle.peek();
|
|
975
1335
|
}
|
|
976
1336
|
throw err;
|
|
977
1337
|
}
|
|
@@ -980,10 +1340,15 @@ export class Harness extends ReadyResource {
|
|
|
980
1340
|
releaseModel(pool, name);
|
|
981
1341
|
};
|
|
982
1342
|
if (releaseAfter) {
|
|
983
|
-
void releaseAfter.then(release, release);
|
|
1343
|
+
void releaseAfter.then(release, release).finally(releaseWork).catch(noop);
|
|
984
1344
|
}
|
|
985
1345
|
else {
|
|
986
|
-
|
|
1346
|
+
try {
|
|
1347
|
+
release();
|
|
1348
|
+
}
|
|
1349
|
+
finally {
|
|
1350
|
+
releaseWork();
|
|
1351
|
+
}
|
|
987
1352
|
}
|
|
988
1353
|
}
|
|
989
1354
|
}
|
|
@@ -1021,9 +1386,18 @@ export class Harness extends ReadyResource {
|
|
|
1021
1386
|
if (this.suspended)
|
|
1022
1387
|
throw new Error('harness is suspended');
|
|
1023
1388
|
const name = residentSrc(model || (await this._defaultVoiceSrc('tts')), 'tts');
|
|
1389
|
+
this._assertNotClosing();
|
|
1024
1390
|
const pool = this._ttsModels;
|
|
1025
|
-
const handle = pool.open(name, name);
|
|
1026
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
|
+
}
|
|
1027
1401
|
let sdk = null;
|
|
1028
1402
|
let modelId = null;
|
|
1029
1403
|
const cancel = () => {
|
|
@@ -1036,6 +1410,7 @@ export class Harness extends ReadyResource {
|
|
|
1036
1410
|
try {
|
|
1037
1411
|
modelId = (await handle.load()).modelId;
|
|
1038
1412
|
sdk = (await this._sdk.loadVoice());
|
|
1413
|
+
this._assertNotClosing();
|
|
1039
1414
|
if (signal?.aborted) {
|
|
1040
1415
|
return;
|
|
1041
1416
|
}
|
|
@@ -1058,53 +1433,98 @@ export class Harness extends ReadyResource {
|
|
|
1058
1433
|
if (!settled) {
|
|
1059
1434
|
cancel();
|
|
1060
1435
|
}
|
|
1061
|
-
void job
|
|
1062
|
-
|
|
1063
|
-
|
|
1436
|
+
void job
|
|
1437
|
+
.catch(noop)
|
|
1438
|
+
.finally(() => {
|
|
1439
|
+
try {
|
|
1440
|
+
releaseModel(pool, name);
|
|
1441
|
+
}
|
|
1442
|
+
finally {
|
|
1443
|
+
releaseWork();
|
|
1444
|
+
}
|
|
1445
|
+
})
|
|
1446
|
+
.catch(noop);
|
|
1064
1447
|
}
|
|
1065
1448
|
}
|
|
1066
1449
|
async list(options = {}) {
|
|
1067
1450
|
if (!this.opened)
|
|
1068
1451
|
await this.ready();
|
|
1069
|
-
const
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1452
|
+
const releaseWork = this._enterWork();
|
|
1453
|
+
try {
|
|
1454
|
+
const sdk = await this._sdk.load();
|
|
1455
|
+
this._assertNotClosing();
|
|
1456
|
+
const rows = await listModels(sdk, options, {
|
|
1457
|
+
cache: this._modelStatus,
|
|
1458
|
+
projectionFor: (name) => this._projectionFor(name)
|
|
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
|
+
}
|
|
1081
1471
|
}
|
|
1082
1472
|
async deleteModel(name) {
|
|
1083
1473
|
if (!this.opened)
|
|
1084
1474
|
await this.ready();
|
|
1085
|
-
const
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
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
|
+
}
|
|
1089
1486
|
}
|
|
1090
1487
|
async clearKvCache() {
|
|
1091
1488
|
if (!this.opened)
|
|
1092
1489
|
await this.ready();
|
|
1093
|
-
|
|
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
|
+
}
|
|
1094
1500
|
}
|
|
1095
1501
|
async deleteKvCache(kvCacheKey) {
|
|
1096
1502
|
if (!this.opened)
|
|
1097
1503
|
await this.ready();
|
|
1098
|
-
|
|
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
|
+
}
|
|
1099
1514
|
}
|
|
1100
1515
|
async infraModels(profile) {
|
|
1101
1516
|
return infraModelsFor(profile);
|
|
1102
1517
|
}
|
|
1103
1518
|
async capability() {
|
|
1104
|
-
if (this.
|
|
1519
|
+
if (!this.opened)
|
|
1520
|
+
await this.ready();
|
|
1521
|
+
this._assertNotClosing();
|
|
1522
|
+
if (this._capability) {
|
|
1105
1523
|
return this._capability;
|
|
1106
|
-
|
|
1524
|
+
}
|
|
1525
|
+
if (this._capabilityInflight) {
|
|
1107
1526
|
return this._capabilityInflight;
|
|
1527
|
+
}
|
|
1108
1528
|
const reads = {
|
|
1109
1529
|
totalmem,
|
|
1110
1530
|
platform,
|
|
@@ -1130,6 +1550,7 @@ export class Harness extends ReadyResource {
|
|
|
1130
1550
|
};
|
|
1131
1551
|
this._capabilityInflight = probeCapabilityProfile(reads)
|
|
1132
1552
|
.then((capability) => {
|
|
1553
|
+
capability = { ...capability, hostRunLog: true, hostRunLogVersion: 1 };
|
|
1133
1554
|
this._capability = capability;
|
|
1134
1555
|
return capability;
|
|
1135
1556
|
})
|
|
@@ -1144,23 +1565,26 @@ export class Harness extends ReadyResource {
|
|
|
1144
1565
|
async _warmModel({ model, agent, signal }) {
|
|
1145
1566
|
if (!this.opened)
|
|
1146
1567
|
await this.ready();
|
|
1147
|
-
|
|
1568
|
+
this._assertNotClosing();
|
|
1569
|
+
if (this.suspended) {
|
|
1148
1570
|
return;
|
|
1571
|
+
}
|
|
1149
1572
|
const target = this._residency(model, agent);
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
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
|
+
}
|
|
1153
1578
|
const roomed = await this._makeRoomForCompletion(model, {
|
|
1154
1579
|
ctxSize: target.ctxSize,
|
|
1155
1580
|
signal,
|
|
1156
1581
|
deadlineMs: WARM_ROOM_WAIT_MS
|
|
1157
1582
|
});
|
|
1158
|
-
if (
|
|
1159
|
-
|
|
1583
|
+
if (roomed) {
|
|
1584
|
+
await this._unloadIdleGenerators();
|
|
1160
1585
|
}
|
|
1161
|
-
|
|
1162
|
-
}
|
|
1163
|
-
await this._loadAndRelease(target, signal);
|
|
1586
|
+
return roomed;
|
|
1587
|
+
});
|
|
1164
1588
|
}
|
|
1165
1589
|
loadModel(context) {
|
|
1166
1590
|
return this._busy(() => this._loadModel(context));
|
|
@@ -1214,13 +1638,9 @@ export class Harness extends ReadyResource {
|
|
|
1214
1638
|
}
|
|
1215
1639
|
return { unloaded };
|
|
1216
1640
|
}
|
|
1217
|
-
async _loadAndRelease(
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
}
|
|
1221
|
-
finally {
|
|
1222
|
-
releaseModel(target.pool, target.key);
|
|
1223
|
-
}
|
|
1641
|
+
async _loadAndRelease({ pool, key, open }, signal, prepare = async () => true) {
|
|
1642
|
+
const lease = await this._acquireModelLease({ pool, key, open, signal, prepare });
|
|
1643
|
+
lease?.release();
|
|
1224
1644
|
}
|
|
1225
1645
|
async _unloadIdleGenerators() {
|
|
1226
1646
|
await this._imageRuntime?.unloadIdle().catch(noop);
|
|
@@ -1283,6 +1703,57 @@ export class Harness extends ReadyResource {
|
|
|
1283
1703
|
projectionModel: this._projectionFor(model)
|
|
1284
1704
|
}, this._deviceConfig());
|
|
1285
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
|
+
}
|
|
1286
1757
|
invoke(context) {
|
|
1287
1758
|
return this._busyStream(() => this._invoke(context));
|
|
1288
1759
|
}
|
|
@@ -1290,8 +1761,10 @@ export class Harness extends ReadyResource {
|
|
|
1290
1761
|
if (!this.opened)
|
|
1291
1762
|
await this.ready();
|
|
1292
1763
|
if (this.suspended) {
|
|
1764
|
+
this._assertNotClosing();
|
|
1293
1765
|
return yield { type: 'aborted' };
|
|
1294
1766
|
}
|
|
1767
|
+
const releaseWork = this._enterWork();
|
|
1295
1768
|
hlog('invoke start', { capability, enhance });
|
|
1296
1769
|
try {
|
|
1297
1770
|
yield* invoke({
|
|
@@ -1311,117 +1784,101 @@ export class Harness extends ReadyResource {
|
|
|
1311
1784
|
}
|
|
1312
1785
|
finally {
|
|
1313
1786
|
hlog('invoke end', { capability });
|
|
1787
|
+
releaseWork();
|
|
1314
1788
|
}
|
|
1315
1789
|
}
|
|
1316
1790
|
completion(context) {
|
|
1317
|
-
|
|
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;
|
|
1318
1805
|
}
|
|
1319
|
-
async *
|
|
1806
|
+
async *_runCompletion(request) {
|
|
1320
1807
|
if (!this.opened)
|
|
1321
1808
|
await this.ready();
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
const key = modelKey(model, loadOpts);
|
|
1327
|
-
const pool = this._models;
|
|
1328
|
-
const resident = isResident(pool, key);
|
|
1329
|
-
hlog('completion start', { model, resident, projection: Boolean(projectionModel) });
|
|
1330
|
-
if (!resident) {
|
|
1331
|
-
await this._makeRoomForCompletion(model, { ctxSize: loadOpts.ctxSize, signal });
|
|
1332
|
-
await this._unloadIdleGenerators();
|
|
1809
|
+
this._assertNotClosing();
|
|
1810
|
+
if (this.suspended || this._runtimeDraining) {
|
|
1811
|
+
yield { type: 'aborted' };
|
|
1812
|
+
return;
|
|
1333
1813
|
}
|
|
1334
|
-
const
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
const
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
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;
|
|
1347
1838
|
}
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
throw err;
|
|
1839
|
+
if (key) {
|
|
1840
|
+
this._modelFaults.delete(modelId);
|
|
1351
1841
|
}
|
|
1352
|
-
};
|
|
1353
|
-
const handle = pool.open(key, model, loadOpts);
|
|
1354
|
-
let held = true;
|
|
1355
|
-
const releaseHeld = () => {
|
|
1356
|
-
if (!held)
|
|
1357
|
-
return;
|
|
1358
|
-
held = false;
|
|
1359
|
-
releaseModel(pool, key);
|
|
1360
|
-
};
|
|
1361
|
-
this._replying.push(key);
|
|
1362
|
-
try {
|
|
1363
|
-
const { modelId, ctxSize } = await raceAbort(handle.load(), signal);
|
|
1364
|
-
hlog('model ready', { model });
|
|
1365
|
-
const sdk = await this._sdk.load();
|
|
1366
|
-
const turns = projectionModel ? messages : messages.map(({ attachments, ...turn }) => turn);
|
|
1367
|
-
const remoteRoots = driveOp && grantedDrives?.length ? createRemoteRoots(grantedDrives, driveOp) : [];
|
|
1368
|
-
const fsRoots = [
|
|
1369
|
-
...(grantedDirs?.length ? await createLocalRoots(grantedDirs) : []),
|
|
1370
|
-
...remoteRoots
|
|
1371
|
-
];
|
|
1372
|
-
const fsWriteRoots = [
|
|
1373
|
-
...(grantedWriteDirs?.length ? await createLocalRoots(grantedWriteDirs) : []),
|
|
1374
|
-
...remoteRoots.filter((root) => root.writable)
|
|
1375
|
-
];
|
|
1376
|
-
releaseHeld();
|
|
1377
|
-
yield* completion(sdk, {
|
|
1378
|
-
modelId,
|
|
1379
|
-
acquireRound,
|
|
1380
|
-
model,
|
|
1381
|
-
agent,
|
|
1382
|
-
ctxSize,
|
|
1383
|
-
cacheKey,
|
|
1384
|
-
thinkingDefault,
|
|
1385
|
-
generationParams,
|
|
1386
|
-
responseFormat,
|
|
1387
|
-
captureInferenceCalls,
|
|
1388
|
-
messages: turns,
|
|
1389
|
-
tools: this._tools,
|
|
1390
|
-
skillCatalog: await this._completionSkills(agent?.id ?? null, model),
|
|
1391
|
-
toolConfigs,
|
|
1392
|
-
fsRoots,
|
|
1393
|
-
fsWriteRoots,
|
|
1394
|
-
approve,
|
|
1395
|
-
attach,
|
|
1396
|
-
fetchAttachment,
|
|
1397
|
-
ragSearch,
|
|
1398
|
-
progress,
|
|
1399
|
-
askUser,
|
|
1400
|
-
signal
|
|
1401
|
-
});
|
|
1402
|
-
this._modelFaults.delete(model);
|
|
1403
1842
|
}
|
|
1404
|
-
catch (
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
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);
|
|
1408
1852
|
}
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
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);
|
|
1417
1869
|
}
|
|
1418
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
|
+
}
|
|
1419
1880
|
finally {
|
|
1420
|
-
|
|
1421
|
-
this._replying.splice(this._replying.indexOf(key), 1);
|
|
1422
|
-
releaseHeld();
|
|
1423
|
-
void this._imageRuntime?.unloadIdle().catch(noop);
|
|
1424
|
-
void this._musicRuntime?.unloadIdle().catch(noop);
|
|
1881
|
+
releaseWork();
|
|
1425
1882
|
}
|
|
1426
1883
|
}
|
|
1427
1884
|
_residentEnhancer() {
|
|
@@ -1511,4 +1968,32 @@ export class Harness extends ReadyResource {
|
|
|
1511
1968
|
}
|
|
1512
1969
|
}
|
|
1513
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
|
+
}
|
|
1514
1999
|
function noop() { }
|