dsh-vision-router 2.2.8 → 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 +16 -7
- package/README.zh.md +16 -7
- package/docs/architecture/2.3.0-compat-retirement.md +147 -0
- package/docs/architecture/2.3.0-execution-plan.md +410 -0
- package/docs/architecture/2.3.0-final-audit.md +279 -0
- package/docs/architecture/2.3.0-reopened-plan.md +720 -0
- 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/compat-inventory.md +26 -34
- package/docs/architecture/dsh-compatibility-matrix.md +65 -54
- package/docs/architecture/dsh-support-window.md +30 -22
- package/docs/architecture/final-closure-audit.md +2 -0
- package/docs/releases/v2.3.0.md +53 -0
- package/docs/releases/v3.0.0.md +85 -0
- package/entry.js +9 -57
- package/index.js +268 -761
- package/lib/agent-request-route-authority.js +1 -20
- package/lib/catalog-corrections.js +1 -2
- package/lib/client-maintenance-actions.js +145 -0
- package/lib/client-presentation-boundary-main.js +75 -17
- package/lib/client-presentation-boundary.js +53 -28
- package/lib/client.js +246 -144
- package/lib/core-apply-lifecycle.js +41 -0
- package/lib/core-cache.js +87 -0
- package/lib/core-primitives.js +91 -278
- 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/desktop-screenshot-tool.js +262 -0
- package/lib/doctor-cli.js +0 -0
- package/lib/doctor-runtime.js +4 -1
- package/lib/dsh-contract-compat.js +37 -256
- package/lib/dsh-host-capabilities.js +3 -9
- package/lib/dsh-settings-017-compat.js +17 -10
- package/lib/dsh-support-window.js +3 -3
- 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/http-compat.js +1 -2
- 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-remote-settings-permission.js +33 -7
- package/lib/local-vision-stabilizer.js +31 -17
- package/lib/maintenance-routes.js +134 -0
- 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 +36 -0
- package/lib/public-entry.d.ts +148 -0
- package/lib/public-entry.js +19 -11
- package/lib/remote-settings-bridge.js +11 -0
- package/lib/remote-settings-risk-client-boundary.js +155 -0
- package/lib/remote-settings-risk-confirmation.js +17 -105
- package/lib/replay-delegation.js +12 -7
- package/lib/replay-envelope-v2-compat.js +12 -7
- package/lib/runtime-composition.js +127 -34
- package/lib/runtime-i18n-boundary.js +10 -11
- 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-message-source-compat.js +7 -3
- 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 +179 -116
- 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/settings-client-loader-lifecycle.js +129 -0
- package/lib/sharp-runtime.js +0 -7
- package/lib/structured-flow-hardening.js +25 -6
- 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 +19 -2
- 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-service.js +1 -0
- 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 +246 -0
- package/lib/vision-evidence-guidance.js +3 -1
- 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 -176
- 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-toggle-root-hardening.js +22 -5
- package/lib/vision-tool-runtime-boundary.js +104 -31
- package/lib/web/dsh-settings-017-root-transport.js +27 -12
- package/lib/web/remote-settings-client.js +6 -9
- package/lib/web-capability-boundary.js +17 -7
- package/lib/wrapper-directory.js +14 -23
- package/package.json +56 -20
- package/lib/android-attachment-compat.js +0 -244
- package/lib/settings-client-rc8-lifecycle.js +0 -397
|
@@ -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
|
}
|
|
@@ -5,40 +5,37 @@
|
|
|
5
5
|
// A user chooses whether routing is automatic or strictly ordered, then chooses
|
|
6
6
|
// a plain-language preference. The actual execution-changing auto mode is
|
|
7
7
|
// enabled only when the runtime is ready to honor this contract.
|
|
8
|
-
|
|
9
|
-
export const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
const MODE_SET = new Set(VISION_ROUTING_MODES)
|
|
13
|
-
const PREFERENCE_SET = new Set(VISION_ROUTING_PREFERENCES)
|
|
14
|
-
|
|
8
|
+
export const VISION_ROUTING_MODES = Object.freeze(['ordered', 'auto']);
|
|
9
|
+
export const VISION_ROUTING_PREFERENCES = Object.freeze(['balanced', 'quality', 'speed', 'local']);
|
|
10
|
+
const MODE_SET = new Set(VISION_ROUTING_MODES);
|
|
11
|
+
const PREFERENCE_SET = new Set(VISION_ROUTING_PREFERENCES);
|
|
15
12
|
export function normalizeVisionRoutingMode(value, fallback = 'ordered') {
|
|
16
|
-
|
|
17
|
-
|
|
13
|
+
const normalizedFallback = typeof fallback === 'string' && MODE_SET.has(fallback) ? fallback : 'ordered';
|
|
14
|
+
const candidate = typeof value === 'string' ? value.trim() : '';
|
|
15
|
+
return MODE_SET.has(candidate) ? candidate : normalizedFallback;
|
|
18
16
|
}
|
|
19
|
-
|
|
20
17
|
export function normalizeVisionRoutingPreference(value) {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
18
|
+
if (typeof value === 'string') {
|
|
19
|
+
const candidate = value.trim();
|
|
20
|
+
if (PREFERENCE_SET.has(candidate))
|
|
21
|
+
return candidate;
|
|
22
|
+
}
|
|
23
|
+
return 'balanced';
|
|
27
24
|
}
|
|
28
|
-
|
|
29
25
|
export function routingPreferenceToCapabilityStrategy(preference) {
|
|
30
|
-
|
|
31
|
-
|
|
26
|
+
const normalized = normalizeVisionRoutingPreference(preference);
|
|
27
|
+
return normalized === 'local' ? 'privacy' : normalized;
|
|
32
28
|
}
|
|
33
|
-
|
|
34
29
|
export function resolveVisionRoutingProduct(config = {}) {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
30
|
+
const source = config && typeof config === 'object' && !Array.isArray(config)
|
|
31
|
+
? config
|
|
32
|
+
: {};
|
|
33
|
+
const mode = normalizeVisionRoutingMode(source.routingMode);
|
|
34
|
+
const preference = normalizeVisionRoutingPreference(source.routingPreference);
|
|
35
|
+
return {
|
|
36
|
+
mode,
|
|
37
|
+
preference,
|
|
38
|
+
strategy: routingPreferenceToCapabilityStrategy(preference),
|
|
39
|
+
automatic: mode === 'auto',
|
|
40
|
+
};
|
|
44
41
|
}
|
|
@@ -446,7 +446,7 @@ export const VISION_ROUTING_SETTINGS_PRELUDE = String.raw`(function(){
|
|
|
446
446
|
try { state.backgroundTimer && state.backgroundTimer.unref && state.backgroundTimer.unref(); } catch (_) {}
|
|
447
447
|
schedule();
|
|
448
448
|
|
|
449
|
-
var dispose = function() {
|
|
449
|
+
var dispose = async function() {
|
|
450
450
|
state.disposed = true;
|
|
451
451
|
if (state.scanTimer !== undefined) clearTimeout(state.scanTimer);
|
|
452
452
|
if (state.backgroundTimer !== undefined) clearInterval(state.backgroundTimer);
|
|
@@ -456,13 +456,18 @@ export const VISION_ROUTING_SETTINGS_PRELUDE = String.raw`(function(){
|
|
|
456
456
|
try { disposeRemote(); } catch (_) {}
|
|
457
457
|
});
|
|
458
458
|
state.remoteDisposers.length = 0;
|
|
459
|
-
if (remote && scope && typeof scope.dispose === 'function') void scope.dispose();
|
|
460
459
|
if (state.panel && state.panel.parentNode) state.panel.parentNode.removeChild(state.panel);
|
|
461
460
|
if (states) states.delete(ctx);
|
|
461
|
+
if (remote && scope && typeof scope.dispose === 'function') await scope.dispose();
|
|
462
462
|
};
|
|
463
|
+
var lifecycleOwned = false;
|
|
463
464
|
try {
|
|
464
|
-
if (typeof ctx.effect === 'function')
|
|
465
|
+
if (typeof ctx.effect === 'function') {
|
|
466
|
+
ctx.effect(function(){ return dispose; }, 'vision-router: routing settings product panel');
|
|
467
|
+
lifecycleOwned = true;
|
|
468
|
+
}
|
|
465
469
|
} catch (_) {}
|
|
470
|
+
if (!lifecycleOwned) void dispose().catch(function(){});
|
|
466
471
|
}
|
|
467
472
|
|
|
468
473
|
function patchLoader(loader) {
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { BENCHMARK_AXES, } from './vision-capability-evidence.js';
|
|
2
|
+
export const DEFAULT_RUNTIME_PERFORMANCE_MAX_AGE_MS = 60 * 60 * 1000;
|
|
3
|
+
export const DEFAULT_RUNTIME_PERFORMANCE_MAX_SAMPLES = 8;
|
|
4
|
+
export const DEFAULT_RUNTIME_PERFORMANCE_MIN_SAMPLES = 2;
|
|
5
|
+
export const DEFAULT_RUNTIME_PERFORMANCE_MAX_BACKENDS = 128;
|
|
6
|
+
function finiteLatency(value) {
|
|
7
|
+
const number = Number(value);
|
|
8
|
+
return Number.isFinite(number) && number >= 0 ? number : undefined;
|
|
9
|
+
}
|
|
10
|
+
function median(values) {
|
|
11
|
+
const sorted = values.filter(Number.isFinite).sort((a, b) => a - b);
|
|
12
|
+
if (sorted.length === 0)
|
|
13
|
+
return undefined;
|
|
14
|
+
const middle = Math.floor(sorted.length / 2);
|
|
15
|
+
const right = sorted[middle];
|
|
16
|
+
if (right === undefined)
|
|
17
|
+
return undefined;
|
|
18
|
+
if (sorted.length % 2 === 1)
|
|
19
|
+
return right;
|
|
20
|
+
const left = sorted[middle - 1];
|
|
21
|
+
return left === undefined ? right : (left + right) / 2;
|
|
22
|
+
}
|
|
23
|
+
function cleanBackendKey(value) {
|
|
24
|
+
const text = typeof value === 'string' ? value.trim() : '';
|
|
25
|
+
return text === '' ? undefined : text.slice(0, 512);
|
|
26
|
+
}
|
|
27
|
+
function isBenchmarkAxis(value) {
|
|
28
|
+
return typeof value === 'string'
|
|
29
|
+
&& BENCHMARK_AXES.includes(value);
|
|
30
|
+
}
|
|
31
|
+
export function createVisionRuntimePerformanceSampleStore(options = {}) {
|
|
32
|
+
const now = typeof options.now === 'function' ? options.now : Date.now;
|
|
33
|
+
const maxAgeMs = Math.max(1_000, Number(options.maxAgeMs) || DEFAULT_RUNTIME_PERFORMANCE_MAX_AGE_MS);
|
|
34
|
+
const maxSamples = Math.max(1, Math.min(64, Math.floor(Number(options.maxSamples) || DEFAULT_RUNTIME_PERFORMANCE_MAX_SAMPLES)));
|
|
35
|
+
const minSamples = Math.max(1, Math.min(maxSamples, Math.floor(Number(options.minSamples) || DEFAULT_RUNTIME_PERFORMANCE_MIN_SAMPLES)));
|
|
36
|
+
const maxBackends = Math.max(1, Math.min(1024, Math.floor(Number(options.maxBackends) || DEFAULT_RUNTIME_PERFORMANCE_MAX_BACKENDS)));
|
|
37
|
+
const records = new Map();
|
|
38
|
+
let identityContext = options.context;
|
|
39
|
+
const identityResolver = options.identityResolver;
|
|
40
|
+
const resolvedStorageKey = (backendKey) => {
|
|
41
|
+
const key = cleanBackendKey(backendKey);
|
|
42
|
+
if (key === undefined)
|
|
43
|
+
return undefined;
|
|
44
|
+
let identity;
|
|
45
|
+
try {
|
|
46
|
+
identity = identityContext && identityResolver !== undefined
|
|
47
|
+
? identityResolver(key, identityContext)
|
|
48
|
+
: undefined;
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
identity = undefined;
|
|
52
|
+
}
|
|
53
|
+
return identity ? `${key}\u0000${String(identity)}` : key;
|
|
54
|
+
};
|
|
55
|
+
const pruneAxis = (samples, at) => samples.filter((sample) => at - sample.at <= maxAgeMs);
|
|
56
|
+
const pruneBackend = (key, at) => {
|
|
57
|
+
const axes = records.get(key);
|
|
58
|
+
if (axes === undefined)
|
|
59
|
+
return undefined;
|
|
60
|
+
for (const [axis, samples] of axes) {
|
|
61
|
+
const current = pruneAxis(samples, at);
|
|
62
|
+
if (current.length === 0)
|
|
63
|
+
axes.delete(axis);
|
|
64
|
+
else if (current.length !== samples.length)
|
|
65
|
+
axes.set(axis, current);
|
|
66
|
+
}
|
|
67
|
+
if (axes.size === 0) {
|
|
68
|
+
records.delete(key);
|
|
69
|
+
return undefined;
|
|
70
|
+
}
|
|
71
|
+
records.delete(key);
|
|
72
|
+
records.set(key, axes);
|
|
73
|
+
return axes;
|
|
74
|
+
};
|
|
75
|
+
const bound = () => {
|
|
76
|
+
while (records.size > maxBackends) {
|
|
77
|
+
const oldest = records.keys().next().value;
|
|
78
|
+
if (oldest === undefined)
|
|
79
|
+
break;
|
|
80
|
+
records.delete(oldest);
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
return {
|
|
84
|
+
maxAgeMs,
|
|
85
|
+
maxSamples,
|
|
86
|
+
minSamples,
|
|
87
|
+
bindContext(context) {
|
|
88
|
+
identityContext = context;
|
|
89
|
+
},
|
|
90
|
+
record(backendKey, axisValue, latencyMsValue, atValue = now()) {
|
|
91
|
+
const key = resolvedStorageKey(backendKey);
|
|
92
|
+
const latencyMs = finiteLatency(latencyMsValue);
|
|
93
|
+
const at = Number(atValue);
|
|
94
|
+
if (key === undefined
|
|
95
|
+
|| !isBenchmarkAxis(axisValue)
|
|
96
|
+
|| latencyMs === undefined
|
|
97
|
+
|| !Number.isFinite(at)) {
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
let axes = pruneBackend(key, at);
|
|
101
|
+
if (axes === undefined) {
|
|
102
|
+
axes = new Map();
|
|
103
|
+
records.set(key, axes);
|
|
104
|
+
}
|
|
105
|
+
const current = pruneAxis(axes.get(axisValue) ?? [], at);
|
|
106
|
+
current.push({ at, latencyMs });
|
|
107
|
+
if (current.length > maxSamples) {
|
|
108
|
+
current.splice(0, current.length - maxSamples);
|
|
109
|
+
}
|
|
110
|
+
axes.set(axisValue, current);
|
|
111
|
+
records.delete(key);
|
|
112
|
+
records.set(key, axes);
|
|
113
|
+
bound();
|
|
114
|
+
return true;
|
|
115
|
+
},
|
|
116
|
+
get(backendKey, atValue = now()) {
|
|
117
|
+
const key = resolvedStorageKey(backendKey);
|
|
118
|
+
const at = Number(atValue);
|
|
119
|
+
if (key === undefined || !Number.isFinite(at))
|
|
120
|
+
return undefined;
|
|
121
|
+
const axes = pruneBackend(key, at);
|
|
122
|
+
if (axes === undefined)
|
|
123
|
+
return undefined;
|
|
124
|
+
const observedLatencyMsByAxis = {};
|
|
125
|
+
const runtimeLatencyMsByAxis = {};
|
|
126
|
+
const sampleCountByAxis = {};
|
|
127
|
+
const observedAtByAxis = {};
|
|
128
|
+
for (const [axis, samples] of axes) {
|
|
129
|
+
const latencyMs = median(samples.map((sample) => sample.latencyMs));
|
|
130
|
+
if (latencyMs === undefined)
|
|
131
|
+
continue;
|
|
132
|
+
observedLatencyMsByAxis[axis] = latencyMs;
|
|
133
|
+
sampleCountByAxis[axis] = samples.length;
|
|
134
|
+
observedAtByAxis[axis] = Math.max(...samples.map((sample) => sample.at));
|
|
135
|
+
if (samples.length >= minSamples)
|
|
136
|
+
runtimeLatencyMsByAxis[axis] = latencyMs;
|
|
137
|
+
}
|
|
138
|
+
return {
|
|
139
|
+
runtimeLatencyMsByAxis,
|
|
140
|
+
observedLatencyMsByAxis,
|
|
141
|
+
sampleCountByAxis,
|
|
142
|
+
observedAtByAxis,
|
|
143
|
+
maxAgeMs,
|
|
144
|
+
minSamples,
|
|
145
|
+
};
|
|
146
|
+
},
|
|
147
|
+
clear(backendKey) {
|
|
148
|
+
if (backendKey === undefined) {
|
|
149
|
+
records.clear();
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
const base = cleanBackendKey(backendKey);
|
|
153
|
+
if (base === undefined)
|
|
154
|
+
return;
|
|
155
|
+
records.delete(base);
|
|
156
|
+
const prefix = `${base}\u0000`;
|
|
157
|
+
for (const key of [...records.keys()]) {
|
|
158
|
+
if (key.startsWith(prefix))
|
|
159
|
+
records.delete(key);
|
|
160
|
+
}
|
|
161
|
+
},
|
|
162
|
+
size() {
|
|
163
|
+
return records.size;
|
|
164
|
+
},
|
|
165
|
+
};
|
|
166
|
+
}
|
|
@@ -1,37 +1,19 @@
|
|
|
1
1
|
import { AsyncLocalStorage } from 'node:async_hooks'
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
inferToolVisionIntent,
|
|
5
|
-
} from './vision-capability-router.js'
|
|
2
|
+
import { benchmarkAxisForVisionIntent } from './vision-capability-evidence.js'
|
|
3
|
+
import { inferToolVisionIntent } from './vision-capability-router.js'
|
|
6
4
|
import { capabilityEvidenceFingerprint } from './vision-capability-identity.js'
|
|
7
5
|
import { providerTransportFor } from './live-model-discovery.js'
|
|
6
|
+
import { createVisionRuntimePerformanceSampleStore } from './vision-runtime-performance-store.js'
|
|
8
7
|
|
|
9
|
-
export
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
8
|
+
export {
|
|
9
|
+
DEFAULT_RUNTIME_PERFORMANCE_MAX_AGE_MS,
|
|
10
|
+
DEFAULT_RUNTIME_PERFORMANCE_MAX_SAMPLES,
|
|
11
|
+
DEFAULT_RUNTIME_PERFORMANCE_MIN_SAMPLES,
|
|
12
|
+
DEFAULT_RUNTIME_PERFORMANCE_MAX_BACKENDS,
|
|
13
|
+
} from './vision-runtime-performance-store.js'
|
|
13
14
|
|
|
14
15
|
const runtimeScope = new AsyncLocalStorage()
|
|
15
16
|
|
|
16
|
-
function finiteLatency(value) {
|
|
17
|
-
const number = Number(value)
|
|
18
|
-
return Number.isFinite(number) && number >= 0 ? number : undefined
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function median(values) {
|
|
22
|
-
const sorted = values.filter(Number.isFinite).sort((a, b) => a - b)
|
|
23
|
-
if (sorted.length === 0) return undefined
|
|
24
|
-
const middle = Math.floor(sorted.length / 2)
|
|
25
|
-
return sorted.length % 2 === 1
|
|
26
|
-
? sorted[middle]
|
|
27
|
-
: (sorted[middle - 1] + sorted[middle]) / 2
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function cleanBackendKey(value) {
|
|
31
|
-
const text = typeof value === 'string' ? value.trim() : ''
|
|
32
|
-
return text === '' ? undefined : text.slice(0, 512)
|
|
33
|
-
}
|
|
34
|
-
|
|
35
17
|
function backendParts(backendKey) {
|
|
36
18
|
const key = cleanBackendKey(backendKey)
|
|
37
19
|
if (!key) return undefined
|
|
@@ -91,124 +73,13 @@ function failureFinish(chunk) {
|
|
|
91
73
|
}
|
|
92
74
|
|
|
93
75
|
export function createVisionRuntimePerformanceStore(options = {}) {
|
|
94
|
-
const now = typeof options.now === 'function' ? options.now : Date.now
|
|
95
|
-
const maxAgeMs = Math.max(1_000, Number(options.maxAgeMs) || DEFAULT_RUNTIME_PERFORMANCE_MAX_AGE_MS)
|
|
96
|
-
const maxSamples = Math.max(1, Math.min(64, Math.floor(Number(options.maxSamples) || DEFAULT_RUNTIME_PERFORMANCE_MAX_SAMPLES)))
|
|
97
|
-
const minSamples = Math.max(1, Math.min(maxSamples, Math.floor(Number(options.minSamples) || DEFAULT_RUNTIME_PERFORMANCE_MIN_SAMPLES)))
|
|
98
|
-
const maxBackends = Math.max(1, Math.min(1024, Math.floor(Number(options.maxBackends) || DEFAULT_RUNTIME_PERFORMANCE_MAX_BACKENDS)))
|
|
99
|
-
const records = new Map()
|
|
100
|
-
let identityContext = options.context
|
|
101
76
|
const identityResolver = typeof options.identityResolver === 'function'
|
|
102
77
|
? options.identityResolver
|
|
103
78
|
: (backendKey, ctx) => runtimePerformanceIdentityFor(ctx, backendKey)
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
let identity
|
|
109
|
-
try { identity = identityContext ? identityResolver(key, identityContext) : undefined } catch { identity = undefined }
|
|
110
|
-
return identity ? `${key}\u0000${identity}` : key
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
const pruneAxis = (samples, at) => samples.filter((sample) => at - sample.at <= maxAgeMs)
|
|
114
|
-
|
|
115
|
-
const pruneBackend = (key, at) => {
|
|
116
|
-
const axes = records.get(key)
|
|
117
|
-
if (!axes) return undefined
|
|
118
|
-
for (const [axis, samples] of axes) {
|
|
119
|
-
const current = pruneAxis(samples, at)
|
|
120
|
-
if (current.length === 0) axes.delete(axis)
|
|
121
|
-
else if (current.length !== samples.length) axes.set(axis, current)
|
|
122
|
-
}
|
|
123
|
-
if (axes.size === 0) {
|
|
124
|
-
records.delete(key)
|
|
125
|
-
return undefined
|
|
126
|
-
}
|
|
127
|
-
// LRU touch only on runtime-observation reads/writes; routing diagnostics
|
|
128
|
-
// are allowed to read this performance store because it is not the v1
|
|
129
|
-
// breaker and has no execution side effects.
|
|
130
|
-
records.delete(key)
|
|
131
|
-
records.set(key, axes)
|
|
132
|
-
return axes
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
const bound = () => {
|
|
136
|
-
while (records.size > maxBackends) {
|
|
137
|
-
const oldest = records.keys().next().value
|
|
138
|
-
if (oldest === undefined) break
|
|
139
|
-
records.delete(oldest)
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
return {
|
|
144
|
-
maxAgeMs,
|
|
145
|
-
maxSamples,
|
|
146
|
-
minSamples,
|
|
147
|
-
bindContext(ctx) {
|
|
148
|
-
identityContext = ctx
|
|
149
|
-
},
|
|
150
|
-
record(backendKey, axis, latencyMs, at = now()) {
|
|
151
|
-
const key = resolvedStorageKey(backendKey)
|
|
152
|
-
const latency = finiteLatency(latencyMs)
|
|
153
|
-
const timestamp = Number(at)
|
|
154
|
-
if (!key || !benchmarkAxisForVisionIntent(axis) || latency === undefined || !Number.isFinite(timestamp)) return false
|
|
155
|
-
let axes = pruneBackend(key, timestamp)
|
|
156
|
-
if (!axes) {
|
|
157
|
-
axes = new Map()
|
|
158
|
-
records.set(key, axes)
|
|
159
|
-
}
|
|
160
|
-
const current = pruneAxis(axes.get(axis) ?? [], timestamp)
|
|
161
|
-
current.push({ at: timestamp, latencyMs: latency })
|
|
162
|
-
if (current.length > maxSamples) current.splice(0, current.length - maxSamples)
|
|
163
|
-
axes.set(axis, current)
|
|
164
|
-
records.delete(key)
|
|
165
|
-
records.set(key, axes)
|
|
166
|
-
bound()
|
|
167
|
-
return true
|
|
168
|
-
},
|
|
169
|
-
get(backendKey, at = now()) {
|
|
170
|
-
const key = resolvedStorageKey(backendKey)
|
|
171
|
-
const timestamp = Number(at)
|
|
172
|
-
if (!key || !Number.isFinite(timestamp)) return undefined
|
|
173
|
-
const axes = pruneBackend(key, timestamp)
|
|
174
|
-
if (!axes) return undefined
|
|
175
|
-
const observedLatencyMsByAxis = {}
|
|
176
|
-
const runtimeLatencyMsByAxis = {}
|
|
177
|
-
const sampleCountByAxis = {}
|
|
178
|
-
const observedAtByAxis = {}
|
|
179
|
-
for (const [axis, samples] of axes) {
|
|
180
|
-
const latencies = samples.map((sample) => sample.latencyMs)
|
|
181
|
-
const value = median(latencies)
|
|
182
|
-
if (value === undefined) continue
|
|
183
|
-
observedLatencyMsByAxis[axis] = value
|
|
184
|
-
sampleCountByAxis[axis] = samples.length
|
|
185
|
-
observedAtByAxis[axis] = Math.max(...samples.map((sample) => sample.at))
|
|
186
|
-
if (samples.length >= minSamples) runtimeLatencyMsByAxis[axis] = value
|
|
187
|
-
}
|
|
188
|
-
return {
|
|
189
|
-
runtimeLatencyMsByAxis,
|
|
190
|
-
observedLatencyMsByAxis,
|
|
191
|
-
sampleCountByAxis,
|
|
192
|
-
observedAtByAxis,
|
|
193
|
-
maxAgeMs,
|
|
194
|
-
minSamples,
|
|
195
|
-
}
|
|
196
|
-
},
|
|
197
|
-
clear(backendKey) {
|
|
198
|
-
if (backendKey === undefined) {
|
|
199
|
-
records.clear()
|
|
200
|
-
return
|
|
201
|
-
}
|
|
202
|
-
const base = cleanBackendKey(backendKey)
|
|
203
|
-
if (!base) return
|
|
204
|
-
records.delete(base)
|
|
205
|
-
const prefix = `${base}\u0000`
|
|
206
|
-
for (const key of [...records.keys()]) if (key.startsWith(prefix)) records.delete(key)
|
|
207
|
-
},
|
|
208
|
-
size() {
|
|
209
|
-
return records.size
|
|
210
|
-
},
|
|
211
|
-
}
|
|
79
|
+
return createVisionRuntimePerformanceSampleStore({
|
|
80
|
+
...options,
|
|
81
|
+
identityResolver,
|
|
82
|
+
})
|
|
212
83
|
}
|
|
213
84
|
|
|
214
85
|
export function withVisionRuntimePerformanceScope(toolName, args, fn) {
|
|
@@ -251,6 +251,7 @@ const CLIENT_PRELUDE = String.raw`(function(){
|
|
|
251
251
|
var listeners = new Set();
|
|
252
252
|
var pending = new WeakMap();
|
|
253
253
|
var recoveries = new WeakMap();
|
|
254
|
+
var selectionGenerations = new WeakMap();
|
|
254
255
|
var inFlight = null;
|
|
255
256
|
var refreshedAt = 0;
|
|
256
257
|
function emit(){ listeners.forEach(function(fn){ try { fn(); } catch (_) {} }); }
|
|
@@ -302,13 +303,29 @@ const CLIENT_PRELUDE = String.raw`(function(){
|
|
|
302
303
|
var key = JSON.stringify(selection || {});
|
|
303
304
|
var held = pending.get(directory);
|
|
304
305
|
if (held && held.key === key) return held.promise;
|
|
306
|
+
var generation = (selectionGenerations.get(directory) || 0) + 1;
|
|
307
|
+
selectionGenerations.set(directory, generation);
|
|
308
|
+
var current = function(){ return selectionGenerations.get(directory) === generation; };
|
|
305
309
|
var recovery = recoveryFor(directory);
|
|
306
310
|
recovery.clear();
|
|
307
311
|
var promise = Promise.resolve().then(function(){ return directory.select(selection); }).then(
|
|
308
|
-
function(value){
|
|
309
|
-
|
|
312
|
+
function(value){
|
|
313
|
+
if (!current()) return value;
|
|
314
|
+
if (value && value.ok === false) {
|
|
315
|
+
var failure = value.error;
|
|
316
|
+
var message = failure && failure.message
|
|
317
|
+
? String(failure.message)
|
|
318
|
+
: String(failure || 'model operation failed');
|
|
319
|
+
if (failure && failure.code) message = String(failure.code) + ': ' + message;
|
|
320
|
+
recovery.fail(new Error(message));
|
|
321
|
+
return value;
|
|
322
|
+
}
|
|
323
|
+
recovery.clear();
|
|
324
|
+
return value;
|
|
325
|
+
},
|
|
326
|
+
function(error){ if (current()) recovery.fail(error); throw error; }
|
|
310
327
|
);
|
|
311
|
-
pending.set(directory, { key: key, promise: promise });
|
|
328
|
+
pending.set(directory, { key: key, promise: promise, generation: generation });
|
|
312
329
|
promise.finally(function(){
|
|
313
330
|
var latest = pending.get(directory);
|
|
314
331
|
if (latest && latest.promise === promise) pending.delete(directory);
|
|
@@ -391,8 +408,8 @@ export function hardenVisionClientSource(source) {
|
|
|
391
408
|
|
|
392
409
|
next = replaceAll(
|
|
393
410
|
next,
|
|
394
|
-
` try {\n return Promise.resolve(directory.select(selection)).then(\n function(){ return
|
|
395
|
-
` var hardening = window.__dshVisionRouterRootHardening;\n var operation;\n try { operation = hardening ? hardening.select(directory, selection) : Promise.resolve(directory.select(selection)); }\n catch (_) { return Promise.resolve(false); }\n return Promise.resolve(operation).then(function(){ return
|
|
411
|
+
` try {\n return Promise.resolve(directory.select(selection)).then(\n function(result){ return !result || result.ok !== false; },\n function(){ return false; }\n );\n } catch (_) {\n return Promise.resolve(false);\n }`,
|
|
412
|
+
` var hardening = window.__dshVisionRouterRootHardening;\n var operation;\n try { operation = hardening ? hardening.select(directory, selection) : Promise.resolve(directory.select(selection)); }\n catch (_) { return Promise.resolve(false); }\n return Promise.resolve(operation).then(function(result){ return !result || result.ok !== false; }, function(){ return false; });`,
|
|
396
413
|
)
|
|
397
414
|
|
|
398
415
|
next = replaceAll(
|