dsh-vision-router 2.3.0 → 3.0.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.
- package/README.md +12 -3
- package/README.zh.md +12 -3
- package/docs/architecture/3.0-architecture-plan.md +252 -0
- package/docs/architecture/3.0-r0-contract-ledger.md +137 -0
- package/docs/architecture/3.0-r1-source-artifact-boundary.md +98 -0
- package/docs/architecture/3.0-r10-final-correctness-closure.md +207 -0
- package/docs/architecture/3.0-r11-adversarial-review.md +441 -0
- package/docs/architecture/3.0-r5-browser-go-no-go.md +165 -0
- package/docs/architecture/3.0-r5-browser-typescript-gate.md +105 -0
- package/docs/architecture/3.0-r6-compat-retirement.md +203 -0
- package/docs/architecture/3.0-r7-public-api.md +151 -0
- package/docs/architecture/3.0-r8-ci-release.md +248 -0
- package/docs/architecture/3.0-r9-final-audit.md +370 -0
- package/docs/architecture/dsh-support-window.md +5 -3
- package/docs/releases/v3.0.0.md +85 -0
- package/entry.js +1 -1
- package/index.js +108 -71
- package/lib/agent-request-route-authority.js +1 -20
- package/lib/core-apply-lifecycle.js +37 -43
- package/lib/core-cache.js +87 -0
- package/lib/core-primitives.js +3 -143
- package/lib/core-vision-surface-domain.js +63 -0
- package/lib/core-vision-surface.js +11 -77
- package/lib/delegated-call-config.js +15 -24
- package/lib/doctor-cli.js +0 -0
- package/lib/doctor-runtime.js +4 -1
- package/lib/dsh-contract-compat.js +26 -230
- package/lib/dsh-host-capabilities.js +3 -9
- package/lib/dsh-settings-017-compat.js +17 -10
- package/lib/file-logger.js +8 -3
- package/lib/grounding-coordinate-runtime.js +1 -2
- package/lib/http-body-limit.js +97 -79
- package/lib/image-offload-compat.js +30 -21
- package/lib/image-resource-governor.js +210 -228
- package/lib/legacy-global-proxy-boundary.js +46 -3
- package/lib/live-model-client-prelude.js +12 -4
- package/lib/live-model-discovery.js +134 -43
- package/lib/local-vision-stabilizer.js +31 -17
- package/lib/maintenance-routes.js +1 -3
- package/lib/native-image-coexistence.js +15 -3
- package/lib/ollama-cold-start.js +87 -23
- package/lib/pi-ai-bridge-wire-compat.js +3 -2
- package/lib/proxy-dispatcher-pool.js +151 -92
- package/lib/proxy-routing.js +75 -51
- package/lib/proxy-url-compat.js +3 -10
- package/lib/public-config.js +23 -44
- package/lib/public-entry.d.ts +148 -0
- package/lib/public-entry.js +17 -4
- package/lib/replay-delegation.js +12 -7
- package/lib/replay-envelope-v2-compat.js +12 -7
- package/lib/runtime-composition.js +22 -4
- package/lib/screenshot-source-boundary.js +2 -2
- package/lib/session-affinity-runtime.js +113 -97
- package/lib/session-affinity.js +89 -76
- package/lib/session-event-history-compat.js +30 -0
- package/lib/session-query-readers.js +263 -0
- package/lib/session-surface-compat.js +30 -31
- package/lib/session-surface-policy-domain.js +85 -0
- package/lib/session-surface-policy.js +2 -111
- package/lib/session-turn-resolver.js +2 -6
- package/lib/session-vision-attachment-index.js +143 -0
- package/lib/session-vision-delegation-boundary.js +34 -0
- package/lib/session-vision-delegation.js +22 -0
- package/lib/session-vision-index.js +25 -107
- package/lib/session-vision-mode-authority.js +229 -192
- package/lib/session-vision-mode-boundary.js +65 -22
- package/lib/session-vision-policy-domain.js +304 -0
- package/lib/session-vision-policy.js +104 -0
- package/lib/session-vision-state.js +368 -378
- package/lib/settings-client-017-compat.js +9 -7
- package/lib/structured-flow-hardening.js +22 -4
- package/lib/tesseract-exec-compat.js +9 -1
- package/lib/turn-budget-context.js +47 -41
- package/lib/update-check.js +11 -2
- package/lib/vision-attachment-handle-runtime.js +176 -156
- package/lib/vision-backend-runtime-policy.js +16 -0
- package/lib/vision-background-benchmark.js +41 -4
- package/lib/vision-background-failure-policy.js +41 -41
- package/lib/vision-background-stop-store-core.js +217 -0
- package/lib/vision-background-stop-store.js +8 -179
- package/lib/vision-capability-benchmark.js +11 -61
- package/lib/vision-capability-evidence.js +35 -0
- package/lib/vision-capability-fingerprint.js +72 -0
- package/lib/vision-capability-identity.js +116 -61
- package/lib/vision-capability-router.js +13 -33
- package/lib/vision-chunk-assembler.js +95 -0
- package/lib/vision-diagnostics-routes.js +34 -4
- package/lib/vision-execution-order-apply.js +45 -55
- package/lib/vision-execution-order.js +19 -19
- package/lib/vision-execution-policy.js +12 -7
- package/lib/vision-model-registry.js +30 -17
- package/lib/vision-provider-transport.js +137 -147
- package/lib/vision-routing-authority.js +47 -48
- package/lib/vision-routing-product.js +26 -29
- package/lib/vision-routing-settings-prelude.js +8 -3
- package/lib/vision-runtime-performance-store.js +166 -0
- package/lib/vision-runtime-performance.js +13 -142
- package/lib/vision-tool-runtime-boundary.js +99 -30
- package/lib/web/dsh-settings-017-root-transport.js +27 -12
- package/lib/web/remote-settings-client.js +4 -7
- package/lib/web-capability-boundary.js +17 -7
- package/lib/wrapper-directory.js +14 -23
- package/package.json +37 -11
|
@@ -1,96 +1,155 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
return
|
|
7
|
-
}
|
|
8
|
-
if (typeof dispatcher.destroy === 'function') await dispatcher.destroy()
|
|
9
|
-
} catch {
|
|
10
|
-
// Cleanup must not turn a completed request into an unload failure.
|
|
11
|
-
}
|
|
1
|
+
import { proxyDispatcherLike, } from './proxy-routing.js';
|
|
2
|
+
function objectRecord(value) {
|
|
3
|
+
return value !== null && (typeof value === 'object' || typeof value === 'function')
|
|
4
|
+
? value
|
|
5
|
+
: undefined;
|
|
12
6
|
}
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
let disposePromise
|
|
18
|
-
const liveEntries = new Set()
|
|
19
|
-
|
|
20
|
-
const maybeCloseEntry = (entry) => {
|
|
21
|
-
if (!entry.obsolete || entry.refs !== 0 || !entry.dispatcher || entry.closing) return
|
|
22
|
-
entry.closing = Promise.resolve().then(() => closeDispatcher(entry.dispatcher)).finally(() => {
|
|
23
|
-
liveEntries.delete(entry)
|
|
24
|
-
entry.resolveDone()
|
|
25
|
-
})
|
|
26
|
-
}
|
|
27
|
-
const retireEntry = (entry) => {
|
|
28
|
-
if (!entry || entry.obsolete) return
|
|
29
|
-
entry.obsolete = true
|
|
30
|
-
maybeCloseEntry(entry)
|
|
31
|
-
}
|
|
32
|
-
const createEntry = (proxyUrl) => {
|
|
33
|
-
let resolveDone
|
|
34
|
-
const entry = {
|
|
35
|
-
proxyUrl, refs: 0, obsolete: false, dispatcher: undefined, getGlobalDispatcher: undefined, closing: undefined,
|
|
36
|
-
done: new Promise((resolve) => { resolveDone = resolve }), resolveDone: () => resolveDone(), promise: undefined,
|
|
37
|
-
}
|
|
38
|
-
liveEntries.add(entry)
|
|
39
|
-
entry.promise = Promise.resolve().then(() => importUndici()).then(({ ProxyAgent, getGlobalDispatcher }) => {
|
|
40
|
-
if (typeof ProxyAgent !== 'function') throw new Error(`${label}: undici ProxyAgent is unavailable`)
|
|
41
|
-
if (typeof getGlobalDispatcher !== 'function') throw new Error(`${label}: undici getGlobalDispatcher is unavailable`)
|
|
42
|
-
entry.getGlobalDispatcher = getGlobalDispatcher
|
|
43
|
-
entry.dispatcher = new ProxyAgent(proxyUrl)
|
|
44
|
-
maybeCloseEntry(entry)
|
|
45
|
-
return entry.dispatcher
|
|
46
|
-
}).catch((error) => {
|
|
47
|
-
if (cachedEntry === entry) cachedEntry = undefined
|
|
48
|
-
liveEntries.delete(entry)
|
|
49
|
-
entry.resolveDone()
|
|
50
|
-
throw error
|
|
51
|
-
})
|
|
52
|
-
return entry
|
|
53
|
-
}
|
|
54
|
-
const reconcile = (proxyUrl) => {
|
|
55
|
-
if (cachedEntry && cachedEntry.proxyUrl !== proxyUrl) {
|
|
56
|
-
const previous = cachedEntry
|
|
57
|
-
cachedEntry = undefined
|
|
58
|
-
retireEntry(previous)
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
const acquire = async (proxyUrl) => {
|
|
62
|
-
if (disposed) throw new Error(`${label}: dispatcher pool is disposed`)
|
|
63
|
-
let entry = cachedEntry
|
|
64
|
-
if (!entry || entry.proxyUrl !== proxyUrl || entry.obsolete) {
|
|
65
|
-
if (entry) retireEntry(entry)
|
|
66
|
-
entry = createEntry(proxyUrl)
|
|
67
|
-
cachedEntry = entry
|
|
68
|
-
}
|
|
69
|
-
entry.refs += 1
|
|
7
|
+
async function closeDispatcher(dispatcher) {
|
|
8
|
+
const value = objectRecord(dispatcher);
|
|
9
|
+
if (value === undefined)
|
|
10
|
+
return;
|
|
70
11
|
try {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
maybeCloseEntry(entry)
|
|
78
|
-
} }
|
|
79
|
-
} catch (error) {
|
|
80
|
-
entry.refs = Math.max(0, entry.refs - 1)
|
|
81
|
-
maybeCloseEntry(entry)
|
|
82
|
-
throw error
|
|
12
|
+
if (typeof value.close === 'function') {
|
|
13
|
+
await value.close.call(dispatcher);
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
if (typeof value.destroy === 'function')
|
|
17
|
+
await value.destroy.call(dispatcher);
|
|
83
18
|
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
cachedEntry
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
19
|
+
catch {
|
|
20
|
+
// Cleanup must not turn a completed request into an unload failure.
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export function createProxyDispatcherPool({ importUndici = () => import('undici'), label = 'vision proxy transport', } = {}) {
|
|
24
|
+
let cachedEntry;
|
|
25
|
+
let disposed = false;
|
|
26
|
+
let disposePromise;
|
|
27
|
+
const liveEntries = new Set();
|
|
28
|
+
const maybeCloseEntry = (entry) => {
|
|
29
|
+
if (!entry.obsolete || entry.refs !== 0 || !entry.dispatcher || entry.closing)
|
|
30
|
+
return;
|
|
31
|
+
entry.closing = Promise.resolve()
|
|
32
|
+
.then(() => closeDispatcher(entry.dispatcher))
|
|
33
|
+
.finally(() => {
|
|
34
|
+
liveEntries.delete(entry);
|
|
35
|
+
entry.resolveDone();
|
|
36
|
+
});
|
|
37
|
+
};
|
|
38
|
+
const retireEntry = (entry) => {
|
|
39
|
+
if (!entry || entry.obsolete)
|
|
40
|
+
return;
|
|
41
|
+
entry.obsolete = true;
|
|
42
|
+
maybeCloseEntry(entry);
|
|
43
|
+
};
|
|
44
|
+
const createEntry = (proxyUrl) => {
|
|
45
|
+
let resolveDone;
|
|
46
|
+
const done = new Promise((resolve) => { resolveDone = resolve; });
|
|
47
|
+
const entry = {
|
|
48
|
+
proxyUrl,
|
|
49
|
+
refs: 0,
|
|
50
|
+
obsolete: false,
|
|
51
|
+
dispatcher: undefined,
|
|
52
|
+
getGlobalDispatcher: undefined,
|
|
53
|
+
closing: undefined,
|
|
54
|
+
done,
|
|
55
|
+
resolveDone,
|
|
56
|
+
promise: undefined,
|
|
57
|
+
};
|
|
58
|
+
liveEntries.add(entry);
|
|
59
|
+
entry.promise = Promise.resolve()
|
|
60
|
+
.then(importUndici)
|
|
61
|
+
.then((moduleValue) => {
|
|
62
|
+
const module = objectRecord(moduleValue);
|
|
63
|
+
const ProxyAgent = module?.ProxyAgent;
|
|
64
|
+
const getGlobalDispatcher = module?.getGlobalDispatcher;
|
|
65
|
+
if (typeof ProxyAgent !== 'function') {
|
|
66
|
+
throw new Error(`${label}: undici ProxyAgent is unavailable`);
|
|
67
|
+
}
|
|
68
|
+
if (typeof getGlobalDispatcher !== 'function') {
|
|
69
|
+
throw new Error(`${label}: undici getGlobalDispatcher is unavailable`);
|
|
70
|
+
}
|
|
71
|
+
const Constructor = ProxyAgent;
|
|
72
|
+
const dispatcher = new Constructor(proxyUrl);
|
|
73
|
+
if (!proxyDispatcherLike(dispatcher)) {
|
|
74
|
+
throw new Error(`${label}: undici ProxyAgent has no dispatcher contract`);
|
|
75
|
+
}
|
|
76
|
+
entry.getGlobalDispatcher = () => {
|
|
77
|
+
const globalDispatcher = getGlobalDispatcher.call(moduleValue);
|
|
78
|
+
if (!proxyDispatcherLike(globalDispatcher)) {
|
|
79
|
+
throw new Error(`${label}: undici global dispatcher has no dispatcher contract`);
|
|
80
|
+
}
|
|
81
|
+
return globalDispatcher;
|
|
82
|
+
};
|
|
83
|
+
entry.dispatcher = dispatcher;
|
|
84
|
+
maybeCloseEntry(entry);
|
|
85
|
+
return entry.dispatcher;
|
|
86
|
+
})
|
|
87
|
+
.catch((error) => {
|
|
88
|
+
if (cachedEntry === entry)
|
|
89
|
+
cachedEntry = undefined;
|
|
90
|
+
liveEntries.delete(entry);
|
|
91
|
+
entry.resolveDone();
|
|
92
|
+
throw error;
|
|
93
|
+
});
|
|
94
|
+
return entry;
|
|
95
|
+
};
|
|
96
|
+
const reconcile = (proxyUrl) => {
|
|
97
|
+
if (cachedEntry && cachedEntry.proxyUrl !== proxyUrl) {
|
|
98
|
+
const previous = cachedEntry;
|
|
99
|
+
cachedEntry = undefined;
|
|
100
|
+
retireEntry(previous);
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
const acquire = async (proxyUrl) => {
|
|
104
|
+
if (disposed)
|
|
105
|
+
throw new Error(`${label}: dispatcher pool is disposed`);
|
|
106
|
+
let entry = cachedEntry;
|
|
107
|
+
if (!entry || entry.proxyUrl !== proxyUrl || entry.obsolete) {
|
|
108
|
+
if (entry)
|
|
109
|
+
retireEntry(entry);
|
|
110
|
+
entry = createEntry(proxyUrl);
|
|
111
|
+
cachedEntry = entry;
|
|
112
|
+
}
|
|
113
|
+
entry.refs += 1;
|
|
114
|
+
try {
|
|
115
|
+
const promise = entry.promise;
|
|
116
|
+
if (!promise)
|
|
117
|
+
throw new Error(`${label}: dispatcher entry failed to initialize`);
|
|
118
|
+
const dispatcher = await promise;
|
|
119
|
+
const getGlobalDispatcher = entry.getGlobalDispatcher;
|
|
120
|
+
if (!getGlobalDispatcher) {
|
|
121
|
+
throw new Error(`${label}: global dispatcher accessor is unavailable`);
|
|
122
|
+
}
|
|
123
|
+
let released = false;
|
|
124
|
+
return {
|
|
125
|
+
dispatcher,
|
|
126
|
+
getGlobalDispatcher,
|
|
127
|
+
release() {
|
|
128
|
+
if (released)
|
|
129
|
+
return;
|
|
130
|
+
released = true;
|
|
131
|
+
entry.refs = Math.max(0, entry.refs - 1);
|
|
132
|
+
maybeCloseEntry(entry);
|
|
133
|
+
},
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
catch (error) {
|
|
137
|
+
entry.refs = Math.max(0, entry.refs - 1);
|
|
138
|
+
maybeCloseEntry(entry);
|
|
139
|
+
throw error;
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
const dispose = () => {
|
|
143
|
+
if (disposePromise)
|
|
144
|
+
return disposePromise;
|
|
145
|
+
disposed = true;
|
|
146
|
+
const current = cachedEntry;
|
|
147
|
+
cachedEntry = undefined;
|
|
148
|
+
retireEntry(current);
|
|
149
|
+
for (const entry of liveEntries)
|
|
150
|
+
retireEntry(entry);
|
|
151
|
+
disposePromise = Promise.all([...liveEntries].map((entry) => entry.done)).then(() => undefined);
|
|
152
|
+
return disposePromise;
|
|
153
|
+
};
|
|
154
|
+
return Object.freeze({ acquire, reconcile, dispose });
|
|
96
155
|
}
|
package/lib/proxy-routing.js
CHANGED
|
@@ -1,76 +1,100 @@
|
|
|
1
|
-
import { domainToASCII } from 'node:url'
|
|
2
|
-
|
|
1
|
+
import { URL, domainToASCII } from 'node:url';
|
|
3
2
|
function objectLike(value) {
|
|
4
|
-
|
|
3
|
+
return value !== null && (typeof value === 'object' || typeof value === 'function');
|
|
5
4
|
}
|
|
6
|
-
|
|
7
5
|
/** True only when the user explicitly configured a Vision Router proxy override. */
|
|
8
6
|
export function visionProxyOverrideConfigured(config = {}) {
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
const value = objectLike(config)
|
|
8
|
+
? config.proxy
|
|
9
|
+
: undefined;
|
|
10
|
+
return typeof value === 'string' && value.trim() !== '';
|
|
11
11
|
}
|
|
12
|
-
|
|
13
12
|
/**
|
|
14
13
|
* Canonicalize a configured/request hostname for proxy admission only.
|
|
15
14
|
* Persisted settings stay untouched; this removes DNS presentation differences
|
|
16
15
|
* (case, one trailing dot and Unicode) without broadening the suffix policy.
|
|
17
16
|
*/
|
|
18
17
|
export function canonicalProxyHost(value) {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
18
|
+
let host = String(value ?? '').trim();
|
|
19
|
+
if (host === '')
|
|
20
|
+
return '';
|
|
21
|
+
if (host.endsWith('.'))
|
|
22
|
+
host = host.slice(0, -1);
|
|
23
|
+
if (host === '')
|
|
24
|
+
return '';
|
|
25
|
+
// WHATWG URL.hostname keeps IPv6 literals bracketed. domainToASCII does not
|
|
26
|
+
// accept a bare IPv6 literal, so preserve the bracketed spelling and only
|
|
27
|
+
// case-fold it. Proxy hosts are otherwise DNS names and can be IDNA-folded.
|
|
28
|
+
if (host.startsWith('[') && host.endsWith(']'))
|
|
29
|
+
return host.toLowerCase();
|
|
30
|
+
const ascii = domainToASCII(host);
|
|
31
|
+
return (ascii || host).toLowerCase();
|
|
30
32
|
}
|
|
31
|
-
|
|
32
33
|
/** Exact host or subdomain match using one shared proxy-host interpretation. */
|
|
33
|
-
export function proxyHostMatchesAny(hostname, hosts) {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
34
|
+
export function proxyHostMatchesAny(hostname, hosts = []) {
|
|
35
|
+
const host = canonicalProxyHost(hostname);
|
|
36
|
+
if (host === '')
|
|
37
|
+
return false;
|
|
38
|
+
return hosts.some((raw) => {
|
|
39
|
+
const candidate = canonicalProxyHost(raw);
|
|
40
|
+
return candidate !== '' && (host === candidate || host.endsWith(`.${candidate}`));
|
|
41
|
+
});
|
|
40
42
|
}
|
|
41
|
-
|
|
42
43
|
export function proxyDispatcherLike(value) {
|
|
43
|
-
|
|
44
|
+
return objectLike(value)
|
|
45
|
+
&& typeof value.dispatch === 'function';
|
|
44
46
|
}
|
|
45
|
-
|
|
46
47
|
function proxyDispatchOriginUrl(options) {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
48
|
+
try {
|
|
49
|
+
const origin = objectLike(options)
|
|
50
|
+
? options.origin
|
|
51
|
+
: undefined;
|
|
52
|
+
if (origin instanceof URL)
|
|
53
|
+
return origin;
|
|
54
|
+
if (origin !== undefined && origin !== null)
|
|
55
|
+
return new URL(String(origin));
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
// Unknown dispatcher shapes never authorize the plugin proxy.
|
|
59
|
+
}
|
|
60
|
+
return undefined;
|
|
55
61
|
}
|
|
56
|
-
|
|
57
62
|
/**
|
|
58
63
|
* Preserve Fetch/Undici redirect semantics while keeping proxyHosts an upper
|
|
59
64
|
* bound on every hop after a request has entered the DVR proxy override.
|
|
60
65
|
* The fallback dispatcher is borrowed, never owned or closed by this selector.
|
|
61
66
|
*/
|
|
62
67
|
export function createPerHopProxyDispatcher(proxyDispatcher, fallbackDispatcher, proxyHosts = []) {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
68
|
+
if (!proxyDispatcherLike(proxyDispatcher) || !proxyDispatcherLike(fallbackDispatcher)) {
|
|
69
|
+
throw new TypeError('vision proxy hop selector requires proxy and fallback dispatchers');
|
|
70
|
+
}
|
|
71
|
+
const hosts = [...proxyHosts];
|
|
72
|
+
const proxy = proxyDispatcher;
|
|
73
|
+
const fallback = fallbackDispatcher;
|
|
74
|
+
// Fetch types model a full Undici Dispatcher, while fetch dispatch itself
|
|
75
|
+
// consumes the dispatch() seam. A Host dispatcher may expose dispatch as a
|
|
76
|
+
// frozen/non-configurable own property; using that borrowed object directly
|
|
77
|
+
// as a Proxy target would make an overriding get trap violate ECMAScript
|
|
78
|
+
// Proxy invariants. Use a neutral shell with the same prototype instead.
|
|
79
|
+
//
|
|
80
|
+
// Every non-dispatch read still resolves against and binds to the real
|
|
81
|
+
// fallback Dispatcher, so the wrapper does not become a second owner for
|
|
82
|
+
// close/destroy/event state and instanceof-style prototype checks remain
|
|
83
|
+
// compatible with the borrowed dispatcher.
|
|
84
|
+
const shell = Object.create(Object.getPrototypeOf(fallback));
|
|
85
|
+
return new Proxy(shell, {
|
|
86
|
+
get(_target, property) {
|
|
87
|
+
if (property === 'dispatch') {
|
|
88
|
+
return (options, handler) => {
|
|
89
|
+
const url = proxyDispatchOriginUrl(options);
|
|
90
|
+
const selected = url && proxyHostMatchesAny(url.hostname, hosts)
|
|
91
|
+
? proxy
|
|
92
|
+
: fallback;
|
|
93
|
+
return selected.dispatch(options, handler);
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
const value = Reflect.get(fallback, property, fallback);
|
|
97
|
+
return typeof value === 'function' ? value.bind(fallback) : value;
|
|
98
|
+
},
|
|
99
|
+
});
|
|
76
100
|
}
|
package/lib/proxy-url-compat.js
CHANGED
|
@@ -1,12 +1,5 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Project the persisted proxy setting into the syntax accepted by Undici.
|
|
3
|
-
*
|
|
4
|
-
* Older Vision Router settings copy explicitly recommended `socks5h://`.
|
|
5
|
-
* Keep that persisted user intent untouched and translate only at the runtime
|
|
6
|
-
* dispatcher boundary. Deliberately do not validate or reinterpret any other
|
|
7
|
-
* proxy scheme here: Undici remains the execution authority.
|
|
8
|
-
*/
|
|
9
1
|
export function effectiveProxyUrlForUndici(value) {
|
|
10
|
-
|
|
11
|
-
|
|
2
|
+
if (typeof value !== 'string')
|
|
3
|
+
return value;
|
|
4
|
+
return value.replace(/^(\s*)socks5h:/i, '$1socks5:');
|
|
12
5
|
}
|
package/lib/public-config.js
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
import z from '@deepseek-ai/schemastery'
|
|
2
|
-
|
|
1
|
+
import z from '@deepseek-ai/schemastery';
|
|
3
2
|
// Increment whenever the browser-visible settings contract gains a field whose
|
|
4
3
|
// absence changes write semantics. The resolved schema also publishes this as
|
|
5
4
|
// a read-only-by-convention handshake for browser/Host compatibility checks.
|
|
6
|
-
export const SETTINGS_CONTRACT_REVISION = 7
|
|
7
|
-
|
|
5
|
+
export const SETTINGS_CONTRACT_REVISION = 7;
|
|
8
6
|
/**
|
|
9
7
|
* Compose DVR's public product settings onto the mature Core schema.
|
|
10
8
|
*
|
|
@@ -14,44 +12,25 @@ export const SETTINGS_CONTRACT_REVISION = 7
|
|
|
14
12
|
* exported contract and runtime validation drift apart.
|
|
15
13
|
*/
|
|
16
14
|
export function composePublicVisionConfig(coreConfig) {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
'
|
|
27
|
-
z.number().step(
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
coreConfig.set(
|
|
39
|
-
'routingPreference',
|
|
40
|
-
z.union(['balanced', 'quality', 'speed', 'local']).default('balanced'),
|
|
41
|
-
)
|
|
42
|
-
coreConfig.set(
|
|
43
|
-
'backgroundBenchmarking',
|
|
44
|
-
z.union(['local-free', 'all', 'off']).default('off'),
|
|
45
|
-
)
|
|
46
|
-
|
|
47
|
-
// Remote Settings remains an explicit opt-in product permission. The remote
|
|
48
|
-
// risk dialog is a reminder/acknowledgement, not a server-side auth proof.
|
|
49
|
-
coreConfig.set('allowRemoteSettings', z.boolean().default(false))
|
|
50
|
-
|
|
51
|
-
coreConfig.set(
|
|
52
|
-
'settingsContractRevision',
|
|
53
|
-
z.number().step(1).min(1).max(SETTINGS_CONTRACT_REVISION).default(SETTINGS_CONTRACT_REVISION),
|
|
54
|
-
)
|
|
55
|
-
|
|
56
|
-
return coreConfig
|
|
15
|
+
if (!coreConfig || typeof coreConfig.set !== 'function') {
|
|
16
|
+
throw new TypeError('Vision Router public config requires a Schemastery object schema');
|
|
17
|
+
}
|
|
18
|
+
coreConfig.set('progressiveTools', z.boolean().default(false));
|
|
19
|
+
// Keep provider/task/turn timeout layers coherent. The whole-turn budget is
|
|
20
|
+
// an optional safety cap: 0 remains unlimited for long-running agent turns.
|
|
21
|
+
coreConfig.set('visionTaskTimeoutMs', z.number().step(1000).min(1000).max(180000).default(120000));
|
|
22
|
+
coreConfig.set('visionTurnBudgetMs', z.number().step(1000).min(0).max(600000).default(0));
|
|
23
|
+
// Both visible settings entry points edit the same Host-owned namespace.
|
|
24
|
+
coreConfig.set('visionDepth', z.union(['fast', 'standard', 'deep', 'custom']).default('standard'));
|
|
25
|
+
coreConfig.set('visionDepthMaxCalls', z.number().step(1).min(0).max(100).default(0));
|
|
26
|
+
// Product semantics: ordered remains the safe default. Auto execution and
|
|
27
|
+
// background measurement each require explicit live user authority.
|
|
28
|
+
coreConfig.set('routingMode', z.union(['ordered', 'auto']).default('ordered'));
|
|
29
|
+
coreConfig.set('routingPreference', z.union(['balanced', 'quality', 'speed', 'local']).default('balanced'));
|
|
30
|
+
coreConfig.set('backgroundBenchmarking', z.union(['local-free', 'all', 'off']).default('off'));
|
|
31
|
+
// Remote Settings remains an explicit opt-in product permission. The remote
|
|
32
|
+
// risk dialog is a reminder/acknowledgement, not a server-side auth proof.
|
|
33
|
+
coreConfig.set('allowRemoteSettings', z.boolean().default(false));
|
|
34
|
+
coreConfig.set('settingsContractRevision', z.number().step(1).min(1).max(SETTINGS_CONTRACT_REVISION).default(SETTINGS_CONTRACT_REVISION));
|
|
35
|
+
return coreConfig;
|
|
57
36
|
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import '@deepseek-ai/schemastery'
|
|
2
|
+
|
|
3
|
+
export const name: 'vision-router'
|
|
4
|
+
export const inject: readonly ['tools', 'llm']
|
|
5
|
+
export const SETTINGS_CONTRACT_REVISION: 7
|
|
6
|
+
|
|
7
|
+
export const Config: Schemastery<
|
|
8
|
+
Record<string, unknown>,
|
|
9
|
+
Record<string, unknown>
|
|
10
|
+
>
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Minimum Cordis shape the plugin requires at the package root.
|
|
14
|
+
*
|
|
15
|
+
* Every supported DSH Host line mounts a Cordis lifecycle context, so this is a
|
|
16
|
+
* precondition rather than an optional capability: cords are installed through
|
|
17
|
+
* `ctx.effect()`, and a generation that cannot own its cleanup is refused
|
|
18
|
+
* instead of publishing routes, wrappers or global mutations without an owner.
|
|
19
|
+
*/
|
|
20
|
+
export interface CordisLifecycleContext {
|
|
21
|
+
readonly effect: (...args: any[]) => unknown
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Install the plugin on a Cordis plugin context.
|
|
26
|
+
*
|
|
27
|
+
* @param ctx Host plugin context. Must expose `effect()`, otherwise a
|
|
28
|
+
* `TypeError` is thrown before anything is published.
|
|
29
|
+
* @throws {TypeError} when `ctx.effect` is not callable.
|
|
30
|
+
*/
|
|
31
|
+
export function apply(
|
|
32
|
+
ctx: CordisLifecycleContext,
|
|
33
|
+
config?: Record<string, unknown>,
|
|
34
|
+
): unknown
|
|
35
|
+
|
|
36
|
+
export interface LegacySessionSurfaceReplacementIntent {
|
|
37
|
+
readonly surfaceOp: Readonly<{
|
|
38
|
+
op: 'replace'
|
|
39
|
+
start: number
|
|
40
|
+
end: number
|
|
41
|
+
}>
|
|
42
|
+
readonly sourceEventSeqs: readonly [number]
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface CurrentSessionSurfaceReplacementIntent {
|
|
46
|
+
readonly surfaceOp: Readonly<{
|
|
47
|
+
op: 'replace'
|
|
48
|
+
startSeq: number
|
|
49
|
+
endSeq: number
|
|
50
|
+
}>
|
|
51
|
+
readonly sourceEventSeqs: readonly [number]
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export type SessionSurfaceReplacementIntent =
|
|
55
|
+
| LegacySessionSurfaceReplacementIntent
|
|
56
|
+
| CurrentSessionSurfaceReplacementIntent
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Build the Host-owned Session surface replacement intent for one exact node.
|
|
60
|
+
*
|
|
61
|
+
* @param seq Non-negative safe integer sequence number. `-0`, fractional,
|
|
62
|
+
* `NaN` and `Infinity` are rejected.
|
|
63
|
+
* @throws {TypeError} when `seq` is not a non-negative safe integer.
|
|
64
|
+
*/
|
|
65
|
+
export function sessionSurfaceReplacementIntent(
|
|
66
|
+
session: unknown,
|
|
67
|
+
seq: number,
|
|
68
|
+
): SessionSurfaceReplacementIntent | undefined
|
|
69
|
+
|
|
70
|
+
export interface VisionAttachmentAdmissionLogger {
|
|
71
|
+
info?: (message: string, ...args: unknown[]) => unknown
|
|
72
|
+
warn?: (message: string, ...args: unknown[]) => unknown
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export interface VisionAttachmentAdmissionOptions {
|
|
76
|
+
readonly maxImageDimension?: number
|
|
77
|
+
readonly normalizedImageMaxPixels?: number
|
|
78
|
+
readonly normalizedImageMaxDimension?: number
|
|
79
|
+
readonly normalizedImageMaxBytes?: number
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export interface VisionAttachmentAdmissionResult {
|
|
83
|
+
readonly changed: boolean
|
|
84
|
+
readonly reason: string
|
|
85
|
+
readonly limits?: Readonly<Record<string, unknown>>
|
|
86
|
+
readonly normalizationPolicy?: Readonly<Record<string, unknown>>
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function hasBatchAttachmentContract(ctx: unknown): boolean
|
|
90
|
+
|
|
91
|
+
export function hostOwnsOfficialDeepSeekProvider(ctx: unknown): boolean
|
|
92
|
+
|
|
93
|
+
export function ensureVisionAttachmentAdmissionPolicy(
|
|
94
|
+
ctx: unknown,
|
|
95
|
+
logger?: VisionAttachmentAdmissionLogger,
|
|
96
|
+
options?: VisionAttachmentAdmissionOptions,
|
|
97
|
+
): VisionAttachmentAdmissionResult
|
|
98
|
+
|
|
99
|
+
export function installVisionAttachmentAdmissionPolicy(
|
|
100
|
+
ctx: unknown,
|
|
101
|
+
logger?: VisionAttachmentAdmissionLogger,
|
|
102
|
+
options?: VisionAttachmentAdmissionOptions,
|
|
103
|
+
): VisionAttachmentAdmissionResult
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Service reads this helper performs on the given context.
|
|
107
|
+
*
|
|
108
|
+
* `llm` is read through the non-owning `get()` probe when the context provides
|
|
109
|
+
* one; a context without it must not throw on an unresolved `llm` property.
|
|
110
|
+
*/
|
|
111
|
+
export interface HostOwnershipContext {
|
|
112
|
+
readonly get?: (name: string) => unknown
|
|
113
|
+
readonly llm?: unknown
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function protectHostProviderOwnership<Context extends HostOwnershipContext>(
|
|
117
|
+
ctx: Context,
|
|
118
|
+
): Context
|
|
119
|
+
|
|
120
|
+
export interface HostSettingsCompatibilityOptions {
|
|
121
|
+
readonly namespace: string
|
|
122
|
+
readonly Config: unknown
|
|
123
|
+
readonly installSettingsSection?: (
|
|
124
|
+
ctx: unknown,
|
|
125
|
+
namespace: unknown,
|
|
126
|
+
Config: unknown,
|
|
127
|
+
entryConfig: Record<string, unknown>,
|
|
128
|
+
hooks: unknown,
|
|
129
|
+
) => unknown
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** Context shape required to register and own the compatibility settings section. */
|
|
133
|
+
export interface HostSettingsCompatibilityContext {
|
|
134
|
+
readonly inject?: (...args: any[]) => unknown
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Present DVR's settings face on Hosts whose Settings service predates the
|
|
139
|
+
* current contract.
|
|
140
|
+
*
|
|
141
|
+
* @throws {TypeError} when `ctx.inject` is not callable, before any settings
|
|
142
|
+
* section or watcher is registered.
|
|
143
|
+
*/
|
|
144
|
+
export function installHostSettingsCompatibility<Context extends HostSettingsCompatibilityContext>(
|
|
145
|
+
ctx: Context,
|
|
146
|
+
entryConfig: Record<string, unknown>,
|
|
147
|
+
options: HostSettingsCompatibilityOptions,
|
|
148
|
+
): Context
|