dsh-vision-router 2.1.5 → 2.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -6
- package/README.zh.md +15 -6
- package/docs/architecture/2x-contract-ledger.md +1 -1
- package/docs/architecture/compat-inventory.md +10 -9
- package/docs/architecture/dsh-compatibility-matrix.md +2 -2
- package/docs/architecture/dsh-support-window.md +4 -2
- package/docs/architecture/host-first-proxy-convergence.md +91 -0
- package/docs/architecture/p3-compat-retirement.md +1 -1
- package/docs/architecture/p3-host-native-seams.md +1 -1
- package/docs/releases/v2.1.6.md +30 -0
- package/index.js +28 -79
- package/lib/adversarial-hardening.js +0 -46
- package/lib/client.js +11 -8
- package/lib/core-primitives.js +2 -1
- package/lib/dsh-support-window.js +2 -2
- package/lib/legacy-global-proxy-boundary.js +181 -60
- package/lib/proxy-dispatcher-pool.js +96 -0
- package/lib/proxy-routing.js +76 -0
- package/lib/proxy-url-compat.js +12 -0
- package/lib/public-entry.js +14 -6
- package/lib/remote-settings-bridge.js +5 -1
- package/lib/runtime-reliability.js +0 -68
- package/lib/settings-ia-client-prelude.js +2 -2
- package/lib/vision-capability-benchmark-service.js +13 -8
- package/lib/vision-provider-transport.js +44 -37
- package/lib/vision-tool-runtime-boundary.js +0 -25
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
</p>
|
|
18
18
|
|
|
19
19
|
<p align="center">
|
|
20
|
-
<a href="https://github.com/ysr666/dsh-vision-router/releases/tag/v2.1.
|
|
20
|
+
<a href="https://github.com/ysr666/dsh-vision-router/releases/tag/v2.1.6"><img src="https://img.shields.io/badge/release-v2.1.6-5B4CF0?style=flat-square" alt="Release v2.1.6" /></a>
|
|
21
21
|
<a href="tests"><img src="https://img.shields.io/badge/verified-Node%2022%20%2B%2024-2EA44F?style=flat-square" alt="Verified: Node 22 + 24" /></a>
|
|
22
22
|
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-2EA44F?style=flat-square" alt="License: MIT" /></a>
|
|
23
23
|
<a href="package.json"><img src="https://img.shields.io/badge/Node.js-%3E%3D22-339933?style=flat-square&logo=nodedotjs&logoColor=white" alt="Node.js >=22" /></a>
|
|
@@ -39,9 +39,9 @@
|
|
|
39
39
|
<p align="center">💬 <strong>QQ community group: 1105463028</strong></p>
|
|
40
40
|
|
|
41
41
|
> [!WARNING]
|
|
42
|
-
> 📌 **Announcement (v2.1.
|
|
42
|
+
> 📌 **Announcement (v2.1.6)**
|
|
43
43
|
>
|
|
44
|
-
> **v2.1.
|
|
44
|
+
> **v2.1.6:** Advances exact stable support to DSH `0.1.5-rc.2`, fixes the remote-settings `webServer` startup error, and completes Host-first proxy isolation, lifecycle, redirect-boundary, HTTP CONNECT, and SOCKS5 hardening without raising the rc.8 Host floor. [What’s new →](docs/releases/v2.1.6.md)
|
|
45
45
|
|
|
46
46
|
<p align="center">
|
|
47
47
|
<img src="assets/vision-demo.gif" width="640" alt="Demo: paste an image, the agent locates the send button with vision_ground / vision_crop / vision_pixel_diff and answers with coordinates" />
|
|
@@ -356,7 +356,7 @@ Everything is optional; defaults work out of the box. Prefer **Settings → Visi
|
|
|
356
356
|
| `wrappedProviders` | `[{ provider: 'deepseek-official', models: [] }]` | optional manual wrapper scope `{ provider, models[] }`, used after disabling auto-wrap or to restrict which models can enter an internal wrapper through “👁 Vision”; changes apply live, no restart |
|
|
357
357
|
| `routing` | `false` | legacy whole-turn chain routing (one-shot answer). `false` = tools-first flow (recommended) |
|
|
358
358
|
| `reverseRouting` | `true` | with `routing: true`, route text turns back to `textProvider` |
|
|
359
|
-
| `wrapperRoute` / `chainRoute` | `deepseek-vision` / `vision-chain` |
|
|
359
|
+
| `wrapperRoute` / `chainRoute` | `deepseek-vision` / `vision-chain` | developer route names. Leaving either field blank in current Settings clears the override and restores its default; explicitly blank values are retained only for legacy/manual persisted-config compatibility. |
|
|
360
360
|
| `stealth` | `false` | take over the official `deepseek-official` route (official row only; custom routes are auto-wrapped by default) |
|
|
361
361
|
| `textProvider` | `deepseek-official` / `deepseek-v4-pro` | the model that reasons (your daily model) |
|
|
362
362
|
| `tool` / `progressiveTools` / `autoActivateOnImage` | `true` / `false` / `true` | vision tools on / progressive mounting (off by default for a stable tool schema) / image-turn auto-mount when progressive mode is enabled; `progressiveTools` is boot-time config |
|
|
@@ -370,9 +370,18 @@ Everything is optional; defaults work out of the box. Prefer **Settings → Visi
|
|
|
370
370
|
| `cache` / `cacheTtlSeconds` / `cacheMaxEntries` | `true` / `3600` / `200` | vision answer cache |
|
|
371
371
|
| `timeoutMs` | `120000` | per vision call deadline |
|
|
372
372
|
| `artifactsDir` | `.dsh-vision-router/artifacts` | artifact directory (relative to the session workspace) |
|
|
373
|
-
| `proxy` / `proxyHosts` | `''` / openrouter hosts |
|
|
373
|
+
| `proxy` / `proxyHosts` | `''` / openrouter hosts | **advanced override**: empty follows the DSH/Host network path; only an explicit value proxies selected vision hosts through Vision Router |
|
|
374
374
|
| `catalogCorrections` | `true` | built-in catalog-routing corrections for known upstream wire-protocol mismatches; each correction disarms itself once the catalog is fixed upstream |
|
|
375
375
|
|
|
376
|
+
|
|
377
|
+
### Network proxy: DSH/Host first
|
|
378
|
+
|
|
379
|
+
Vision Router does **not own network egress by default**. When `proxy` is empty, Router-owned vision HTTP injects no private dispatcher and the Host-owned compatibility wrapper stays transparent, so requests continue on the current DSH/Host network path. With an explicit override, Host-owned adapter traffic is intercepted only while Vision Router is actively executing that configured visual provider; unrelated concurrent Host traffic keeps the Host route.
|
|
380
|
+
|
|
381
|
+
On the DSH 0.1.5 line (rc.1+), the Host provides process-wide outbound proxy policy from `HTTP_PROXY` / `HTTPS_PROXY` / `ALL_PROXY` / `NO_PROXY`; TUN mode also works transparently below the process. DSH does not automatically read the macOS/Windows “system proxy” switch, so enabling only an app's System Proxy is not the same as proxying the CLI.
|
|
382
|
+
|
|
383
|
+
Set `proxy` only when you intentionally need a **vision-only proxy override**. It remains available for older Hosts, SOCKS5 users, and deployments that need selected vision domains to take a different route; `proxyHosts` scopes only this plugin override and does not redefine the Host's global proxy policy. Redirects cannot widen that scope: once a DVR-proxied request redirects to a host outside `proxyHosts`, that hop returns to the request's inherited Host/caller dispatcher. A request whose initial URL is outside `proxyHosts` stays fully Host-owned, even if a later redirect enters the list.
|
|
384
|
+
|
|
376
385
|
### Local Ollama vision backend (merged from dsh-vision)
|
|
377
386
|
|
|
378
387
|
> **Incremental author**: [shaoqiuyuavailable](https://github.com/shaoqiuyuavailable) (router local-vision increment)
|
|
@@ -413,7 +422,7 @@ ollama pull qwen2.5vl
|
|
|
413
422
|
## Requirements
|
|
414
423
|
|
|
415
424
|
- DeepSeek Harness Web profile. Normal installs can use `npx @deepseek-ai/dsh ...`; source checkouts use `pnpm dsh ...`. A bare `dsh ...` command only works when the CLI is already on your shell `PATH`.
|
|
416
|
-
- **DSH Host support policy:** DVR 2.1.x keeps DSH `0.1.0-rc.8` as the public minimum and currently supports the released stable channel through `0.1.5-rc.
|
|
425
|
+
- **DSH Host support policy:** DVR 2.1.x keeps DSH `0.1.0-rc.8` as the public minimum and currently supports the released stable channel through `0.1.5-rc.2`. Exact `0.1.5-alpha.2` coverage is **verification evidence only**, not a preview support promise; scheduled `latest`/`alpha` canaries monitor drift without changing the support policy. DVR 2.0.x was the final train with public support for rc.6/rc.7. See [DSH Host support window](docs/architecture/dsh-support-window.md).
|
|
417
426
|
- Node ≥ 22 (host side).
|
|
418
427
|
- No API key for the default free chain; a credential reference (`apiKeyEnv`) only for paid `httpProviders`.
|
|
419
428
|
- Chrome / Chromium / Edge is needed only for `vision_html_screenshot`; every other tool works without a browser.
|
package/README.zh.md
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
</p>
|
|
18
18
|
|
|
19
19
|
<p align="center">
|
|
20
|
-
<a href="https://github.com/ysr666/dsh-vision-router/releases/tag/v2.1.
|
|
20
|
+
<a href="https://github.com/ysr666/dsh-vision-router/releases/tag/v2.1.6"><img src="https://img.shields.io/badge/release-v2.1.6-5B4CF0?style=flat-square" alt="Release v2.1.6" /></a>
|
|
21
21
|
<a href="tests"><img src="https://img.shields.io/badge/verified-Node%2022%20%2B%2024-2EA44F?style=flat-square" alt="已验证 Node 22 + 24" /></a>
|
|
22
22
|
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-2EA44F?style=flat-square" alt="MIT 许可证" /></a>
|
|
23
23
|
<a href="package.json"><img src="https://img.shields.io/badge/Node.js-%3E%3D22-339933?style=flat-square&logo=nodedotjs&logoColor=white" alt="Node.js >=22" /></a>
|
|
@@ -39,9 +39,9 @@
|
|
|
39
39
|
<p align="center">💬 <strong>QQ 用户交流群:1105463028</strong></p>
|
|
40
40
|
|
|
41
41
|
> [!WARNING]
|
|
42
|
-
> 📌 **公告(v2.1.
|
|
42
|
+
> 📌 **公告(v2.1.6)**
|
|
43
43
|
>
|
|
44
|
-
> **v2.1.
|
|
44
|
+
> **v2.1.6:正式验证 DSH `0.1.5-rc.2`,修复远程设置 `webServer` 启动报错,并完成 Host-first 代理隔离、生命周期、redirect 边界、HTTP CONNECT 与 SOCKS5 加固;公开最低 Host 仍为 rc.8。** [查看完整更新 →](docs/releases/v2.1.6.md)
|
|
45
45
|
|
|
46
46
|
<p align="center">
|
|
47
47
|
<img src="assets/vision-demo.gif" width="640" alt="演示:粘贴图片,Agent 用 vision_ground / vision_crop / vision_pixel_diff 定位发送按钮并给出坐标" />
|
|
@@ -354,7 +354,7 @@ Web profile 现在提供一级 **设置 → Vision Router** 页面。常规页
|
|
|
354
354
|
| `wrappedProviders` | `[{ provider: 'deepseek-official', models: [] }]` | 可选手动包装范围 `{ provider, models[] }`;用于关闭自动包装后手动指定,或限制某个 provider 只有部分模型可通过「👁 识图」进入 wrapper |
|
|
355
355
|
| `routing` | `false` | 旧版整轮链路由(一次性整轮回答)。`false` = 工具优先流程(推荐) |
|
|
356
356
|
| `reverseRouting` | `true` | 开启 `routing` 时,文字轮路由回 `textProvider` |
|
|
357
|
-
| `wrapperRoute` / `chainRoute` | `deepseek-vision` / `vision-chain` |
|
|
357
|
+
| `wrapperRoute` / `chainRoute` | `deepseek-vision` / `vision-chain` | 开发者路由名。当前设置页留空会清除覆盖并恢复默认值;显式空字符串仅作为旧版/手工持久化配置兼容保留。 |
|
|
358
358
|
| `stealth` | `false` | 接管官方 `deepseek-official` 路由(仅官方行;自定义路由默认由自动包装处理) |
|
|
359
359
|
| `textProvider` | `deepseek-official` / `deepseek-v4-pro` | 负责思考的模型(你的日常模型) |
|
|
360
360
|
| `tool` / `progressiveTools` / `autoActivateOnImage` | `true` / `false` / `true` | 视觉工具总开关 / 渐进式挂载(默认关闭以稳定工具 schema)/ 渐进模式下图片轮自动挂载;`progressiveTools` 为启动期配置 |
|
|
@@ -368,9 +368,18 @@ Web profile 现在提供一级 **设置 → Vision Router** 页面。常规页
|
|
|
368
368
|
| `cache` / `cacheTtlSeconds` / `cacheMaxEntries` | `true` / `3600` / `200` | 视觉答案缓存 |
|
|
369
369
|
| `timeoutMs` | `120000` | 单次视觉调用超时 |
|
|
370
370
|
| `artifactsDir` | `.dsh-vision-router/artifacts` | 产物目录(相对会话工作区) |
|
|
371
|
-
| `proxy` / `proxyHosts` | `''` / openrouter 域名 |
|
|
371
|
+
| `proxy` / `proxyHosts` | `''` / openrouter 域名 | **高级覆盖**:留空沿用 DSH/Host 网络路径;仅显式填写时让指定视觉域名使用插件代理 |
|
|
372
372
|
| `catalogCorrections` | `true` | 内置目录纠错:当已安装目录把已知模型路由到错误协议时按正确协议应答;上游修复后对应纠错自动失效 |
|
|
373
373
|
|
|
374
|
+
|
|
375
|
+
### 网络代理:默认跟随 DSH/Host
|
|
376
|
+
|
|
377
|
+
Vision Router 默认**不接管网络出口**。`proxy` 留空时,Router-owned 视觉 HTTP 不注入自有 dispatcher,Host-owned 兼容层也保持透明,因此请求继续沿用 DSH/Host 当前网络路径。显式填写覆盖时,Host-owned adapter 也只会在 Vision Router 正在执行该已配置视觉模型的调用作用域内被代理;并发的其他 Host 请求仍沿用 Host 路径。
|
|
378
|
+
|
|
379
|
+
在 DSH 0.1.5 系列(rc.1+)中,Host 已提供统一出网代理:`HTTP_PROXY` / `HTTPS_PROXY` / `ALL_PROXY` / `NO_PROXY` 在启动时解析并应用;TUN 模式也会在更底层透明生效。DSH 本身不会自动读取 macOS/Windows 的“系统代理”开关,所以仅打开代理软件的 System Proxy 并不等价于 CLI 已经走代理。
|
|
380
|
+
|
|
381
|
+
只有在你确实需要“**视觉请求单独走另一条代理**”时才填写 `proxy`。这仍保留给旧 Host、SOCKS5 用户和按视觉域名定向覆盖的高级场景;`proxyHosts` 只约束这一插件级覆盖,不会重定义 Host 的全局代理策略。 Redirect 也不会扩大这个范围:已进入 DVR 代理的请求一旦跳转到 `proxyHosts` 之外,该跳立即回到请求原本继承的 Host/caller dispatcher;首个 URL 不在 `proxyHosts` 的请求则全程保持 Host-owned,即使后续跳转进入名单也不会临时加载 DVR 代理。
|
|
382
|
+
|
|
374
383
|
### 本地 Ollama 视觉后端(并入自 dsh-vision)
|
|
375
384
|
|
|
376
385
|
> **增量开发作者**:[shaoqiuyuavailable](https://github.com/shaoqiuyuavailable)(router 本地视觉增量)
|
|
@@ -411,7 +420,7 @@ ollama pull qwen2.5vl
|
|
|
411
420
|
## 环境要求
|
|
412
421
|
|
|
413
422
|
- DeepSeek Harness 的 Web profile。普通安装可用 `npx @deepseek-ai/dsh ...`;从源码仓库运行时用 `pnpm dsh ...`。只有 CLI 已经进入系统 `PATH` 时才能直接写 `dsh ...`。
|
|
414
|
-
- **DSH Host 支持策略:** DVR 2.1.x 的公开最低 Host 仍为 DSH `0.1.0-rc.8`,当前正式发布通道已验证并支持到 `0.1.5-rc.
|
|
423
|
+
- **DSH Host 支持策略:** DVR 2.1.x 的公开最低 Host 仍为 DSH `0.1.0-rc.8`,当前正式发布通道已验证并支持到 `0.1.5-rc.2`。对 `0.1.5-alpha.2` 的精确覆盖**只属于验证证据**,不代表对 preview 的公开支持承诺;定时 `latest`/`alpha` canary 只负责发现上游漂移,也不会自动改变支持策略。DVR 2.0.x 是最后公开支持 rc.6/rc.7 的版本线。详见 [DSH Host 支持窗口](docs/architecture/dsh-support-window.md)。
|
|
415
424
|
- Node ≥ 22(宿主侧)。
|
|
416
425
|
- 默认免费链路无需 API Key;付费 `httpProviders` 只需一个凭据引用(`apiKeyEnv`)。
|
|
417
426
|
- 只有 `vision_html_screenshot` 需要 Chrome / Chromium / Edge;其余工具无浏览器也能用。
|
|
@@ -40,7 +40,7 @@ A green test run while the old path can still silently rescue the new path is no
|
|
|
40
40
|
| session surface repair | existing `surfaceOp: { op: 'replace' }` and `sourceEventSeqs` semantics remain compatible |
|
|
41
41
|
| attachment identity | durable attachment ids remain Host/session identities; no cross-session lookup is introduced |
|
|
42
42
|
| artifact publication | existing public derived-artifact paths/return values remain compatible; managed run cleanup remains confined to Router-owned provenance/namespaces |
|
|
43
|
-
| proxy setting |
|
|
43
|
+
| proxy setting | persisted `proxy` / `proxyHosts` remain compatible; blank `proxy` means Host-owned egress, while an explicit value remains a vision-only advanced override. Router-owned HTTP stays independent of process-global fetch mutation |
|
|
44
44
|
| Doctor | Closure does not turn advisory architecture/support diagnostics into new failure exit codes |
|
|
45
45
|
| support window | DVR 2.0.x continues to support the published minimum/previous/current DSH window; rc.6 support is not removed by a patch-level closure |
|
|
46
46
|
| runtime platforms | Node 22/24 and the existing Ubuntu/macOS/Windows host-sharp contract remain supported |
|
|
@@ -70,18 +70,19 @@ P0 records why each major compatibility seam exists and the condition that permi
|
|
|
70
70
|
- **Reason:** carry the Router-owned provider transport into compatibility callers whose mature function signatures still accept only a raw `fetch` or use an internal direct HTTP call, while keeping Router traffic off the process-global fetch patch.
|
|
71
71
|
- **Host gap:** this is an internal composition gap rather than a DSH version persona: `fetchWithOpenAICompatibility(...)` and the Anthropic catalog-correction path do not yet receive a `VisionProviderTransport` parameter explicitly.
|
|
72
72
|
- **First needed for:** provider-scoped transport ownership and proxy narrowing without rewriting the mature compatibility call signatures in the same migration.
|
|
73
|
-
- **Feature detection:** explicit transport-aware callers bypass the registry; only compatibility paths that call `currentVisionProviderTransport()` consume the currently installed process/profile transport. The registry never patches `globalThis.fetch`.
|
|
74
|
-
- **Removal condition:** every Router-owned compatibility caller receives `VisionProviderTransport` explicitly, production has zero reads of `currentVisionProviderTransport()`, and the install/release registry can be removed without changing proxy, credential, bounded-body or
|
|
75
|
-
- **Tests:** `vision-provider-transport
|
|
73
|
+
- **Feature detection:** explicit transport-aware callers bypass the registry; only compatibility paths that call `currentVisionProviderTransport()` consume the currently installed process/profile transport. The registry never patches `globalThis.fetch`. Router-owned and scoped Host-owned explicit proxies share one lease/retire dispatcher pool. First-hop `proxyHosts` admission preserves the no-Undici Host path for non-matches; admitted requests use a request-scoped selector that re-checks every redirect origin and delegates non-matches to the request-start Host/caller dispatcher.
|
|
74
|
+
- **Removal condition:** every Router-owned compatibility caller receives `VisionProviderTransport` explicitly, production has zero reads of `currentVisionProviderTransport()`, and the install/release registry can be removed without changing proxy, redirect-hop authority, credential, bounded-body, cancellation, or dispatcher-lifecycle behavior.
|
|
75
|
+
- **Tests:** `vision-provider-transport` (A→B lifecycle, proxy clear, pending-construction unload, synchronous loader failure, real Fetch listed→unlisted redirect, initial-unlisted no-import and explicit-caller fallback), `legacy-global-proxy-boundary` (scoped Host-owned redirect and pool cleanup), `http-compat`, `catalog-corrections`, P2 Data Boundary provider-transport Node 22/24, Host pack/install smoke.
|
|
76
76
|
|
|
77
77
|
## `lib/legacy-global-proxy-boundary.js`
|
|
78
78
|
|
|
79
|
-
- **Reason:**
|
|
80
|
-
- **Host gap:**
|
|
81
|
-
- **First needed for:** legacy/custom Host-owned visual provider compatibility when users configure Vision Router proxy
|
|
82
|
-
- **Feature detection:** live visual-
|
|
83
|
-
- **
|
|
84
|
-
- **
|
|
79
|
+
- **Reason:** preserve the explicit Vision Router proxy override for Host-owned/raw-fetch visual providers without granting that configuration process-wide routing authority. Router-owned HTTP already uses `VisionProviderTransport`; blank `proxy` leaves the compatibility wrapper fully transparent.
|
|
80
|
+
- **Host gap:** DSH 0.1.5 owns ordinary process egress, but a Host adapter invoked through `ctx.llm.stream()` still exposes no DVR-owned request/dispatcher parameter for a vision-only SOCKS/selective override. A separate legacy direct whole-turn mode (`routing=true` plus blank `chainRoute`) also hands control to the Host after the DVR hook returns, leaving no adapter-iteration scope to authorize.
|
|
81
|
+
- **First needed for:** legacy/custom Host-owned visual provider compatibility when users configure a Vision Router-specific proxy override.
|
|
82
|
+
- **Feature detection:** live explicit proxy + exact configured Host-owned provider/model + AsyncLocalStorage visual-call scope. Router-owned routes, blank proxy, different pairs and concurrent ordinary Host traffic are pass-through. Only the explicit direct whole-turn blank-`chainRoute` configuration retains unscoped compatibility.
|
|
83
|
+
- **H3 product decision:** KEEP `proxy` / `proxyHosts` supported and non-deprecated as a local-only Advanced override; do not add a second legacy-proxy toggle. Explicit blank `chainRoute` is legacy persisted/manual compatibility, not a current Settings workflow.
|
|
84
|
+
- **Removal condition:** Host-owned adapter requests can receive the explicit DVR override through a scoped Host transport (or the override is intentionally retired under an announced product policy), and the direct whole-turn explicit-empty `chainRoute` state is migrated/retired by support-window policy.
|
|
85
|
+
- **Tests:** `legacy-global-proxy-boundary`, `vision-provider-transport`, exact Host proxy egress contract, Benchmark/Exact Check coverage, P2 Data Boundary Node 22/24.
|
|
85
86
|
|
|
86
87
|
## `lib/legacy-core-vision-policy-bridge.js`
|
|
87
88
|
|
|
@@ -10,7 +10,7 @@ The matrix is capability-based. Runtime code must feature-detect the seam it nee
|
|
|
10
10
|
| --- | --- | --- |
|
|
11
11
|
| `minimum-contract` | `0.1.0-rc.6` | Historical compatibility-retention fixture. The name is legacy; DVR 2.1.x public support starts at rc.8. |
|
|
12
12
|
| `legacy-contract` | `0.1.0-rc.8` | Public support-floor fixture carrying batch attachments and dimension policy. |
|
|
13
|
-
| `current-contract` | `0.1.5-rc.
|
|
13
|
+
| `current-contract` | `0.1.5-rc.2` | Current stable Host contract baseline. Must remain green. |
|
|
14
14
|
| exact preview gates | `0.1.5-alpha.2` | Required verification evidence only; not a public preview-support claim. |
|
|
15
15
|
| `latest-dsh` / alpha canaries | resolved dynamically from npm dist-tags | Scheduled drift surveillance only. Never changes support policy by itself. |
|
|
16
16
|
|
|
@@ -20,7 +20,7 @@ Node 22 and Node 24 remain the general required runtime matrix. The Host contrac
|
|
|
20
20
|
|
|
21
21
|
`yes` means the fixture has a direct positive test or feature probe. `no` means a direct negative probe exists. `compat` means the fixture proves Vision Router can safely carry the newer input/config through that Host, but does **not** claim the Host owns that capability. `probe` means the capability is intentionally not inferred from the version label and is verified at runtime/contract-test time.
|
|
22
22
|
|
|
23
|
-
| Capability | minimum-contract rc.6 | legacy-contract rc.8 | current-contract rc.
|
|
23
|
+
| Capability | minimum-contract rc.6 | legacy-contract rc.8 | current-contract rc.2 (0.1.5) | Evidence / detection |
|
|
24
24
|
| --- | --- | --- | --- | --- |
|
|
25
25
|
| Batch attachment save | no | yes | yes | `hasBatchAttachmentContract()` checks the released `attachments.saveImages` prototype; `tests/rc6-rc7-compat.test.js`; contract CI. |
|
|
26
26
|
| Max image dimension policy | compat | yes | yes | All fixtures parse the complete attachment-local row; rc.8/current positively retain the field and the established admission tests exercise the 10000/10001 boundary. Older Schemastery passthrough is not treated as ownership evidence. |
|
|
@@ -9,10 +9,12 @@ The public support policy contains only released Host semantics. Preview/canary
|
|
|
9
9
|
| Role | DSH train | Meaning |
|
|
10
10
|
|---|---|---|
|
|
11
11
|
| Minimum Supported Host | `0.1.0-rc.8` | Oldest Host generation that DVR 2.1.x publicly supports. |
|
|
12
|
-
| Current Stable Host | `0.1.5-rc.
|
|
12
|
+
| Current Stable Host | `0.1.5-rc.2` | Current npm stable-channel release covered by required exact Host and browser evidence. |
|
|
13
13
|
|
|
14
14
|
DVR `2.1.x` therefore keeps `0.1.0-rc.8` as its public floor and supports released Host trains through the current stable channel. Runtime branching remains capability-based rather than version-string-driven.
|
|
15
15
|
|
|
16
|
+
DSH `0.1.5-rc.1` remains explicitly admitted for existing installations; current exact stable evidence has advanced to `0.1.5-rc.2`. Advancing this evidence does not raise the public minimum.
|
|
17
|
+
|
|
16
18
|
No later support-floor increase is currently announced.
|
|
17
19
|
|
|
18
20
|
## Verification evidence — not support policy
|
|
@@ -21,7 +23,7 @@ Compatibility evidence answers a different question: what exact upstream release
|
|
|
21
23
|
|
|
22
24
|
| Evidence role | DSH source | Meaning |
|
|
23
25
|
|---|---|---|
|
|
24
|
-
| Exact stable evidence | `0.1.5-rc.
|
|
26
|
+
| Exact stable evidence | `0.1.5-rc.2` | Required Host/wire and real Host + Chromium coverage for the current stable release. |
|
|
25
27
|
| Exact preview evidence | `0.1.5-alpha.2` | Required preview Host/wire/lifecycle/browser evidence. This is not a preview support promise. |
|
|
26
28
|
| Stable drift canary | npm dist-tag `latest` | Scheduled, dynamically resolved surveillance. A failure starts compatibility investigation; it does not rewrite support policy. |
|
|
27
29
|
| Preview drift canary | npm dist-tag `alpha` | Scheduled, dynamically resolved surveillance with preview-specific lifecycle coverage. A failure does not rewrite support policy. |
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# Host-first Proxy Convergence
|
|
2
|
+
|
|
3
|
+
Status: **H0 + H1 + H2 + H3 complete without raising the DSH support floor**
|
|
4
|
+
|
|
5
|
+
H1 baseline: `main@9fb7f1736813f1f5c63c1f0e8214eb12a779f24e`
|
|
6
|
+
|
|
7
|
+
H2 merged: `main@40b43d78b7c105c19188059172c2883eea9e681c`
|
|
8
|
+
|
|
9
|
+
## Decision
|
|
10
|
+
|
|
11
|
+
Network egress is a Host concern, not a vision-routing concern. Vision Router therefore treats DSH/Host as the default network authority and keeps its own proxy only as an explicit, vision-only compatibility/advanced override.
|
|
12
|
+
|
|
13
|
+
`proxy: ''` means **do not override transport**. Router-owned HTTP calls pass no private dispatcher, and the Host-owned compatibility wrapper stays transparent. Whatever path DSH/Host currently owns — direct, environment proxy, or a lower-level TUN — remains authoritative.
|
|
14
|
+
|
|
15
|
+
An explicit non-empty `proxy` keeps the existing Vision Router behavior for users who need a different route for selected `proxyHosts`, including legacy SOCKS5 configurations. Persisted settings are not migrated or rewritten.
|
|
16
|
+
|
|
17
|
+
## Why this direction
|
|
18
|
+
|
|
19
|
+
DSH's 0.1.5 line introduced a process-wide outbound proxy subsystem (`@deepseek-ai/dsh-http-proxy`) with one Host policy and a public `proxyRouteFor()` transport seam. Keeping a second generic proxy authority inside Vision Router would duplicate routing, redirect, dispatcher-lifecycle, and ownership policy.
|
|
20
|
+
|
|
21
|
+
The production plugin does not import or require that Host package. The current public support window still includes older DSH releases, and the upstream package contract explicitly says ordinary `fetch()` callers should do nothing: the Host global dispatcher already owns routing. Calling `proxyRouteFor()` is reserved for consumers that must branch on proxy state or own a transport that cannot use normal fetch. H1 therefore adopts the Host capability by preserving plain fetch and proving the behavior against exact Host source, rather than adding a second route decision.
|
|
22
|
+
|
|
23
|
+
## Compatibility matrix
|
|
24
|
+
|
|
25
|
+
| Host / user state | Authority after H0 | Vision Router behavior |
|
|
26
|
+
|---|---|---|
|
|
27
|
+
| Any supported Host, `proxy=''` | DSH/Host | inject no dispatcher; compatibility wrapper is transparent |
|
|
28
|
+
| Explicit `proxy` + Router-owned HTTP | Vision Router for matching vision hosts | provider-scoped dispatcher; no global interception |
|
|
29
|
+
| Explicit `proxy` + Host-owned visual adapter | Vision Router only inside that visual adapter call | AsyncLocalStorage-scoped dispatcher interception |
|
|
30
|
+
| `routing=true` + explicit blank `chainRoute` + Host-owned provider | Vision Router legacy compatibility | narrow unscoped direct whole-turn fallback retained until H3 |
|
|
31
|
+
| SOCKS5 / legacy `socks5h` override | Vision Router | preserved; `socks5h` is projected at the Undici boundary |
|
|
32
|
+
| Non-matching `proxyHosts` | DSH/Host | no plugin dispatcher and no Undici import |
|
|
33
|
+
|
|
34
|
+
## H0 invariants
|
|
35
|
+
|
|
36
|
+
1. Blank/whitespace `proxy` never authorizes the legacy proxy compatibility wrapper.
|
|
37
|
+
2. Blank `proxy` never imports Vision Router's userland Undici ProxyAgent.
|
|
38
|
+
3. Router-owned HTTP with blank `proxy` calls the captured Host fetch without an explicit dispatcher, so ambient Host dispatcher changes remain visible at request time.
|
|
39
|
+
4. Explicit `proxy` remains live-editable and preserves `proxyHosts` narrowing.
|
|
40
|
+
5. No schema migration, no setting rewrite, no DSH peer-range increase, and no new dependency on `@deepseek-ai/dsh-http-proxy`.
|
|
41
|
+
6. The legacy compatibility seam is relevant only for the intersection: explicit plugin proxy **and** Host-owned/raw-fetch visual provider.
|
|
42
|
+
|
|
43
|
+
## H1 — Host capability adoption
|
|
44
|
+
|
|
45
|
+
H1 is complete as an egress contract, not as a new production dependency. Exact DSH source gates now run a local fake proxy through the Host's real `installProxyFromEnvironment()` implementation and drive Vision Router's shipping provider transport with `proxy: ''`. The contract proves all of the following:
|
|
46
|
+
|
|
47
|
+
1. Host proxy policy receives Vision Router egress while Vision Router reports no private override and never imports its ProxyAgent.
|
|
48
|
+
2. Host `NO_PROXY` remains authoritative for a bypassed target.
|
|
49
|
+
3. A redirect from a proxied origin to a `NO_PROXY` origin is re-evaluated by the Host dispatcher per hop; the first-hop proxy decision is not pinned across the redirect.
|
|
50
|
+
4. The same contract runs against the exact current stable Host and exact preview evidence on Linux, macOS, and Windows through the existing source-contract matrix.
|
|
51
|
+
5. Production code contains no `@deepseek-ai/dsh-http-proxy` dependency or import. `proxyRouteFor()` is used only by the exact-source test as an oracle for the Host decision.
|
|
52
|
+
|
|
53
|
+
The current stable evidence advances to DSH `0.1.5-rc.2`; `0.1.5-rc.1` remains explicitly peer-admitted, and the public minimum remains `0.1.0-rc.8`.
|
|
54
|
+
|
|
55
|
+
## H2 — Scope Host-owned override authority
|
|
56
|
+
|
|
57
|
+
H2 retires the **configuration-wide proxy authority** without removing the compatibility feature. Core no longer constructs ProxyAgent instances or installs its own process proxy patch. One compatibility wrapper remains outside runtime composition, but its default behavior is an exact pass-through to the DSH/Host fetch chain that existed when it was installed.
|
|
58
|
+
|
|
59
|
+
For DVR-owned calls into a Host adapter, `streamWithLegacyGlobalProxyScope(provider, model, ...)` keeps an AsyncLocalStorage authorization alive across lazy AsyncIterable creation and every `next()` / `return()` / `throw()` operation. The wrapper injects a private dispatcher only when the live settings still contain an explicit proxy and the active scope exactly matches a configured Host-owned vision pair. Vision-chain adapter calls, `vision_describe`, capability Benchmark and Exact Check use this same boundary. Router-owned direct HTTP continues through `VisionProviderTransport` instead.
|
|
60
|
+
|
|
61
|
+
H2 regression proof includes a deliberately blocked Host-owned visual stream plus a concurrent same-origin ordinary Host fetch. The ordinary request receives no DVR dispatcher; only the visual request receives the marked ProxyAgent dispatcher after its stream resumes. Clearing the proxy while the visual stream is active immediately returns later requests to Host authority.
|
|
62
|
+
|
|
63
|
+
One unscoped compatibility case remains intentionally: `routing=true` with an explicitly blank `chainRoute` routes the whole image turn directly to the first Host provider after the DVR routing hook returns, so there is no DVR-owned adapter-iteration boundary to scope. H2 preserves that old configuration narrowly rather than silently breaking it in a patch release.
|
|
64
|
+
|
|
65
|
+
## H3 — Product contract for the override and last wrapper
|
|
66
|
+
|
|
67
|
+
H3 decides to **keep `proxy` / `proxyHosts` supported and non-deprecated** as a local-only Advanced override. No extra “legacy proxy” toggle is added: blank `proxy` is already the opt-out/default, the controls are already behind Advanced and local-only, and another authority bit would make the settings contract harder to reason about without removing the compatibility boundary.
|
|
68
|
+
|
|
69
|
+
This is a current capability decision, not inertia. The H3 audit on 2026-09-11 checked DSH `0.1.5-rc.2` plus upstream `master@c291e7961a515f6d7af9304e7fd1d257929aef26`: `@deepseek-ai/dsh-http-proxy` still accepts only `http:` / `https:` and explicitly diagnoses SOCKS-family URLs as unsupported. The Host LLM adapter surface at that revision also still has no generic per-call dispatcher/transport input that a plugin can pass through `ctx.llm.stream()`. DVR issue #455 provides current Windows + DSH 0.1.5-rc.1 user evidence that `socks5://` plus selective `proxyHosts` is a real supported deployment shape. Removing the override now would therefore remove capability, not merely delete compatibility code.
|
|
70
|
+
|
|
71
|
+
The remaining unscoped `routing=true` + explicit `chainRoute=''` behavior is classified as **legacy persisted/manual-config compatibility only**. Current Settings does not create that state: leaving `chainRoute` blank clears the user-layer override, so the schema default `vision-chain` becomes effective again. Existing persisted/manual explicit-empty values continue to work in this support window; H3 neither migrates nor rewrites them. Public documentation must not teach “leave `chainRoute` blank to disable” as a current Settings workflow.
|
|
72
|
+
|
|
73
|
+
The final Host-owned compatibility wrapper remains justified until both conditions are satisfied: (1) Host-owned adapter calls can receive the DVR-specific override through a scoped Host transport/dispatcher seam, or the DVR override is intentionally removed under a future announced product policy; and (2) the legacy direct whole-turn explicit-empty `chainRoute` state has been migrated or retired under the applicable support-window policy. A Host release that merely has a global HTTP(S) proxy does not satisfy those conditions.
|
|
74
|
+
|
|
75
|
+
### Post-H3 dispatcher lifecycle hardening
|
|
76
|
+
|
|
77
|
+
Both explicit DVR proxy paths now use one `createProxyDispatcherPool()` lease/retire owner. A live proxy identity change retires the previous dispatcher immediately as an authority decision, but request leases prevent graceful close until already-admitted fetch calls have handed control back. Clearing the plugin proxy retires the cached dispatcher without constructing a replacement, Router-owned plugin cleanup calls `transport.dispose()`, and the Host-owned compatibility boundary disposes its pool with its own fiber. Pending constructors that resolve after replacement or unload are still retired and closed; synchronous loader/fetch failures cannot strand leases or poison the cache.
|
|
78
|
+
|
|
79
|
+
### Per-hop redirect authority
|
|
80
|
+
|
|
81
|
+
`proxyHosts` is an authorization upper bound, not merely a first-request filter. DVR first checks the original request URL before importing its userland Undici; if that URL is not listed, the entire request remains on the inherited Host path, including any later redirect into a listed domain. This deliberately preserves #149's no-Undici boundary for non-admitted traffic.
|
|
82
|
+
|
|
83
|
+
When the original URL is listed, DVR supplies Fetch with a tiny request-scoped dispatcher selector rather than a fixed ProxyAgent. Each Undici `dispatch()` re-checks `options.origin`: listed hops use the leased DVR ProxyAgent; non-listed or malformed origins use the dispatcher that the request inherited at admission time (an explicit caller dispatcher when present, otherwise Undici's current Host global dispatcher snapshot). The selector borrows that fallback and never owns or closes it. Fetch/Undici therefore remains authoritative for redirect status handling, method/body rewriting, replay, credential/header stripping, redirect limits, and cancellation.
|
|
84
|
+
|
|
85
|
+
This also preserves the DSH 0.1.5 Host policy across the repository's Undici-major split: Undici 8 publishes its Dispatcher v2 globally and a `Dispatcher1Wrapper` under the legacy global symbol; DVR's Undici 7 reads that legacy symbol, so a non-listed redirect hop can safely delegate back to the Host-owned dispatcher instead of manufacturing a direct connection.
|
|
86
|
+
|
|
87
|
+
After the shared pool became authoritative for both explicit-proxy paths, the older runtime-level `ProxyDispatcherTracker` and its `globalThis.fetch` observer were removed. Those components could no longer see the real ProxyAgent after per-hop selectors were introduced, and retaining them would create a third, ineffective dispatcher-lifecycle authority. The shared pool is now the only owner that closes DVR-created ProxyAgents; borrowed Host/caller dispatchers remain outside DVR ownership.
|
|
88
|
+
|
|
89
|
+
## System proxy terminology
|
|
90
|
+
|
|
91
|
+
Vision Router must not implement OS-specific proxy discovery. DSH's 0.1.5 network guide distinguishes OS “system proxy” settings, standard proxy environment variables, and TUN mode; DSH itself follows the environment policy and does not automatically read macOS/Windows system-proxy switches. Host-first therefore means “follow DSH/Host”, not “reimplement operating-system proxy detection in this plugin”.
|
|
@@ -24,7 +24,7 @@ P3-B is intentionally a retirement audit, not a quota to delete files. Under the
|
|
|
24
24
|
| replay envelope v2 compatibility | old durable histories remain valid inputs even when the live Host is newer | only when the supported history/runtime window no longer needs producer rebinding or Host provides an equivalent native replay identity seam |
|
|
25
25
|
| adapter prepareCall/coalescing compatibility | the support matrix still spans Host generations with different adapter-registration/update behavior | only when the minimum Host and every DVR-owned adapter satisfy one stable registration/update contract |
|
|
26
26
|
| pi-ai bridge wire compatibility | legacy direct-bridge traffic remains a supported route shape | only after the direct bridge is retired or all supported Hosts execute that path through an equivalent native wire seam |
|
|
27
|
-
|
|
|
27
|
+
| scoped Host-owned proxy compatibility | H0/H1 makes DSH/Host the default authority; H2 removes configuration-wide interception; H3 keeps `proxy` / `proxyHosts` supported because current Host proxy still lacks SOCKS and arbitrary Host adapters expose no DVR per-call dispatcher seam. Explicit blank `chainRoute` is legacy persisted/manual compatibility only | when Host-owned adapter calls can carry the DVR override through a scoped Host transport (or an announced product policy retires the override) **and** the explicit-empty `chainRoute` state is migrated/retired |
|
|
28
28
|
|
|
29
29
|
## First planned deletion window
|
|
30
30
|
|
|
@@ -20,7 +20,7 @@ Current result: **NO NEW HOST-NATIVE MIGRATION IS AUTHORIZED IN DVR 2.0.x.**
|
|
|
20
20
|
| Candidate | Current evidence | Decision |
|
|
21
21
|
|---|---|---|
|
|
22
22
|
| settings exposure | Newer Hosts expose stronger live settings seams, but rc.6 remains the minimum and existing compatibility is still required for the supported window | NO-GO in 2.0.x |
|
|
23
|
-
| provider transport / proxy | P2-D moved Router-owned HTTP to `VisionProviderTransport`;
|
|
23
|
+
| provider transport / proxy | P2-D moved Router-owned HTTP to `VisionProviderTransport`; H0/H1 proved Host-owned default egress; H2 scopes the explicit DVR override to exact Host-owned visual adapter execution. H3 verifies current DSH still supports only HTTP(S) proxy URLs, while real DVR users rely on SOCKS/selective overrides and arbitrary Host adapters expose no DVR per-call dispatcher seam | KEEP `proxy` / `proxyHosts` supported and KEEP the scoped compatibility boundary; no extra legacy toggle and no generic Host route duplication |
|
|
24
24
|
| `ctx.jobs` | P2-F's ten-point spike found that Jobs does not replace DVR's priority, authority-revoke, topology-abort and evidence-publication fencing without retaining the custom scheduler | NO-GO; retain current scheduler |
|
|
25
25
|
| adapter registration replacement | Current Host generations expose better replacement behavior, but a read-only capability probe cannot prove a stable returned handle across the full minimum/previous/current window | NO-GO until minimum Host contract and parity tests prove one handle contract |
|
|
26
26
|
| scoped tool execution hooks | Current DSH has richer tool pipeline seams, but the rc.6 support floor and existing mature wrapper behavior mean a migration would be a split-path compatibility rewrite rather than simplification | NO-GO in 2.0.x |
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# v2.1.6
|
|
2
|
+
|
|
3
|
+
DVR 2.1.6 is a compatibility and network-hardening patch for the 2.1.x train. It advances exact stable validation to DSH `0.1.5-rc.2`, fixes the DSH 0.1.5 remote-settings startup error, and finishes the Host-first proxy convergence work without changing the public routing model, persisted settings schema, or minimum Host floor (`0.1.0-rc.8`).
|
|
4
|
+
|
|
5
|
+
## DSH 0.1.5-rc.2 and remote Settings
|
|
6
|
+
|
|
7
|
+
- Advances the fixed stable/exact Host evidence to DSH `0.1.5-rc.2`; exact `0.1.5-alpha.2` coverage remains preview verification evidence only. `0.1.5-rc.1` remains explicitly peer-compatible and the public DVR 2.1.x floor stays at `0.1.0-rc.8`.
|
|
8
|
+
- Fixes #465 by declaring `settings`, `connection`, and `webServer` on the same Cordis caller fiber that invokes `connection.rpc.handle()`. This prevents `cannot get property "webServer" without inject` under DSH 0.1.5 strict service-access semantics.
|
|
9
|
+
- Keeps the remote Settings security model unchanged: the RPC channel remains `trusted-host`, sensitive network/credential/local-device fields stay local-only, and the explicit remote field allow-list plus risk-confirmation flow remain authoritative.
|
|
10
|
+
- Keeps the separate Web-bundle `modules -> webServer` compatibility overlay in place while current upstream DSH still lacks that Loader activation dependency; #448 continues to track its eventual retirement.
|
|
11
|
+
|
|
12
|
+
## Host-first proxy convergence
|
|
13
|
+
|
|
14
|
+
- Makes the DSH/Host network path authoritative whenever Vision Router's `proxy` is blank. Ordinary Host traffic no longer passes through a second DVR proxy-routing decision.
|
|
15
|
+
- Keeps explicit `proxy` / `proxyHosts` as a supported, local-only Advanced override for visual traffic, including SOCKS5 and selective-host deployments that current DSH Host proxy policy cannot replace.
|
|
16
|
+
- Scopes Host-owned visual adapter overrides with AsyncLocalStorage, so concurrent same-origin Host requests do not inherit DVR proxy state. Vision chain, `vision_describe`, Benchmark, and Exact Check share the same scoped authority.
|
|
17
|
+
- Gives Router-owned and scoped Host-owned ProxyAgents one shared lease/retire lifecycle. Hot proxy replacement, clearing configuration, pending construction, synchronous failures, and plugin unload now retire Agents without leaking or closing in-flight work.
|
|
18
|
+
- Enforces `proxyHosts` on every redirect hop after the initial request is admitted. Redirects that leave the allow-list return to the request-start Host/caller dispatcher while Fetch/Undici remains responsible for redirect semantics, body replay, header stripping, limits, and abort behavior.
|
|
19
|
+
- Preserves historical `socks5h://` settings without rewriting persisted configuration: the compatibility projection to Undici-native `socks5://` happens only after a request is admitted by `proxyHosts`.
|
|
20
|
+
- Removes the obsolete process-wide dispatcher tracker/global-fetch observer after lifecycle ownership moved to the shared dispatcher pool.
|
|
21
|
+
|
|
22
|
+
## Protocol and regression coverage
|
|
23
|
+
|
|
24
|
+
- Adds real loopback HTTP CONNECT and SOCKS5 protocol tests in addition to MockAgent/FakeProxyAgent coverage. The SOCKS case verifies proxy-side DNS by sending an unresolved domain name through the SOCKS5 tunnel.
|
|
25
|
+
- Exact-source stable/preview contracts continue to run on Ubuntu, macOS, and Windows; Node 22/24 CI, routing parity, adversarial fuzzing, dependency review, CodeQL, native multimodal cold resume, Windows screenshot runtime, and host-sharp integration remain release gates.
|
|
26
|
+
- Pre-release acceptance on macOS used the actual `npm pack` tarball installed through DSH `0.1.5-rc.2`, then exercised the real Chromium Settings UI, a settings write/readback round trip, cold Web restart, and a real headless vision turn. `vision_ocr`, `vision_describe`, and `vision_colors` correctly read a generated image using the machine's normal DSH model credentials.
|
|
27
|
+
|
|
28
|
+
## Upgrade
|
|
29
|
+
|
|
30
|
+
Upgrade to 2.1.6 and restart DSH Web/Desktop. Existing 2.1.x settings remain compatible; no migration is required.
|
package/index.js
CHANGED
|
@@ -12,9 +12,10 @@
|
|
|
12
12
|
// are downscaled with sharp, results are cached by content hash + question,
|
|
13
13
|
// and an optional JSON mode validates structured output.
|
|
14
14
|
//
|
|
15
|
-
// Proxy:
|
|
16
|
-
//
|
|
17
|
-
//
|
|
15
|
+
// Proxy: network egress is Host-owned by default. A blank `proxy` leaves fetch
|
|
16
|
+
// entirely on DSH/Host's current network path. An explicit plugin proxy is an
|
|
17
|
+
// advanced vision-only override for `proxyHosts`; Host-owned visual adapters use
|
|
18
|
+
// a scoped compatibility wrapper, never configuration-wide process routing.
|
|
18
19
|
|
|
19
20
|
// Compatibility shim: dsh 0.1.2-alpha.4 removed `session.events` in favor of
|
|
20
21
|
// `session.snapshotEvents()`. This helper returns an array (or undefined) that
|
|
@@ -105,6 +106,7 @@ import {
|
|
|
105
106
|
} from './lib/http-body-limit.js'
|
|
106
107
|
import { writeArtifactFile } from './lib/artifact-boundary.js'
|
|
107
108
|
import { stripTrailingSlashes } from './lib/string-normalization.js'
|
|
109
|
+
import { streamWithLegacyGlobalProxyScope } from './lib/legacy-global-proxy-boundary.js'
|
|
108
110
|
import { parseVersionComparator } from './lib/version-range.js'
|
|
109
111
|
import { createCoalescingRunner } from './lib/adapter-update-coalescer.js'
|
|
110
112
|
import { captureWindowsDesktop } from './lib/windows-desktop-capture.js'
|
|
@@ -1765,7 +1767,15 @@ export function apply(ctx, config = {}, runtime = {}) {
|
|
|
1765
1767
|
const corrected = await correctedVisionAnswer(pair, messages, options)
|
|
1766
1768
|
if (corrected !== undefined) return corrected
|
|
1767
1769
|
assertOpenCodeGoAffinityForPair(pair, options.sessionId)
|
|
1768
|
-
return visionAnswer(
|
|
1770
|
+
return visionAnswer({
|
|
1771
|
+
stream(streamOptions) {
|
|
1772
|
+
return streamWithLegacyGlobalProxyScope(
|
|
1773
|
+
pair.provider,
|
|
1774
|
+
pair.model,
|
|
1775
|
+
() => ctx.llm.stream(streamOptions),
|
|
1776
|
+
)
|
|
1777
|
+
},
|
|
1778
|
+
}, {
|
|
1769
1779
|
provider: pair.provider,
|
|
1770
1780
|
model: pair.model,
|
|
1771
1781
|
messages,
|
|
@@ -2079,14 +2089,18 @@ export function apply(ctx, config = {}, runtime = {}) {
|
|
|
2079
2089
|
})
|
|
2080
2090
|
if (text === undefined) {
|
|
2081
2091
|
assertOpenCodeGoAffinityForPair(pair, options.sessionId)
|
|
2082
|
-
yield*
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2092
|
+
yield* streamWithLegacyGlobalProxyScope(
|
|
2093
|
+
pair.provider,
|
|
2094
|
+
pair.model,
|
|
2095
|
+
() => streamWithVisionSessionAffinity(options.sessionId, () => ctx.llm.stream({
|
|
2096
|
+
...options,
|
|
2097
|
+
provider: pair.provider,
|
|
2098
|
+
model: pair.model,
|
|
2099
|
+
reasoningEffort: undefined,
|
|
2100
|
+
messages,
|
|
2101
|
+
signal: attemptSignal,
|
|
2102
|
+
})),
|
|
2103
|
+
)
|
|
2090
2104
|
return
|
|
2091
2105
|
}
|
|
2092
2106
|
if (text !== '') {
|
|
@@ -2227,73 +2241,8 @@ export function apply(ctx, config = {}, runtime = {}) {
|
|
|
2227
2241
|
// #208: attachment refs, description memory and the event-log cursor are
|
|
2228
2242
|
// owned by the same bounded SessionVisionStateStore above.
|
|
2229
2243
|
|
|
2230
|
-
//
|
|
2231
|
-
//
|
|
2232
|
-
// Resolved per request from the live settings section (`current()`), so the
|
|
2233
|
-
// Web settings panel can change the proxy URL and host list without a
|
|
2234
|
-
// restart. The fetch patcher itself is installed once for the plugin fiber.
|
|
2235
|
-
|
|
2236
|
-
const currentProxyUrl = () => {
|
|
2237
|
-
const value = current().proxy
|
|
2238
|
-
return typeof value === 'string' && value !== '' ? value : undefined
|
|
2239
|
-
}
|
|
2240
|
-
const currentProxyHosts = () => {
|
|
2241
|
-
const value = current().proxyHosts
|
|
2242
|
-
return Array.isArray(value)
|
|
2243
|
-
? value.filter((host) => typeof host === 'string' && host !== '')
|
|
2244
|
-
: []
|
|
2245
|
-
}
|
|
2246
|
-
|
|
2247
|
-
{
|
|
2248
|
-
const originalFetch = globalThis.fetch
|
|
2249
|
-
let cachedAgentUrl
|
|
2250
|
-
let cachedAgentPromise
|
|
2251
|
-
const agentFor = (url) => {
|
|
2252
|
-
if (cachedAgentUrl === url && cachedAgentPromise !== undefined) return cachedAgentPromise
|
|
2253
|
-
cachedAgentUrl = url
|
|
2254
|
-
// Do not import userland Undici at plugin/module load time. Loading a
|
|
2255
|
-
// different Undici major can disturb the dispatcher used by the host's
|
|
2256
|
-
// built-in fetch even when Vision Router's own proxy setting is empty.
|
|
2257
|
-
// Load ProxyAgent only when this plugin's selective proxy is actually used.
|
|
2258
|
-
cachedAgentPromise = import('undici')
|
|
2259
|
-
.then(({ ProxyAgent }) => {
|
|
2260
|
-
if (typeof ProxyAgent !== 'function') {
|
|
2261
|
-
throw new Error('dsh-vision-router: undici ProxyAgent is unavailable')
|
|
2262
|
-
}
|
|
2263
|
-
return new ProxyAgent(url)
|
|
2264
|
-
})
|
|
2265
|
-
.catch((error) => {
|
|
2266
|
-
if (cachedAgentUrl === url) {
|
|
2267
|
-
cachedAgentUrl = undefined
|
|
2268
|
-
cachedAgentPromise = undefined
|
|
2269
|
-
}
|
|
2270
|
-
throw error
|
|
2271
|
-
})
|
|
2272
|
-
return cachedAgentPromise
|
|
2273
|
-
}
|
|
2274
|
-
const patchedFetch = (input, init) => {
|
|
2275
|
-
const proxyUrl = currentProxyUrl()
|
|
2276
|
-
if (proxyUrl === undefined) return originalFetch(input, init)
|
|
2277
|
-
let url
|
|
2278
|
-
try {
|
|
2279
|
-
url = new URL(
|
|
2280
|
-
typeof input === 'string' ? input : input && input.url ? input.url : String(input),
|
|
2281
|
-
)
|
|
2282
|
-
} catch {
|
|
2283
|
-
return originalFetch(input, init)
|
|
2284
|
-
}
|
|
2285
|
-
if (!hostMatchesAny(url.hostname, currentProxyHosts())) return originalFetch(input, init)
|
|
2286
|
-
return agentFor(proxyUrl).then((dispatcher) =>
|
|
2287
|
-
originalFetch(input, { ...(init ?? {}), dispatcher }),
|
|
2288
|
-
)
|
|
2289
|
-
}
|
|
2290
|
-
ctx.effect(() => {
|
|
2291
|
-
globalThis.fetch = patchedFetch
|
|
2292
|
-
return () => {
|
|
2293
|
-
globalThis.fetch = originalFetch
|
|
2294
|
-
}
|
|
2295
|
-
}, 'vision-router: proxy fetch')
|
|
2296
|
-
}
|
|
2244
|
+
// Host-owned proxy overrides are scoped by lib/legacy-global-proxy-boundary.js.
|
|
2245
|
+
// Core no longer owns or installs a process-wide proxy fetch implementation.
|
|
2297
2246
|
|
|
2298
2247
|
const lookupAttachment = (session, id) => sessionVisionIndex.lookupAttachment(session, id)
|
|
2299
2248
|
|
|
@@ -470,52 +470,6 @@ export function installAdversarialHardening(ctx, config = {}, core) {
|
|
|
470
470
|
const wrapped = new Proxy(ctx, {
|
|
471
471
|
get(target, property) {
|
|
472
472
|
if (property === 'tools') return wrapTools(target.tools, wrapped, core, hardenedConfig)
|
|
473
|
-
if (property === 'effect') {
|
|
474
|
-
const effect = Reflect.get(target, property, target)
|
|
475
|
-
if (typeof effect !== 'function') return effect
|
|
476
|
-
return (factory, label) => {
|
|
477
|
-
if (label !== 'vision-router: proxy fetch') return effect.call(target, factory, label)
|
|
478
|
-
return effect.call(target, () => {
|
|
479
|
-
const originalFetch = globalThis.fetch
|
|
480
|
-
const disposer = factory()
|
|
481
|
-
let installedFetch = globalThis.fetch
|
|
482
|
-
let active = true
|
|
483
|
-
// Later plugins may capture this function. Making it an inert
|
|
484
|
-
// delegator on unload removes Vision Router from the chain even if
|
|
485
|
-
// another plugin still sits above it, and prevents it resurfacing
|
|
486
|
-
// when that later plugin restores the fetch value it captured.
|
|
487
|
-
const guardedFetch = (...args) => {
|
|
488
|
-
const fetchImpl = active && typeof installedFetch === 'function'
|
|
489
|
-
? installedFetch
|
|
490
|
-
: originalFetch
|
|
491
|
-
return fetchImpl(...args)
|
|
492
|
-
}
|
|
493
|
-
globalThis.fetch = guardedFetch
|
|
494
|
-
return () => {
|
|
495
|
-
if (!active) return
|
|
496
|
-
active = false
|
|
497
|
-
// Drop the raw Vision Router fetch closure as soon as ownership
|
|
498
|
-
// ends. This releases its cached ProxyAgent/undici closure even
|
|
499
|
-
// when a later plugin still retains guardedFetch.
|
|
500
|
-
installedFetch = undefined
|
|
501
|
-
const currentFetch = globalThis.fetch
|
|
502
|
-
try {
|
|
503
|
-
// Always run the underlying effect disposer. It may own more
|
|
504
|
-
// than the process-level pointer; skipping it merely because a
|
|
505
|
-
// later plugin wrapped fetch leaks that lifecycle forever.
|
|
506
|
-
if (typeof disposer === 'function') disposer()
|
|
507
|
-
} finally {
|
|
508
|
-
// The core disposer currently restores originalFetch
|
|
509
|
-
// unconditionally. Preserve any later plugin that was above us,
|
|
510
|
-
// while an outermost Vision Router cleanup returns to the host.
|
|
511
|
-
globalThis.fetch = currentFetch === guardedFetch
|
|
512
|
-
? originalFetch
|
|
513
|
-
: currentFetch
|
|
514
|
-
}
|
|
515
|
-
}
|
|
516
|
-
}, label)
|
|
517
|
-
}
|
|
518
|
-
}
|
|
519
473
|
// Deliberately do NOT wrap ctx.inject or injected child contexts. DSH
|
|
520
474
|
// rc.6 ties route/effect ownership to the exact child-context identity;
|
|
521
475
|
// replacing it with a Proxy previously made host routes disappear (#160).
|