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,21 +1,21 @@
|
|
|
1
|
-
import { AsyncLocalStorage } from 'node:async_hooks'
|
|
2
|
-
|
|
3
|
-
const executionOrderScope = new AsyncLocalStorage()
|
|
4
|
-
|
|
1
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
2
|
+
const executionOrderScope = new AsyncLocalStorage();
|
|
5
3
|
function normalizedPair(pair) {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
4
|
+
const value = pair !== null && typeof pair === 'object'
|
|
5
|
+
? pair
|
|
6
|
+
: {};
|
|
7
|
+
const provider = typeof value.provider === 'string' ? value.provider.trim() : '';
|
|
8
|
+
const model = typeof value.model === 'string' ? value.model.trim() : '';
|
|
9
|
+
if (provider === '' || model === '') {
|
|
10
|
+
throw new TypeError('vision execution order entries require non-empty provider and model');
|
|
11
|
+
}
|
|
12
|
+
return Object.freeze({ provider, model });
|
|
12
13
|
}
|
|
13
|
-
|
|
14
14
|
function normalizedOrder(order) {
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
if (!Array.isArray(order))
|
|
16
|
+
throw new TypeError('vision execution order must be an array');
|
|
17
|
+
return Object.freeze(order.map(normalizedPair));
|
|
17
18
|
}
|
|
18
|
-
|
|
19
19
|
/**
|
|
20
20
|
* Run one Router-owned visual execution with an explicit provider/model order.
|
|
21
21
|
*
|
|
@@ -24,15 +24,15 @@ function normalizedOrder(order) {
|
|
|
24
24
|
* Host services, or mutable caller objects.
|
|
25
25
|
*/
|
|
26
26
|
export function withVisionExecutionOrder(order, fn) {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
if (typeof fn !== 'function')
|
|
28
|
+
throw new TypeError('withVisionExecutionOrder requires a function');
|
|
29
|
+
const scopedOrder = normalizedOrder(order);
|
|
30
|
+
return executionOrderScope.run({ order: scopedOrder }, fn);
|
|
30
31
|
}
|
|
31
|
-
|
|
32
32
|
/**
|
|
33
33
|
* Current Router-owned visual order for this async call chain, or undefined
|
|
34
34
|
* outside an explicit visual execution scope.
|
|
35
35
|
*/
|
|
36
36
|
export function currentVisionExecutionOrder() {
|
|
37
|
-
|
|
37
|
+
return executionOrderScope.getStore()?.order;
|
|
38
38
|
}
|
|
@@ -517,15 +517,20 @@ export function contextWithVisionExecutionPolicy(ctx, options = {}) {
|
|
|
517
517
|
},
|
|
518
518
|
})
|
|
519
519
|
wrappedContexts.set(ctx, wrapped)
|
|
520
|
+
let lifecycleOwned = false
|
|
520
521
|
try {
|
|
521
|
-
ctx.effect
|
|
522
|
-
(
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
522
|
+
if (typeof ctx.effect === 'function') {
|
|
523
|
+
ctx.effect(
|
|
524
|
+
() => () => {
|
|
525
|
+
if (wrappedContexts.get(ctx) === wrapped) wrappedContexts.delete(ctx)
|
|
526
|
+
},
|
|
527
|
+
'vision-router: strict vision execution policy context',
|
|
528
|
+
)
|
|
529
|
+
lifecycleOwned = true
|
|
530
|
+
}
|
|
527
531
|
} catch {
|
|
528
|
-
|
|
532
|
+
// The current caller may still use this private view.
|
|
529
533
|
}
|
|
534
|
+
if (!lifecycleOwned && wrappedContexts.get(ctx) === wrapped) wrappedContexts.delete(ctx)
|
|
530
535
|
return wrapped
|
|
531
536
|
}
|
|
@@ -222,16 +222,23 @@ export function installVisionModelRegistry(ctx, liveDiscovery, options = {}) {
|
|
|
222
222
|
if (!liveDiscovery || typeof liveDiscovery.snapshot !== 'function') return liveDiscovery
|
|
223
223
|
if (liveDiscovery.snapshot.__visionRouterRegistry === true) return liveDiscovery
|
|
224
224
|
|
|
225
|
-
const originalSnapshot = liveDiscovery.snapshot
|
|
225
|
+
const originalSnapshot = liveDiscovery.snapshot
|
|
226
226
|
const originalHasModel = typeof liveDiscovery.hasModel === 'function'
|
|
227
|
-
? liveDiscovery.hasModel
|
|
228
|
-
:
|
|
227
|
+
? liveDiscovery.hasModel
|
|
228
|
+
: undefined
|
|
229
229
|
const originalEvidenceSource = typeof liveDiscovery.evidenceSource === 'function'
|
|
230
|
-
? liveDiscovery.evidenceSource
|
|
230
|
+
? liveDiscovery.evidenceSource
|
|
231
231
|
: undefined
|
|
232
|
+
const callSnapshot = (request) => Reflect.apply(originalSnapshot, liveDiscovery, [request])
|
|
233
|
+
const callHasModel = (provider, model) => originalHasModel === undefined
|
|
234
|
+
? false
|
|
235
|
+
: Reflect.apply(originalHasModel, liveDiscovery, [provider, model])
|
|
236
|
+
const callEvidenceSource = (provider, model) => originalEvidenceSource === undefined
|
|
237
|
+
? undefined
|
|
238
|
+
: Reflect.apply(originalEvidenceSource, liveDiscovery, [provider, model])
|
|
232
239
|
|
|
233
240
|
const wrappedSnapshot = async (request) => decorateVisionModelSnapshot(
|
|
234
|
-
await
|
|
241
|
+
await callSnapshot(request),
|
|
235
242
|
{ ctx, config: options.config ?? {} },
|
|
236
243
|
)
|
|
237
244
|
// Diagnostics-only provenance. This must never become the admission oracle:
|
|
@@ -239,15 +246,15 @@ export function installVisionModelRegistry(ctx, liveDiscovery, options = {}) {
|
|
|
239
246
|
// endpoint evidence OR exact endpoint-scoped trusted hint).
|
|
240
247
|
const wrappedEvidenceSource = (provider, model) => {
|
|
241
248
|
if (isProviderActive(ctx, provider) && hasTrustedVisionHint(ctx, provider, model)) return 'known'
|
|
242
|
-
if (
|
|
249
|
+
if (callHasModel(provider, model)) return 'live'
|
|
243
250
|
try {
|
|
244
|
-
return
|
|
251
|
+
return callEvidenceSource(provider, model)
|
|
245
252
|
} catch {
|
|
246
253
|
return undefined
|
|
247
254
|
}
|
|
248
255
|
}
|
|
249
256
|
const wrappedHasModel = (provider, model) => (
|
|
250
|
-
|
|
257
|
+
callHasModel(provider, model) ||
|
|
251
258
|
(isProviderActive(ctx, provider) && hasTrustedVisionHint(ctx, provider, model))
|
|
252
259
|
)
|
|
253
260
|
|
|
@@ -258,17 +265,23 @@ export function installVisionModelRegistry(ctx, liveDiscovery, options = {}) {
|
|
|
258
265
|
liveDiscovery.hasModel = wrappedHasModel
|
|
259
266
|
liveDiscovery.evidenceSource = wrappedEvidenceSource
|
|
260
267
|
|
|
268
|
+
const restore = () => {
|
|
269
|
+
if (liveDiscovery.snapshot === wrappedSnapshot) liveDiscovery.snapshot = originalSnapshot
|
|
270
|
+
if (liveDiscovery.hasModel === wrappedHasModel) {
|
|
271
|
+
if (originalHasModel === undefined) delete liveDiscovery.hasModel
|
|
272
|
+
else liveDiscovery.hasModel = originalHasModel
|
|
273
|
+
}
|
|
274
|
+
if (liveDiscovery.evidenceSource === wrappedEvidenceSource) {
|
|
275
|
+
if (originalEvidenceSource === undefined) delete liveDiscovery.evidenceSource
|
|
276
|
+
else liveDiscovery.evidenceSource = originalEvidenceSource
|
|
277
|
+
}
|
|
278
|
+
}
|
|
261
279
|
try {
|
|
262
|
-
ctx?.effect?.(() =>
|
|
263
|
-
if (liveDiscovery.snapshot === wrappedSnapshot) liveDiscovery.snapshot = originalSnapshot
|
|
264
|
-
if (liveDiscovery.hasModel === wrappedHasModel) liveDiscovery.hasModel = originalHasModel
|
|
265
|
-
if (liveDiscovery.evidenceSource === wrappedEvidenceSource) {
|
|
266
|
-
if (originalEvidenceSource === undefined) delete liveDiscovery.evidenceSource
|
|
267
|
-
else liveDiscovery.evidenceSource = originalEvidenceSource
|
|
268
|
-
}
|
|
269
|
-
}, 'vision-router: private model registry sources')
|
|
280
|
+
ctx?.effect?.(() => restore, 'vision-router: private model registry sources')
|
|
270
281
|
} catch {
|
|
271
|
-
// The manager
|
|
282
|
+
// The manager can outlive this failed composition attempt. Do not leave
|
|
283
|
+
// generation-specific method projections installed without an owner.
|
|
284
|
+
restore()
|
|
272
285
|
}
|
|
273
286
|
return liveDiscovery
|
|
274
287
|
}
|
|
@@ -1,77 +1,76 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
readResponseTextBounded,
|
|
6
|
-
} from './http-body-limit.js'
|
|
7
|
-
import { effectiveProxyUrlForUndici } from './proxy-url-compat.js'
|
|
8
|
-
import { createProxyDispatcherPool } from './proxy-dispatcher-pool.js'
|
|
9
|
-
import {
|
|
10
|
-
createPerHopProxyDispatcher,
|
|
11
|
-
proxyDispatcherLike,
|
|
12
|
-
proxyHostMatchesAny,
|
|
13
|
-
visionProxyOverrideConfigured,
|
|
14
|
-
} from './proxy-routing.js'
|
|
15
|
-
|
|
1
|
+
import { ERROR_RESPONSE_MAX_BYTES, MODEL_RESPONSE_MAX_BYTES, readResponseJsonBounded, readResponseTextBounded, } from './http-body-limit.js';
|
|
2
|
+
import { effectiveProxyUrlForUndici } from './proxy-url-compat.js';
|
|
3
|
+
import { createProxyDispatcherPool } from './proxy-dispatcher-pool.js';
|
|
4
|
+
import { createPerHopProxyDispatcher, proxyDispatcherLike, proxyHostMatchesAny, visionProxyOverrideConfigured, } from './proxy-routing.js';
|
|
16
5
|
const DEFAULT_PROXY_HOSTS = Object.freeze([
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
])
|
|
27
|
-
|
|
28
|
-
// Capture before core.apply installs the legacy process-wide fetch patch. The
|
|
29
|
-
// Router-owned transport uses this original function explicitly, so direct
|
|
30
|
-
// provider calls are not coupled to later globalThis.fetch mutation.
|
|
31
|
-
const moduleFetch =
|
|
32
|
-
typeof globalThis.fetch === 'function'
|
|
6
|
+
'api.openrouter.ai',
|
|
7
|
+
'openrouter.ai',
|
|
8
|
+
'api.openai.com',
|
|
9
|
+
'api.anthropic.com',
|
|
10
|
+
'api.groq.com',
|
|
11
|
+
'api.mistral.ai',
|
|
12
|
+
'api.together.xyz',
|
|
13
|
+
'generativelanguage.googleapis.com',
|
|
14
|
+
'api.x.ai',
|
|
15
|
+
]);
|
|
16
|
+
const capturedHostFetch = typeof globalThis.fetch === 'function'
|
|
33
17
|
? globalThis.fetch.bind(globalThis)
|
|
34
|
-
: undefined
|
|
35
|
-
|
|
18
|
+
: undefined;
|
|
19
|
+
const moduleFetch = capturedHostFetch
|
|
20
|
+
? (input, init) => capturedHostFetch(input, init)
|
|
21
|
+
: undefined;
|
|
22
|
+
function objectRecord(value) {
|
|
23
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value)
|
|
24
|
+
? value
|
|
25
|
+
: undefined;
|
|
26
|
+
}
|
|
36
27
|
function live(value) {
|
|
37
|
-
|
|
28
|
+
return typeof value === 'function' ? value() : value;
|
|
38
29
|
}
|
|
39
|
-
|
|
40
30
|
function asObject(value) {
|
|
41
|
-
|
|
31
|
+
return objectRecord(value) ?? {};
|
|
42
32
|
}
|
|
43
|
-
|
|
44
33
|
function asUrl(input) {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
34
|
+
try {
|
|
35
|
+
if (input instanceof URL)
|
|
36
|
+
return input;
|
|
37
|
+
if (typeof input === 'string')
|
|
38
|
+
return new URL(input);
|
|
39
|
+
const raw = objectRecord(input)?.url;
|
|
40
|
+
return typeof raw === 'string' ? new URL(raw) : undefined;
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return undefined;
|
|
44
|
+
}
|
|
50
45
|
}
|
|
51
|
-
|
|
52
46
|
function proxySettings(config) {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
47
|
+
const value = asObject(live(config));
|
|
48
|
+
const rawProxy = value.proxy;
|
|
49
|
+
const proxy = visionProxyOverrideConfigured(value) && typeof rawProxy === 'string'
|
|
50
|
+
? rawProxy.trim()
|
|
51
|
+
: undefined;
|
|
52
|
+
const proxyHosts = Array.isArray(value.proxyHosts)
|
|
53
|
+
? value.proxyHosts
|
|
54
|
+
.filter((host) => typeof host === 'string' && host.trim() !== '')
|
|
55
|
+
.map((host) => host.trim())
|
|
56
|
+
: [...DEFAULT_PROXY_HOSTS];
|
|
57
|
+
return { proxy, proxyHosts };
|
|
59
58
|
}
|
|
60
|
-
|
|
61
59
|
function credentialService(ctx) {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
60
|
+
try {
|
|
61
|
+
const service = ctx?.get?.('credentials');
|
|
62
|
+
return service !== null && (typeof service === 'object' || typeof service === 'function')
|
|
63
|
+
? service
|
|
64
|
+
: undefined;
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
return undefined;
|
|
68
|
+
}
|
|
67
69
|
}
|
|
68
|
-
|
|
69
70
|
function envCredential(ref) {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
return typeof value === 'string' && value !== '' ? value : undefined
|
|
71
|
+
const value = process.env[ref];
|
|
72
|
+
return typeof value === 'string' && value !== '' ? value : undefined;
|
|
73
73
|
}
|
|
74
|
-
|
|
75
74
|
/**
|
|
76
75
|
* Router-owned provider transport.
|
|
77
76
|
*
|
|
@@ -82,94 +81,85 @@ function envCredential(ref) {
|
|
|
82
81
|
* policy remains authoritative. It intentionally does not cover update checks,
|
|
83
82
|
* GitHub/npm maintenance traffic or Host-owned LLM adapter requests.
|
|
84
83
|
*/
|
|
85
|
-
export function createVisionProviderTransport({
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
fetchImpl = moduleFetch,
|
|
89
|
-
importUndici = () => import('undici'),
|
|
90
|
-
} = {}) {
|
|
91
|
-
if (typeof fetchImpl !== 'function') {
|
|
92
|
-
throw new TypeError('vision provider transport requires fetch')
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
let disposed = false
|
|
96
|
-
let disposePromise
|
|
97
|
-
const dispatcherPool = createProxyDispatcherPool({
|
|
98
|
-
importUndici,
|
|
99
|
-
label: 'vision provider transport',
|
|
100
|
-
})
|
|
101
|
-
|
|
102
|
-
const fetchProvider = async (input, init = {}, context = {}) => {
|
|
103
|
-
if (disposed) return fetchImpl(input, init)
|
|
104
|
-
const url = asUrl(input)
|
|
105
|
-
const { proxy, proxyHosts } = proxySettings(config)
|
|
106
|
-
const effectiveProxyUrl = proxy ? effectiveProxyUrlForUndici(proxy) : undefined
|
|
107
|
-
dispatcherPool.reconcile(effectiveProxyUrl)
|
|
108
|
-
if (!url || !effectiveProxyUrl || context.allowProxy === false || !proxyHostMatchesAny(url.hostname, proxyHosts)) {
|
|
109
|
-
return fetchImpl(input, init)
|
|
110
|
-
}
|
|
111
|
-
const lease = await dispatcherPool.acquire(effectiveProxyUrl)
|
|
112
|
-
try {
|
|
113
|
-
const fallbackDispatcher = proxyDispatcherLike(init?.dispatcher)
|
|
114
|
-
? init.dispatcher
|
|
115
|
-
: lease.getGlobalDispatcher()
|
|
116
|
-
const dispatcher = createPerHopProxyDispatcher(lease.dispatcher, fallbackDispatcher, proxyHosts)
|
|
117
|
-
return await fetchImpl(input, { ...init, dispatcher })
|
|
118
|
-
} finally {
|
|
119
|
-
lease.release()
|
|
84
|
+
export function createVisionProviderTransport({ ctx, config = {}, fetchImpl = moduleFetch, importUndici = () => import('undici'), } = {}) {
|
|
85
|
+
if (typeof fetchImpl !== 'function') {
|
|
86
|
+
throw new TypeError('vision provider transport requires fetch');
|
|
120
87
|
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
88
|
+
let disposed = false;
|
|
89
|
+
let disposePromise;
|
|
90
|
+
const dispatcherPool = createProxyDispatcherPool({
|
|
91
|
+
importUndici,
|
|
92
|
+
label: 'vision provider transport',
|
|
93
|
+
});
|
|
94
|
+
const fetchProvider = async (input, init = {}, context = {}) => {
|
|
95
|
+
if (disposed)
|
|
96
|
+
return fetchImpl(input, init);
|
|
97
|
+
const url = asUrl(input);
|
|
98
|
+
const { proxy, proxyHosts } = proxySettings(config);
|
|
99
|
+
const effectiveProxyUrl = proxy ? effectiveProxyUrlForUndici(proxy) : undefined;
|
|
100
|
+
dispatcherPool.reconcile(effectiveProxyUrl);
|
|
101
|
+
if (!url
|
|
102
|
+
|| !effectiveProxyUrl
|
|
103
|
+
|| context.allowProxy === false
|
|
104
|
+
|| !proxyHostMatchesAny(url.hostname, proxyHosts)) {
|
|
105
|
+
return fetchImpl(input, init);
|
|
106
|
+
}
|
|
107
|
+
const lease = await dispatcherPool.acquire(effectiveProxyUrl);
|
|
139
108
|
try {
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
109
|
+
const fallbackDispatcher = proxyDispatcherLike(init.dispatcher)
|
|
110
|
+
? init.dispatcher
|
|
111
|
+
: lease.getGlobalDispatcher();
|
|
112
|
+
const dispatcher = createPerHopProxyDispatcher(lease.dispatcher, fallbackDispatcher, proxyHosts);
|
|
113
|
+
return await fetchImpl(input, { ...init, dispatcher });
|
|
114
|
+
}
|
|
115
|
+
finally {
|
|
116
|
+
lease.release();
|
|
144
117
|
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
118
|
+
};
|
|
119
|
+
const dispose = () => {
|
|
120
|
+
if (disposePromise)
|
|
121
|
+
return disposePromise;
|
|
122
|
+
disposed = true;
|
|
123
|
+
disposePromise = dispatcherPool.dispose();
|
|
124
|
+
return disposePromise;
|
|
125
|
+
};
|
|
126
|
+
return Object.freeze({
|
|
127
|
+
fetch: fetchProvider,
|
|
128
|
+
dispose,
|
|
129
|
+
async resolveCredential(ref) {
|
|
130
|
+
const name = typeof ref === 'string' ? ref.trim() : '';
|
|
131
|
+
if (!name)
|
|
132
|
+
return undefined;
|
|
133
|
+
const credentials = credentialService(ctx);
|
|
134
|
+
try {
|
|
135
|
+
const resolve = credentials?.resolve;
|
|
136
|
+
if (typeof resolve === 'function') {
|
|
137
|
+
const hit = await Reflect.apply(resolve, credentials, [name]);
|
|
138
|
+
const value = objectRecord(hit)?.value;
|
|
139
|
+
if (typeof value === 'string' && value !== '')
|
|
140
|
+
return value;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
// A missing/partial/throwing Host credential service must not suppress
|
|
145
|
+
// the supported environment-variable compatibility fallback.
|
|
146
|
+
}
|
|
147
|
+
return envCredential(name);
|
|
148
|
+
},
|
|
149
|
+
readErrorText(response, options = {}) {
|
|
150
|
+
return readResponseTextBounded(response, options.maxBytes ?? ERROR_RESPONSE_MAX_BYTES, { label: options.label ?? 'vision provider error response' });
|
|
151
|
+
},
|
|
152
|
+
readModelJson(response, options = {}) {
|
|
153
|
+
return readResponseJsonBounded(response, options.maxBytes ?? MODEL_RESPONSE_MAX_BYTES, { label: options.label ?? 'vision provider response' });
|
|
154
|
+
},
|
|
155
|
+
proxyDecision(input) {
|
|
156
|
+
const url = asUrl(input);
|
|
157
|
+
const { proxy, proxyHosts } = proxySettings(config);
|
|
158
|
+
return Object.freeze({
|
|
159
|
+
proxied: Boolean(url && proxy && proxyHostMatchesAny(url.hostname, proxyHosts)),
|
|
160
|
+
proxy,
|
|
161
|
+
hostname: url?.hostname,
|
|
162
|
+
});
|
|
163
|
+
},
|
|
164
|
+
});
|
|
175
165
|
}
|
|
@@ -1,46 +1,42 @@
|
|
|
1
|
-
import { normalizeVisionRoutingMode } from './vision-routing-product.js'
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
const BACKGROUND_MEASUREMENT_SET = new Set(BACKGROUND_MEASUREMENT_MODES)
|
|
6
|
-
const MANUAL_MEASUREMENT_GRANT = Symbol('vision-router-manual-measurement-grant')
|
|
7
|
-
|
|
1
|
+
import { normalizeVisionRoutingMode, } from './vision-routing-product.js';
|
|
2
|
+
export const BACKGROUND_MEASUREMENT_MODES = Object.freeze(['off', 'local-free', 'all']);
|
|
3
|
+
const BACKGROUND_MEASUREMENT_SET = new Set(BACKGROUND_MEASUREMENT_MODES);
|
|
4
|
+
const MANUAL_MEASUREMENT_GRANT = Symbol('vision-router-manual-measurement-grant');
|
|
8
5
|
function plainObject(value) {
|
|
9
|
-
|
|
6
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
10
7
|
}
|
|
11
|
-
|
|
12
8
|
export function normalizeBackgroundMeasurementAuthority(value, fallback = 'off') {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
:
|
|
9
|
+
const normalizedFallback = typeof fallback === 'string' && BACKGROUND_MEASUREMENT_SET.has(fallback)
|
|
10
|
+
? fallback
|
|
11
|
+
: 'off';
|
|
12
|
+
const candidate = typeof value === 'string' ? value.trim() : '';
|
|
13
|
+
return BACKGROUND_MEASUREMENT_SET.has(candidate)
|
|
14
|
+
? candidate
|
|
15
|
+
: normalizedFallback;
|
|
17
16
|
}
|
|
18
|
-
|
|
19
17
|
/**
|
|
20
18
|
* Resolve only user-delegated v2 authority. Evidence, preference, credentials,
|
|
21
19
|
* capability gaps, or internal shadow switches must never widen this result.
|
|
22
20
|
*/
|
|
23
21
|
export function resolveVisionRoutingAuthority(config = {}) {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
})
|
|
22
|
+
const source = plainObject(config) ? config : {};
|
|
23
|
+
const execution = normalizeVisionRoutingMode(source.routingMode);
|
|
24
|
+
const backgroundMeasurement = normalizeBackgroundMeasurementAuthority(source.backgroundBenchmarking);
|
|
25
|
+
const autoSelectionAuthorized = execution === 'auto';
|
|
26
|
+
const backgroundMeasurementAuthorized = backgroundMeasurement !== 'off';
|
|
27
|
+
return Object.freeze({
|
|
28
|
+
execution,
|
|
29
|
+
autoSelectionAuthorized,
|
|
30
|
+
backgroundMeasurement,
|
|
31
|
+
backgroundMeasurementAuthorized,
|
|
32
|
+
// Current background profiler is useful only for Auto preparation, but the
|
|
33
|
+
// standing measurement grant remains independent from execution authority.
|
|
34
|
+
backgroundMeasurementActive: autoSelectionAuthorized && backgroundMeasurementAuthorized,
|
|
35
|
+
ephemeralRuntimeObservation: autoSelectionAuthorized,
|
|
36
|
+
// No v2 setting currently grants durable user-specific behavioral learning.
|
|
37
|
+
persistentLearning: false,
|
|
38
|
+
});
|
|
42
39
|
}
|
|
43
|
-
|
|
44
40
|
/**
|
|
45
41
|
* A manual measurement grant is intentionally opaque and process-local. The
|
|
46
42
|
* local HTTP action creates one only after an explicit user POST. Requiring the
|
|
@@ -48,23 +44,26 @@ export function resolveVisionRoutingAuthority(config = {}) {
|
|
|
48
44
|
* inheriting "manual" semantics by convention.
|
|
49
45
|
*/
|
|
50
46
|
export function grantManualMeasurementFromUserAction(source = 'local-ui') {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
47
|
+
if (source !== 'local-ui') {
|
|
48
|
+
throw new TypeError('manual measurement authority source must be local-ui');
|
|
49
|
+
}
|
|
50
|
+
const grant = {
|
|
51
|
+
kind: 'manual-measurement',
|
|
52
|
+
source,
|
|
53
|
+
[MANUAL_MEASUREMENT_GRANT]: true,
|
|
54
|
+
};
|
|
55
|
+
return Object.freeze(grant);
|
|
59
56
|
}
|
|
60
|
-
|
|
61
57
|
export function hasManualMeasurementAuthority(value) {
|
|
62
|
-
|
|
58
|
+
if (!value || typeof value !== 'object')
|
|
59
|
+
return false;
|
|
60
|
+
const candidate = value;
|
|
61
|
+
return candidate[MANUAL_MEASUREMENT_GRANT] === true && candidate.kind === 'manual-measurement';
|
|
63
62
|
}
|
|
64
|
-
|
|
65
63
|
export function assertManualMeasurementAuthority(value) {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
64
|
+
if (hasManualMeasurementAuthority(value))
|
|
65
|
+
return value;
|
|
66
|
+
const error = new Error('explicit manual measurement authority is required');
|
|
67
|
+
error.code = 'CAPABILITY_BENCHMARK_AUTHORITY_REQUIRED';
|
|
68
|
+
throw error;
|
|
70
69
|
}
|