dsh-vision-router 2.1.5 → 2.1.7
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/cordis.patch.yml +14 -0
- 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/docs/releases/v2.1.7.md +13 -0
- package/index.js +62 -94
- package/lib/adversarial-hardening.js +0 -46
- package/lib/client.js +282 -39
- package/lib/core-primitives.js +9 -3
- package/lib/dsh-support-window.js +2 -2
- package/lib/legacy-global-proxy-boundary.js +181 -60
- package/lib/live-model-discovery.js +125 -15
- 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 +10 -1
- package/lib/replay-delegation.js +35 -29
- package/lib/runtime-config-normalizer.js +271 -1
- package/lib/runtime-reliability.js +0 -68
- package/lib/settings-ia-client-prelude.js +2 -2
- package/lib/vision-background-stop-store.js +30 -20
- package/lib/vision-capability-benchmark-service.js +13 -8
- package/lib/vision-capability-probe.js +24 -14
- package/lib/vision-image-input-verdict.js +22 -7
- package/lib/vision-provider-transport.js +44 -37
- package/lib/vision-tool-runtime-boundary.js +0 -25
- package/lib/web-capability-boundary.js +85 -22
- package/package.json +8 -6
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'
|
|
@@ -1129,8 +1131,8 @@ export function apply(ctx, config = {}, runtime = {}) {
|
|
|
1129
1131
|
// session model does not declare image input, and the DeepSeek adapter
|
|
1130
1132
|
// hardcodes text-only. This wrapper route (`deepseek-vision` by default)
|
|
1131
1133
|
// declares image input so the admission passes, shows up in the model
|
|
1132
|
-
// picker as "DeepSeek + 自动识图", and delegates to the
|
|
1133
|
-
// adapter
|
|
1134
|
+
// picker as "DeepSeek + 自动识图", and delegates only to the official
|
|
1135
|
+
// DeepSeek adapter (or the hidden native route during stealth takeover).
|
|
1134
1136
|
//
|
|
1135
1137
|
// The adapter is built unconditionally; whether (and under which name) it
|
|
1136
1138
|
// mounts is reconciled reactively against the resolved settings document by
|
|
@@ -1138,12 +1140,16 @@ export function apply(ctx, config = {}, runtime = {}) {
|
|
|
1138
1140
|
// take effect without a restart.
|
|
1139
1141
|
let wrapperAdapter
|
|
1140
1142
|
{
|
|
1141
|
-
const WRAPPER_MODEL_IDS = ['deepseek-v4-pro', 'deepseek-v4-flash']
|
|
1142
1143
|
const wrapName = (name) => name ?? 'DeepSeek'
|
|
1143
|
-
|
|
1144
|
+
// The row is explicitly branded as DeepSeek, so its metadata and network
|
|
1145
|
+
// authority must come from DeepSeek as well. `textProvider` is legacy
|
|
1146
|
+
// configuration and must never let an arbitrary relay masquerade behind
|
|
1147
|
+
// the special wrapper. During stealth takeover old wrapper sessions keep
|
|
1148
|
+
// delegating to the hidden native DeepSeek route.
|
|
1149
|
+
const wrapperDelegateRoute = () => (stealthActive ? nativeRoute : 'deepseek-official')
|
|
1144
1150
|
const delegateAdapter = () => {
|
|
1145
1151
|
try {
|
|
1146
|
-
return ctx.llm.registration(
|
|
1152
|
+
return ctx.llm.registration(wrapperDelegateRoute()).adapter
|
|
1147
1153
|
} catch {
|
|
1148
1154
|
return undefined
|
|
1149
1155
|
}
|
|
@@ -1154,7 +1160,7 @@ export function apply(ctx, config = {}, runtime = {}) {
|
|
|
1154
1160
|
},
|
|
1155
1161
|
providerRetryPolicy() {
|
|
1156
1162
|
try {
|
|
1157
|
-
return ctx.llm.registration(
|
|
1163
|
+
return ctx.llm.registration(wrapperDelegateRoute()).retryPolicy
|
|
1158
1164
|
} catch {
|
|
1159
1165
|
return undefined
|
|
1160
1166
|
}
|
|
@@ -1165,12 +1171,12 @@ export function apply(ctx, config = {}, runtime = {}) {
|
|
|
1165
1171
|
if (stealthActive) return []
|
|
1166
1172
|
const entries = []
|
|
1167
1173
|
const real = delegateAdapter()
|
|
1168
|
-
if (real !== undefined) {
|
|
1174
|
+
if (real !== undefined && typeof real.listModels === 'function') {
|
|
1169
1175
|
try {
|
|
1170
|
-
const listed = await real.listModels(
|
|
1176
|
+
const listed = await real.listModels('deepseek-official')
|
|
1171
1177
|
entries.push(
|
|
1172
|
-
...listed
|
|
1173
|
-
.filter((model) =>
|
|
1178
|
+
...(Array.isArray(listed) ? listed : [])
|
|
1179
|
+
.filter((model) => model && typeof model.id === 'string' && model.id !== '')
|
|
1174
1180
|
.map((model) => ({
|
|
1175
1181
|
...model,
|
|
1176
1182
|
provider: wrapperRoute(),
|
|
@@ -1219,14 +1225,29 @@ export function apply(ctx, config = {}, runtime = {}) {
|
|
|
1219
1225
|
inputModalities: ['text', 'image'],
|
|
1220
1226
|
}
|
|
1221
1227
|
} catch {
|
|
1222
|
-
/* fall through to the
|
|
1228
|
+
/* fall through to the official DeepSeek path */
|
|
1223
1229
|
}
|
|
1224
1230
|
}
|
|
1225
1231
|
const real = delegateAdapter()
|
|
1226
|
-
if (real === undefined) {
|
|
1227
|
-
throw new Error('vision-router: the
|
|
1232
|
+
if (real === undefined || typeof real.resolveModel !== 'function') {
|
|
1233
|
+
throw new Error('vision-router: the official DeepSeek adapter is not available')
|
|
1228
1234
|
}
|
|
1229
|
-
|
|
1235
|
+
// Outside stealth mode, accept only models the live official catalog
|
|
1236
|
+
// actually publishes. Some adapters can resolve arbitrary ids; that is
|
|
1237
|
+
// not permission to expose them under the DeepSeek product identity.
|
|
1238
|
+
if (!stealthActive) {
|
|
1239
|
+
if (typeof real.listModels !== 'function') {
|
|
1240
|
+
throw new Error('vision-router: the official DeepSeek catalog is not available')
|
|
1241
|
+
}
|
|
1242
|
+
const listed = await real.listModels('deepseek-official')
|
|
1243
|
+
const admitted = Array.isArray(listed) && listed.some(
|
|
1244
|
+
(entry) => entry && entry.id === model,
|
|
1245
|
+
)
|
|
1246
|
+
if (!admitted) {
|
|
1247
|
+
throw new Error(`vision-router: DeepSeek model "${model}" is not in the live official catalog`)
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
const base = await real.resolveModel(wrapperDelegateRoute(), model)
|
|
1230
1251
|
return {
|
|
1231
1252
|
...base,
|
|
1232
1253
|
provider: wrapperRoute(),
|
|
@@ -1236,7 +1257,7 @@ export function apply(ctx, config = {}, runtime = {}) {
|
|
|
1236
1257
|
},
|
|
1237
1258
|
...createWrapperStreamBody(ctx, {
|
|
1238
1259
|
imageMemory,
|
|
1239
|
-
delegateProvider:
|
|
1260
|
+
delegateProvider: wrapperDelegateRoute,
|
|
1240
1261
|
instantLocal: instantLocalProvider,
|
|
1241
1262
|
instantLocalStyle,
|
|
1242
1263
|
instantLocalTimeoutMs: timeoutMs,
|
|
@@ -1765,7 +1786,15 @@ export function apply(ctx, config = {}, runtime = {}) {
|
|
|
1765
1786
|
const corrected = await correctedVisionAnswer(pair, messages, options)
|
|
1766
1787
|
if (corrected !== undefined) return corrected
|
|
1767
1788
|
assertOpenCodeGoAffinityForPair(pair, options.sessionId)
|
|
1768
|
-
return visionAnswer(
|
|
1789
|
+
return visionAnswer({
|
|
1790
|
+
stream(streamOptions) {
|
|
1791
|
+
return streamWithLegacyGlobalProxyScope(
|
|
1792
|
+
pair.provider,
|
|
1793
|
+
pair.model,
|
|
1794
|
+
() => ctx.llm.stream(streamOptions),
|
|
1795
|
+
)
|
|
1796
|
+
},
|
|
1797
|
+
}, {
|
|
1769
1798
|
provider: pair.provider,
|
|
1770
1799
|
model: pair.model,
|
|
1771
1800
|
messages,
|
|
@@ -2079,14 +2108,18 @@ export function apply(ctx, config = {}, runtime = {}) {
|
|
|
2079
2108
|
})
|
|
2080
2109
|
if (text === undefined) {
|
|
2081
2110
|
assertOpenCodeGoAffinityForPair(pair, options.sessionId)
|
|
2082
|
-
yield*
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2111
|
+
yield* streamWithLegacyGlobalProxyScope(
|
|
2112
|
+
pair.provider,
|
|
2113
|
+
pair.model,
|
|
2114
|
+
() => streamWithVisionSessionAffinity(options.sessionId, () => ctx.llm.stream({
|
|
2115
|
+
...options,
|
|
2116
|
+
provider: pair.provider,
|
|
2117
|
+
model: pair.model,
|
|
2118
|
+
reasoningEffort: undefined,
|
|
2119
|
+
messages,
|
|
2120
|
+
signal: attemptSignal,
|
|
2121
|
+
})),
|
|
2122
|
+
)
|
|
2090
2123
|
return
|
|
2091
2124
|
}
|
|
2092
2125
|
if (text !== '') {
|
|
@@ -2227,73 +2260,8 @@ export function apply(ctx, config = {}, runtime = {}) {
|
|
|
2227
2260
|
// #208: attachment refs, description memory and the event-log cursor are
|
|
2228
2261
|
// owned by the same bounded SessionVisionStateStore above.
|
|
2229
2262
|
|
|
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
|
-
}
|
|
2263
|
+
// Host-owned proxy overrides are scoped by lib/legacy-global-proxy-boundary.js.
|
|
2264
|
+
// Core no longer owns or installs a process-wide proxy fetch implementation.
|
|
2297
2265
|
|
|
2298
2266
|
const lookupAttachment = (session, id) => sessionVisionIndex.lookupAttachment(session, id)
|
|
2299
2267
|
|
|
@@ -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).
|