@qvac/harness 0.6.0 → 0.7.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/CHANGELOG.md +132 -1
- package/README.md +47 -29
- package/dist-lib/index.d.ts +4 -0
- package/dist-lib/index.js +31 -19
- package/dist-lib/lib/capabilities/preview-model-source.d.ts +0 -1
- package/dist-lib/lib/connect.js +5 -2
- package/dist-lib/lib/contract-types.d.ts +1 -0
- package/dist-lib/lib/docx/index.d.ts +1 -1
- package/dist-lib/lib/docx/index.js +1 -1
- package/dist-lib/lib/hardware/capability-probe.js +9 -4
- package/dist-lib/lib/hooks/built-in/sandbox.js +0 -5
- package/dist-lib/lib/models/model-catalog-constants.d.ts +7 -0
- package/dist-lib/lib/models/model-catalog-constants.js +17 -0
- package/dist-lib/lib/models/model-catalog.d.ts +14 -6
- package/dist-lib/lib/models/model-catalog.js +14 -6
- package/dist-lib/lib/models/model-handle.d.ts +5 -1
- package/dist-lib/lib/models/model-handle.js +8 -3
- package/dist-lib/lib/models/model-list.d.ts +7 -3
- package/dist-lib/lib/models/model-list.js +9 -4
- package/dist-lib/lib/models/model-pool.d.ts +4 -1
- package/dist-lib/lib/models/model-pool.js +33 -10
- package/dist-lib/lib/models/model-task.d.ts +3 -0
- package/dist-lib/lib/models/model-task.js +14 -0
- package/dist-lib/lib/models/music-catalog.d.ts +2 -0
- package/dist-lib/lib/models/music-catalog.js +9 -0
- package/dist-lib/lib/office/index.d.ts +1 -1
- package/dist-lib/lib/office/index.js +1 -1
- package/dist-lib/lib/office/python.js +1 -2
- package/dist-lib/lib/serve.js +1 -1
- package/dist-lib/lib/tools/exec/adapters/python/deadline-state.d.ts +10 -0
- package/dist-lib/lib/tools/exec/adapters/python/deadline-state.js +5 -0
- package/dist-lib/lib/tools/exec/adapters/python/interrupt-timer.js +10 -17
- package/dist-lib/lib/tools/exec/adapters/python/loader.js +15 -11
- package/dist-lib/lib/tools/exec/adapters/python/pyodide.js +1 -15
- package/dist-lib/lib/tools/exec/adapters/python/python-adapter.d.ts +1 -0
- package/dist-lib/lib/tools/exec/adapters/python/python-adapter.js +13 -2
- package/dist-lib/lib/tools/exec/adapters/python/wheels.d.ts +3 -3
- package/dist-lib/lib/tools/exec/adapters/python/wheels.js +3 -14
- package/dist-lib/lib/tools/exec/adapters/python/worker/active.d.ts +19 -0
- package/dist-lib/lib/tools/exec/adapters/python/worker/active.js +72 -0
- package/dist-lib/lib/tools/exec/adapters/python/worker/client.d.ts +0 -1
- package/dist-lib/lib/tools/exec/adapters/python/worker/client.js +8 -76
- package/dist-lib/lib/tools/exec/adapters/python/worker/entry.js +2 -126
- package/dist-lib/lib/tools/exec/adapters/python/worker/interrupt-deadline.d.ts +1 -0
- package/dist-lib/lib/tools/exec/adapters/python/worker/interrupt-deadline.js +21 -0
- package/dist-lib/lib/tools/exec/adapters/python/worker/serve.d.ts +2 -0
- package/dist-lib/lib/tools/exec/adapters/python/worker/serve.js +90 -0
- package/dist-lib/lib/tools/exec/adapters/python/worker/{spawn-node.d.ts → spawn.d.ts} +0 -1
- package/dist-lib/lib/tools/exec/adapters/python/worker/{spawn-node.js → spawn.js} +0 -8
- package/dist-lib/lib/tools/exec/bare-runtime.d.ts +2 -0
- package/dist-lib/lib/tools/exec/bare-runtime.js +10 -0
- package/dist-lib/lib/tools/exec.d.ts +0 -2
- package/dist-lib/lib/tools/exec.js +6 -11
- package/dist-lib/schema/harness.js +4 -2
- package/dist-lib/spec/hrpc/messages.js +11 -8
- package/dist-lib/spec/hyperschema/index.js +11 -8
- package/dist-lib/spec/hyperschema/schema.json +11 -1
- package/dist-lib/spec/hyperschema/types.d.ts +3 -1
- package/package.json +39 -20
- package/dist/harness.bundle +0 -42313
- package/dist/harness.cjs +0 -18
- package/dist/harness.d.ts +0 -8
- package/dist/prebuilds-manifest.json +0 -372
- package/dist-lib/lib/tools/exec/adapters/python/worker/spawn-bare.d.ts +0 -3
- package/dist-lib/lib/tools/exec/adapters/python/worker/spawn-bare.js +0 -38
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,135 @@ While the major version is `0`, a breaking change bumps the minor version.
|
|
|
8
8
|
|
|
9
9
|
## [Unreleased]
|
|
10
10
|
|
|
11
|
+
## [0.7.1] - 2026-09-30
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- `list()` rows carry `isLoading`: true from the moment a load of that model
|
|
16
|
+
starts, whoever asked for it, until it is ready or the load fails. It is read
|
|
17
|
+
off the pool, so a host shows a model as loading without counting loads
|
|
18
|
+
itself. A model loading again under other settings while a copy of it is
|
|
19
|
+
resident reads `isLoaded` and `isLoading` both true. Chat models read it
|
|
20
|
+
today: image, music and translation models load for each generation, and
|
|
21
|
+
speech, transcription and embedding pools key by source, so those rows read
|
|
22
|
+
`false` for now. An optional field on the wire; a row from an older harness
|
|
23
|
+
reads `false`. (#143)
|
|
24
|
+
|
|
25
|
+
```js
|
|
26
|
+
const loading = (await harness.list({ cachedOnly: true })).filter((row) => row.isLoading)
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### Changed
|
|
30
|
+
|
|
31
|
+
- The `model` event carries `catalogChanged` at every edge a `list()` re-read
|
|
32
|
+
would show: the `loading` that starts a load and the `ready` or `error` that
|
|
33
|
+
ends it, beside `unloaded`. Progress ticks still do not. A host that re-reads
|
|
34
|
+
the list on `catalogChanged` sees a model turn loading, then loaded, with no
|
|
35
|
+
bookkeeping of its own. The served event stream never drops a frame that
|
|
36
|
+
carries it, so two `ready` frames of one model under two keys both cross. (#143)
|
|
37
|
+
- `ModelInfo` requires `isLoading`, and `listModels`, `customModelRows` and
|
|
38
|
+
`mergeCustomRows` return `CatalogRow`, the row before the pools are asked. A
|
|
39
|
+
typed fake `list()` adds the field. (#143)
|
|
40
|
+
- A custom model's row reads `isLoaded` only once its load has landed; it used
|
|
41
|
+
to read `true` from the first attempt. (#143)
|
|
42
|
+
|
|
43
|
+
### Fixed
|
|
44
|
+
|
|
45
|
+
- A load that ends without the model in memory emits `model` with `error` and
|
|
46
|
+
the reason: it failed, or a suspend or close cancelled it. It used to end in
|
|
47
|
+
silence, so a host could not tell a failed load from one still running. The
|
|
48
|
+
`error` also refreshes the cached `isCached`, since a load may have downloaded
|
|
49
|
+
the weights before it failed. (#143)
|
|
50
|
+
|
|
51
|
+
## [0.7.0] - 2026-09-29
|
|
52
|
+
|
|
53
|
+
### Added
|
|
54
|
+
|
|
55
|
+
- `list()` rows carry `taskType`, what the model is for: `llm`, `embeddings`,
|
|
56
|
+
`speech-to-text`, `text-to-speech`, `image-generation`, `audio-generation`,
|
|
57
|
+
`translation`, `text-recognition` or `other`. It is decided from the addon in
|
|
58
|
+
one place, `lib/models/model-task.ts`, on each side of the wire rather than
|
|
59
|
+
sent over it. `ModelTaskType` is exported, and grows the four new values a
|
|
60
|
+
preview never returns. (#140)
|
|
61
|
+
|
|
62
|
+
- `modelsForAgent(agent, host)` lists every model an agent runs on a device,
|
|
63
|
+
each with its `use`: the chat model, clamped by `runnableModelName`, then the
|
|
64
|
+
image and music models its skills need. A host deciding what to download, or
|
|
65
|
+
which agents a model delete affects, asks this instead of working it out.
|
|
66
|
+
`runnableModelName(requested, host)` and `isMobilePlatform(platform)` move here
|
|
67
|
+
from `@qvac/core`: on a phone a pick above the device's profile runs as its
|
|
68
|
+
recommended model. Whether the host is a phone is the required `mobile` on
|
|
69
|
+
`HostCapability`, which `capability()` now reports, so its answer passes
|
|
70
|
+
straight in; a host built by hand has to say. Both functions are on the
|
|
71
|
+
browser-safe `@qvac/harness/model-catalog` entry too. (#141)
|
|
72
|
+
|
|
73
|
+
```js
|
|
74
|
+
const chat = modelsForAgent(agent, await harness.capability()).find((m) => m.use === 'chat')
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
- `capability()` answers `mobile`, whether the harness runs on a phone, read off
|
|
78
|
+
the platform it probes. It rides `capability-response` as an optional field;
|
|
79
|
+
a response from an older harness reads as `false`. (#141)
|
|
80
|
+
|
|
81
|
+
- `musicModelChoiceFrom(settings)` reads the music model pick out of an agent's
|
|
82
|
+
`generate_music` tool settings, so a host no longer knows what `modelChoice`
|
|
83
|
+
means there. `isMusicModelChoice` is exported beside it. (#141)
|
|
84
|
+
|
|
85
|
+
### Changed
|
|
86
|
+
|
|
87
|
+
- **Breaking:** the package no longer publishes a prebuilt sidecar:
|
|
88
|
+
`dist/harness.bundle`, `dist/harness.cjs`, `dist/prebuilds-manifest.json` and
|
|
89
|
+
the `./dist/harness.cjs` export are gone. A host stows
|
|
90
|
+
`worker/harness-sidecar.mjs` itself, as desktop and mobile already do, or
|
|
91
|
+
spawns `worker/stow-entry.mjs` straight from its install. (#127)
|
|
92
|
+
- **Breaking:** a stowed harness runs python in a worker rather than in-process.
|
|
93
|
+
A host that stows the harness stops deferring `pyodide`, keeps
|
|
94
|
+
`QVAC_PYODIDE_CACHE_DIR` writable, no longer stages a separate worker bundle,
|
|
95
|
+
and ships what stow offloads beside the bundle, which now includes pyodide's
|
|
96
|
+
wasm, stdlib and lock file and the vendored wheels. A target that embeds
|
|
97
|
+
assets instead of offloading them cannot run python, since `fs` cannot read
|
|
98
|
+
inside a bundle. (#127)
|
|
99
|
+
- A bundle of the package root carries neither pyodide nor the vendored wheels.
|
|
100
|
+
Only an entry that runs python, the exec tool or `@qvac/harness/docx` and
|
|
101
|
+
`/office`, pulls them in. (#127)
|
|
102
|
+
- `SandboxHook` no longer checks `packages`. The exec tool refuses an install
|
|
103
|
+
the vendored wheels do not hold in full in sandbox mode, whether declared in
|
|
104
|
+
`packages` or on a pip line, with the same message as before. (#127)
|
|
105
|
+
- The vendored wheels are the `#pyodide-wheels` directory asset on Bare;
|
|
106
|
+
`QVAC_PYTHON_WHEELS_DIR` still overrides it. (#127)
|
|
107
|
+
- The python worker and the in-process deadline are spawned through
|
|
108
|
+
`worker_threads`, which the package maps to `bare-worker`;
|
|
109
|
+
`#pyodide-worker-spawn` is gone. (#127)
|
|
110
|
+
- **Breaking:** `engines.bare` is `>=1.34.0`, so Bare refuses to load the
|
|
111
|
+
package on older runtimes. `bare-runtime` is pinned to 1.34.0 and
|
|
112
|
+
`bare-sidecar` to 0.5.7 (Bare 1.34.0) across the tree, `bare-supervisor`'s copy
|
|
113
|
+
included, with `bare-thread` at least 1.2.7 and `bare-module` 7.1.0 for
|
|
114
|
+
`bare-worker`'s copy. (#127)
|
|
115
|
+
- `bare-module` is no longer a dependency or an override, and
|
|
116
|
+
`bare-module-traverse` is overridden to 2.5.8: earlier versions drop the
|
|
117
|
+
`asset` condition on `bare-sidecar`'s binary, and a stowed harness then fails
|
|
118
|
+
to boot on Bare 1.33 and later. (#127)
|
|
119
|
+
- `skillModelsForAgent` rows carry `use`, `image` or `music`, so a caller can
|
|
120
|
+
tell which skill a model is for without looking the name up in the catalogs. (#141)
|
|
121
|
+
- `CapabilityInfo` has a required `mobile`, so a `HarnessContract` whose
|
|
122
|
+
`capability()` builds its own answer, a test stub included, returns it too. (#141)
|
|
123
|
+
|
|
124
|
+
### Removed
|
|
125
|
+
|
|
126
|
+
- **Breaking:** `bareRuntime` is no longer exported from `./lib/tools/exec.ts`.
|
|
127
|
+
It is at `./lib/tools/exec/bare-runtime.ts`, so the package root no longer
|
|
128
|
+
loads the exec tool to reach it. (#127)
|
|
129
|
+
- **Breaking:** `QVAC_PYODIDE_TEST_LOADER`: tests point
|
|
130
|
+
`QVAC_PYODIDE_WORKER_ENTRY` at an entry that serves their fake interpreter. (#127)
|
|
131
|
+
|
|
132
|
+
### Fixed
|
|
133
|
+
|
|
134
|
+
- Python runs start on Bare 1.34, whose threads cannot read a module off disk,
|
|
135
|
+
including in a stowed harness. The worker is spawned through `worker_threads`,
|
|
136
|
+
which the package maps to `bare-worker` on Bare, pyodide is handed its asm
|
|
137
|
+
module as `createPyodideModule`, and the files pyodide reads are named as
|
|
138
|
+
assets, so a bundler carries them beside the bundle. (#127)
|
|
139
|
+
|
|
11
140
|
## [0.6.0] - 2026-09-28
|
|
12
141
|
|
|
13
142
|
### Added
|
|
@@ -890,7 +1019,9 @@ Tagged but never published to npm; these changes first shipped in 0.2.12.
|
|
|
890
1019
|
- GPU stats are read through `bare-gpu-info`, so every dependency installs from
|
|
891
1020
|
the public npm registry. (#12)
|
|
892
1021
|
|
|
893
|
-
[Unreleased]: https://github.com/tetherto/qvac-harness/compare/v0.
|
|
1022
|
+
[Unreleased]: https://github.com/tetherto/qvac-harness/compare/v0.7.1...HEAD
|
|
1023
|
+
[0.7.1]: https://github.com/tetherto/qvac-harness/compare/v0.7.0...v0.7.1
|
|
1024
|
+
[0.7.0]: https://github.com/tetherto/qvac-harness/compare/v0.6.0...v0.7.0
|
|
894
1025
|
[0.6.0]: https://github.com/tetherto/qvac-harness/compare/v0.5.0...v0.6.0
|
|
895
1026
|
[0.5.0]: https://github.com/tetherto/qvac-harness/compare/v0.4.0...v0.5.0
|
|
896
1027
|
[0.4.0]: https://github.com/tetherto/qvac-harness/compare/v0.3.3...v0.4.0
|
package/README.md
CHANGED
|
@@ -12,41 +12,40 @@ the supervision contract.
|
|
|
12
12
|
|
|
13
13
|
## The published package
|
|
14
14
|
|
|
15
|
-
The published package ships the
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
- `dist
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
15
|
+
The published package ships the library and its runtime content, not a prebuilt
|
|
16
|
+
engine:
|
|
17
|
+
|
|
18
|
+
- `dist-lib/` — the compiled library and its declarations. The package exports
|
|
19
|
+
resolve to it; the TypeScript source stays in the repository.
|
|
20
|
+
- `worker/harness-sidecar.mjs` — the sidecar entry a host stows into its own
|
|
21
|
+
bundle, and `worker/stow-entry.mjs`, the same entry as a plain file for a
|
|
22
|
+
host that spawns it straight from its install
|
|
22
23
|
- the hash-pinned Python `wheels/` the tool runtime consumes (skills come from
|
|
23
24
|
[`@qvac/skills`](https://github.com/tetherto/qvac-skills), a content-addressed
|
|
24
25
|
bundle this package materializes at runtime)
|
|
25
26
|
|
|
26
|
-
|
|
27
|
-
`
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
model catalogs, skill helpers and DOCX extractor — ships as compiled JavaScript
|
|
32
|
-
and declarations in `dist-lib/`. The package exports resolve to that surface;
|
|
33
|
-
the TypeScript source stays in the repository.
|
|
27
|
+
A host that stows the harness defers the `@qvac` inference addons
|
|
28
|
+
(`scripts/addon-externals.cjs` lists them), so they resolve from real files
|
|
29
|
+
beside the bundle, and ships everything stow offloads there: prebuilds, and
|
|
30
|
+
pyodide's data and the wheels, which the harness names as assets. A target that
|
|
31
|
+
embeds assets instead cannot run python.
|
|
34
32
|
|
|
35
33
|
## Spawning
|
|
36
34
|
|
|
37
|
-
|
|
38
|
-
|
|
35
|
+
`spawnHarness` spawns an entry as a bare-sidecar process and hands back a
|
|
36
|
+
client over hrpc:
|
|
39
37
|
|
|
40
38
|
```js
|
|
41
|
-
|
|
39
|
+
import { spawnHarness } from '@qvac/harness'
|
|
42
40
|
|
|
43
|
-
const {
|
|
41
|
+
const harness = spawnHarness({ entry, sdkStorage })
|
|
44
42
|
```
|
|
45
43
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
44
|
+
`entry` is the host's own stowed bundle, or `@qvac/harness/worker/stow-entry.mjs`
|
|
45
|
+
resolved from its install. The argv slots the entry reads (skills dir, sdk
|
|
46
|
+
storage, model config, wheels dir, ...) are the ones
|
|
47
|
+
`worker/harness-sidecar.mjs` documents; `harnessChild` does the same under
|
|
48
|
+
bare-supervisor.
|
|
50
49
|
|
|
51
50
|
## Custom models
|
|
52
51
|
|
|
@@ -166,9 +165,11 @@ const custom = models.filter((model) => model.registrySource === 'custom')
|
|
|
166
165
|
// registrySource: 'custom',
|
|
167
166
|
// isCached: false,
|
|
168
167
|
// isLoaded: false,
|
|
168
|
+
// isLoading: false,
|
|
169
169
|
// supportsImages: false,
|
|
170
170
|
// expectedSize: 1868506272,
|
|
171
|
-
// label: 'Nanbeige 4.2 3B'
|
|
171
|
+
// label: 'Nanbeige 4.2 3B',
|
|
172
|
+
// taskType: 'llm'
|
|
172
173
|
// }
|
|
173
174
|
```
|
|
174
175
|
|
|
@@ -181,6 +182,11 @@ sidecar knows from `sdkStorage.models`; an in-process `Harness` needs
|
|
|
181
182
|
`new Harness({ modelsDir })` for it. A sharded download is looked for in the
|
|
182
183
|
folder the SDK keeps its shards in, and is cached once every shard is there.
|
|
183
184
|
|
|
185
|
+
Every row, catalog or custom, carries `taskType`: what the model is for, in the
|
|
186
|
+
vocabulary a preview uses, so a Models screen groups by task rather than by
|
|
187
|
+
addon. `whisper` and `parakeet` are both `speech-to-text`, and an addon with no
|
|
188
|
+
task of its own is `other`.
|
|
189
|
+
|
|
184
190
|
### Delete a download
|
|
185
191
|
|
|
186
192
|
```js
|
|
@@ -229,8 +235,20 @@ if (big.reason === 'memory') showNotice('Unload a model first')
|
|
|
229
235
|
`unloadModel` waits for any request holding the model to finish, then unloads
|
|
230
236
|
every copy of it. `unloaded` is false when a request still held it after the
|
|
231
237
|
wait, so a caller that wants the model gone at once stops those requests first.
|
|
232
|
-
Both change `isLoaded` in the next `list()
|
|
233
|
-
|
|
238
|
+
Both change `isLoaded` in the next `list()`. `isLoading` is true on a row from
|
|
239
|
+
the moment a load of its model starts, whoever asked for it (a request,
|
|
240
|
+
`warmModel` or `loadModel`), until the model is ready or the load fails; it is
|
|
241
|
+
read off the pool, so a host keeps no count of its own. A model loading again
|
|
242
|
+
under other settings while a copy of it is resident reads `isLoaded: true` and
|
|
243
|
+
`isLoading: true` at once. The `model` event carries `catalogChanged` at each
|
|
244
|
+
edge a `list()` re-read would show: the `loading` that starts a load, the
|
|
245
|
+
`ready` or `error` that ends it, and `unloaded`. Progress ticks between them do
|
|
246
|
+
not. A load that ends without the model in memory reports
|
|
247
|
+
`error` with the reason in `message`: it failed, or a suspend or close cancelled it.
|
|
248
|
+
An image, music or translation model loads for each generation and never reads
|
|
249
|
+
`isLoading: true`; a speech, transcription or embedding model loads under its
|
|
250
|
+
source rather than its row name, so its row reads `false` for now. A load or an
|
|
251
|
+
unload counts as a call in flight for
|
|
234
252
|
[`setModelIdleTimeout`](#freeing-memory-when-idle), and one that lands during an
|
|
235
253
|
idle unload waits for it, so a load never loads a second copy and an unload
|
|
236
254
|
answers once the memory is free.
|
|
@@ -273,8 +291,8 @@ are not extracted. Legacy XLS/PPT formats are unsupported. File extensions selec
|
|
|
273
291
|
format, with `mimeType` as a fallback when the name has no recognized Office extension.
|
|
274
292
|
|
|
275
293
|
The extractor accepts up to 20 MiB and requires the shipped Python wheels. With the
|
|
276
|
-
cache configured, extraction uses those wheels offline.
|
|
277
|
-
|
|
294
|
+
cache configured, extraction uses those wheels offline. A host that stows the harness ships them
|
|
295
|
+
with what stow offloads beside the bundle; `QVAC_PYTHON_WHEELS_DIR` names another directory.
|
|
278
296
|
Source checkouts prepare wheels with `bun run vendor:wheels`.
|
|
279
297
|
|
|
280
298
|
The existing `extractDocxText` and `closeDocxWorker` exports from `@qvac/harness/docx`
|
|
@@ -287,7 +305,7 @@ bun install
|
|
|
287
305
|
bun run lint # prettier + lunte + tsc
|
|
288
306
|
bun run test # brittle, node and bare lanes
|
|
289
307
|
bun run build:schema # regenerates spec/ (wire-compat rewind once v* tags exist)
|
|
290
|
-
bun run build:bundle # stows dist
|
|
308
|
+
bun run build:bundle # stows the sidecar entry into dist/, to prove it still stows
|
|
291
309
|
```
|
|
292
310
|
|
|
293
311
|
The schema baseline rewinds `spec/` against this repo's `v*` release tags and
|
package/dist-lib/index.d.ts
CHANGED
|
@@ -38,6 +38,7 @@ export type { EmbedDevice } from './lib/models/embed-config.ts';
|
|
|
38
38
|
export type { MemoryBudget } from './lib/hardware/memory-budget.ts';
|
|
39
39
|
export * from './lib/models/model-catalog.ts';
|
|
40
40
|
export * from './lib/models/model-list.ts';
|
|
41
|
+
export type { ModelTaskType } from './lib/models/model-task.ts';
|
|
41
42
|
export type { DeleteModelResult, DeleteModelSummary } from './lib/models/model-delete.ts';
|
|
42
43
|
export { harnessChild, spawnHarness, type HarnessChildOptions, type SpawnHarnessOptions } from './lib/spawn.ts';
|
|
43
44
|
export { settingsDimension, settingsSteps } from './lib/tools/generate-image.ts';
|
|
@@ -217,6 +218,9 @@ export declare class Harness extends ReadyResource {
|
|
|
217
218
|
describe(): Promise<HarnessInfo>;
|
|
218
219
|
diagnostics(): HarnessDiagnostics;
|
|
219
220
|
private _loadedModels;
|
|
221
|
+
private _loadingModels;
|
|
222
|
+
private _settledModels;
|
|
223
|
+
private _pools;
|
|
220
224
|
private _loadPluginSdk;
|
|
221
225
|
private _loadDiffusionSdk;
|
|
222
226
|
private _slmTranslate;
|
package/dist-lib/index.js
CHANGED
|
@@ -4,7 +4,7 @@ import process from 'process';
|
|
|
4
4
|
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
|
-
import { closePythonWorker } from './lib/tools/exec/adapters/python/worker/
|
|
7
|
+
import { closePythonWorker } from './lib/tools/exec/adapters/python/worker/active.js';
|
|
8
8
|
import { completion, imageSettingsFrom } from './lib/capabilities/completion.js';
|
|
9
9
|
import { detectLanguage } from './lib/capabilities/detect-language.js';
|
|
10
10
|
import { invoke } from './lib/capabilities/invoke.js';
|
|
@@ -24,7 +24,7 @@ import { AbortedError, raceAbort } from './lib/helpers/race-abort.js';
|
|
|
24
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, evictIdleWhere, evictModel, evictModelWithin, isResident, leaseResidentModel, leaseCount, loadedModels, modelKey, modelOf, releaseModel, resolveLoadOptions, progressState } from './lib/models/model-pool.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';
|
|
28
28
|
import { defaultContextSize } from './lib/models/context.js';
|
|
29
29
|
import { resolveMainGpu } from './lib/models/gpu-selection.js';
|
|
30
30
|
import { createIdleReaper } from './lib/models/idle-unload.js';
|
|
@@ -46,7 +46,7 @@ import { hlog } from './lib/log.js';
|
|
|
46
46
|
import { harness } from './spec/index.js';
|
|
47
47
|
import { installResolutionDetector, resolutionDetail } from './lib/resolution-detector.js';
|
|
48
48
|
import { probeBinVersions } from './lib/tools/skills/bin-check.js';
|
|
49
|
-
import { bareRuntime } from './lib/tools/exec.js';
|
|
49
|
+
import { bareRuntime } from './lib/tools/exec/bare-runtime.js';
|
|
50
50
|
import { runCommand } from './lib/tools/exec/runner.js';
|
|
51
51
|
import { createLocalRoots } from './lib/tools/fs/local.js';
|
|
52
52
|
import { createRemoteRoots } from './lib/tools/fs/remote.js';
|
|
@@ -558,13 +558,22 @@ export class Harness extends ReadyResource {
|
|
|
558
558
|
};
|
|
559
559
|
}
|
|
560
560
|
_loadedModels() {
|
|
561
|
-
|
|
561
|
+
return this._pools().flatMap((pool) => loadedModels(pool));
|
|
562
|
+
}
|
|
563
|
+
_loadingModels() {
|
|
564
|
+
return new Set(this._pools().flatMap((pool) => loadingModels(pool)));
|
|
565
|
+
}
|
|
566
|
+
_settledModels() {
|
|
567
|
+
return new Set(this._pools().flatMap((pool) => settledModels(pool)));
|
|
568
|
+
}
|
|
569
|
+
_pools() {
|
|
570
|
+
return [
|
|
571
|
+
this._models,
|
|
562
572
|
this._embedModels,
|
|
563
573
|
...this._retiredEmbedModels,
|
|
564
574
|
this._ttsModels,
|
|
565
575
|
this._transcriptionModels
|
|
566
576
|
];
|
|
567
|
-
return [...loadedModels(this._models), ...pools.flatMap((pool) => loadedModels(pool))];
|
|
568
577
|
}
|
|
569
578
|
_loadPluginSdk(current, store, importPlugin) {
|
|
570
579
|
let pending = current();
|
|
@@ -667,8 +676,7 @@ export class Harness extends ReadyResource {
|
|
|
667
676
|
});
|
|
668
677
|
}
|
|
669
678
|
_notifyModel(state) {
|
|
670
|
-
|
|
671
|
-
if (catalogChanged) {
|
|
679
|
+
if (state.status === 'ready' || state.status === 'unloaded' || state.status === 'error') {
|
|
672
680
|
invalidateModelStatusCache(this._modelStatus);
|
|
673
681
|
}
|
|
674
682
|
if (state.status === 'unloaded') {
|
|
@@ -678,7 +686,11 @@ export class Harness extends ReadyResource {
|
|
|
678
686
|
}
|
|
679
687
|
}
|
|
680
688
|
}
|
|
681
|
-
this.emit('model', {
|
|
689
|
+
this.emit('model', {
|
|
690
|
+
...state,
|
|
691
|
+
catalogChanged: state.catalogChanged === true,
|
|
692
|
+
loaded: this._loadedModels()
|
|
693
|
+
});
|
|
682
694
|
}
|
|
683
695
|
async _collectRetiredEmbedPool(pool) {
|
|
684
696
|
if (!this._retiredEmbedModels.has(pool) || pool.idle !== pool.size)
|
|
@@ -1055,17 +1067,17 @@ export class Harness extends ReadyResource {
|
|
|
1055
1067
|
if (!this.opened)
|
|
1056
1068
|
await this.ready();
|
|
1057
1069
|
const sdk = await this._sdk.load();
|
|
1058
|
-
const
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1070
|
+
const rows = await listModels(sdk, options, {
|
|
1071
|
+
cache: this._modelStatus,
|
|
1072
|
+
projectionFor: (name) => this._projectionFor(name)
|
|
1073
|
+
});
|
|
1074
|
+
const loaded = this._settledModels();
|
|
1075
|
+
const loading = this._loadingModels();
|
|
1076
|
+
const custom = await customModelRows(options, { modelsDir: this._modelsDir, loaded });
|
|
1077
|
+
return mergeCustomRows(rows, custom).map((row) => ({
|
|
1078
|
+
...row,
|
|
1079
|
+
isLoading: loading.has(row.name)
|
|
1080
|
+
}));
|
|
1069
1081
|
}
|
|
1070
1082
|
async deleteModel(name) {
|
|
1071
1083
|
if (!this.opened)
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import type { HarnessPreviewModelSourceResponse } from '../../spec/hyperschema/types.d.ts';
|
|
2
2
|
export type ModelFitVerdict = 'likely-fits' | 'likely-too-large' | 'unknown';
|
|
3
3
|
export type ModelSourceRefusal = 'invalid-source' | 'not-found' | 'private' | 'gated' | 'web-page' | 'unreachable' | 'not-gguf' | 'not-a-model' | 'no-model-files' | 'invalid-projection';
|
|
4
|
-
export type ModelTaskType = 'llm' | 'embeddings' | 'speech-to-text' | 'text-to-speech' | 'image-generation';
|
|
5
4
|
export interface ModelSourceFile {
|
|
6
5
|
src: string;
|
|
7
6
|
label: string;
|
package/dist-lib/lib/connect.js
CHANGED
|
@@ -8,6 +8,7 @@ import { unsupportedKvCacheDeletion } from './capabilities/kv-cache.js';
|
|
|
8
8
|
import { readModelSourcePreview } from './capabilities/preview-model-source.js';
|
|
9
9
|
import { hlog } from './log.js';
|
|
10
10
|
import { assertEmbedDevice } from './models/embed-config.js';
|
|
11
|
+
import { addonTaskType } from './models/model-task.js';
|
|
11
12
|
import { errorCode, errorMessage, isRecord } from './guards.js';
|
|
12
13
|
import { pushQueue } from './queue.js';
|
|
13
14
|
import { stageFile, sweepStaging } from './staging.js';
|
|
@@ -568,7 +569,8 @@ export class RemoteHarness extends EventEmitter {
|
|
|
568
569
|
return (response.models ?? []).map(({ actualSize, label, ...row }) => ({
|
|
569
570
|
...row,
|
|
570
571
|
actualSize: actualSize || undefined,
|
|
571
|
-
label: label || undefined
|
|
572
|
+
label: label || undefined,
|
|
573
|
+
taskType: addonTaskType(row.type)
|
|
572
574
|
}));
|
|
573
575
|
}
|
|
574
576
|
async deleteModel(name) {
|
|
@@ -624,7 +626,8 @@ export class RemoteHarness extends EventEmitter {
|
|
|
624
626
|
source: memoryBudget.source,
|
|
625
627
|
gpuClass: memoryBudget.gpuClass || undefined
|
|
626
628
|
}
|
|
627
|
-
: undefined
|
|
629
|
+
: undefined,
|
|
630
|
+
mobile: response.mobile === true
|
|
628
631
|
};
|
|
629
632
|
}
|
|
630
633
|
close() {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { AbortSignal } from 'bare-abort-controller';
|
|
2
|
-
export { closePythonWorker as closeDocxWorker } from '../tools/exec/adapters/python/worker/
|
|
2
|
+
export { closePythonWorker as closeDocxWorker } from '../tools/exec/adapters/python/worker/active.ts';
|
|
3
3
|
export interface DocxExtractOptions {
|
|
4
4
|
readonly signal?: AbortSignal;
|
|
5
5
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { extractWithPython } from '../office/python.js';
|
|
2
|
-
export { closePythonWorker as closeDocxWorker } from '../tools/exec/adapters/python/worker/
|
|
2
|
+
export { closePythonWorker as closeDocxWorker } from '../tools/exec/adapters/python/worker/active.js';
|
|
3
3
|
const PACKAGES = ['python-docx==1.2.0'];
|
|
4
4
|
const EXTRACT = `from docx import Document
|
|
5
5
|
from docx.text.paragraph import Paragraph
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import process from 'process';
|
|
2
|
-
import { capabilityProfileToModelName } from '../models/model-catalog.js';
|
|
2
|
+
import { capabilityProfileToModelName, isMobilePlatform } from '../models/model-catalog.js';
|
|
3
3
|
import { probeWithAddon } from './gpu-probe-addon.js';
|
|
4
4
|
import { mobileBudget, parseNvidiaSmiOutput, parseRocmSmiOutput, parseWindowsRegistryOutput, sharedRamBudget } from './parsers.js';
|
|
5
5
|
const GIB = 1024 * 1024 * 1024;
|
|
@@ -85,7 +85,7 @@ async function isPixel(reads) {
|
|
|
85
85
|
}
|
|
86
86
|
async function probeMemoryBudget(reads) {
|
|
87
87
|
const os = reads.platform();
|
|
88
|
-
if (os
|
|
88
|
+
if (isMobilePlatform(os)) {
|
|
89
89
|
return mobileBudget(reads.totalmem(), await isPixel(reads));
|
|
90
90
|
}
|
|
91
91
|
if (os === 'darwin') {
|
|
@@ -101,9 +101,14 @@ function forcedProfile() {
|
|
|
101
101
|
return undefined;
|
|
102
102
|
}
|
|
103
103
|
export async function probeCapabilityProfile(reads) {
|
|
104
|
+
const mobile = isMobilePlatform(reads.platform());
|
|
104
105
|
const forced = forcedProfile();
|
|
105
106
|
if (forced) {
|
|
106
|
-
return {
|
|
107
|
+
return {
|
|
108
|
+
capabilityProfile: forced,
|
|
109
|
+
recommendedModelName: capabilityProfileToModelName(forced),
|
|
110
|
+
mobile
|
|
111
|
+
};
|
|
107
112
|
}
|
|
108
|
-
return recommend(await probeMemoryBudget(reads));
|
|
113
|
+
return { ...recommend(await probeMemoryBudget(reads)), mobile };
|
|
109
114
|
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { planVendoredInstall } from '../../tools/exec/adapters/python/wheels.js';
|
|
2
1
|
import { bareBin, firstCommandToken } from '../../tools/exec/scope.js';
|
|
3
2
|
const NETWORK_BINS = new Set([
|
|
4
3
|
'curl',
|
|
@@ -33,10 +32,6 @@ export class SandboxHook {
|
|
|
33
32
|
return;
|
|
34
33
|
if (!ctx.sandboxMode || ctx.toolName !== 'exec')
|
|
35
34
|
return;
|
|
36
|
-
const packages = ctx.args.packages;
|
|
37
|
-
if (Array.isArray(packages) && packages.length && !planVendoredInstall(packages.map(String))) {
|
|
38
|
-
return { error: 'sandbox mode: installing packages reaches the network', guidance: true };
|
|
39
|
-
}
|
|
40
35
|
const bin = bareBin(firstCommandToken(String(ctx.args.command ?? '')).replace(/^.*[/\\]/, ''));
|
|
41
36
|
if (bin && NETWORK_BINS.has(bin)) {
|
|
42
37
|
return { error: `sandbox mode: ${bin} reaches the network`, guidance: true };
|
|
@@ -11,6 +11,13 @@ export declare const MEDPSY_4B = "medpsy-4b-q4_k_m-imat";
|
|
|
11
11
|
export declare function isCapabilityProfile(value: string): value is CapabilityProfile;
|
|
12
12
|
export declare function meetsMinCapabilityProfile(active: CapabilityProfile, required: CapabilityProfile): boolean;
|
|
13
13
|
export declare function modelNameToCapabilityProfile(modelName: string): CapabilityProfile | undefined;
|
|
14
|
+
export interface HostCapability {
|
|
15
|
+
readonly capabilityProfile: string;
|
|
16
|
+
readonly recommendedModelName: string;
|
|
17
|
+
readonly mobile: boolean;
|
|
18
|
+
}
|
|
19
|
+
export declare function isMobilePlatform(platform: string): boolean;
|
|
20
|
+
export declare function runnableModelName(requested: string, host: HostCapability | null): string;
|
|
14
21
|
export declare function capabilityProfileToModelName(profile: CapabilityProfile): string;
|
|
15
22
|
export declare const AGENT_MODEL_NAMES: readonly ["Qwen3.5-2B-Q4_K_M", "Qwen3.5-4B-Q4_K_M", "Qwen3.5-9B-Q4_K_M", "Qwen3.6-27B-UD-Q4_K_XL", "Qwen3.6-35B-A3B-UD-Q4_K_M", "Qwen3.8-27B-UD-Q4_K_XL", "medpsy-1.7b-q4_k_m-imat", "medpsy-4b-q4_k_m-imat"];
|
|
16
23
|
export declare function modelDisplayName(modelName: string): string;
|
|
@@ -32,6 +32,23 @@ const MODEL_NAME_TO_PROFILE = new Map([
|
|
|
32
32
|
export function modelNameToCapabilityProfile(modelName) {
|
|
33
33
|
return MODEL_NAME_TO_PROFILE.get(modelName);
|
|
34
34
|
}
|
|
35
|
+
export function isMobilePlatform(platform) {
|
|
36
|
+
return platform === 'android' || platform === 'ios';
|
|
37
|
+
}
|
|
38
|
+
export function runnableModelName(requested, host) {
|
|
39
|
+
if (!host?.mobile || !isCapabilityProfile(host.capabilityProfile)) {
|
|
40
|
+
return requested;
|
|
41
|
+
}
|
|
42
|
+
const required = modelNameToCapabilityProfile(requested);
|
|
43
|
+
if (!required || meetsMinCapabilityProfile(host.capabilityProfile, required)) {
|
|
44
|
+
return requested;
|
|
45
|
+
}
|
|
46
|
+
const fallback = modelNameToCapabilityProfile(host.recommendedModelName);
|
|
47
|
+
if (!fallback || !meetsMinCapabilityProfile(required, fallback)) {
|
|
48
|
+
return requested;
|
|
49
|
+
}
|
|
50
|
+
return host.recommendedModelName;
|
|
51
|
+
}
|
|
35
52
|
const PROFILE_TO_MODEL_NAME = new Map([
|
|
36
53
|
['tiny', QWEN_35_2B],
|
|
37
54
|
['low', QWEN_35_2B],
|
|
@@ -5,9 +5,9 @@ import type { ModelAddon } from './model-source.ts';
|
|
|
5
5
|
export interface SdkModelAsset extends ModelRegistryEntry {
|
|
6
6
|
readonly src: string;
|
|
7
7
|
}
|
|
8
|
-
import { CAPABILITY_PROFILE_ORDER, QWEN_35_2B, QWEN_35_4B, QWEN_35_9B, QWEN_36_27B, QWEN_36_35B_A3B, QWEN_38_27B, MEDPSY_1_7B, MEDPSY_4B, isCapabilityProfile, meetsMinCapabilityProfile, modelNameToCapabilityProfile, capabilityProfileToModelName, isMultimodalLlm, AGENT_MODEL_NAMES, modelDisplayName, capabilityProfileModelLabel, DEFAULT_IMAGE_MODEL_CHOICE, IMAGE_CHOICE_CATALOG, type CapabilityProfile, type ImageModelChoice, type ImageChoiceEntry } from './model-catalog-constants.ts';
|
|
9
|
-
export { CAPABILITY_PROFILE_ORDER, QWEN_35_2B, QWEN_35_4B, QWEN_35_9B, QWEN_36_27B, QWEN_36_35B_A3B, QWEN_38_27B, MEDPSY_1_7B, MEDPSY_4B, isCapabilityProfile, meetsMinCapabilityProfile, modelNameToCapabilityProfile, capabilityProfileToModelName, isMultimodalLlm, AGENT_MODEL_NAMES, modelDisplayName, capabilityProfileModelLabel, DEFAULT_IMAGE_MODEL_CHOICE, IMAGE_CHOICE_CATALOG };
|
|
10
|
-
export type { CapabilityProfile, ImageModelChoice, ImageChoiceEntry };
|
|
8
|
+
import { CAPABILITY_PROFILE_ORDER, QWEN_35_2B, QWEN_35_4B, QWEN_35_9B, QWEN_36_27B, QWEN_36_35B_A3B, QWEN_38_27B, MEDPSY_1_7B, MEDPSY_4B, isCapabilityProfile, meetsMinCapabilityProfile, modelNameToCapabilityProfile, capabilityProfileToModelName, isMultimodalLlm, AGENT_MODEL_NAMES, modelDisplayName, capabilityProfileModelLabel, DEFAULT_IMAGE_MODEL_CHOICE, IMAGE_CHOICE_CATALOG, isMobilePlatform, runnableModelName, type CapabilityProfile, type HostCapability, type ImageModelChoice, type ImageChoiceEntry } from './model-catalog-constants.ts';
|
|
9
|
+
export { CAPABILITY_PROFILE_ORDER, QWEN_35_2B, QWEN_35_4B, QWEN_35_9B, QWEN_36_27B, QWEN_36_35B_A3B, QWEN_38_27B, MEDPSY_1_7B, MEDPSY_4B, isCapabilityProfile, meetsMinCapabilityProfile, modelNameToCapabilityProfile, capabilityProfileToModelName, isMultimodalLlm, AGENT_MODEL_NAMES, modelDisplayName, capabilityProfileModelLabel, DEFAULT_IMAGE_MODEL_CHOICE, IMAGE_CHOICE_CATALOG, isMobilePlatform, runnableModelName };
|
|
10
|
+
export type { CapabilityProfile, HostCapability, ImageModelChoice, ImageChoiceEntry };
|
|
11
11
|
export interface LlmCatalogEntry {
|
|
12
12
|
readonly name: string;
|
|
13
13
|
readonly weight: SdkModelAsset;
|
|
@@ -49,8 +49,8 @@ export interface DiffusionDownloadMetadata {
|
|
|
49
49
|
}
|
|
50
50
|
export declare function diffusionDownloadName(model: string): string | null;
|
|
51
51
|
export declare function diffusionDownloadMetadata(model: string, addon: ModelAddon | undefined): DiffusionDownloadMetadata | undefined;
|
|
52
|
-
import { DEFAULT_MUSIC_MODEL_CHOICE, type MusicModelChoice } from './music-catalog.ts';
|
|
53
|
-
export { DEFAULT_MUSIC_MODEL_CHOICE, type MusicModelChoice };
|
|
52
|
+
import { DEFAULT_MUSIC_MODEL_CHOICE, isMusicModelChoice, musicModelChoiceFrom, type MusicModelChoice } from './music-catalog.ts';
|
|
53
|
+
export { DEFAULT_MUSIC_MODEL_CHOICE, isMusicModelChoice, musicModelChoiceFrom, type MusicModelChoice };
|
|
54
54
|
export interface AudioGenCatalogEntry {
|
|
55
55
|
readonly choice: MusicModelChoice;
|
|
56
56
|
readonly name: string;
|
|
@@ -78,7 +78,15 @@ export interface AgentSkillModelInput {
|
|
|
78
78
|
readonly imageModelChoice?: ImageModelChoice | null;
|
|
79
79
|
readonly musicModelChoice?: MusicModelChoice | null;
|
|
80
80
|
}
|
|
81
|
-
export
|
|
81
|
+
export type AgentModelUse = 'chat' | 'image' | 'music';
|
|
82
|
+
export interface AgentModel extends CatalogModel {
|
|
83
|
+
readonly use: AgentModelUse;
|
|
84
|
+
}
|
|
85
|
+
export interface AgentModelInput extends AgentSkillModelInput {
|
|
86
|
+
readonly modelName?: string | null;
|
|
87
|
+
}
|
|
88
|
+
export declare function skillModelsForAgent(agent: AgentSkillModelInput): ReadonlyArray<AgentModel>;
|
|
89
|
+
export declare function modelsForAgent(agent: AgentModelInput, host: HostCapability | null): ReadonlyArray<AgentModel>;
|
|
82
90
|
export declare function diffusionSizeBytes(entry: DiffusionCatalogEntry): number;
|
|
83
91
|
export declare function imageModelFor(choice: string | undefined): ImageModelSet | null;
|
|
84
92
|
export interface ImageClamps {
|