@qvac/harness 0.3.2 → 0.4.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.
Files changed (36) hide show
  1. package/CHANGELOG.md +56 -1
  2. package/README.md +134 -0
  3. package/dist/harness.bundle +304 -175
  4. package/dist-lib/index.d.ts +6 -1
  5. package/dist-lib/index.js +29 -4
  6. package/dist-lib/lib/capabilities/completion.js +2 -5
  7. package/dist-lib/lib/capabilities/preview-model-source.d.ts +67 -0
  8. package/dist-lib/lib/capabilities/preview-model-source.js +387 -0
  9. package/dist-lib/lib/connect.d.ts +4 -5
  10. package/dist-lib/lib/connect.js +9 -0
  11. package/dist-lib/lib/contract-types.d.ts +4 -5
  12. package/dist-lib/lib/models/custom-model-cache.d.ts +4 -0
  13. package/dist-lib/lib/models/custom-model-cache.js +42 -0
  14. package/dist-lib/lib/models/gguf-header.d.ts +30 -0
  15. package/dist-lib/lib/models/gguf-header.js +208 -0
  16. package/dist-lib/lib/models/model-catalog.d.ts +6 -1
  17. package/dist-lib/lib/models/model-catalog.js +7 -2
  18. package/dist-lib/lib/models/model-delete.d.ts +4 -1
  19. package/dist-lib/lib/models/model-delete.js +19 -14
  20. package/dist-lib/lib/models/model-list.d.ts +6 -0
  21. package/dist-lib/lib/models/model-list.js +23 -1
  22. package/dist-lib/lib/models/model-source.js +1 -1
  23. package/dist-lib/lib/serve.js +21 -5
  24. package/dist-lib/schema/harness.js +42 -1
  25. package/dist-lib/spec/bind.js +3 -0
  26. package/dist-lib/spec/calls.js +1 -0
  27. package/dist-lib/spec/hrpc/hrpc.json +13 -0
  28. package/dist-lib/spec/hrpc/index.js +15 -3
  29. package/dist-lib/spec/hrpc/messages.js +173 -5
  30. package/dist-lib/spec/hyperschema/index.js +173 -5
  31. package/dist-lib/spec/hyperschema/schema.json +136 -1
  32. package/dist-lib/spec/hyperschema/types.d.ts +34 -1
  33. package/package.json +6 -7
  34. package/worker/harness-sidecar.mjs +1 -0
  35. package/dist-lib/lib/capabilities/vision-history.d.ts +0 -3
  36. package/dist-lib/lib/capabilities/vision-history.js +0 -21
package/CHANGELOG.md CHANGED
@@ -8,6 +8,59 @@ While the major version is `0`, a breaking change bumps the minor version.
8
8
 
9
9
  ## [Unreleased]
10
10
 
11
+ ## [0.4.0] - 2026-09-24
12
+
13
+ ### Added
14
+
15
+ - `Harness#previewModelSource({ src, mmprojSrc })`: checks a Hugging Face link,
16
+ a direct `.gguf` link or a local path without downloading it. Returns the
17
+ download link, size, context length, architecture, license, task type and a
18
+ fit verdict, lists the files of a repo that holds several, or refuses with a
19
+ reason. (#121)
20
+ - `setCustomModels` takes `taskType` and `expectedSize`. Only `llm` custom
21
+ models are registered, and a custom name never resolves for another task.
22
+ (#121)
23
+ - `list()` returns custom models with their task, size, and whether they are
24
+ loaded and on disk; the new `modelsDir` option tells the harness where
25
+ downloads live. (#121)
26
+ - `Harness#deleteModel(name)` removes what a custom model downloaded, never a
27
+ local path the user pointed at. (#121)
28
+
29
+ ### Fixed
30
+
31
+ - A turn that preloads a skill with `@skill` no longer caches the inlined
32
+ SKILL.md body under the chat's KV key. Each such turn left about 7k tokens
33
+ stranded in the cache, until a later turn overflowed the context. (#120)
34
+
35
+ ## [0.3.3] - 2026-09-24
36
+
37
+ ### Changed
38
+
39
+ - `@qvac/sdk` 0.20.0. Some addons now ship their prebuilds as per-platform
40
+ packages (`@qvac/<addon>-<os>-<cpu>`, e.g. `audiogen-ggml` 0.4, `tts-ggml`
41
+ 0.9), and `@qvac/model-fit` and `@qvac/fabric` join the native graph. A host
42
+ that bundles the harness resolves prebuilds through each addon's
43
+ `#host-addon` map, defers the new addons, and exempts the platform packages
44
+ from any install age gate. (#85)
45
+ - `@qvac/skills` 0.1.8: the diagrams skill carries every recipe inline, so a
46
+ 2B no longer loops on a second `skill` load for a reference file it cannot
47
+ name (QVAC-24436, QVAC-24502); the excel skill routes a chat image to create,
48
+ not edit; the Google OAuth routes drop the loopback port. (#115)
49
+ - A user message with several images reaches the SDK as one turn again. The
50
+ per-image split added for QVAC-24156 is gone: SDK 0.20 carries the upstream
51
+ mtmd fix (llama.cpp #27348), so equal-size still images no longer merge as
52
+ video frames and abort the encode. `<key>-vision-history-v1` cache
53
+ directories left by the split are no longer read; clearing all caches
54
+ removes them. (#116)
55
+
56
+ ### Fixed
57
+
58
+ - Retrying or regenerating in a chat that held a multi-image message could
59
+ reuse a stale cached prefix, because the split kept that chat's cache under a
60
+ key the host never deletes. (#116)
61
+ - `@qvac/skills` 0.1.9: the Asana skill's first setup step names the Asana MCP
62
+ app to connect. (#118)
63
+
11
64
  ## [0.3.2] - 2026-09-23
12
65
 
13
66
  ### Changed
@@ -240,7 +293,9 @@ Tagged but never published to npm; these changes first shipped in 0.2.12.
240
293
  and the hash-pinned Python wheels), and `prebuilds-manifest.json` naming the
241
294
  native addons it defers (#13, #15)
242
295
 
243
- [Unreleased]: https://github.com/tetherto/qvac-harness/compare/v0.3.2...HEAD
296
+ [Unreleased]: https://github.com/tetherto/qvac-harness/compare/v0.4.0...HEAD
297
+ [0.4.0]: https://github.com/tetherto/qvac-harness/compare/v0.3.3...v0.4.0
298
+ [0.3.3]: https://github.com/tetherto/qvac-harness/compare/v0.3.2...v0.3.3
244
299
  [0.3.2]: https://github.com/tetherto/qvac-harness/compare/v0.3.1...v0.3.2
245
300
  [0.3.1]: https://github.com/tetherto/qvac-harness/compare/v0.3.0...v0.3.1
246
301
  [0.3.0]: https://github.com/tetherto/qvac-harness/compare/v0.2.12...v0.3.0
package/README.md CHANGED
@@ -48,6 +48,140 @@ dir, ...) are the ones `worker/harness-sidecar.mjs` documents in this repo. The
48
48
  in-house hosts don't call this directly — they use the repo's library surface
49
49
  (`harnessChild` under bare-supervisor), which owns that argv contract.
50
50
 
51
+ ## Custom models
52
+
53
+ A host can let its users add a GGUF the catalog does not carry: a Hugging Face
54
+ link, a direct link to a `.gguf` file, or a local path. The harness checks the
55
+ source, runs it by the name the host registers it under, lists it next to the
56
+ catalog and deletes what it downloaded. Which custom models a device has is the
57
+ host's state; the harness keeps only the set it was last given.
58
+
59
+ The calls below are on the client `spawnHarness` returns (a `RemoteHarness`) and
60
+ on an in-process `Harness` alike.
61
+
62
+ ### Preview a source before adding it
63
+
64
+ `previewModelSource` reads the source's Hugging Face metadata and the first
65
+ megabyte of the file. It never downloads the model.
66
+
67
+ ```js
68
+ const preview = await harness.previewModelSource({
69
+ src: 'https://huggingface.co/bartowski/Nanbeige_Nanbeige4.2-3B-GGUF/resolve/main/Nanbeige_Nanbeige4.2-3B-IQ2_M.gguf',
70
+ mmprojSrc: undefined // a vision model's projection, checked with it
71
+ })
72
+
73
+ // {
74
+ // ok: true,
75
+ // src: 'https://huggingface.co/.../Nanbeige_Nanbeige4.2-3B-IQ2_M.gguf',
76
+ // sizeBytes: 1868506272,
77
+ // contextLength: 262144,
78
+ // architecture: 'nanbeige',
79
+ // sizeLabel: '3B',
80
+ // taskType: 'llm',
81
+ // license: 'apache-2.0',
82
+ // fit: 'unknown',
83
+ // fitReasons: ['...why the fit is unknown']
84
+ // }
85
+ ```
86
+
87
+ A preview is one of three answers:
88
+
89
+ - **A file**: `ok: true` with `src`, the download link to register. A Hugging
90
+ Face page link is resolved to its `resolve/` url.
91
+ - **A choice**: `ok: true` with `files: [{ src, label, sizeBytes }]` and no
92
+ `src`, for a repo that holds several weights. Split shards are one entry and
93
+ projections are left out. Preview the chosen `files[i].src` again.
94
+ - **A refusal**: `ok: false` with `refusal` and a `reason` ready to show. The
95
+ refusals are `invalid-source`, `not-found`, `private`, `gated`, `web-page`,
96
+ `unreachable`, `not-gguf`, `not-a-model` (a projection or LoRA adapter),
97
+ `no-model-files` and `invalid-projection`.
98
+
99
+ ```js
100
+ if (!preview.ok) return showError(preview.reason)
101
+ if (!preview.src) return pickOneOf(preview.files)
102
+ ```
103
+
104
+ `taskType` says what the model does: `llm`, `embeddings`, `speech-to-text`,
105
+ `text-to-speech` or `image-generation`. It comes from the Hugging Face pipeline
106
+ tag, then the card's and the file's tags, then the architecture. The preview
107
+ reports it for any model and refuses none for its task, so the host decides
108
+ what may be added. Only an `llm` runs as a custom model today:
109
+
110
+ ```js
111
+ if (preview.taskType !== 'llm') {
112
+ return showError(`This is a ${preview.taskType} model. Custom models run as the text model only.`)
113
+ }
114
+ ```
115
+
116
+ `fit` is `likely-fits`, `likely-too-large` or `unknown`, and `fitReasons` says
117
+ why. It comes only from the SDK, for a file whose sha256 its catalog knows, so
118
+ most custom files are `unknown`. It is never a reason to refuse.
119
+
120
+ `sizeLabel` is the file's own `general.size_label` as written (`0.6B`, `3B`,
121
+ `30B-A3B`), and is absent when the file records none.
122
+
123
+ ### Register the set
124
+
125
+ `setCustomModels` replaces the whole set. Send it on start and whenever the
126
+ host's store changes; the client sends the last set again after a reconnect.
127
+
128
+ ```js
129
+ await harness.setCustomModels([
130
+ {
131
+ name: 'nanbeige-3b',
132
+ src: preview.src,
133
+ projectionSrc: preview.mmprojSrc,
134
+ taskType: preview.taskType, // 'llm'; an entry for another task is not registered
135
+ expectedSize: preview.sizeBytes
136
+ }
137
+ ])
138
+ ```
139
+
140
+ A registered name loads like a catalog model wherever an `llm` name is taken,
141
+ and never resolves for another task. A missing `taskType` means `llm`.
142
+ `expectedSize` is what `isCached` compares the downloaded files against.
143
+
144
+ ### List them
145
+
146
+ `list` returns custom models after the catalog rows, under the same `type` and
147
+ `cachedOnly` filters:
148
+
149
+ ```js
150
+ const models = await harness.list({ type: 'llm' })
151
+ const custom = models.filter((model) => model.registrySource === 'custom')
152
+
153
+ // {
154
+ // name: 'nanbeige-3b',
155
+ // type: 'llm',
156
+ // registryPath: 'https://huggingface.co/.../Nanbeige_Nanbeige4.2-3B-IQ2_M.gguf',
157
+ // registrySource: 'custom',
158
+ // isCached: false,
159
+ // isLoaded: false,
160
+ // supportsImages: false,
161
+ // expectedSize: 1868506272
162
+ // }
163
+ ```
164
+
165
+ `registryPath` is the model's `src`, and `supportsImages` is true when it has a
166
+ projection. `isCached` means every file is on disk and, when `expectedSize` is
167
+ known, their sizes add up to it, so a partial download is not cached. A local
168
+ path is checked where it is. A downloaded file is looked for at the path the SDK
169
+ gives a single-file http download under the models cache dir, which the spawned
170
+ sidecar knows from `sdkStorage.models`; an in-process `Harness` needs
171
+ `new Harness({ modelsDir })` for it. A sharded or archive url always reads as
172
+ not cached.
173
+
174
+ ### Delete a download
175
+
176
+ ```js
177
+ const { removed, freedBytes } = await harness.deleteModel('nanbeige-3b')
178
+ ```
179
+
180
+ For a custom model this removes the weight and projection it downloaded, and
181
+ never a local path: those are the user's files. The model stays registered and
182
+ lists as not cached, ready to download again. Like a catalog delete, it does not
183
+ unload a model that is running.
184
+
51
185
  ## Office extraction
52
186
 
53
187
  Hosts can supply the bundled Python extractor to their document pipeline: