dsh-vision-router 2.3.0 → 3.0.1
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 +18 -3
- package/README.zh.md +16 -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 +447 -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 +13 -3
- package/docs/releases/v3.0.0.md +85 -0
- package/docs/releases/v3.0.1.md +77 -0
- package/entry.js +1 -1
- package/index.js +110 -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 +11 -144
- 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 +33 -18
- 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 +30 -10
- 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 +16 -10
- 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 +55 -6
- 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 +42 -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-model-visibility-boundary-main.js +15 -16
- 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,61 +1,51 @@
|
|
|
1
1
|
function pairIdentity(pair) {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
const value = pair !== null && typeof pair === 'object'
|
|
3
|
+
? pair
|
|
4
|
+
: {};
|
|
5
|
+
const provider = typeof value.provider === 'string' ? value.provider.trim() : '';
|
|
6
|
+
const model = typeof value.model === 'string' ? value.model.trim() : '';
|
|
7
|
+
return provider !== '' && model !== '' ? `${provider}\u0000${model}` : undefined;
|
|
5
8
|
}
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Apply an explicit P1 execution order to a base pair list without changing
|
|
9
|
-
* the base set of executable routes.
|
|
10
|
-
*
|
|
11
|
-
* This is intentionally a stable reorder, not replacement:
|
|
12
|
-
* - a scoped pair not present in `basePairs` is ignored (cannot invent a route);
|
|
13
|
-
* - every base pair omitted by the planner is appended in its original order
|
|
14
|
-
* (cannot delete configured/local/discovered fallback);
|
|
15
|
-
* - duplicate base/scoped identities collapse to the first base occurrence;
|
|
16
|
-
* - no scoped order means byte-for-shape legacy behavior: a detached copy of
|
|
17
|
-
* the base list in the same order.
|
|
18
|
-
*
|
|
19
|
-
* The caller remains responsible for constructing `basePairs` with its normal
|
|
20
|
-
* adapter/HTTP/local availability rules. This helper owns ordering only.
|
|
21
|
-
*/
|
|
22
9
|
export function applyVisionExecutionOrder(basePairs, scopedOrder) {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
10
|
+
const base = Array.isArray(basePairs)
|
|
11
|
+
? basePairs.filter((pair) => pairIdentity(pair) !== undefined)
|
|
12
|
+
: [];
|
|
13
|
+
const baseById = new Map();
|
|
14
|
+
for (const pair of base) {
|
|
15
|
+
const id = pairIdentity(pair);
|
|
16
|
+
if (id !== undefined && !baseById.has(id))
|
|
17
|
+
baseById.set(id, pair);
|
|
18
|
+
}
|
|
19
|
+
if (!Array.isArray(scopedOrder) || scopedOrder.length === 0) {
|
|
20
|
+
return [...baseById.values()];
|
|
21
|
+
}
|
|
22
|
+
const out = [];
|
|
23
|
+
const seen = new Set();
|
|
24
|
+
const addBasePair = (pair) => {
|
|
25
|
+
const id = pairIdentity(pair);
|
|
26
|
+
if (id === undefined || seen.has(id))
|
|
27
|
+
return;
|
|
28
|
+
seen.add(id);
|
|
29
|
+
out.push(pair);
|
|
30
|
+
};
|
|
31
|
+
for (const requested of scopedOrder) {
|
|
32
|
+
const id = pairIdentity(requested);
|
|
33
|
+
if (id === undefined)
|
|
34
|
+
continue;
|
|
35
|
+
const pair = baseById.get(id);
|
|
36
|
+
if (pair !== undefined)
|
|
37
|
+
addBasePair(pair);
|
|
38
|
+
}
|
|
39
|
+
for (const pair of baseById.values())
|
|
40
|
+
addBasePair(pair);
|
|
41
|
+
return out;
|
|
53
42
|
}
|
|
54
|
-
|
|
55
43
|
export function sameVisionPairOrder(left, right) {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
44
|
+
if (!Array.isArray(left) || !Array.isArray(right) || left.length !== right.length)
|
|
45
|
+
return false;
|
|
46
|
+
for (let index = 0; index < left.length; index += 1) {
|
|
47
|
+
if (pairIdentity(left[index]) !== pairIdentity(right[index]))
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
return true;
|
|
61
51
|
}
|
|
@@ -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
|
}
|
|
@@ -419,6 +419,7 @@ export const VISION_MODEL_VISIBILITY_PRELUDE = String.raw`(function(){
|
|
|
419
419
|
function create() {
|
|
420
420
|
var result = originalCreate.apply(this, arguments);
|
|
421
421
|
patchLoader(loader);
|
|
422
|
+
if (result && result !== loader) patchLoader(result);
|
|
422
423
|
return result;
|
|
423
424
|
}
|
|
424
425
|
try { Object.defineProperty(create, '__visionRouterModelVisibility', { value: true }); } catch (_) {}
|
|
@@ -427,31 +428,29 @@ export const VISION_MODEL_VISIBILITY_PRELUDE = String.raw`(function(){
|
|
|
427
428
|
}
|
|
428
429
|
|
|
429
430
|
function install() {
|
|
430
|
-
if (window.__ModuleLoader__) {
|
|
431
|
-
patchLoader(window.__ModuleLoader__);
|
|
432
|
-
return;
|
|
433
|
-
}
|
|
434
431
|
var descriptor = Object.getOwnPropertyDescriptor(window, '__ModuleLoader__');
|
|
432
|
+
var current;
|
|
433
|
+
try { current = window.__ModuleLoader__; } catch (_) { current = undefined; }
|
|
434
|
+
if (current) patchLoader(current);
|
|
435
435
|
if (descriptor && descriptor.configurable === false) return;
|
|
436
|
-
var
|
|
436
|
+
var previousGet = descriptor && descriptor.get;
|
|
437
|
+
var previousSet = descriptor && descriptor.set;
|
|
438
|
+
var stored = descriptor && Object.prototype.hasOwnProperty.call(descriptor, 'value')
|
|
439
|
+
? descriptor.value
|
|
440
|
+
: current;
|
|
437
441
|
Object.defineProperty(window, '__ModuleLoader__', {
|
|
438
442
|
configurable: true,
|
|
439
|
-
enumerable:
|
|
440
|
-
get: function(){ return stored; },
|
|
443
|
+
enumerable: !descriptor || descriptor.enumerable !== false,
|
|
444
|
+
get: function(){ return previousGet ? previousGet.call(window) : stored; },
|
|
441
445
|
set: function(value) {
|
|
442
|
-
stored = value;
|
|
443
|
-
patchLoader(value);
|
|
444
|
-
Object.defineProperty(window, '__ModuleLoader__', {
|
|
445
|
-
configurable: true,
|
|
446
|
-
enumerable: true,
|
|
447
|
-
writable: true,
|
|
448
|
-
value: stored
|
|
449
|
-
});
|
|
446
|
+
if (previousSet) previousSet.call(window, value); else stored = value;
|
|
447
|
+
try { patchLoader(previousGet ? previousGet.call(window) : value); } catch (_) {}
|
|
450
448
|
}
|
|
451
449
|
});
|
|
450
|
+
if (stored) patchLoader(stored);
|
|
452
451
|
}
|
|
453
452
|
|
|
454
|
-
install();
|
|
453
|
+
try { install(); } catch (_) {}
|
|
455
454
|
})();`
|
|
456
455
|
|
|
457
456
|
export function injectVisionModelVisibilityBoundary(html) {
|
|
@@ -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
|
}
|