@qvac/harness 0.4.0 → 0.6.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 +740 -141
- package/README.md +75 -7
- package/dist/harness.bundle +72 -50
- package/dist-lib/index.d.ts +49 -39
- package/dist-lib/index.js +448 -161
- package/dist-lib/lib/audio-transcode-bare.d.ts +2 -2
- package/dist-lib/lib/audio-transcode-bare.js +28 -18
- package/dist-lib/lib/audio-transcode-node.d.ts +2 -2
- package/dist-lib/lib/capabilities/audio-resample.d.ts +7 -6
- package/dist-lib/lib/capabilities/audio-resample.js +30 -18
- package/dist-lib/lib/capabilities/completion.d.ts +20 -19
- package/dist-lib/lib/capabilities/completion.js +193 -132
- package/dist-lib/lib/capabilities/download.d.ts +6 -6
- package/dist-lib/lib/capabilities/download.js +15 -8
- package/dist-lib/lib/capabilities/embed.js +2 -1
- package/dist-lib/lib/capabilities/enhance.d.ts +6 -1
- package/dist-lib/lib/capabilities/enhance.js +7 -4
- package/dist-lib/lib/capabilities/invoke.js +7 -4
- package/dist-lib/lib/capabilities/kv-cache.js +2 -1
- package/dist-lib/lib/capabilities/preview-model-source.d.ts +9 -7
- package/dist-lib/lib/capabilities/preview-model-source.js +80 -28
- package/dist-lib/lib/capabilities/speech-text.js +5 -4
- package/dist-lib/lib/capabilities/tool-selection.js +10 -9
- package/dist-lib/lib/capabilities/tool-turns.js +30 -16
- package/dist-lib/lib/capabilities/usage-breakdown.js +2 -1
- package/dist-lib/lib/capabilities/voice.d.ts +5 -1
- package/dist-lib/lib/capabilities/voice.js +22 -12
- package/dist-lib/lib/connect.d.ts +13 -32
- package/dist-lib/lib/connect.js +170 -112
- package/dist-lib/lib/context-window.js +25 -21
- package/dist-lib/lib/contract-types.d.ts +35 -15
- package/dist-lib/lib/diagram-repair.d.ts +4 -4
- package/dist-lib/lib/diagram-repair.js +6 -3
- package/dist-lib/lib/docx/index.d.ts +3 -2
- package/dist-lib/lib/{error-fields.d.ts → guards.d.ts} +1 -0
- package/dist-lib/lib/{error-fields.js → guards.js} +3 -0
- package/dist-lib/lib/hardware/capability-probe.js +20 -10
- package/dist-lib/lib/hardware/gpu-classify.js +6 -3
- package/dist-lib/lib/hardware/gpu-probe-addon.js +8 -4
- package/dist-lib/lib/hardware/gpu-probe.js +4 -2
- package/dist-lib/lib/hardware/gpu-stats.native.cjs +8 -9
- package/dist-lib/lib/hardware/parsers.js +8 -4
- package/dist-lib/lib/helpers/buffer-burst.js +4 -2
- package/dist-lib/lib/helpers/control-tokens.js +4 -2
- package/dist-lib/lib/helpers/join-guard.d.ts +2 -2
- package/dist-lib/lib/helpers/join-guard.js +4 -2
- package/dist-lib/lib/helpers/load-stall.js +4 -2
- package/dist-lib/lib/helpers/markup-stream-filter.d.ts +2 -2
- package/dist-lib/lib/helpers/markup-stream-filter.js +6 -3
- package/dist-lib/lib/helpers/race-abort.js +2 -1
- package/dist-lib/lib/helpers/race-timeout.d.ts +5 -0
- package/dist-lib/lib/helpers/race-timeout.js +14 -0
- package/dist-lib/lib/helpers/run-outcome.d.ts +6 -8
- package/dist-lib/lib/helpers/run-outcome.js +36 -20
- package/dist-lib/lib/helpers/spawn-path.js +6 -3
- package/dist-lib/lib/helpers/text-coalescer.js +6 -3
- package/dist-lib/lib/helpers/thinking.js +38 -45
- package/dist-lib/lib/helpers/tool-call-markup.d.ts +9 -7
- package/dist-lib/lib/helpers/tool-call-markup.js +36 -20
- package/dist-lib/lib/helpers/tool-intent.d.ts +3 -2
- package/dist-lib/lib/helpers/tool-intent.js +4 -2
- package/dist-lib/lib/helpers/tool-label.js +18 -9
- package/dist-lib/lib/hooks/built-in/arg-repair.js +23 -13
- package/dist-lib/lib/hooks/built-in/egress.js +2 -1
- package/dist-lib/lib/hooks/built-in/skill-catalog.js +2 -1
- package/dist-lib/lib/hooks/pipeline.d.ts +5 -4
- package/dist-lib/lib/hooks/pipeline.js +6 -3
- package/dist-lib/lib/hooks/run.js +12 -8
- package/dist-lib/lib/model-download-progress.js +10 -8
- package/dist-lib/lib/models/context.d.ts +1 -1
- package/dist-lib/lib/models/context.js +5 -1
- package/dist-lib/lib/models/custom-model-cache.d.ts +10 -1
- package/dist-lib/lib/models/custom-model-cache.js +43 -15
- package/dist-lib/lib/models/gguf-header.d.ts +3 -2
- package/dist-lib/lib/models/gguf-header.js +44 -22
- package/dist-lib/lib/models/idle-unload.d.ts +14 -0
- package/dist-lib/lib/models/idle-unload.js +51 -0
- package/dist-lib/lib/models/kv-cache.d.ts +3 -2
- package/dist-lib/lib/models/kv-cache.js +4 -3
- package/dist-lib/lib/models/llm-presets.d.ts +2 -2
- package/dist-lib/lib/models/llm-presets.js +4 -2
- package/dist-lib/lib/models/model-catalog-constants.js +3 -2
- package/dist-lib/lib/models/model-catalog.d.ts +15 -20
- package/dist-lib/lib/models/model-catalog.js +66 -31
- package/dist-lib/lib/models/model-delete.js +18 -8
- package/dist-lib/lib/models/model-handle.js +2 -1
- package/dist-lib/lib/models/model-list.d.ts +8 -3
- package/dist-lib/lib/models/model-list.js +11 -6
- package/dist-lib/lib/models/model-pool.d.ts +48 -10
- package/dist-lib/lib/models/model-pool.js +106 -45
- package/dist-lib/lib/models/model-source.js +10 -5
- package/dist-lib/lib/models/music-catalog.d.ts +2 -2
- package/dist-lib/lib/models/nmt-catalog.d.ts +4 -3
- package/dist-lib/lib/models/nmt-catalog.js +10 -5
- package/dist-lib/lib/models/opencl-cache.d.ts +6 -1
- package/dist-lib/lib/models/opencl-cache.js +5 -3
- package/dist-lib/lib/models/sdk-faults.d.ts +11 -8
- package/dist-lib/lib/models/sdk-faults.js +9 -6
- package/dist-lib/lib/models/shards.d.ts +11 -0
- package/dist-lib/lib/models/shards.js +15 -0
- package/dist-lib/lib/models/tool-dialect.js +10 -5
- package/dist-lib/lib/no-redirect-fetch-bare.d.ts +3 -3
- package/dist-lib/lib/no-redirect-fetch-bare.js +1 -1
- package/dist-lib/lib/no-redirect-fetch-impl.js +6 -3
- package/dist-lib/lib/no-redirect-fetch-node.d.ts +1 -1
- package/dist-lib/lib/no-redirect-fetch-node.js +9 -9
- package/dist-lib/lib/office/index.d.ts +3 -2
- package/dist-lib/lib/office/python.d.ts +3 -2
- package/dist-lib/lib/office/python.js +2 -1
- package/dist-lib/lib/prompt.d.ts +5 -4
- package/dist-lib/lib/prompt.js +8 -4
- package/dist-lib/lib/queue.js +10 -5
- package/dist-lib/lib/resolution-detector.js +21 -11
- package/dist-lib/lib/rpc-session.js +2 -1
- package/dist-lib/lib/sdk-loader.js +2 -1
- package/dist-lib/lib/sdk-storage.js +4 -2
- package/dist-lib/lib/serve.js +122 -66
- package/dist-lib/lib/spawn.js +7 -4
- package/dist-lib/lib/tmp-path.d.ts +7 -6
- package/dist-lib/lib/tool-call-display.d.ts +2 -2
- package/dist-lib/lib/tools/create-documents.js +18 -10
- package/dist-lib/lib/tools/edit-documents.js +23 -13
- package/dist-lib/lib/tools/edit-image.d.ts +2 -2
- package/dist-lib/lib/tools/edit-image.js +60 -36
- package/dist-lib/lib/tools/exec/adapters/adapter.d.ts +10 -10
- package/dist-lib/lib/tools/exec/adapters/python/attachments.d.ts +17 -11
- package/dist-lib/lib/tools/exec/adapters/python/attachments.js +53 -40
- package/dist-lib/lib/tools/exec/adapters/python/create-effect.d.ts +2 -2
- package/dist-lib/lib/tools/exec/adapters/python/create-effect.js +60 -32
- package/dist-lib/lib/tools/exec/adapters/python/guarded-fetch.d.ts +33 -22
- package/dist-lib/lib/tools/exec/adapters/python/guarded-fetch.js +58 -29
- package/dist-lib/lib/tools/exec/adapters/python/interrupt-timer.d.ts +2 -2
- package/dist-lib/lib/tools/exec/adapters/python/loader.js +23 -12
- package/dist-lib/lib/tools/exec/adapters/python/memfs.d.ts +13 -11
- package/dist-lib/lib/tools/exec/adapters/python/memfs.js +10 -5
- package/dist-lib/lib/tools/exec/adapters/python/net-shim.js +3 -1
- package/dist-lib/lib/tools/exec/adapters/python/output-policy.d.ts +6 -4
- package/dist-lib/lib/tools/exec/adapters/python/output-policy.js +20 -10
- package/dist-lib/lib/tools/exec/adapters/python/pyodide.d.ts +2 -2
- package/dist-lib/lib/tools/exec/adapters/python/pyodide.js +14 -7
- package/dist-lib/lib/tools/exec/adapters/python/python-adapter.d.ts +41 -34
- package/dist-lib/lib/tools/exec/adapters/python/python-adapter.js +316 -166
- package/dist-lib/lib/tools/exec/adapters/python/run-session.d.ts +9 -8
- package/dist-lib/lib/tools/exec/adapters/python/run-session.js +56 -28
- package/dist-lib/lib/tools/exec/adapters/python/wheels.d.ts +8 -10
- package/dist-lib/lib/tools/exec/adapters/python/wheels.js +22 -11
- package/dist-lib/lib/tools/exec/adapters/python/worker/client.d.ts +3 -2
- package/dist-lib/lib/tools/exec/adapters/python/worker/client.js +41 -26
- package/dist-lib/lib/tools/exec/adapters/python/worker/entry.js +27 -18
- package/dist-lib/lib/tools/exec/adapters/python/worker/protocol.d.ts +2 -2
- package/dist-lib/lib/tools/exec/adapters/python/worker/protocol.js +14 -13
- package/dist-lib/lib/tools/exec/adapters/python/worker/spawn-node.d.ts +4 -4
- package/dist-lib/lib/tools/exec/adapters/python/worker/spawn-node.js +10 -5
- package/dist-lib/lib/tools/exec/adapters/shell/shell-adapter.d.ts +4 -4
- package/dist-lib/lib/tools/exec/adapters/shell/shell-adapter.js +2 -1
- package/dist-lib/lib/tools/exec/obsidian/cli-process.d.ts +6 -1
- package/dist-lib/lib/tools/exec/obsidian/cli-process.js +8 -5
- package/dist-lib/lib/tools/exec/obsidian/cli.d.ts +6 -6
- package/dist-lib/lib/tools/exec/obsidian/cli.js +4 -4
- package/dist-lib/lib/tools/exec/obsidian/command.d.ts +3 -2
- package/dist-lib/lib/tools/exec/obsidian/command.js +6 -3
- package/dist-lib/lib/tools/exec/obsidian/output.js +2 -1
- package/dist-lib/lib/tools/exec/obsidian/route.d.ts +4 -6
- package/dist-lib/lib/tools/exec/obsidian/route.js +17 -11
- package/dist-lib/lib/tools/exec/obsidian/safe-path.js +2 -1
- package/dist-lib/lib/tools/exec/obsidian/vault-create.js +15 -7
- package/dist-lib/lib/tools/exec/obsidian/vault-paths.d.ts +2 -2
- package/dist-lib/lib/tools/exec/obsidian/vault-paths.js +20 -10
- package/dist-lib/lib/tools/exec/obsidian/vault-read.js +20 -13
- package/dist-lib/lib/tools/exec/obsidian/vault-write.js +24 -12
- package/dist-lib/lib/tools/exec/runner.d.ts +14 -14
- package/dist-lib/lib/tools/exec/runner.js +5 -3
- package/dist-lib/lib/tools/exec/scope.js +52 -28
- package/dist-lib/lib/tools/exec/shell-argv.js +31 -18
- package/dist-lib/lib/tools/exec/shell-guidance.d.ts +6 -1
- package/dist-lib/lib/tools/exec/shell-guidance.js +35 -20
- package/dist-lib/lib/tools/exec/video-tools.js +2 -2
- package/dist-lib/lib/tools/exec.d.ts +6 -8
- package/dist-lib/lib/tools/exec.js +113 -54
- package/dist-lib/lib/tools/file-glob.d.ts +4 -5
- package/dist-lib/lib/tools/file-glob.js +16 -11
- package/dist-lib/lib/tools/fs/glob.js +13 -18
- package/dist-lib/lib/tools/fs/remote-protocol.d.ts +10 -10
- package/dist-lib/lib/tools/fs/remote.d.ts +3 -2
- package/dist-lib/lib/tools/fs/remote.js +13 -7
- package/dist-lib/lib/tools/fs/scope.d.ts +8 -3
- package/dist-lib/lib/tools/fs/scope.js +20 -12
- package/dist-lib/lib/tools/fs/walk.js +6 -3
- package/dist-lib/lib/tools/fs/write.d.ts +3 -2
- package/dist-lib/lib/tools/generate-image.d.ts +11 -9
- package/dist-lib/lib/tools/generate-image.js +73 -37
- package/dist-lib/lib/tools/generate-music.d.ts +22 -11
- package/dist-lib/lib/tools/generate-music.js +79 -48
- package/dist-lib/lib/tools/grep.js +66 -41
- package/dist-lib/lib/tools/http-request/encode-fields.js +18 -9
- package/dist-lib/lib/tools/http-request/{utils.js → request.js} +20 -11
- package/dist-lib/lib/tools/http-request/truncate-response.js +57 -46
- package/dist-lib/lib/tools/http-request.d.ts +1 -1
- package/dist-lib/lib/tools/http-request.js +33 -18
- package/dist-lib/lib/tools/mcp/protocol.d.ts +53 -0
- package/dist-lib/lib/tools/mcp/{utils.js → protocol.js} +45 -23
- package/dist-lib/lib/tools/mcp-call.d.ts +1 -1
- package/dist-lib/lib/tools/mcp-call.js +37 -16
- package/dist-lib/lib/tools/operations/builders.js +10 -6
- package/dist-lib/lib/tools/operations/compile.js +20 -10
- package/dist-lib/lib/tools/operations/decoders.js +12 -6
- package/dist-lib/lib/tools/operations/gmail-message.js +3 -2
- package/dist-lib/lib/tools/outbound-guard.d.ts +4 -3
- package/dist-lib/lib/tools/outbound-guard.js +14 -8
- package/dist-lib/lib/tools/private-ip.js +8 -7
- package/dist-lib/lib/tools/rag-search.js +14 -7
- package/dist-lib/lib/tools/read-files.js +23 -20
- package/dist-lib/lib/tools/skill.js +19 -10
- package/dist-lib/lib/tools/skills/auth-resolver.d.ts +11 -18
- package/dist-lib/lib/tools/skills/auth-resolver.js +30 -18
- package/dist-lib/lib/tools/skills/bin-check.d.ts +3 -2
- package/dist-lib/lib/tools/skills/bin-check.js +10 -3
- package/dist-lib/lib/tools/skills/bundled-dir.js +20 -10
- package/dist-lib/lib/tools/skills/bundled-hash.d.ts +3 -2
- package/dist-lib/lib/tools/skills/bundled-hash.js +6 -1
- package/dist-lib/lib/tools/skills/catalog.d.ts +3 -2
- package/dist-lib/lib/tools/skills/catalog.js +33 -28
- package/dist-lib/lib/tools/skills/cli-argv.d.ts +3 -2
- package/dist-lib/lib/tools/skills/cli-argv.js +55 -52
- package/dist-lib/lib/tools/skills/cli-schema-check.js +21 -10
- package/dist-lib/lib/tools/skills/cli-schema-load.js +28 -20
- package/dist-lib/lib/tools/skills/cli-schema-types.d.ts +6 -6
- package/dist-lib/lib/tools/skills/cli-schema.js +2 -1
- package/dist-lib/lib/tools/skills/credential-check.d.ts +6 -3
- package/dist-lib/lib/tools/skills/credential-check.js +24 -12
- package/dist-lib/lib/tools/skills/credential-grants.js +1 -6
- package/dist-lib/lib/tools/skills/credential-health.d.ts +5 -1
- package/dist-lib/lib/tools/skills/credential-health.js +5 -3
- package/dist-lib/lib/tools/skills/frontmatter-parser.js +10 -5
- package/dist-lib/lib/tools/skills/manifest-parse.js +84 -64
- package/dist-lib/lib/tools/skills/mention.js +7 -6
- package/dist-lib/lib/tools/skills/obsidian-readiness.d.ts +2 -2
- package/dist-lib/lib/tools/skills/obsidian-readiness.js +11 -3
- package/dist-lib/lib/tools/skills/overlay-crud.d.ts +6 -4
- package/dist-lib/lib/tools/skills/overlay-crud.js +12 -6
- package/dist-lib/lib/tools/skills/overlay-dir.js +4 -2
- package/dist-lib/lib/tools/skills/overlay-watch.d.ts +3 -2
- package/dist-lib/lib/tools/skills/overlay-watch.js +8 -4
- package/dist-lib/lib/tools/skills/setup-routes.js +1 -3
- package/dist-lib/lib/tools/skills/skill-settings.js +4 -1
- package/dist-lib/lib/tools/skills/skill-state.d.ts +6 -11
- package/dist-lib/lib/tools/skills/skill-state.js +48 -29
- package/dist-lib/lib/tools/skills/tool-grants.js +6 -3
- package/dist-lib/lib/tools/toolsets.js +2 -1
- package/dist-lib/lib/tools/translate.d.ts +4 -5
- package/dist-lib/lib/tools/translate.js +31 -14
- package/dist-lib/lib/tools/url-guard.js +2 -1
- package/dist-lib/lib/tools/weather-lookup/place.js +8 -4
- package/dist-lib/lib/tools/weather-lookup/report.d.ts +7 -1
- package/dist-lib/lib/tools/weather-lookup/report.js +46 -9
- package/dist-lib/lib/tools/weather-lookup.js +18 -12
- package/dist-lib/lib/tools/web-fetch.js +47 -24
- package/dist-lib/lib/tools/web-search.js +8 -3
- package/dist-lib/lib/validating-lookup.d.ts +4 -4
- package/dist-lib/lib/wav-encode.d.ts +1 -1
- package/dist-lib/lib/wav-encode.js +11 -10
- package/dist-lib/lib/web-fetch/embedded-state.js +24 -12
- package/dist-lib/lib/web-fetch/extract.js +4 -2
- package/dist-lib/lib/web-fetch/images.js +8 -4
- package/dist-lib/lib/web-fetch/video.d.ts +2 -2
- package/dist-lib/lib/web-fetch/video.js +8 -4
- package/dist-lib/lib/web-search/engine-registry.d.ts +2 -2
- package/dist-lib/lib/web-search/engines/aol.js +1 -1
- package/dist-lib/lib/web-search/engines/bing.js +1 -1
- package/dist-lib/lib/web-search/engines/brave.js +4 -1
- package/dist-lib/lib/web-search/engines/duckduckgo.js +3 -2
- package/dist-lib/lib/web-search/engines/google.js +7 -3
- package/dist-lib/lib/web-search/engines/wikipedia.js +5 -4
- package/dist-lib/lib/web-search/engines/yahoo.js +1 -1
- package/dist-lib/lib/web-search/fetch-html.d.ts +5 -4
- package/dist-lib/lib/web-search/fetch-html.js +13 -7
- package/dist-lib/lib/web-search/orchestrator.d.ts +9 -5
- package/dist-lib/lib/web-search/orchestrator.js +3 -3
- package/dist-lib/lib/wire.d.ts +3 -2
- package/dist-lib/lib/wire.js +23 -15
- package/dist-lib/schema/baseline.js +10 -5
- package/dist-lib/schema/build.js +2 -1
- package/dist-lib/schema/harness.d.ts +15 -2
- package/dist-lib/schema/harness.js +39 -2
- package/dist-lib/schema/release-tags.d.ts +3 -2
- package/dist-lib/schema/release-tags.js +6 -3
- package/dist-lib/spec/bind.js +9 -0
- package/dist-lib/spec/calls.js +3 -0
- package/dist-lib/spec/hrpc/hrpc.json +39 -0
- package/dist-lib/spec/hrpc/index.js +39 -3
- package/dist-lib/spec/hrpc/messages.js +105 -8
- package/dist-lib/spec/hyperschema/index.js +105 -8
- package/dist-lib/spec/hyperschema/schema.json +77 -1
- package/dist-lib/spec/hyperschema/types.d.ts +25 -1
- package/package.json +14 -14
- package/worker/harness-sidecar.mjs +15 -22
- package/worker/stow-entry.mjs +7 -4
- package/dist-lib/lib/tools/mcp/utils.d.ts +0 -47
- /package/dist-lib/lib/tools/http-request/{utils.d.ts → request.d.ts} +0 -0
package/dist-lib/index.js
CHANGED
|
@@ -21,11 +21,13 @@ import { buildWavBuffer } from './lib/wav-encode.js';
|
|
|
21
21
|
import { createVoiceLane, packSamples, speakOnce, speakStreamOnce, transcribeOnce, transcribeStreaming, SUPERTONIC_SAMPLE_RATE } from './lib/capabilities/voice.js';
|
|
22
22
|
import { pushQueue } from './lib/queue.js';
|
|
23
23
|
import { AbortedError, raceAbort } from './lib/helpers/race-abort.js';
|
|
24
|
-
import { audioGenDownloadMetadata, customProjectionMap, defaultProjectionMap, defaultTtsFor, defaultVoiceModel, diffusionDownloadMetadata, infraModelsFor, modelNameToCapabilityProfile, musicModelSet, setCustomModels } from './lib/models/model-catalog.js';
|
|
24
|
+
import { audioGenDownloadMetadata, customProjectionMap, defaultProjectionMap, defaultTtsFor, defaultVoiceModel, diffusionDownloadMetadata, curatedModelAsset, infraModelsFor, modelAddonOf, modelNameToCapabilityProfile, musicModelSet, residentSrc, setCustomModels } from './lib/models/model-catalog.js';
|
|
25
25
|
import { createModelStatusCache, customModelRows, invalidateModelStatusCache, mergeCustomRows, listModels } from './lib/models/model-list.js';
|
|
26
26
|
import { deleteModelFiles, summarizeDelete } from './lib/models/model-delete.js';
|
|
27
|
-
import { createEmbedPool, createLoadTracker, createModelPool, createVoicePool, awaitModelCapacity, evictIdle, evictModelWithin, isResident, leaseResidentModel, leaseCount, loadedModels, modelKey, releaseModel, resolveLoadOptions, progressState } from './lib/models/model-pool.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';
|
|
28
|
+
import { defaultContextSize } from './lib/models/context.js';
|
|
28
29
|
import { resolveMainGpu } from './lib/models/gpu-selection.js';
|
|
30
|
+
import { createIdleReaper } from './lib/models/idle-unload.js';
|
|
29
31
|
import { openclCacheDir } from './lib/models/opencl-cache.js';
|
|
30
32
|
import { assertEmbedDevice, createEmbedConfig } from './lib/models/embed-config.js';
|
|
31
33
|
import { createGpuFaultState, isSdkGpuDeviceLostError, isSdkModelWedgedError } from './lib/models/sdk-faults.js';
|
|
@@ -39,6 +41,7 @@ import { ensureBundledSkillsDir } from './lib/tools/skills/bundled-dir.js';
|
|
|
39
41
|
import { ensureSkillsOverlayDir } from './lib/tools/skills/overlay-dir.js';
|
|
40
42
|
import { copyBaseForEdit, deleteOverlaySkill, listManagedSkills, readSkillFile, writeSkillFile } from './lib/tools/skills/overlay-crud.js';
|
|
41
43
|
import { watchSkillsOverlay } from './lib/tools/skills/overlay-watch.js';
|
|
44
|
+
import { errorMessage } from './lib/guards.js';
|
|
42
45
|
import { hlog } from './lib/log.js';
|
|
43
46
|
import { harness } from './spec/index.js';
|
|
44
47
|
import { installResolutionDetector, resolutionDetail } from './lib/resolution-detector.js';
|
|
@@ -79,6 +82,7 @@ const MUSIC_EVICT_WAIT_MS = 15_000;
|
|
|
79
82
|
const WEDGED_EVICT_WAIT_MS = 10_000;
|
|
80
83
|
const MODEL_ROOM_WAIT_MS = 300_000;
|
|
81
84
|
const WARM_ROOM_WAIT_MS = 2_000;
|
|
85
|
+
const UNLOAD_LEASE_WAIT_MS = 10_000;
|
|
82
86
|
const DEFAULT_COMPLETION_MODEL_CAP = 1;
|
|
83
87
|
const WEDGED_EVICT_LEASED_MS = 120_000;
|
|
84
88
|
const TTS_MODEL_CONFIG = {
|
|
@@ -91,6 +95,8 @@ export class Harness extends ReadyResource {
|
|
|
91
95
|
_tools;
|
|
92
96
|
_sdk;
|
|
93
97
|
_loads = createLoadTracker();
|
|
98
|
+
_pinned = new Set();
|
|
99
|
+
_replying = [];
|
|
94
100
|
_modelStatus = createModelStatusCache();
|
|
95
101
|
_models;
|
|
96
102
|
_embedModels;
|
|
@@ -133,6 +139,7 @@ export class Harness extends ReadyResource {
|
|
|
133
139
|
_capability = null;
|
|
134
140
|
_capabilityInflight = null;
|
|
135
141
|
_modelFaults = new Map();
|
|
142
|
+
_idle;
|
|
136
143
|
constructor({ tools = [], sdkLoader, skillsDir, modelsDir, projectionModel, imageModel, imageClamps, musicModel, musicClamps, llmDevice, llmGpuLayers, contextSize, kvCacheK, kvCacheV, embedDevice, embedGpuLayers, embedBatchSize, transcriptionDevice, completionModelCap, credentialStore, platform, gpuStats } = {}) {
|
|
137
144
|
super();
|
|
138
145
|
this._sdk = sdkLoader ?? createSdkLoader();
|
|
@@ -177,8 +184,9 @@ export class Harness extends ReadyResource {
|
|
|
177
184
|
},
|
|
178
185
|
onLoaded: (model) => {
|
|
179
186
|
const transfer = imageTransfers.get(model);
|
|
180
|
-
if (!transfer)
|
|
187
|
+
if (!transfer) {
|
|
181
188
|
return;
|
|
189
|
+
}
|
|
182
190
|
imageTransfers.delete(model);
|
|
183
191
|
invalidateModelStatusCache(this._modelStatus);
|
|
184
192
|
this.emit('model', { ...transfer, status: 'downloaded' });
|
|
@@ -186,9 +194,14 @@ export class Harness extends ReadyResource {
|
|
|
186
194
|
onBeforeLoad: async (signal) => {
|
|
187
195
|
if (this._models.destroyed)
|
|
188
196
|
return;
|
|
189
|
-
const evicted = await awaitModelCapacity(this._models,
|
|
190
|
-
|
|
197
|
+
const evicted = await awaitModelCapacity(this._models, {
|
|
198
|
+
cap: 1,
|
|
199
|
+
deadlineMs: DIFFUSION_EVICT_WAIT_MS,
|
|
200
|
+
signal
|
|
201
|
+
});
|
|
202
|
+
if (signal?.aborted) {
|
|
191
203
|
throw new AbortedError();
|
|
204
|
+
}
|
|
192
205
|
if (!evicted) {
|
|
193
206
|
throw new Error('the chat model is busy; try the image again once the current reply finishes');
|
|
194
207
|
}
|
|
@@ -205,9 +218,14 @@ export class Harness extends ReadyResource {
|
|
|
205
218
|
onBeforeLoad: async (signal) => {
|
|
206
219
|
if (this._models.destroyed)
|
|
207
220
|
return;
|
|
208
|
-
const evicted = await awaitModelCapacity(this._models,
|
|
209
|
-
|
|
221
|
+
const evicted = await awaitModelCapacity(this._models, {
|
|
222
|
+
cap: 1,
|
|
223
|
+
deadlineMs: MUSIC_EVICT_WAIT_MS,
|
|
224
|
+
signal
|
|
225
|
+
});
|
|
226
|
+
if (signal?.aborted) {
|
|
210
227
|
throw new AbortedError();
|
|
228
|
+
}
|
|
211
229
|
if (!evicted) {
|
|
212
230
|
throw new Error('the chat model is busy; try the music again once the current reply finishes');
|
|
213
231
|
}
|
|
@@ -245,13 +263,18 @@ export class Harness extends ReadyResource {
|
|
|
245
263
|
this._diffusionSdk = null;
|
|
246
264
|
this._musicSdk = null;
|
|
247
265
|
this._translationSdk = null;
|
|
248
|
-
}
|
|
249
|
-
async _open() {
|
|
250
|
-
installResolutionDetector(import.meta);
|
|
251
266
|
this._models = this._createPool();
|
|
252
267
|
this._embedModels = this._createEmbedPool();
|
|
253
268
|
this._ttsModels = this._createTtsPool();
|
|
254
269
|
this._transcriptionModels = this._createTranscriptionPool();
|
|
270
|
+
this._idle = createIdleReaper({
|
|
271
|
+
sweep: () => this._unloadIdleModels(),
|
|
272
|
+
waitMs: WEDGED_EVICT_WAIT_MS,
|
|
273
|
+
onError: (err) => hlog('idle unload failed', { err: errorMessage(err) })
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
async _open() {
|
|
277
|
+
installResolutionDetector(import.meta);
|
|
255
278
|
await this._reloadSkills();
|
|
256
279
|
await this._startOverlayWatch();
|
|
257
280
|
hlog('open', { skills: this._supportedSkills.length, tools: this._tools.length });
|
|
@@ -269,10 +292,7 @@ export class Harness extends ReadyResource {
|
|
|
269
292
|
if (!token)
|
|
270
293
|
continue;
|
|
271
294
|
const storeKey = scopeKey({ agentId, skill, key });
|
|
272
|
-
const record = {
|
|
273
|
-
token,
|
|
274
|
-
...(typeof expiresAt === 'number' && expiresAt > 0 ? { expiresAt } : {})
|
|
275
|
-
};
|
|
295
|
+
const record = typeof expiresAt === 'number' && expiresAt > 0 ? { token, expiresAt } : { token };
|
|
276
296
|
const current = previous.get(storeKey);
|
|
277
297
|
if (!current || current.token !== record.token || current.expiresAt !== record.expiresAt) {
|
|
278
298
|
resetCredentialHealthAfterSnapshot(key, agentId, skill);
|
|
@@ -310,48 +330,57 @@ export class Harness extends ReadyResource {
|
|
|
310
330
|
}
|
|
311
331
|
async writeSkillFile(name, content) {
|
|
312
332
|
const overlay = await ensureSkillsOverlayDir();
|
|
313
|
-
if (!overlay)
|
|
333
|
+
if (!overlay) {
|
|
314
334
|
throw new Error('no writable skills overlay is configured');
|
|
335
|
+
}
|
|
315
336
|
await writeSkillFile(overlay, name, content);
|
|
316
337
|
await this._reloadSkills();
|
|
317
338
|
return { ok: true };
|
|
318
339
|
}
|
|
319
340
|
async copyBaseForEdit(name) {
|
|
320
341
|
const overlay = await ensureSkillsOverlayDir();
|
|
321
|
-
if (!overlay)
|
|
342
|
+
if (!overlay) {
|
|
322
343
|
throw new Error('no writable skills overlay is configured');
|
|
344
|
+
}
|
|
323
345
|
const result = await copyBaseForEdit(await this._baseSkillsDir(), overlay, name);
|
|
324
|
-
if (result.copied)
|
|
346
|
+
if (result.copied) {
|
|
325
347
|
await this._reloadSkills();
|
|
348
|
+
}
|
|
326
349
|
return result;
|
|
327
350
|
}
|
|
328
351
|
async deleteOverlaySkill(name) {
|
|
329
352
|
const overlay = await ensureSkillsOverlayDir();
|
|
330
|
-
if (!overlay)
|
|
353
|
+
if (!overlay) {
|
|
331
354
|
throw new Error('no writable skills overlay is configured');
|
|
355
|
+
}
|
|
332
356
|
const result = await deleteOverlaySkill(overlay, name);
|
|
333
|
-
if (result.removed)
|
|
357
|
+
if (result.removed) {
|
|
334
358
|
await this._reloadSkills();
|
|
359
|
+
}
|
|
335
360
|
return result;
|
|
336
361
|
}
|
|
337
362
|
resolveCredential(key, ctx) {
|
|
338
|
-
if (this._credentialStore)
|
|
363
|
+
if (this._credentialStore) {
|
|
339
364
|
return this._credentialStore(key, ctx);
|
|
365
|
+
}
|
|
340
366
|
const agentId = ctx?.agentId ?? null;
|
|
341
367
|
const deviceShared = isDeviceSharedCredentialKey(key);
|
|
342
368
|
for (const skill of ctx?.skills ?? []) {
|
|
343
369
|
const owned = this._credentials.get(scopeKey({ agentId, skill, key })) ??
|
|
344
370
|
(agentId && deviceShared ? this._credentials.get(scopeKey({ skill, key })) : undefined);
|
|
345
|
-
if (owned)
|
|
371
|
+
if (owned) {
|
|
346
372
|
return owned;
|
|
373
|
+
}
|
|
347
374
|
}
|
|
348
375
|
if (agentId) {
|
|
349
376
|
const scoped = this._credentials.get(scopeKey({ agentId, key }));
|
|
350
|
-
if (scoped)
|
|
377
|
+
if (scoped) {
|
|
351
378
|
return scoped;
|
|
379
|
+
}
|
|
352
380
|
}
|
|
353
|
-
if (!agentId || deviceShared)
|
|
381
|
+
if (!agentId || deviceShared) {
|
|
354
382
|
return this._credentials.get(scopeKey({ key })) ?? null;
|
|
383
|
+
}
|
|
355
384
|
return null;
|
|
356
385
|
}
|
|
357
386
|
async _completionSkills(agentId, model) {
|
|
@@ -362,7 +391,8 @@ export class Harness extends ReadyResource {
|
|
|
362
391
|
}));
|
|
363
392
|
}
|
|
364
393
|
async skills(agentId, modelName, options) {
|
|
365
|
-
|
|
394
|
+
if (!this.opened)
|
|
395
|
+
await this.ready();
|
|
366
396
|
const platform = this._platform ?? detectPlatform();
|
|
367
397
|
const refresh = options?.refresh === true;
|
|
368
398
|
return resolveSkillStatuses(this._allSkills, {
|
|
@@ -376,7 +406,7 @@ export class Harness extends ReadyResource {
|
|
|
376
406
|
: await this._probeBinVersions(platform, refresh),
|
|
377
407
|
refresh,
|
|
378
408
|
skipCredentialProbe: options?.skipCredentialProbe === true,
|
|
379
|
-
|
|
409
|
+
activeProfile: modelName ? modelNameToCapabilityProfile(modelName) : undefined
|
|
380
410
|
});
|
|
381
411
|
}
|
|
382
412
|
async _reloadSkills() {
|
|
@@ -391,14 +421,15 @@ export class Harness extends ReadyResource {
|
|
|
391
421
|
return;
|
|
392
422
|
try {
|
|
393
423
|
const overlay = await ensureSkillsOverlayDir();
|
|
394
|
-
if (!overlay)
|
|
424
|
+
if (!overlay) {
|
|
395
425
|
return;
|
|
426
|
+
}
|
|
396
427
|
this._overlayWatcher = watchSkillsOverlay(overlay, () => {
|
|
397
|
-
this._reloadSkills().catch((err) => hlog('overlay reload failed', { err:
|
|
428
|
+
this._reloadSkills().catch((err) => hlog('overlay reload failed', { err: errorMessage(err) }));
|
|
398
429
|
});
|
|
399
430
|
}
|
|
400
431
|
catch (err) {
|
|
401
|
-
hlog('overlay watch unavailable', { err:
|
|
432
|
+
hlog('overlay watch unavailable', { err: errorMessage(err) });
|
|
402
433
|
}
|
|
403
434
|
}
|
|
404
435
|
async tools() {
|
|
@@ -411,8 +442,9 @@ export class Harness extends ReadyResource {
|
|
|
411
442
|
if (!refresh && this._binVersionCache && this._binVersionCache.expiresAt > now) {
|
|
412
443
|
return this._binVersionCache.bySkill;
|
|
413
444
|
}
|
|
414
|
-
if (this._binVersionInflight)
|
|
445
|
+
if (this._binVersionInflight) {
|
|
415
446
|
return this._binVersionInflight;
|
|
447
|
+
}
|
|
416
448
|
this._binVersionInflight = this._runBinVersionProbes(platform)
|
|
417
449
|
.then((bySkill) => {
|
|
418
450
|
this._binVersionCache = {
|
|
@@ -427,12 +459,14 @@ export class Harness extends ReadyResource {
|
|
|
427
459
|
return this._binVersionInflight;
|
|
428
460
|
}
|
|
429
461
|
async _runBinVersionProbes(platform) {
|
|
430
|
-
if (process.env.QVAC_NO_SKILL_BIN_PROBE)
|
|
462
|
+
if (process.env.QVAC_NO_SKILL_BIN_PROBE) {
|
|
431
463
|
return new Map();
|
|
464
|
+
}
|
|
432
465
|
const installed = new Set(this._tools.map((tool) => tool.name));
|
|
433
466
|
const candidates = versionProbeSkills(this._allSkills, installed, platform);
|
|
434
|
-
if (candidates.length === 0)
|
|
467
|
+
if (candidates.length === 0) {
|
|
435
468
|
return new Map();
|
|
469
|
+
}
|
|
436
470
|
let runtime;
|
|
437
471
|
try {
|
|
438
472
|
runtime = await bareRuntime();
|
|
@@ -452,8 +486,9 @@ export class Harness extends ReadyResource {
|
|
|
452
486
|
const bySkill = new Map();
|
|
453
487
|
for (const skill of candidates) {
|
|
454
488
|
const states = await probeBinVersions(skill.requires, { platform, run });
|
|
455
|
-
if (states.size > 0)
|
|
489
|
+
if (states.size > 0) {
|
|
456
490
|
bySkill.set(skill.name, states);
|
|
491
|
+
}
|
|
457
492
|
}
|
|
458
493
|
return bySkill;
|
|
459
494
|
}
|
|
@@ -472,31 +507,35 @@ export class Harness extends ReadyResource {
|
|
|
472
507
|
}
|
|
473
508
|
_projectionFor(model) {
|
|
474
509
|
const custom = this._customProjection[model];
|
|
475
|
-
if (custom)
|
|
510
|
+
if (custom) {
|
|
476
511
|
return custom;
|
|
512
|
+
}
|
|
477
513
|
const projection = this._projectionModel;
|
|
478
|
-
if (!projection)
|
|
514
|
+
if (!projection) {
|
|
479
515
|
return undefined;
|
|
516
|
+
}
|
|
480
517
|
return typeof projection === 'string' ? projection : projection[model];
|
|
481
518
|
}
|
|
482
519
|
setCustomModels(entries) {
|
|
483
520
|
setCustomModels(entries);
|
|
484
521
|
this._customProjection = customProjectionMap();
|
|
485
522
|
}
|
|
523
|
+
setModelIdleTimeout(ms) {
|
|
524
|
+
this._idle.configure(ms);
|
|
525
|
+
}
|
|
486
526
|
previewModelSource(context) {
|
|
487
|
-
return previewModelSource(context, {
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
const model = result.models[0];
|
|
494
|
-
return {
|
|
495
|
-
fit: model?.verdict ?? result.verdict,
|
|
496
|
-
fitReasons: [...(model?.reasons ?? result.reasons)]
|
|
497
|
-
};
|
|
498
|
-
}
|
|
527
|
+
return previewModelSource(context, { assessFit: (query) => this._assessFit(query) });
|
|
528
|
+
}
|
|
529
|
+
async _assessFit({ sha256Checksum, name, contextTokens }) {
|
|
530
|
+
const sdk = await this._sdk.load();
|
|
531
|
+
const result = await sdk.assessModelFit({
|
|
532
|
+
models: [{ model: { sha256Checksum, name }, workload: { kind: 'llm', contextTokens } }]
|
|
499
533
|
});
|
|
534
|
+
const model = result.models[0];
|
|
535
|
+
return {
|
|
536
|
+
fit: model?.verdict ?? result.verdict,
|
|
537
|
+
fitReasons: [...(model?.reasons ?? result.reasons)]
|
|
538
|
+
};
|
|
500
539
|
}
|
|
501
540
|
async describe() {
|
|
502
541
|
if (!this.opened)
|
|
@@ -519,11 +558,12 @@ export class Harness extends ReadyResource {
|
|
|
519
558
|
};
|
|
520
559
|
}
|
|
521
560
|
_loadedModels() {
|
|
522
|
-
const pools = [
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
561
|
+
const pools = [
|
|
562
|
+
this._embedModels,
|
|
563
|
+
...this._retiredEmbedModels,
|
|
564
|
+
this._ttsModels,
|
|
565
|
+
this._transcriptionModels
|
|
566
|
+
];
|
|
527
567
|
return [...loadedModels(this._models), ...pools.flatMap((pool) => loadedModels(pool))];
|
|
528
568
|
}
|
|
529
569
|
_loadPluginSdk(current, store, importPlugin) {
|
|
@@ -557,8 +597,9 @@ export class Harness extends ReadyResource {
|
|
|
557
597
|
const loadOpts = resolveLoadOptions({}, this._deviceConfig());
|
|
558
598
|
const key = modelKey(model, loadOpts);
|
|
559
599
|
const pool = this._models;
|
|
560
|
-
if (!isResident(pool, key))
|
|
561
|
-
await this._makeRoomForCompletion(signal);
|
|
600
|
+
if (!isResident(pool, key)) {
|
|
601
|
+
await this._makeRoomForCompletion(model, { ctxSize: loadOpts.ctxSize, signal });
|
|
602
|
+
}
|
|
562
603
|
try {
|
|
563
604
|
const { modelId } = await raceAbort(pool.open(key, model, loadOpts).load(), signal);
|
|
564
605
|
const sdk = await this._sdk.load();
|
|
@@ -592,25 +633,51 @@ export class Harness extends ReadyResource {
|
|
|
592
633
|
}
|
|
593
634
|
_createPool() {
|
|
594
635
|
const loadSdk = this._sdk.load;
|
|
595
|
-
return createModelPool(loadSdk, (state) => this._notifyModel(state
|
|
636
|
+
return createModelPool(loadSdk, (state) => this._notifyModel(state), {
|
|
637
|
+
deviceConfig: this._deviceConfig(),
|
|
638
|
+
loads: this._loads
|
|
639
|
+
});
|
|
596
640
|
}
|
|
597
641
|
_createEmbedPool() {
|
|
598
642
|
const loadSdk = () => this._sdk.loadEmbed();
|
|
599
|
-
return createEmbedPool(loadSdk, (state) => this._notifyModel(state
|
|
643
|
+
return createEmbedPool(loadSdk, (state) => this._notifyModel(state), {
|
|
644
|
+
faults: this._embedFaults,
|
|
645
|
+
batchSize: this._embedBatchSize,
|
|
646
|
+
openclCacheDir,
|
|
647
|
+
loads: this._loads
|
|
648
|
+
});
|
|
600
649
|
}
|
|
601
650
|
_createTtsPool() {
|
|
602
651
|
const loadSdk = () => this._sdk.loadVoice();
|
|
603
|
-
return createVoicePool(loadSdk, (state) => this._notifyModel(state
|
|
652
|
+
return createVoicePool(loadSdk, (state) => this._notifyModel(state), {
|
|
653
|
+
modelType: 'tts-ggml',
|
|
654
|
+
addon: 'tts',
|
|
655
|
+
modelConfig: TTS_MODEL_CONFIG,
|
|
656
|
+
loads: this._loads
|
|
657
|
+
});
|
|
604
658
|
}
|
|
605
659
|
_createTranscriptionPool() {
|
|
606
660
|
const loadSdk = () => this._sdk.loadVoice();
|
|
607
|
-
return createVoicePool(loadSdk, (state) => this._notifyModel({ ...state, model: TRANSCRIPTION_MODEL_LABEL }
|
|
661
|
+
return createVoicePool(loadSdk, (state) => this._notifyModel({ ...state, model: TRANSCRIPTION_MODEL_LABEL }), {
|
|
662
|
+
modelType: 'parakeet-transcription',
|
|
663
|
+
addon: 'parakeet',
|
|
664
|
+
modelConfig: { useGPU: this._transcriptionDevice !== 'cpu' },
|
|
665
|
+
openclCacheDir,
|
|
666
|
+
loads: this._loads
|
|
667
|
+
});
|
|
608
668
|
}
|
|
609
|
-
_notifyModel(state
|
|
610
|
-
const catalogChanged =
|
|
669
|
+
_notifyModel(state) {
|
|
670
|
+
const catalogChanged = state.status === 'ready' || state.status === 'unloaded';
|
|
611
671
|
if (catalogChanged) {
|
|
612
672
|
invalidateModelStatusCache(this._modelStatus);
|
|
613
673
|
}
|
|
674
|
+
if (state.status === 'unloaded') {
|
|
675
|
+
for (const key of this._pinned) {
|
|
676
|
+
if (!isResident(this._models, key)) {
|
|
677
|
+
this._pinned.delete(key);
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
}
|
|
614
681
|
this.emit('model', { ...state, catalogChanged, loaded: this._loadedModels() });
|
|
615
682
|
}
|
|
616
683
|
async _collectRetiredEmbedPool(pool) {
|
|
@@ -646,14 +713,17 @@ export class Harness extends ReadyResource {
|
|
|
646
713
|
return this._enqueueEmbedControl(() => this._setEmbeddingDevice(device));
|
|
647
714
|
}
|
|
648
715
|
async _setEmbeddingDevice(device) {
|
|
649
|
-
|
|
716
|
+
if (!this.opened)
|
|
717
|
+
await this.ready();
|
|
650
718
|
if (this.closing)
|
|
651
719
|
throw new Error('harness is closing');
|
|
652
|
-
if (this._embedFaults.device === device)
|
|
720
|
+
if (this._embedFaults.device === device) {
|
|
653
721
|
return;
|
|
722
|
+
}
|
|
654
723
|
this._embedFaults = createGpuFaultState({ device, gpuLayers: this._embedGpuLayers });
|
|
655
|
-
if (this.suspended)
|
|
724
|
+
if (this.suspended) {
|
|
656
725
|
return;
|
|
726
|
+
}
|
|
657
727
|
const retired = this._embedModels;
|
|
658
728
|
this._retiredEmbedModels.add(retired);
|
|
659
729
|
this._embedModels = this._createEmbedPool();
|
|
@@ -667,15 +737,18 @@ export class Harness extends ReadyResource {
|
|
|
667
737
|
}
|
|
668
738
|
async suspend() {
|
|
669
739
|
hlog('suspend called');
|
|
670
|
-
if (this._state === HarnessState.SUSPEND)
|
|
740
|
+
if (this._state === HarnessState.SUSPEND) {
|
|
671
741
|
return;
|
|
672
|
-
|
|
742
|
+
}
|
|
743
|
+
if (this.closing) {
|
|
673
744
|
return;
|
|
745
|
+
}
|
|
674
746
|
this._state = HarnessState.SUSPEND;
|
|
675
747
|
hlog('suspending', { models: loadedModels(this._models).map((entry) => entry.model) });
|
|
676
748
|
const pending = this._sdk.peek();
|
|
677
|
-
if (pending)
|
|
749
|
+
if (pending) {
|
|
678
750
|
this._loads.drain(await pending);
|
|
751
|
+
}
|
|
679
752
|
await this._releaseRuntimes();
|
|
680
753
|
await this._sdkLifecycle('suspend');
|
|
681
754
|
invalidateModelStatusCache(this._modelStatus);
|
|
@@ -683,18 +756,20 @@ export class Harness extends ReadyResource {
|
|
|
683
756
|
}
|
|
684
757
|
async _sdkLifecycle(op) {
|
|
685
758
|
const sdk = await this._sdk.peek()?.catch(noop);
|
|
686
|
-
if (!sdk)
|
|
759
|
+
if (!sdk) {
|
|
687
760
|
return;
|
|
761
|
+
}
|
|
688
762
|
try {
|
|
689
763
|
await sdk[op]();
|
|
690
764
|
}
|
|
691
765
|
catch (err) {
|
|
692
|
-
hlog(`sdk ${op} failed`, { err:
|
|
693
|
-
if (op === 'suspend')
|
|
766
|
+
hlog(`sdk ${op} failed`, { err: errorMessage(err) });
|
|
767
|
+
if (op === 'suspend') {
|
|
694
768
|
return;
|
|
769
|
+
}
|
|
695
770
|
await sdk.resume().catch((retryErr) => {
|
|
696
771
|
hlog('sdk resume retry failed', {
|
|
697
|
-
err:
|
|
772
|
+
err: errorMessage(retryErr)
|
|
698
773
|
});
|
|
699
774
|
});
|
|
700
775
|
}
|
|
@@ -713,15 +788,38 @@ export class Harness extends ReadyResource {
|
|
|
713
788
|
if (outcome.status !== 'rejected')
|
|
714
789
|
continue;
|
|
715
790
|
const err = outcome.reason;
|
|
716
|
-
hlog('unload failed', { err:
|
|
791
|
+
hlog('unload failed', { err: errorMessage(err) });
|
|
717
792
|
}
|
|
718
793
|
}
|
|
794
|
+
async _unloadIdleModels() {
|
|
795
|
+
if (!this.opened || this.closing || this.suspended)
|
|
796
|
+
return;
|
|
797
|
+
hlog('idle unload', { models: this._loadedModels().map((entry) => entry.model) });
|
|
798
|
+
const released = await Promise.allSettled([
|
|
799
|
+
evictIdle(this._models),
|
|
800
|
+
evictIdle(this._embedModels),
|
|
801
|
+
...[...this._retiredEmbedModels].map((pool) => this._collectRetiredEmbedPool(pool)),
|
|
802
|
+
evictIdle(this._ttsModels),
|
|
803
|
+
evictIdle(this._transcriptionModels),
|
|
804
|
+
this._imageRuntime?.unloadIdle(),
|
|
805
|
+
this._musicRuntime?.unloadIdle(),
|
|
806
|
+
this._translationRuntime.unload()
|
|
807
|
+
]);
|
|
808
|
+
for (const outcome of released) {
|
|
809
|
+
if (outcome.status !== 'rejected')
|
|
810
|
+
continue;
|
|
811
|
+
hlog('idle unload failed', { err: errorMessage(outcome.reason) });
|
|
812
|
+
}
|
|
813
|
+
invalidateModelStatusCache(this._modelStatus);
|
|
814
|
+
}
|
|
719
815
|
async resume() {
|
|
720
816
|
hlog('resume called');
|
|
721
|
-
if (this._state === HarnessState.RESUME)
|
|
817
|
+
if (this._state === HarnessState.RESUME) {
|
|
722
818
|
return;
|
|
723
|
-
|
|
819
|
+
}
|
|
820
|
+
if (this.closing) {
|
|
724
821
|
return;
|
|
822
|
+
}
|
|
725
823
|
if (this._state === HarnessState.PRESUSPEND) {
|
|
726
824
|
this._state = HarnessState.RESUME;
|
|
727
825
|
return;
|
|
@@ -744,22 +842,26 @@ export class Harness extends ReadyResource {
|
|
|
744
842
|
}
|
|
745
843
|
async _close() {
|
|
746
844
|
hlog('closing');
|
|
845
|
+
this._idle.close();
|
|
747
846
|
this._overlayWatcher?.stop();
|
|
748
847
|
this._overlayWatcher = null;
|
|
749
848
|
const pending = this._sdk.peek();
|
|
750
|
-
if (pending)
|
|
849
|
+
if (pending) {
|
|
751
850
|
this._loads.drain(await pending);
|
|
851
|
+
}
|
|
752
852
|
await Promise.all([this._releaseRuntimes(), closePythonWorker()]);
|
|
753
853
|
const sdk = await this._sdk.peek()?.catch(noop);
|
|
754
|
-
if (sdk)
|
|
854
|
+
if (sdk) {
|
|
755
855
|
await sdk.close();
|
|
856
|
+
}
|
|
756
857
|
}
|
|
757
858
|
download(args) {
|
|
758
859
|
const projection = (args.addon ?? 'llm') === 'llm' ? this._projectionFor(args.model) : undefined;
|
|
759
860
|
const multiFile = diffusionDownloadMetadata(args.model, args.addon) ??
|
|
760
861
|
audioGenDownloadMetadata(args.model, args.addon);
|
|
761
862
|
return download(async () => {
|
|
762
|
-
|
|
863
|
+
if (!this.opened)
|
|
864
|
+
await this.ready();
|
|
763
865
|
return this._sdk.load();
|
|
764
866
|
}, {
|
|
765
867
|
...args,
|
|
@@ -768,23 +870,28 @@ export class Harness extends ReadyResource {
|
|
|
768
870
|
assetSizes: multiFile?.assetSizes
|
|
769
871
|
}, () => invalidateModelStatusCache(this._modelStatus));
|
|
770
872
|
}
|
|
771
|
-
|
|
772
|
-
|
|
873
|
+
embed(context) {
|
|
874
|
+
return this._busy(() => this._embed(context));
|
|
875
|
+
}
|
|
876
|
+
async _embed({ model, texts, signal }) {
|
|
877
|
+
if (!this.opened)
|
|
878
|
+
await this.ready();
|
|
773
879
|
if (this.suspended)
|
|
774
880
|
throw new Error('harness is suspended');
|
|
775
|
-
if (texts.length === 0)
|
|
881
|
+
if (texts.length === 0) {
|
|
776
882
|
return [];
|
|
883
|
+
}
|
|
777
884
|
const pool = this._embedModels;
|
|
778
885
|
const faults = this._embedFaults;
|
|
779
886
|
const generation = faults.generation;
|
|
780
|
-
const key = faults.key(model);
|
|
887
|
+
const key = faults.key(residentSrc(model, 'embeddings'));
|
|
781
888
|
const handle = pool.open(key, model);
|
|
782
889
|
let loaded = null;
|
|
783
890
|
let job = null;
|
|
784
891
|
let releaseAfter = null;
|
|
785
892
|
try {
|
|
786
893
|
loaded = await raceAbort(handle.load(), signal);
|
|
787
|
-
const sdk =
|
|
894
|
+
const sdk = await this._sdk.loadEmbed();
|
|
788
895
|
job = this._embedLane(sdk, { modelId: loaded.modelId, texts });
|
|
789
896
|
const vectors = await raceAbort(job, signal);
|
|
790
897
|
faults.reset();
|
|
@@ -807,15 +914,18 @@ export class Harness extends ReadyResource {
|
|
|
807
914
|
throw err;
|
|
808
915
|
}
|
|
809
916
|
finally {
|
|
810
|
-
if (releaseAfter)
|
|
917
|
+
if (releaseAfter) {
|
|
811
918
|
this._releaseEmbedResourceAfter(releaseAfter, pool, key);
|
|
812
|
-
|
|
919
|
+
}
|
|
920
|
+
else {
|
|
813
921
|
await this._releaseEmbedResource(pool, key);
|
|
922
|
+
}
|
|
814
923
|
}
|
|
815
924
|
}
|
|
816
925
|
async _defaultVoiceSrc(category) {
|
|
817
|
-
if (category !== 'tts')
|
|
926
|
+
if (category !== 'tts') {
|
|
818
927
|
return defaultVoiceModel(category);
|
|
928
|
+
}
|
|
819
929
|
try {
|
|
820
930
|
const { capabilityProfile } = await this.capability();
|
|
821
931
|
return defaultTtsFor(capabilityProfile).asset.src;
|
|
@@ -824,11 +934,13 @@ export class Harness extends ReadyResource {
|
|
|
824
934
|
return defaultVoiceModel('tts');
|
|
825
935
|
}
|
|
826
936
|
}
|
|
827
|
-
async _voiceJob(pool, category, model, signal, run) {
|
|
828
|
-
|
|
937
|
+
async _voiceJob(pool, { category, model, signal }, run) {
|
|
938
|
+
if (!this.opened)
|
|
939
|
+
await this.ready();
|
|
829
940
|
if (this.suspended)
|
|
830
941
|
throw new Error('harness is suspended');
|
|
831
|
-
const
|
|
942
|
+
const addon = category === 'tts' ? 'tts' : 'parakeet';
|
|
943
|
+
const name = residentSrc(model || (await this._defaultVoiceSrc(category)), addon);
|
|
832
944
|
const handle = pool.open(name, name);
|
|
833
945
|
let job = null;
|
|
834
946
|
let releaseAfter = null;
|
|
@@ -844,8 +956,9 @@ export class Harness extends ReadyResource {
|
|
|
844
956
|
}
|
|
845
957
|
catch (err) {
|
|
846
958
|
if (err instanceof AbortedError) {
|
|
847
|
-
if (sdk && modelId)
|
|
959
|
+
if (sdk && modelId) {
|
|
848
960
|
void sdk.cancel({ modelId, operation: 'inference' }).catch(noop);
|
|
961
|
+
}
|
|
849
962
|
releaseAfter = job;
|
|
850
963
|
}
|
|
851
964
|
throw err;
|
|
@@ -854,19 +967,25 @@ export class Harness extends ReadyResource {
|
|
|
854
967
|
const release = () => {
|
|
855
968
|
releaseModel(pool, name);
|
|
856
969
|
};
|
|
857
|
-
if (releaseAfter)
|
|
970
|
+
if (releaseAfter) {
|
|
858
971
|
void releaseAfter.then(release, release);
|
|
859
|
-
|
|
972
|
+
}
|
|
973
|
+
else {
|
|
860
974
|
release();
|
|
975
|
+
}
|
|
861
976
|
}
|
|
862
977
|
}
|
|
863
|
-
|
|
864
|
-
return this.
|
|
978
|
+
transcribe(context) {
|
|
979
|
+
return this._busy(() => this._transcribe(context));
|
|
980
|
+
}
|
|
981
|
+
async _transcribe({ model, audio, signal }) {
|
|
982
|
+
return this._voiceJob(this._transcriptionModels, { category: 'transcription', model, signal }, async (sdk, id) => {
|
|
865
983
|
const pcm = await pcmForTranscription(audio);
|
|
866
|
-
if (signal?.aborted)
|
|
984
|
+
if (signal?.aborted) {
|
|
867
985
|
throw new AbortedError();
|
|
986
|
+
}
|
|
868
987
|
if (pcm && pcm.length >= TRANSCRIBE_STREAM_MIN_BYTES) {
|
|
869
|
-
return transcribeStreaming(sdk,
|
|
988
|
+
return transcribeStreaming(sdk, pcm, { modelId: id, signal });
|
|
870
989
|
}
|
|
871
990
|
return transcribeOnce(sdk, id, pcm ? buildWavBuffer(pcm) : audio);
|
|
872
991
|
});
|
|
@@ -874,33 +993,43 @@ export class Harness extends ReadyResource {
|
|
|
874
993
|
detectLanguage(context) {
|
|
875
994
|
return detectLanguage(context);
|
|
876
995
|
}
|
|
877
|
-
|
|
878
|
-
|
|
996
|
+
speak(context) {
|
|
997
|
+
return this._busy(() => this._speak(context));
|
|
998
|
+
}
|
|
999
|
+
async _speak({ model, text, signal }) {
|
|
1000
|
+
const samples = await this._voiceJob(this._ttsModels, { category: 'tts', model, signal }, (sdk, id) => speakOnce(sdk, id, text));
|
|
879
1001
|
return { audio: packSamples(samples), sampleRate: SUPERTONIC_SAMPLE_RATE };
|
|
880
1002
|
}
|
|
881
|
-
|
|
882
|
-
|
|
1003
|
+
speakStream(context) {
|
|
1004
|
+
return this._busyStream(() => this._speakStream(context));
|
|
1005
|
+
}
|
|
1006
|
+
async *_speakStream({ model, text, signal }) {
|
|
1007
|
+
if (!this.opened)
|
|
1008
|
+
await this.ready();
|
|
883
1009
|
if (this.suspended)
|
|
884
1010
|
throw new Error('harness is suspended');
|
|
885
|
-
const name = model || (await this._defaultVoiceSrc('tts'));
|
|
1011
|
+
const name = residentSrc(model || (await this._defaultVoiceSrc('tts')), 'tts');
|
|
886
1012
|
const pool = this._ttsModels;
|
|
887
1013
|
const handle = pool.open(name, name);
|
|
888
1014
|
const chunks = pushQueue();
|
|
889
1015
|
let sdk = null;
|
|
890
1016
|
let modelId = null;
|
|
891
1017
|
const cancel = () => {
|
|
892
|
-
if (sdk && modelId)
|
|
893
|
-
void sdk.cancel({ modelId, operation: 'inference' }).catch(
|
|
1018
|
+
if (sdk && modelId) {
|
|
1019
|
+
void sdk.cancel({ modelId, operation: 'inference' }).catch(noop);
|
|
1020
|
+
}
|
|
894
1021
|
};
|
|
895
1022
|
signal?.addEventListener('abort', cancel, { once: true });
|
|
896
1023
|
const job = this._voiceLane(async () => {
|
|
897
1024
|
try {
|
|
898
1025
|
modelId = (await handle.load()).modelId;
|
|
899
1026
|
sdk = (await this._sdk.loadVoice());
|
|
900
|
-
if (signal?.aborted)
|
|
1027
|
+
if (signal?.aborted) {
|
|
901
1028
|
return;
|
|
902
|
-
|
|
1029
|
+
}
|
|
1030
|
+
for await (const chunk of speakStreamOnce(sdk, modelId, text)) {
|
|
903
1031
|
chunks.push(chunk);
|
|
1032
|
+
}
|
|
904
1033
|
}
|
|
905
1034
|
finally {
|
|
906
1035
|
chunks.end();
|
|
@@ -914,18 +1043,23 @@ export class Harness extends ReadyResource {
|
|
|
914
1043
|
}
|
|
915
1044
|
finally {
|
|
916
1045
|
signal?.removeEventListener('abort', cancel);
|
|
917
|
-
if (!settled)
|
|
1046
|
+
if (!settled) {
|
|
918
1047
|
cancel();
|
|
1048
|
+
}
|
|
919
1049
|
void job.catch(noop).finally(() => {
|
|
920
1050
|
releaseModel(pool, name);
|
|
921
1051
|
});
|
|
922
1052
|
}
|
|
923
1053
|
}
|
|
924
1054
|
async list(options = {}) {
|
|
925
|
-
|
|
926
|
-
|
|
1055
|
+
if (!this.opened)
|
|
1056
|
+
await this.ready();
|
|
1057
|
+
const sdk = await this._sdk.load();
|
|
927
1058
|
const [rows, custom] = await Promise.all([
|
|
928
|
-
listModels(sdk, options,
|
|
1059
|
+
listModels(sdk, options, {
|
|
1060
|
+
cache: this._modelStatus,
|
|
1061
|
+
projectionFor: (name) => this._projectionFor(name)
|
|
1062
|
+
}),
|
|
929
1063
|
customModelRows(options, {
|
|
930
1064
|
modelsDir: this._modelsDir,
|
|
931
1065
|
loaded: new Set(this._loadedModels().map((entry) => entry.model))
|
|
@@ -934,18 +1068,21 @@ export class Harness extends ReadyResource {
|
|
|
934
1068
|
return mergeCustomRows(rows, custom);
|
|
935
1069
|
}
|
|
936
1070
|
async deleteModel(name) {
|
|
937
|
-
|
|
938
|
-
|
|
1071
|
+
if (!this.opened)
|
|
1072
|
+
await this.ready();
|
|
1073
|
+
const sdk = await this._sdk.load();
|
|
939
1074
|
const result = await deleteModelFiles(sdk, name, { modelsDir: this._modelsDir });
|
|
940
1075
|
invalidateModelStatusCache(this._modelStatus);
|
|
941
1076
|
return summarizeDelete(result);
|
|
942
1077
|
}
|
|
943
1078
|
async clearKvCache() {
|
|
944
|
-
|
|
1079
|
+
if (!this.opened)
|
|
1080
|
+
await this.ready();
|
|
945
1081
|
return deleteAllKvCaches(() => this._sdk.load());
|
|
946
1082
|
}
|
|
947
1083
|
async deleteKvCache(kvCacheKey) {
|
|
948
|
-
|
|
1084
|
+
if (!this.opened)
|
|
1085
|
+
await this.ready();
|
|
949
1086
|
return deleteKvCacheKey(() => this._sdk.load(), kvCacheKey);
|
|
950
1087
|
}
|
|
951
1088
|
async infraModels(profile) {
|
|
@@ -989,37 +1126,160 @@ export class Harness extends ReadyResource {
|
|
|
989
1126
|
});
|
|
990
1127
|
return this._capabilityInflight;
|
|
991
1128
|
}
|
|
992
|
-
|
|
993
|
-
|
|
1129
|
+
warmModel(context) {
|
|
1130
|
+
return this._busy(() => this._warmModel(context));
|
|
1131
|
+
}
|
|
1132
|
+
async _warmModel({ model, agent, signal }) {
|
|
1133
|
+
if (!this.opened)
|
|
1134
|
+
await this.ready();
|
|
994
1135
|
if (this.suspended)
|
|
995
1136
|
return;
|
|
996
|
-
const
|
|
997
|
-
const
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
if (!isResident(pool, key)) {
|
|
1007
|
-
if (!(await this._makeRoomForCompletion(signal, WARM_ROOM_WAIT_MS)))
|
|
1137
|
+
const target = this._residency(model, agent);
|
|
1138
|
+
const resident = isResident(target.pool, target.key);
|
|
1139
|
+
hlog('warm model', { model, resident });
|
|
1140
|
+
if (target.pool === this._models && !resident) {
|
|
1141
|
+
const roomed = await this._makeRoomForCompletion(model, {
|
|
1142
|
+
ctxSize: target.ctxSize,
|
|
1143
|
+
signal,
|
|
1144
|
+
deadlineMs: WARM_ROOM_WAIT_MS
|
|
1145
|
+
});
|
|
1146
|
+
if (!roomed) {
|
|
1008
1147
|
return;
|
|
1009
|
-
|
|
1148
|
+
}
|
|
1149
|
+
await this._unloadIdleGenerators();
|
|
1010
1150
|
}
|
|
1151
|
+
await this._loadAndRelease(target, signal);
|
|
1152
|
+
}
|
|
1153
|
+
loadModel(context) {
|
|
1154
|
+
return this._busy(() => this._loadModel(context));
|
|
1155
|
+
}
|
|
1156
|
+
async _loadModel({ model, agent, signal }) {
|
|
1157
|
+
if (!this.opened)
|
|
1158
|
+
await this.ready();
|
|
1159
|
+
if (this.suspended) {
|
|
1160
|
+
throw new Error('harness is suspended');
|
|
1161
|
+
}
|
|
1162
|
+
const target = this._residency(model, agent);
|
|
1163
|
+
const resident = isResident(target.pool, target.key);
|
|
1164
|
+
hlog('load model', { model, resident });
|
|
1165
|
+
if (target.pool !== this._models) {
|
|
1166
|
+
await this._loadAndRelease(target, signal);
|
|
1167
|
+
return { loaded: true };
|
|
1168
|
+
}
|
|
1169
|
+
if (!resident) {
|
|
1170
|
+
if (!(await this._fits(model, target.ctxSize))) {
|
|
1171
|
+
return { loaded: false, reason: 'memory' };
|
|
1172
|
+
}
|
|
1173
|
+
await evictIdleWhere(this._models, (key) => modelOf(key) === model && !this._replying.includes(key));
|
|
1174
|
+
await this._unloadIdleGenerators();
|
|
1175
|
+
}
|
|
1176
|
+
this._pinned.add(target.key);
|
|
1177
|
+
try {
|
|
1178
|
+
await this._loadAndRelease(target, signal);
|
|
1179
|
+
}
|
|
1180
|
+
catch (err) {
|
|
1181
|
+
this._pinned.delete(target.key);
|
|
1182
|
+
throw err;
|
|
1183
|
+
}
|
|
1184
|
+
return { loaded: true };
|
|
1185
|
+
}
|
|
1186
|
+
unloadModel(context) {
|
|
1187
|
+
return this._busy(() => this._unloadModel(context));
|
|
1188
|
+
}
|
|
1189
|
+
async _unloadModel({ model }) {
|
|
1190
|
+
if (!this.opened)
|
|
1191
|
+
await this.ready();
|
|
1192
|
+
const target = this._residency(model);
|
|
1193
|
+
const name = modelOf(target.key);
|
|
1194
|
+
const unloaded = await evictModel(target.pool, name, { deadlineMs: UNLOAD_LEASE_WAIT_MS });
|
|
1195
|
+
hlog('unload model', { model, unloaded });
|
|
1196
|
+
if (unloaded) {
|
|
1197
|
+
for (const key of this._pinned) {
|
|
1198
|
+
if (modelOf(key) === name) {
|
|
1199
|
+
this._pinned.delete(key);
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1203
|
+
return { unloaded };
|
|
1204
|
+
}
|
|
1205
|
+
async _loadAndRelease(target, signal) {
|
|
1011
1206
|
try {
|
|
1012
|
-
await raceAbort(
|
|
1207
|
+
await raceAbort(target.open().load(), signal);
|
|
1013
1208
|
}
|
|
1014
1209
|
finally {
|
|
1015
|
-
releaseModel(pool, key);
|
|
1210
|
+
releaseModel(target.pool, target.key);
|
|
1016
1211
|
}
|
|
1017
1212
|
}
|
|
1018
|
-
async
|
|
1213
|
+
async _unloadIdleGenerators() {
|
|
1214
|
+
await this._imageRuntime?.unloadIdle().catch(noop);
|
|
1215
|
+
await this._musicRuntime?.unloadIdle().catch(noop);
|
|
1216
|
+
}
|
|
1217
|
+
async _fits(model, ctxSize) {
|
|
1218
|
+
const asset = curatedModelAsset(model, 'llm');
|
|
1219
|
+
if (!asset) {
|
|
1220
|
+
return true;
|
|
1221
|
+
}
|
|
1222
|
+
const assessed = await this._assessFit({
|
|
1223
|
+
sha256Checksum: asset.sha256Checksum,
|
|
1224
|
+
name: asset.name,
|
|
1225
|
+
contextTokens: ctxSize || defaultContextSize(asset)
|
|
1226
|
+
}).catch((err) => {
|
|
1227
|
+
hlog('fit estimate failed', { model, err: errorMessage(err) });
|
|
1228
|
+
return null;
|
|
1229
|
+
});
|
|
1230
|
+
return assessed?.fit !== 'likely-too-large';
|
|
1231
|
+
}
|
|
1232
|
+
async _yieldPins(model, ctxSize) {
|
|
1233
|
+
for (const key of this._pinned) {
|
|
1234
|
+
if (leaseCount(this._models, key) > 0 || this._replying.includes(key)) {
|
|
1235
|
+
continue;
|
|
1236
|
+
}
|
|
1237
|
+
if (await this._fits(model, ctxSize))
|
|
1238
|
+
return;
|
|
1239
|
+
this._pinned.delete(key);
|
|
1240
|
+
await evictModelWithin(this._models, key, { deadlineMs: WEDGED_EVICT_WAIT_MS });
|
|
1241
|
+
}
|
|
1242
|
+
}
|
|
1243
|
+
_residency(model, agent) {
|
|
1244
|
+
const addon = modelAddonOf(model);
|
|
1245
|
+
if (addon === 'llm') {
|
|
1246
|
+
const loadOpts = this._completionLoadOptions(model, agent);
|
|
1247
|
+
const key = modelKey(model, loadOpts);
|
|
1248
|
+
return {
|
|
1249
|
+
pool: this._models,
|
|
1250
|
+
key,
|
|
1251
|
+
ctxSize: loadOpts.ctxSize,
|
|
1252
|
+
open: () => this._models.open(key, model, loadOpts)
|
|
1253
|
+
};
|
|
1254
|
+
}
|
|
1255
|
+
if (addon === 'embeddings') {
|
|
1256
|
+
const key = this._embedFaults.key(residentSrc(model, addon));
|
|
1257
|
+
return { pool: this._embedModels, key, open: () => this._embedModels.open(key, model) };
|
|
1258
|
+
}
|
|
1259
|
+
if (addon === 'tts' || addon === 'parakeet') {
|
|
1260
|
+
const pool = addon === 'tts' ? this._ttsModels : this._transcriptionModels;
|
|
1261
|
+
const key = residentSrc(model, addon);
|
|
1262
|
+
return { pool, key, open: () => pool.open(key, key) };
|
|
1263
|
+
}
|
|
1264
|
+
throw new Error(`${model} loads for each generation and is not kept in memory`);
|
|
1265
|
+
}
|
|
1266
|
+
_completionLoadOptions(model, agent) {
|
|
1267
|
+
return resolveLoadOptions({
|
|
1268
|
+
ctxSize: agent?.ctxSize ?? undefined,
|
|
1269
|
+
kvCacheK: agent?.kvCacheK ?? undefined,
|
|
1270
|
+
kvCacheV: agent?.kvCacheV ?? undefined,
|
|
1271
|
+
projectionModel: this._projectionFor(model)
|
|
1272
|
+
}, this._deviceConfig());
|
|
1273
|
+
}
|
|
1274
|
+
invoke(context) {
|
|
1275
|
+
return this._busyStream(() => this._invoke(context));
|
|
1276
|
+
}
|
|
1277
|
+
async *_invoke({ capability, args, enhance, agent, attach, fetchAttachment, progress, signal }) {
|
|
1019
1278
|
if (!this.opened)
|
|
1020
1279
|
await this.ready();
|
|
1021
|
-
if (this.suspended)
|
|
1280
|
+
if (this.suspended) {
|
|
1022
1281
|
return yield { type: 'aborted' };
|
|
1282
|
+
}
|
|
1023
1283
|
hlog('invoke start', { capability, enhance });
|
|
1024
1284
|
try {
|
|
1025
1285
|
yield* invoke({
|
|
@@ -1041,31 +1301,28 @@ export class Harness extends ReadyResource {
|
|
|
1041
1301
|
hlog('invoke end', { capability });
|
|
1042
1302
|
}
|
|
1043
1303
|
}
|
|
1044
|
-
|
|
1045
|
-
|
|
1304
|
+
completion(context) {
|
|
1305
|
+
return this._busyStream(() => this._completion(context));
|
|
1306
|
+
}
|
|
1307
|
+
async *_completion({ model, agent, messages, cacheKey, thinkingDefault, toolConfigs, generationParams, responseFormat, captureInferenceCalls, grantedDirs, grantedDrives, driveOp, grantedWriteDirs, approve, attach, fetchAttachment, ragSearch, progress, askUser, signal }) {
|
|
1308
|
+
if (!this.opened)
|
|
1309
|
+
await this.ready();
|
|
1046
1310
|
if (this.suspended)
|
|
1047
1311
|
throw new Error('harness is suspended');
|
|
1048
|
-
const
|
|
1049
|
-
const
|
|
1050
|
-
ctxSize: agent?.ctxSize ?? undefined,
|
|
1051
|
-
kvCacheK: agent?.kvCacheK ?? undefined,
|
|
1052
|
-
kvCacheV: agent?.kvCacheV ?? undefined,
|
|
1053
|
-
projectionModel
|
|
1054
|
-
}, this._deviceConfig());
|
|
1312
|
+
const loadOpts = this._completionLoadOptions(model, agent);
|
|
1313
|
+
const projectionModel = loadOpts.projectionModel;
|
|
1055
1314
|
const key = modelKey(model, loadOpts);
|
|
1056
1315
|
const pool = this._models;
|
|
1057
1316
|
const resident = isResident(pool, key);
|
|
1058
1317
|
hlog('completion start', { model, resident, projection: Boolean(projectionModel) });
|
|
1059
1318
|
if (!resident) {
|
|
1060
|
-
await this._makeRoomForCompletion(signal);
|
|
1061
|
-
await this.
|
|
1062
|
-
await this._musicRuntime?.unloadIdle().catch(noop);
|
|
1319
|
+
await this._makeRoomForCompletion(model, { ctxSize: loadOpts.ctxSize, signal });
|
|
1320
|
+
await this._unloadIdleGenerators();
|
|
1063
1321
|
}
|
|
1064
1322
|
const acquireRound = async () => {
|
|
1065
1323
|
if (!isResident(pool, key)) {
|
|
1066
|
-
await this._makeRoomForCompletion(signal);
|
|
1067
|
-
await this.
|
|
1068
|
-
await this._musicRuntime?.unloadIdle().catch(noop);
|
|
1324
|
+
await this._makeRoomForCompletion(model, { ctxSize: loadOpts.ctxSize, signal });
|
|
1325
|
+
await this._unloadIdleGenerators();
|
|
1069
1326
|
}
|
|
1070
1327
|
try {
|
|
1071
1328
|
const { modelId } = await raceAbort(pool.open(key, model, loadOpts).load(), signal);
|
|
@@ -1089,6 +1346,7 @@ export class Harness extends ReadyResource {
|
|
|
1089
1346
|
held = false;
|
|
1090
1347
|
releaseModel(pool, key);
|
|
1091
1348
|
};
|
|
1349
|
+
this._replying.push(key);
|
|
1092
1350
|
try {
|
|
1093
1351
|
const { modelId, ctxSize } = await raceAbort(handle.load(), signal);
|
|
1094
1352
|
hlog('model ready', { model });
|
|
@@ -1137,21 +1395,18 @@ export class Harness extends ReadyResource {
|
|
|
1137
1395
|
yield { type: 'aborted' };
|
|
1138
1396
|
}
|
|
1139
1397
|
else {
|
|
1140
|
-
const message =
|
|
1398
|
+
const message = errorMessage(err);
|
|
1141
1399
|
hlog('completion failed', { model, ...resolutionDetail(err), err: message });
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
process.exit(1);
|
|
1147
|
-
}
|
|
1148
|
-
else {
|
|
1149
|
-
yield { type: 'error', message };
|
|
1400
|
+
const wedged = isSdkModelWedgedError(err) ? await this._evictWedgedModel(model, key) : null;
|
|
1401
|
+
yield { type: 'error', message: wedged ? wedged.message : message };
|
|
1402
|
+
if (wedged?.fatal) {
|
|
1403
|
+
process.exit(1);
|
|
1150
1404
|
}
|
|
1151
1405
|
}
|
|
1152
1406
|
}
|
|
1153
1407
|
finally {
|
|
1154
1408
|
hlog('completion end', { model });
|
|
1409
|
+
this._replying.splice(this._replying.indexOf(key), 1);
|
|
1155
1410
|
releaseHeld();
|
|
1156
1411
|
void this._imageRuntime?.unloadIdle().catch(noop);
|
|
1157
1412
|
void this._musicRuntime?.unloadIdle().catch(noop);
|
|
@@ -1160,14 +1415,19 @@ export class Harness extends ReadyResource {
|
|
|
1160
1415
|
_residentEnhancer() {
|
|
1161
1416
|
return async (prompt, signal) => {
|
|
1162
1417
|
const lease = await leaseResidentModel(this._models, (model) => !isTranslationModel(model));
|
|
1163
|
-
if (!lease)
|
|
1418
|
+
if (!lease) {
|
|
1164
1419
|
return null;
|
|
1420
|
+
}
|
|
1165
1421
|
try {
|
|
1166
1422
|
const sdk = await this._sdk.load();
|
|
1167
|
-
return await expandImagePrompt(sdk,
|
|
1423
|
+
return await expandImagePrompt(sdk, prompt, {
|
|
1424
|
+
modelId: lease.modelId,
|
|
1425
|
+
model: lease.model,
|
|
1426
|
+
signal
|
|
1427
|
+
});
|
|
1168
1428
|
}
|
|
1169
1429
|
catch (err) {
|
|
1170
|
-
hlog('enhance failed', { err:
|
|
1430
|
+
hlog('enhance failed', { err: errorMessage(err) });
|
|
1171
1431
|
return null;
|
|
1172
1432
|
}
|
|
1173
1433
|
finally {
|
|
@@ -1175,24 +1435,33 @@ export class Harness extends ReadyResource {
|
|
|
1175
1435
|
}
|
|
1176
1436
|
};
|
|
1177
1437
|
}
|
|
1178
|
-
async _makeRoomForCompletion(signal, deadlineMs = MODEL_ROOM_WAIT_MS) {
|
|
1179
|
-
const roomed = await awaitModelCapacity(this._models,
|
|
1438
|
+
async _makeRoomForCompletion(model, { ctxSize, signal, deadlineMs = MODEL_ROOM_WAIT_MS } = {}) {
|
|
1439
|
+
const roomed = await awaitModelCapacity(this._models, {
|
|
1440
|
+
cap: this._completionModelCap,
|
|
1441
|
+
deadlineMs,
|
|
1442
|
+
signal,
|
|
1443
|
+
evictable: (key) => !this._pinned.has(key) || modelOf(key) === model
|
|
1444
|
+
});
|
|
1180
1445
|
if (!roomed) {
|
|
1181
1446
|
hlog('model room wait gave up', {
|
|
1182
1447
|
cap: this._completionModelCap,
|
|
1183
1448
|
loaded: loadedModels(this._models).length
|
|
1184
1449
|
});
|
|
1185
1450
|
}
|
|
1451
|
+
await this._yieldPins(model, ctxSize);
|
|
1186
1452
|
return roomed;
|
|
1187
1453
|
}
|
|
1188
1454
|
async _evictWedgedModel(model, key) {
|
|
1189
1455
|
const faults = (this._modelFaults.get(model) ?? 0) + 1;
|
|
1190
1456
|
this._modelFaults.set(model, faults);
|
|
1191
1457
|
const leased = leaseCount(this._models, key) > 0;
|
|
1192
|
-
const evicted = leased
|
|
1458
|
+
const evicted = leased
|
|
1459
|
+
? false
|
|
1460
|
+
: await evictModelWithin(this._models, key, { deadlineMs: WEDGED_EVICT_WAIT_MS });
|
|
1193
1461
|
hlog('wedged model evicted', { model, faults, evicted, leased });
|
|
1194
|
-
if (leased)
|
|
1462
|
+
if (leased) {
|
|
1195
1463
|
this._evictAfterLeases(model, key);
|
|
1464
|
+
}
|
|
1196
1465
|
if (!evicted && !leased) {
|
|
1197
1466
|
return {
|
|
1198
1467
|
message: `${model} is stuck in device memory — restarting the model runtime`,
|
|
@@ -1207,9 +1476,27 @@ export class Harness extends ReadyResource {
|
|
|
1207
1476
|
};
|
|
1208
1477
|
}
|
|
1209
1478
|
_evictAfterLeases(model, key) {
|
|
1210
|
-
void evictModelWithin(this._models, key, WEDGED_EVICT_LEASED_MS)
|
|
1479
|
+
void evictModelWithin(this._models, key, { deadlineMs: WEDGED_EVICT_LEASED_MS })
|
|
1211
1480
|
.then((evicted) => hlog('wedged model evicted after leases', { model, evicted }))
|
|
1212
1481
|
.catch(noop);
|
|
1213
1482
|
}
|
|
1483
|
+
async _busy(run) {
|
|
1484
|
+
await this._idle.enter();
|
|
1485
|
+
try {
|
|
1486
|
+
return await run();
|
|
1487
|
+
}
|
|
1488
|
+
finally {
|
|
1489
|
+
this._idle.exit();
|
|
1490
|
+
}
|
|
1491
|
+
}
|
|
1492
|
+
async *_busyStream(run) {
|
|
1493
|
+
await this._idle.enter();
|
|
1494
|
+
try {
|
|
1495
|
+
yield* run();
|
|
1496
|
+
}
|
|
1497
|
+
finally {
|
|
1498
|
+
this._idle.exit();
|
|
1499
|
+
}
|
|
1500
|
+
}
|
|
1214
1501
|
}
|
|
1215
1502
|
function noop() { }
|