dsh-tool-vision 0.9.1 → 0.9.3

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/LICENSE CHANGED
@@ -1,23 +1,23 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 Scorp1o117
4
- Bridge image preview contributed by xing666173 (dsh-bridge-preview),
5
- Copyright (c) 2026 xing666173
6
-
7
- Permission is hereby granted, free of charge, to any person obtaining a copy
8
- of this software and associated documentation files (the "Software"), to deal
9
- in the Software without restriction, including without limitation the rights
10
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
- copies of the Software, and to permit persons to whom the Software is
12
- furnished to do so, subject to the following conditions:
13
-
14
- The above copyright notice and this permission notice shall be included in all
15
- copies or substantial portions of the Software.
16
-
17
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23
- SOFTWARE.
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Scorp1o117
4
+ Bridge image preview contributed by xing666173 (dsh-bridge-preview),
5
+ Copyright (c) 2026 xing666173
6
+
7
+ Permission is hereby granted, free of charge, to any person obtaining a copy
8
+ of this software and associated documentation files (the "Software"), to deal
9
+ in the Software without restriction, including without limitation the rights
10
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
+ copies of the Software, and to permit persons to whom the Software is
12
+ furnished to do so, subject to the following conditions:
13
+
14
+ The above copyright notice and this permission notice shall be included in all
15
+ copies or substantial portions of the Software.
16
+
17
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23
+ SOFTWARE.
package/README.md CHANGED
@@ -42,15 +42,15 @@ bridge keeps its conversion inside that durable path:
42
42
  key lives in `settings.yaml`, not the profile patch. Mount by package name
43
43
  (`name: 'dsh-tool-vision'`) so the web client bundle is discovered.
44
44
 
45
- ## Compatibility (v0.9.1)
46
-
47
- Verified in a DSH `0.1.5-rc.3` (`next`) disposable Web profile; DSH
48
- `0.1.5-rc.2` remains `latest`. The rc.3 host currently references an
49
- unpublished `dsh-client-ui-sidebar-documentpreview@0.1.5-rc.3`, so the smoke
50
- profile temporarily used that unrelated UI package at rc.2. A clean rc.3
51
- installation is blocked upstream. Alpha releases remain `unknown`.
52
-
53
- ## Install
45
+ ## Compatibility (v0.9.1)
46
+
47
+ Verified in a DSH `0.1.5-rc.3` (`next`) disposable Web profile; DSH
48
+ `0.1.5-rc.2` remains `latest`. The rc.3 host currently references an
49
+ unpublished `dsh-client-ui-sidebar-documentpreview@0.1.5-rc.3`, so the smoke
50
+ profile temporarily used that unrelated UI package at rc.2. A clean rc.3
51
+ installation is blocked upstream. Alpha releases remain `unknown`.
52
+
53
+ ## Install
54
54
 
55
55
  Mount in a profile patch (`$DSH_HOME/profiles/<name>/cordis.patch.yml`):
56
56
 
@@ -382,6 +382,81 @@ the panel's picker carries a badge:
382
382
  > A route on an unknown protocol (e.g. `openai-responses`) returns `unknown`
383
383
  > with a reason, rather than guessing confidently with `chat/completions`.
384
384
 
385
+ ## v0.9.3: adapter snapshot model admission (pi-ai guard)
386
+
387
+ **The bug.** `v0.9.2`'s `installDispatchImageAdmission` satisfied DSH core's
388
+ `LlmService.generate` by declaring `image` on `adapterCall.model.inputModalities`.
389
+ However, `dsh-llm-pi-ai`'s adapter executes a second internal guard inside its
390
+ streaming path (`streamWithSnapshot`):
391
+
392
+ ```js
393
+ const model = this.modelOf(snapshot, options.provider, options.model);
394
+ if (containsImage && !model.input.includes("image"))
395
+ throw new LlmError(`pi-ai model "${model.id}" does not support image input`, "UNSUPPORTED_CONTENT");
396
+ ```
397
+
398
+ `this.modelOf` resolves from the adapter's own `snapshot.models` catalog, which
399
+ defaults to `input: ["text"]` unless the user explicitly declared
400
+ `input: [text, image]` in `settings.yaml`. When admitted by `v0.9.2`, the raw image
401
+ reached `streamWithSnapshot` and triggered `UNSUPPORTED_CONTENT`. Since the image
402
+ was already in the session's durable transcript, every subsequent turn failed.
403
+
404
+ **The fix.** On direct routes, `installDispatchImageAdmission` now additionally:
405
+ 1. Wraps `adapter.modelOf` (when present) to include `"image"` in `resolved.input`;
406
+ 2. Enriches the live snapshot model (`snapshot.models.getModel(route, model).input`)
407
+ with `"image"`;
408
+ 3. Fully restores `adapter.modelOf` and reverts modified input arrays upon dispose.
409
+
410
+ ## v0.9.2: admission is not dispatch
411
+
412
+ **The bug.** Every capability signal this plugin collects — `probeResults`,
413
+ `multimodalModels`, `autoDetectMultimodal` — decided whether the *bridge*
414
+ intercepts an image. None of them decided whether the model *receives* it.
415
+
416
+ `LlmService.generate` resolves modalities from the adapter, not from the plugin:
417
+
418
+ ```js
419
+ const adapterCall = await adapter.prepareCall(provider, model, signal);
420
+ modelInfo = this.normalizeModelInfo(registration, model, adapterCall.model);
421
+ if (modelInfo.inputModalities !== undefined
422
+ && !modelInfo.inputModalities.includes("image")
423
+ && projectedMessages.some((message) => contentHasImage(message.content)))
424
+ projectedMessages = projectImagesForTextModel(projectedMessages);
425
+ ```
426
+
427
+ `projectImagesForTextModel` rewrites every image block into
428
+ `[image omitted because this model accepts text only; attachment sha256:…]`
429
+ **before the adapter is called**. So on a route the plugin had measured as
430
+ image-capable — where the bridge therefore stepped aside and let the image
431
+ through — the image was still destroyed, by a check that reads the adapter's
432
+ declaration and nothing else. The `resolveModelInfo` wrap (`bridgeAutoImage`) is
433
+ *admission*: it decides who may offer an image, and it never changes what the
434
+ adapter streams. Nothing in the plugin reached the layer that does.
435
+
436
+ The symptom is a route that probes `yes`, is listed in `multimodalModels`, and
437
+ still answers `[image omitted …]`.
438
+
439
+ **The fix.** `installDispatchImageAdmission` wraps `llm.registration` — the
440
+ accessor the core itself calls — so every adapter, including one registered
441
+ later, answers `prepareCall` through the same `routeDirectDecision` the bridge
442
+ uses. One precedence rule, two seams, so the two can never disagree about a
443
+ route. When the decision is "bridge", nothing changes: the bridge already turned
444
+ the image into an `inspect_image` hint, and the core's projection stays as the
445
+ correct fallback for any image that still reaches dispatch.
446
+
447
+ Three properties worth stating, because each one is a test:
448
+
449
+ - **Fail closed.** A decision that cannot be made (a throwing config) leaves the
450
+ call untouched, rather than sending pixels a route may reject.
451
+ - **Reversible.** Dispose restores every adapter it patched *and* the accessor
452
+ itself; a re-install after dispose wraps again.
453
+ - **Late providers count.** The wrap is on the accessor rather than on a snapshot
454
+ of adapters, so a provider registered after install is covered too.
455
+
456
+ `routeDirectDecision` is now the single implementation of the precedence rule,
457
+ shared by the bridge and the dispatch path — two copies of it would drift, and
458
+ this one has three inputs.
459
+
385
460
  ## v0.8.0: master switch, and the save-path fix
386
461
 
387
462
  **Master switch.** `enabled`, plus a one-click button at the top of the section