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,15 +1,19 @@
|
|
|
1
|
-
import { createHash } from 'node:crypto'
|
|
2
|
-
import process from 'node:process'
|
|
3
1
|
import { VISION_INTENTS } from './vision-capability-router.js'
|
|
2
|
+
import {
|
|
3
|
+
CAPABILITY_BENCHMARK_RENDERER_SCOPE,
|
|
4
|
+
CAPABILITY_BENCHMARK_SUITE_REVISION,
|
|
5
|
+
capabilityBenchmarkFingerprint,
|
|
6
|
+
} from './vision-capability-fingerprint.js'
|
|
7
|
+
|
|
8
|
+
export {
|
|
9
|
+
CAPABILITY_BENCHMARK_RENDERER_SCOPE,
|
|
10
|
+
CAPABILITY_BENCHMARK_SUITE_REVISION,
|
|
11
|
+
capabilityBenchmarkFingerprint,
|
|
12
|
+
} from './vision-capability-fingerprint.js'
|
|
4
13
|
|
|
5
14
|
const WIDTH = 768
|
|
6
15
|
const HEIGHT = 512
|
|
7
16
|
|
|
8
|
-
export const CAPABILITY_BENCHMARK_SUITE_REVISION = 5
|
|
9
|
-
// Renderer-only fixes must invalidate prior evidence even when the scoring and
|
|
10
|
-
// prompt contract (the suite revision) is unchanged.
|
|
11
|
-
export const CAPABILITY_BENCHMARK_RENDERER_SCOPE = `${process.platform}/${process.arch}-proof-badge-v2`
|
|
12
|
-
|
|
13
17
|
function esc(value) {
|
|
14
18
|
return String(value).replace(/[&<>"']/g, (ch) => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' })[ch])
|
|
15
19
|
}
|
|
@@ -160,60 +164,6 @@ export function listCapabilityBenchmarkFixtures(intents = CORE_BENCHMARK_INTENTS
|
|
|
160
164
|
return fixtures
|
|
161
165
|
}
|
|
162
166
|
|
|
163
|
-
function isSensitiveKey(key) {
|
|
164
|
-
return /(^|[_-])(api[_-]?key|key|token|secret|password|authorization|auth|signature|sig)([_-]|$)/i.test(String(key ?? ''))
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
function normalizeEndpoint(value) {
|
|
168
|
-
const raw = String(value ?? '').trim()
|
|
169
|
-
if (raw === '') return ''
|
|
170
|
-
try {
|
|
171
|
-
const url = new URL(raw)
|
|
172
|
-
url.username = ''
|
|
173
|
-
url.password = ''
|
|
174
|
-
url.hash = ''
|
|
175
|
-
for (const key of [...url.searchParams.keys()]) {
|
|
176
|
-
if (isSensitiveKey(key)) url.searchParams.delete(key)
|
|
177
|
-
}
|
|
178
|
-
url.searchParams.sort()
|
|
179
|
-
const pathname = url.pathname === '/' ? '' : url.pathname.replace(/\/+$/, '')
|
|
180
|
-
return `${url.protocol.toLowerCase()}//${url.host.toLowerCase()}${pathname}${url.search}`
|
|
181
|
-
} catch {
|
|
182
|
-
return raw.replace(/\/+$/, '')
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
function sanitizeFingerprintValue(value, keyHint = '') {
|
|
187
|
-
if (value === null || value === undefined) return value ?? null
|
|
188
|
-
if (Array.isArray(value)) return value.map((item) => sanitizeFingerprintValue(item))
|
|
189
|
-
if (typeof value === 'object') {
|
|
190
|
-
return Object.fromEntries(
|
|
191
|
-
Object.entries(value)
|
|
192
|
-
.filter(([key]) => !isSensitiveKey(key))
|
|
193
|
-
.sort(([a], [b]) => a.localeCompare(b))
|
|
194
|
-
.map(([key, child]) => [key, sanitizeFingerprintValue(child, key)]),
|
|
195
|
-
)
|
|
196
|
-
}
|
|
197
|
-
if (typeof value === 'string' && /(endpoint|base[_-]?url|url)$/i.test(keyHint)) return normalizeEndpoint(value)
|
|
198
|
-
if (typeof value === 'bigint') return value.toString()
|
|
199
|
-
if (typeof value === 'number' || typeof value === 'boolean' || typeof value === 'string') return value
|
|
200
|
-
return String(value)
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
export function capabilityBenchmarkFingerprint({ provider, model, endpoint, config, credentialFingerprint } = {}) {
|
|
204
|
-
const payload = JSON.stringify({
|
|
205
|
-
schema: 'vision-capability-endpoint-v2',
|
|
206
|
-
suiteRevision: CAPABILITY_BENCHMARK_SUITE_REVISION,
|
|
207
|
-
rendererScope: CAPABILITY_BENCHMARK_RENDERER_SCOPE,
|
|
208
|
-
provider: String(provider ?? '').trim(),
|
|
209
|
-
model: String(model ?? '').trim(),
|
|
210
|
-
endpoint: normalizeEndpoint(endpoint),
|
|
211
|
-
config: sanitizeFingerprintValue(config && typeof config === 'object' ? config : null),
|
|
212
|
-
credentialFingerprint: String(credentialFingerprint ?? 'none'),
|
|
213
|
-
})
|
|
214
|
-
return `ep2_${createHash('sha256').update(payload).digest('hex').slice(0, 32)}`
|
|
215
|
-
}
|
|
216
|
-
|
|
217
167
|
function clamp01(value) {
|
|
218
168
|
return Math.max(0, Math.min(1, Number.isFinite(value) ? value : 0))
|
|
219
169
|
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export const VISION_INTENTS = [
|
|
2
|
+
'structured',
|
|
3
|
+
'ocr',
|
|
4
|
+
'document',
|
|
5
|
+
'ui',
|
|
6
|
+
'grounding',
|
|
7
|
+
'detection',
|
|
8
|
+
'general',
|
|
9
|
+
'chart_diagram',
|
|
10
|
+
'code_screenshot',
|
|
11
|
+
'visual_compare',
|
|
12
|
+
];
|
|
13
|
+
export const BENCHMARK_AXES = [
|
|
14
|
+
'structured',
|
|
15
|
+
'ocr',
|
|
16
|
+
'document',
|
|
17
|
+
'grounding',
|
|
18
|
+
'general',
|
|
19
|
+
];
|
|
20
|
+
const DIRECT_TASK_AXIS = Object.freeze({
|
|
21
|
+
structured: 'structured',
|
|
22
|
+
ocr: 'ocr',
|
|
23
|
+
document: 'document',
|
|
24
|
+
grounding: 'grounding',
|
|
25
|
+
general: 'general',
|
|
26
|
+
});
|
|
27
|
+
export function normalizeVisionIntent(intent) {
|
|
28
|
+
return typeof intent === 'string'
|
|
29
|
+
&& VISION_INTENTS.includes(intent)
|
|
30
|
+
? intent
|
|
31
|
+
: 'general';
|
|
32
|
+
}
|
|
33
|
+
export function benchmarkAxisForVisionIntent(intent) {
|
|
34
|
+
return DIRECT_TASK_AXIS[normalizeVisionIntent(intent)];
|
|
35
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import process from 'node:process';
|
|
3
|
+
export const CAPABILITY_BENCHMARK_SUITE_REVISION = 5;
|
|
4
|
+
// Renderer-only fixes must invalidate prior evidence even when the scoring and
|
|
5
|
+
// prompt contract (the suite revision) is unchanged.
|
|
6
|
+
export const CAPABILITY_BENCHMARK_RENDERER_SCOPE = `${process.platform}/${process.arch}-proof-badge-v2`;
|
|
7
|
+
function isSensitiveKey(key) {
|
|
8
|
+
return /(^|[_-])(api[_-]?key|key|token|secret|password|authorization|auth|signature|sig)([_-]|$)/i
|
|
9
|
+
.test(String(key ?? ''));
|
|
10
|
+
}
|
|
11
|
+
function normalizeEndpoint(value) {
|
|
12
|
+
const raw = String(value ?? '').trim();
|
|
13
|
+
if (raw === '')
|
|
14
|
+
return '';
|
|
15
|
+
try {
|
|
16
|
+
const url = new URL(raw);
|
|
17
|
+
url.username = '';
|
|
18
|
+
url.password = '';
|
|
19
|
+
url.hash = '';
|
|
20
|
+
for (const key of [...url.searchParams.keys()]) {
|
|
21
|
+
if (isSensitiveKey(key))
|
|
22
|
+
url.searchParams.delete(key);
|
|
23
|
+
}
|
|
24
|
+
url.searchParams.sort();
|
|
25
|
+
const pathname = url.pathname === '/' ? '' : url.pathname.replace(/\/+$/, '');
|
|
26
|
+
return `${url.protocol.toLowerCase()}//${url.host.toLowerCase()}${pathname}${url.search}`;
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return raw.replace(/\/+$/, '');
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function sanitizeFingerprintValue(value, keyHint = '') {
|
|
33
|
+
if (value === null || value === undefined)
|
|
34
|
+
return value ?? null;
|
|
35
|
+
if (Array.isArray(value)) {
|
|
36
|
+
return value.map((item) => sanitizeFingerprintValue(item));
|
|
37
|
+
}
|
|
38
|
+
if (typeof value === 'object') {
|
|
39
|
+
return Object.fromEntries(Object.entries(value)
|
|
40
|
+
.filter(([key]) => !isSensitiveKey(key))
|
|
41
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
42
|
+
.map(([key, child]) => [
|
|
43
|
+
key,
|
|
44
|
+
sanitizeFingerprintValue(child, key),
|
|
45
|
+
]));
|
|
46
|
+
}
|
|
47
|
+
if (typeof value === 'string'
|
|
48
|
+
&& /(endpoint|base[_-]?url|url)$/i.test(keyHint)) {
|
|
49
|
+
return normalizeEndpoint(value);
|
|
50
|
+
}
|
|
51
|
+
if (typeof value === 'bigint')
|
|
52
|
+
return value.toString();
|
|
53
|
+
if (typeof value === 'number'
|
|
54
|
+
|| typeof value === 'boolean'
|
|
55
|
+
|| typeof value === 'string') {
|
|
56
|
+
return value;
|
|
57
|
+
}
|
|
58
|
+
return String(value);
|
|
59
|
+
}
|
|
60
|
+
export function capabilityBenchmarkFingerprint({ provider, model, endpoint, config, credentialFingerprint, } = {}) {
|
|
61
|
+
const payload = JSON.stringify({
|
|
62
|
+
schema: 'vision-capability-endpoint-v2',
|
|
63
|
+
suiteRevision: CAPABILITY_BENCHMARK_SUITE_REVISION,
|
|
64
|
+
rendererScope: CAPABILITY_BENCHMARK_RENDERER_SCOPE,
|
|
65
|
+
provider: String(provider ?? '').trim(),
|
|
66
|
+
model: String(model ?? '').trim(),
|
|
67
|
+
endpoint: normalizeEndpoint(endpoint),
|
|
68
|
+
config: sanitizeFingerprintValue(config && typeof config === 'object' ? config : null),
|
|
69
|
+
credentialFingerprint: String(credentialFingerprint ?? 'none'),
|
|
70
|
+
});
|
|
71
|
+
return `ep2_${createHash('sha256').update(payload).digest('hex').slice(0, 32)}`;
|
|
72
|
+
}
|
|
@@ -1,73 +1,128 @@
|
|
|
1
|
-
import { createHash } from 'node:crypto'
|
|
2
|
-
import { capabilityBenchmarkFingerprint } from './vision-capability-
|
|
3
|
-
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { capabilityBenchmarkFingerprint } from './vision-capability-fingerprint.js';
|
|
3
|
+
function propertyBag(value) {
|
|
4
|
+
return value !== null && (typeof value === 'object' || typeof value === 'function')
|
|
5
|
+
? value
|
|
6
|
+
: undefined;
|
|
7
|
+
}
|
|
4
8
|
function nonEmpty(value) {
|
|
5
|
-
|
|
9
|
+
return typeof value === 'string' && value.trim() !== '' ? value.trim() : undefined;
|
|
10
|
+
}
|
|
11
|
+
function contextGetter(ctx) {
|
|
12
|
+
const get = propertyBag(ctx)?.get;
|
|
13
|
+
return typeof get === 'function'
|
|
14
|
+
? (name) => get.call(ctx, name)
|
|
15
|
+
: undefined;
|
|
16
|
+
}
|
|
17
|
+
function credentialValue(value) {
|
|
18
|
+
return typeof value === 'string' && value !== '' ? value : undefined;
|
|
6
19
|
}
|
|
7
|
-
|
|
8
20
|
export function visionCredentialFingerprint(value) {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
21
|
+
const text = typeof value === 'string' ? value : '';
|
|
22
|
+
if (text === '')
|
|
23
|
+
return 'none';
|
|
24
|
+
return `cred_${createHash('sha256').update(text).digest('hex').slice(0, 24)}`;
|
|
12
25
|
}
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
config,
|
|
24
|
-
} = {}) {
|
|
25
|
-
return capabilityBenchmarkFingerprint({ provider, model, endpoint, config })
|
|
26
|
+
/**
|
|
27
|
+
* Capability identity deliberately excludes credential identity.
|
|
28
|
+
*
|
|
29
|
+
* Credentials decide whether a route can be accessed right now; they do not
|
|
30
|
+
* define what the same model deployment is capable of. Deployment/project
|
|
31
|
+
* identity belongs in endpoint/config when it actually changes the served
|
|
32
|
+
* model, never in secret material.
|
|
33
|
+
*/
|
|
34
|
+
export function capabilityEvidenceFingerprint({ provider, model, endpoint, config, } = {}) {
|
|
35
|
+
return capabilityBenchmarkFingerprint({ provider, model, endpoint, config });
|
|
26
36
|
}
|
|
27
|
-
|
|
28
37
|
export async function resolveVisionCredential(ctx, ref) {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
38
|
+
const key = nonEmpty(ref);
|
|
39
|
+
if (key === undefined) {
|
|
40
|
+
return {
|
|
41
|
+
required: false,
|
|
42
|
+
value: undefined,
|
|
43
|
+
fingerprint: 'none',
|
|
44
|
+
source: 'none',
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
let credentials;
|
|
35
48
|
try {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
49
|
+
credentials = contextGetter(ctx)?.('credentials');
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
credentials = undefined;
|
|
53
|
+
}
|
|
54
|
+
if (credentials !== undefined) {
|
|
55
|
+
try {
|
|
56
|
+
const resolve = propertyBag(credentials)?.resolve;
|
|
57
|
+
const hit = typeof resolve === 'function'
|
|
58
|
+
? await resolve.call(credentials, key)
|
|
59
|
+
: undefined;
|
|
60
|
+
const value = credentialValue(propertyBag(hit)?.value);
|
|
61
|
+
return {
|
|
62
|
+
required: true,
|
|
63
|
+
value,
|
|
64
|
+
fingerprint: value === undefined
|
|
65
|
+
? 'unresolved'
|
|
66
|
+
: visionCredentialFingerprint(value),
|
|
67
|
+
source: value === undefined ? 'credentials-miss' : 'credentials',
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
return {
|
|
72
|
+
required: true,
|
|
73
|
+
value: undefined,
|
|
74
|
+
fingerprint: 'unresolved',
|
|
75
|
+
source: 'credentials-error',
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
let launchEnvironment;
|
|
80
|
+
try {
|
|
81
|
+
launchEnvironment = contextGetter(ctx)?.('launchEnvironment');
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
return {
|
|
85
|
+
required: true,
|
|
86
|
+
value: undefined,
|
|
87
|
+
fingerprint: 'unresolved',
|
|
88
|
+
source: 'launch-environment-error',
|
|
89
|
+
};
|
|
46
90
|
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
try {
|
|
50
|
-
const launchEnvironment = ctx?.get?.('launchEnvironment')
|
|
51
91
|
if (launchEnvironment !== undefined) {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
92
|
+
try {
|
|
93
|
+
const get = propertyBag(launchEnvironment)?.get;
|
|
94
|
+
const hit = typeof get === 'function'
|
|
95
|
+
? get.call(launchEnvironment, key)
|
|
96
|
+
: undefined;
|
|
97
|
+
const value = credentialValue(propertyBag(hit)?.value);
|
|
98
|
+
return {
|
|
99
|
+
required: true,
|
|
100
|
+
value,
|
|
101
|
+
fingerprint: value === undefined
|
|
102
|
+
? 'unresolved'
|
|
103
|
+
: visionCredentialFingerprint(value),
|
|
104
|
+
source: value === undefined
|
|
105
|
+
? 'launch-environment-miss'
|
|
106
|
+
: 'launch-environment',
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
return {
|
|
111
|
+
required: true,
|
|
112
|
+
value: undefined,
|
|
113
|
+
fingerprint: 'unresolved',
|
|
114
|
+
source: 'launch-environment-error',
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
const ambient = process.env[key];
|
|
119
|
+
const value = typeof ambient === 'string' && ambient !== '' ? ambient : undefined;
|
|
120
|
+
return {
|
|
55
121
|
required: true,
|
|
56
122
|
value,
|
|
57
|
-
fingerprint: value === undefined
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
return { required: true, value: undefined, fingerprint: 'unresolved', source: 'launch-environment-error' }
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const ambient = process.env[key]
|
|
66
|
-
const value = typeof ambient === 'string' && ambient !== '' ? ambient : undefined
|
|
67
|
-
return {
|
|
68
|
-
required: true,
|
|
69
|
-
value,
|
|
70
|
-
fingerprint: value === undefined ? 'unresolved' : visionCredentialFingerprint(value),
|
|
71
|
-
source: value === undefined ? 'ambient-miss' : 'ambient',
|
|
72
|
-
}
|
|
123
|
+
fingerprint: value === undefined
|
|
124
|
+
? 'unresolved'
|
|
125
|
+
: visionCredentialFingerprint(value),
|
|
126
|
+
source: value === undefined ? 'ambient-miss' : 'ambient',
|
|
127
|
+
};
|
|
73
128
|
}
|
|
@@ -5,26 +5,18 @@
|
|
|
5
5
|
// health only gates temporary availability. Model names/families never create
|
|
6
6
|
// capability scores and unmeasured tasks never reorder the user's chain.
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
export const BENCHMARK_AXES = Object.freeze([
|
|
22
|
-
'structured',
|
|
23
|
-
'ocr',
|
|
24
|
-
'document',
|
|
25
|
-
'grounding',
|
|
26
|
-
'general',
|
|
27
|
-
])
|
|
8
|
+
import {
|
|
9
|
+
BENCHMARK_AXES,
|
|
10
|
+
VISION_INTENTS,
|
|
11
|
+
benchmarkAxisForVisionIntent,
|
|
12
|
+
normalizeVisionIntent,
|
|
13
|
+
} from './vision-capability-evidence.js'
|
|
14
|
+
|
|
15
|
+
export {
|
|
16
|
+
BENCHMARK_AXES,
|
|
17
|
+
VISION_INTENTS,
|
|
18
|
+
benchmarkAxisForVisionIntent,
|
|
19
|
+
} from './vision-capability-evidence.js'
|
|
28
20
|
|
|
29
21
|
// Internal compatibility vocabulary. `privacy` is the historical scorer name
|
|
30
22
|
// for the user-facing `local` preference; unlike the old implementation it is
|
|
@@ -33,14 +25,6 @@ export const VISION_STRATEGIES = Object.freeze(['quality', 'balanced', 'speed',
|
|
|
33
25
|
|
|
34
26
|
export const AUTO_REORDER_MIN_ADVANTAGE = 0.08
|
|
35
27
|
|
|
36
|
-
const DIRECT_TASK_AXIS = Object.freeze({
|
|
37
|
-
structured: 'structured',
|
|
38
|
-
ocr: 'ocr',
|
|
39
|
-
document: 'document',
|
|
40
|
-
grounding: 'grounding',
|
|
41
|
-
general: 'general',
|
|
42
|
-
})
|
|
43
|
-
|
|
44
28
|
const BOOTSTRAP_VISUAL_INTENT = Object.freeze({
|
|
45
29
|
chat: 'ui',
|
|
46
30
|
document: 'document',
|
|
@@ -60,7 +44,7 @@ function clamp01(value, fallback = 0) {
|
|
|
60
44
|
}
|
|
61
45
|
|
|
62
46
|
function normalizedIntent(intent) {
|
|
63
|
-
return
|
|
47
|
+
return normalizeVisionIntent(intent)
|
|
64
48
|
}
|
|
65
49
|
|
|
66
50
|
function normalizedStrategy(strategy) {
|
|
@@ -72,10 +56,6 @@ function cleanEnum(value, allowed) {
|
|
|
72
56
|
return allowed.has(text) ? text : 'unknown'
|
|
73
57
|
}
|
|
74
58
|
|
|
75
|
-
export function benchmarkAxisForVisionIntent(intent) {
|
|
76
|
-
return DIRECT_TASK_AXIS[normalizedIntent(intent)]
|
|
77
|
-
}
|
|
78
|
-
|
|
79
59
|
export function inferBootstrapVisionIntents(bootstrap = {}) {
|
|
80
60
|
const source = bootstrap && typeof bootstrap === 'object' && !Array.isArray(bootstrap) ? bootstrap : {}
|
|
81
61
|
const visualKind = cleanEnum(source.visual_kind, BOOTSTRAP_VISUAL_KINDS)
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
function propertyBag(value) {
|
|
2
|
+
return value !== null && typeof value === 'object'
|
|
3
|
+
? value
|
|
4
|
+
: undefined;
|
|
5
|
+
}
|
|
6
|
+
function failureMessage(failure) {
|
|
7
|
+
const message = propertyBag(failure)?.message;
|
|
8
|
+
return message ? String(message) : String(failure);
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Minimal DVR text assembler over the public DSH StreamChunk contract.
|
|
12
|
+
*
|
|
13
|
+
* Current DSH failures arrive inside the terminal finish reason. The two
|
|
14
|
+
* standalone terminal shapes remain explicit compatibility input for released
|
|
15
|
+
* historical/partial adapters. Runtime shape checks stay in place because JS
|
|
16
|
+
* callers may still provide malformed chunks.
|
|
17
|
+
*/
|
|
18
|
+
export function createChunkAssembler() {
|
|
19
|
+
const parts = new Map();
|
|
20
|
+
const order = [];
|
|
21
|
+
let finishKind;
|
|
22
|
+
let failure;
|
|
23
|
+
const pushUnknown = (value) => {
|
|
24
|
+
const chunk = propertyBag(value);
|
|
25
|
+
if (chunk === undefined || typeof chunk.type !== 'string')
|
|
26
|
+
return;
|
|
27
|
+
switch (chunk.type) {
|
|
28
|
+
case 'block-start': {
|
|
29
|
+
if (!parts.has(chunk.index)) {
|
|
30
|
+
order.push(chunk.index);
|
|
31
|
+
parts.set(chunk.index, { type: chunk.blockType, text: '' });
|
|
32
|
+
}
|
|
33
|
+
break;
|
|
34
|
+
}
|
|
35
|
+
case 'text-delta': {
|
|
36
|
+
const part = parts.get(chunk.index);
|
|
37
|
+
if (part !== undefined)
|
|
38
|
+
part.text += (chunk.text ?? '');
|
|
39
|
+
break;
|
|
40
|
+
}
|
|
41
|
+
case 'reasoning-delta':
|
|
42
|
+
case 'tool-call-delta':
|
|
43
|
+
case 'usage':
|
|
44
|
+
break;
|
|
45
|
+
case 'block-end': {
|
|
46
|
+
const part = parts.get(chunk.index);
|
|
47
|
+
const block = propertyBag(chunk.block);
|
|
48
|
+
if (part !== undefined && typeof block?.text === 'string') {
|
|
49
|
+
part.text = block.text;
|
|
50
|
+
}
|
|
51
|
+
break;
|
|
52
|
+
}
|
|
53
|
+
case 'finish': {
|
|
54
|
+
const reason = propertyBag(chunk.reason);
|
|
55
|
+
if (reason !== undefined
|
|
56
|
+
&& (reason.kind === 'error' || reason.kind === 'aborted')) {
|
|
57
|
+
failure = reason.failure;
|
|
58
|
+
}
|
|
59
|
+
finishKind =
|
|
60
|
+
reason !== undefined && reason.kind
|
|
61
|
+
? String(reason.kind)
|
|
62
|
+
: 'stop';
|
|
63
|
+
break;
|
|
64
|
+
}
|
|
65
|
+
case 'error':
|
|
66
|
+
case 'aborted':
|
|
67
|
+
failure = chunk.failure;
|
|
68
|
+
break;
|
|
69
|
+
default:
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
return {
|
|
74
|
+
push(chunk) {
|
|
75
|
+
pushUnknown(chunk);
|
|
76
|
+
},
|
|
77
|
+
finish() {
|
|
78
|
+
if (failure)
|
|
79
|
+
throw new Error(failureMessage(failure));
|
|
80
|
+
if (finishKind !== undefined
|
|
81
|
+
&& finishKind !== 'stop'
|
|
82
|
+
&& finishKind !== 'max-tokens') {
|
|
83
|
+
throw new Error(`vision call finished with "${finishKind}"`);
|
|
84
|
+
}
|
|
85
|
+
return order
|
|
86
|
+
.map((index) => parts.get(index))
|
|
87
|
+
.filter((part) => part !== undefined && part.type === 'text')
|
|
88
|
+
.map((part) => part.text)
|
|
89
|
+
.join('')
|
|
90
|
+
.trim();
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
const _officialFailureAuthority = undefined;
|
|
95
|
+
void _officialFailureAuthority;
|
|
@@ -1,9 +1,18 @@
|
|
|
1
|
+
import {
|
|
2
|
+
currentLegacyGlobalProxyFetch,
|
|
3
|
+
legacyGlobalProxyPairFor,
|
|
4
|
+
runWithLegacyGlobalProxyScope,
|
|
5
|
+
} from './legacy-global-proxy-boundary.js'
|
|
1
6
|
import { probeLocalBackends } from './local-connection-probe.js'
|
|
2
7
|
import {
|
|
3
8
|
METADATA_RESPONSE_MAX_BYTES,
|
|
4
9
|
readResponseJsonBounded,
|
|
5
10
|
} from './http-body-limit.js'
|
|
6
11
|
|
|
12
|
+
const moduleFetch = typeof globalThis.fetch === 'function'
|
|
13
|
+
? globalThis.fetch.bind(globalThis)
|
|
14
|
+
: undefined
|
|
15
|
+
|
|
7
16
|
export const VISION_TEST_CONNECTION_PATH = '/_dsh/vision-router/test-connection'
|
|
8
17
|
export const VISION_MODEL_CAPABILITIES_PATH = '/_dsh/vision-router/model-capabilities'
|
|
9
18
|
|
|
@@ -92,7 +101,36 @@ export function installVisionDiagnosticsRoutes(ctx, options = {}) {
|
|
|
92
101
|
const httpRoute = typeof connection.httpRoute === 'string' && connection.httpRoute !== ''
|
|
93
102
|
? connection.httpRoute
|
|
94
103
|
: 'vision-http'
|
|
95
|
-
|
|
104
|
+
// The proxy boundary installs its wrapper after apply(), so a capture taken here
|
|
105
|
+
// would make "test connection" report failures that real vision turns (which run
|
|
106
|
+
// inside the adapter's proxy scope) do not have. Use the fetch the boundary
|
|
107
|
+
// currently owns, with the module-load capture as the fallback.
|
|
108
|
+
const injectedFetch = typeof options.fetchImpl === 'function' ? options.fetchImpl : undefined
|
|
109
|
+
// Probes must describe the egress real vision turns use. That egress is the
|
|
110
|
+
// Router-owned provider transport, which also owns the proxy/proxyHosts decision;
|
|
111
|
+
// the legacy pair scope below only covers non-router-owned providers, so a
|
|
112
|
+
// vision-http direct backend would otherwise probe with ambient fetch.
|
|
113
|
+
const transport = options.transport
|
|
114
|
+
const transportFetch = typeof transport?.fetch === 'function'
|
|
115
|
+
? (input, init) => transport.fetch(input, init, { allowProxy: true })
|
|
116
|
+
: undefined
|
|
117
|
+
const resolveFetch = () => injectedFetch ?? transportFetch ?? currentLegacyGlobalProxyFetch() ?? moduleFetch
|
|
118
|
+
const connectionConfig = () => {
|
|
119
|
+
try {
|
|
120
|
+
const live = ctx?.get?.('settings')?.get?.('vision-router')
|
|
121
|
+
if (live !== null && typeof live === 'object' && !Array.isArray(live)) return live
|
|
122
|
+
} catch {
|
|
123
|
+
// No settings namespace on this Host: probes stay unscoped, as before.
|
|
124
|
+
}
|
|
125
|
+
return {}
|
|
126
|
+
}
|
|
127
|
+
const scopedProbe = (providerName, baseURL, expectedModel, startedAt) => {
|
|
128
|
+
const work = () => probeModels(resolveFetch(), baseURL, expectedModel, startedAt)
|
|
129
|
+
const pair = legacyGlobalProxyPairFor(connectionConfig(), providerName)
|
|
130
|
+
return pair === undefined
|
|
131
|
+
? work()
|
|
132
|
+
: runWithLegacyGlobalProxyScope(pair.provider, pair.model, work)
|
|
133
|
+
}
|
|
96
134
|
|
|
97
135
|
const probeConnection = async () => {
|
|
98
136
|
const started = Date.now()
|
|
@@ -109,7 +147,7 @@ export function installVisionDiagnosticsRoutes(ctx, options = {}) {
|
|
|
109
147
|
|
|
110
148
|
const localProbe = await probeLocalBackends(
|
|
111
149
|
getArray(connection.localBackends),
|
|
112
|
-
(provider) =>
|
|
150
|
+
(provider) => scopedProbe(provider.name ?? provider.id, provider.baseURL, provider.model, started),
|
|
113
151
|
started,
|
|
114
152
|
)
|
|
115
153
|
if (localProbe !== undefined) return localProbe
|
|
@@ -117,7 +155,7 @@ export function installVisionDiagnosticsRoutes(ctx, options = {}) {
|
|
|
117
155
|
const httpBackends = getArray(connection.httpBackends)
|
|
118
156
|
if (first !== undefined && first.provider === httpRoute) {
|
|
119
157
|
const entry = httpBackends.find((provider) => `${provider.name}/${provider.model}` === first.model)
|
|
120
|
-
if (entry !== undefined) return
|
|
158
|
+
if (entry !== undefined) return scopedProbe(entry.name, entry.baseURL, entry.model, started)
|
|
121
159
|
}
|
|
122
160
|
|
|
123
161
|
if (first !== undefined) {
|
|
@@ -138,7 +176,7 @@ export function installVisionDiagnosticsRoutes(ctx, options = {}) {
|
|
|
138
176
|
}
|
|
139
177
|
|
|
140
178
|
const httpFirst = httpBackends[0]
|
|
141
|
-
if (httpFirst !== undefined) return
|
|
179
|
+
if (httpFirst !== undefined) return scopedProbe(httpFirst.provider ?? httpFirst.name ?? httpRoute, httpFirst.baseURL, httpFirst.model, started)
|
|
142
180
|
return { ok: false, error: 'no usable vision provider configured' }
|
|
143
181
|
}
|
|
144
182
|
|