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
package/lib/http-body-limit.js
CHANGED
|
@@ -1,98 +1,116 @@
|
|
|
1
|
-
const DEFAULT_FALLBACK_MAX_BYTES = 4 * 1024 * 1024
|
|
2
|
-
|
|
3
|
-
export const
|
|
4
|
-
export const
|
|
5
|
-
export const
|
|
6
|
-
|
|
7
|
-
|
|
1
|
+
const DEFAULT_FALLBACK_MAX_BYTES = 4 * 1024 * 1024;
|
|
2
|
+
export const MODEL_RESPONSE_MAX_BYTES = 4 * 1024 * 1024;
|
|
3
|
+
export const METADATA_RESPONSE_MAX_BYTES = 512 * 1024;
|
|
4
|
+
export const ERROR_RESPONSE_MAX_BYTES = 64 * 1024;
|
|
5
|
+
export const DOCTOR_RESPONSE_MAX_BYTES = 16 * 1024;
|
|
6
|
+
function responseLike(value) {
|
|
7
|
+
return value !== null && typeof value === 'object'
|
|
8
|
+
? value
|
|
9
|
+
: {};
|
|
10
|
+
}
|
|
8
11
|
function normalizedLimit(value) {
|
|
9
|
-
|
|
10
|
-
|
|
12
|
+
const number = Number(value);
|
|
13
|
+
return Number.isSafeInteger(number) && number > 0 ? number : DEFAULT_FALLBACK_MAX_BYTES;
|
|
11
14
|
}
|
|
12
|
-
|
|
13
15
|
function responseTooLarge(label, limit, observed) {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
const suffix = Number.isFinite(observed) ? ` (observed ${observed} bytes)` : '';
|
|
17
|
+
const error = new Error(`${label} exceeds the ${limit}-byte response limit${suffix}`);
|
|
18
|
+
error.code = 'HTTP_RESPONSE_TOO_LARGE';
|
|
19
|
+
error.maxBytes = limit;
|
|
20
|
+
if (typeof observed === 'number' && Number.isFinite(observed))
|
|
21
|
+
error.observedBytes = observed;
|
|
22
|
+
return error;
|
|
20
23
|
}
|
|
21
|
-
|
|
22
24
|
function declaredLength(response) {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
const raw = responseLike(response).headers?.get?.('content-length');
|
|
26
|
+
if (raw === null || raw === undefined || raw === '')
|
|
27
|
+
return undefined;
|
|
28
|
+
const value = Number(raw);
|
|
29
|
+
return Number.isFinite(value) && value >= 0 ? value : undefined;
|
|
30
|
+
}
|
|
31
|
+
function chunkBytes(value) {
|
|
32
|
+
if (value instanceof Uint8Array)
|
|
33
|
+
return value;
|
|
34
|
+
return new Uint8Array((value ?? []));
|
|
27
35
|
}
|
|
28
|
-
|
|
29
36
|
/**
|
|
30
37
|
* Consume one HTTP response body with a hard byte ceiling enforced while the
|
|
31
38
|
* stream is read. Content-Length is only a preflight: fetch may transparently
|
|
32
39
|
* decompress a response, so the decoded body stream is independently counted.
|
|
33
40
|
*/
|
|
34
41
|
export async function readResponseBytesBounded(response, maxBytes, options = {}) {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
42
|
+
const limit = normalizedLimit(maxBytes);
|
|
43
|
+
const label = typeof options.label === 'string' && options.label !== '' ? options.label : 'HTTP response';
|
|
44
|
+
const declared = declaredLength(response);
|
|
45
|
+
const readable = responseLike(response);
|
|
46
|
+
if (declared !== undefined && declared > limit) {
|
|
47
|
+
try {
|
|
48
|
+
await readable.body?.cancel?.('response body limit exceeded');
|
|
49
|
+
}
|
|
50
|
+
catch { /* best effort */ }
|
|
51
|
+
throw responseTooLarge(label, limit, declared);
|
|
52
|
+
}
|
|
53
|
+
const body = readable.body;
|
|
54
|
+
if (body && typeof body.getReader === 'function') {
|
|
55
|
+
const reader = body.getReader();
|
|
56
|
+
const chunks = [];
|
|
57
|
+
let total = 0;
|
|
58
|
+
try {
|
|
59
|
+
while (true) {
|
|
60
|
+
const { done, value } = await reader.read();
|
|
61
|
+
if (done)
|
|
62
|
+
break;
|
|
63
|
+
const bytes = chunkBytes(value);
|
|
64
|
+
if (total + bytes.byteLength > limit) {
|
|
65
|
+
try {
|
|
66
|
+
await reader.cancel?.('response body limit exceeded');
|
|
67
|
+
}
|
|
68
|
+
catch { /* best effort */ }
|
|
69
|
+
throw responseTooLarge(label, limit, total + bytes.byteLength);
|
|
70
|
+
}
|
|
71
|
+
total += bytes.byteLength;
|
|
72
|
+
chunks.push(Buffer.from(bytes));
|
|
73
|
+
}
|
|
74
|
+
return Buffer.concat(chunks, total);
|
|
75
|
+
}
|
|
76
|
+
finally {
|
|
77
|
+
try {
|
|
78
|
+
reader.releaseLock?.();
|
|
79
|
+
}
|
|
80
|
+
catch { /* best effort */ }
|
|
56
81
|
}
|
|
57
|
-
total += bytes.byteLength
|
|
58
|
-
chunks.push(Buffer.from(bytes))
|
|
59
|
-
}
|
|
60
|
-
return Buffer.concat(chunks, total)
|
|
61
|
-
} finally {
|
|
62
|
-
try { reader.releaseLock() } catch { /* best effort */ }
|
|
63
82
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
83
|
+
// Synthetic test doubles may expose text()/arrayBuffer() without a WHATWG
|
|
84
|
+
// body stream. Real Node fetch responses take the streaming path above, so
|
|
85
|
+
// this compatibility fallback is not the production admission boundary.
|
|
86
|
+
if (typeof readable.arrayBuffer === 'function') {
|
|
87
|
+
const bytes = Buffer.from(await readable.arrayBuffer());
|
|
88
|
+
if (bytes.length > limit)
|
|
89
|
+
throw responseTooLarge(label, limit, bytes.length);
|
|
90
|
+
return bytes;
|
|
91
|
+
}
|
|
92
|
+
if (typeof readable.text === 'function') {
|
|
93
|
+
const text = await readable.text();
|
|
94
|
+
const bytes = Buffer.from(String(text ?? ''), 'utf8');
|
|
95
|
+
if (bytes.length > limit)
|
|
96
|
+
throw responseTooLarge(label, limit, bytes.length);
|
|
97
|
+
return bytes;
|
|
98
|
+
}
|
|
99
|
+
throw new Error(`${label} has no readable response body`);
|
|
81
100
|
}
|
|
82
|
-
|
|
83
101
|
export async function readResponseTextBounded(response, maxBytes, options = {}) {
|
|
84
|
-
|
|
102
|
+
return (await readResponseBytesBounded(response, maxBytes, options)).toString('utf8');
|
|
85
103
|
}
|
|
86
|
-
|
|
87
104
|
export async function readResponseJsonBounded(response, maxBytes, options = {}) {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
105
|
+
const label = typeof options.label === 'string' && options.label !== '' ? options.label : 'HTTP response';
|
|
106
|
+
const text = await readResponseTextBounded(response, maxBytes, options);
|
|
107
|
+
try {
|
|
108
|
+
return JSON.parse(text);
|
|
109
|
+
}
|
|
110
|
+
catch (cause) {
|
|
111
|
+
const error = new Error(`${label} returned invalid JSON`);
|
|
112
|
+
error.code = 'HTTP_RESPONSE_INVALID_JSON';
|
|
113
|
+
error.cause = cause;
|
|
114
|
+
throw error;
|
|
115
|
+
}
|
|
98
116
|
}
|
|
@@ -1,25 +1,34 @@
|
|
|
1
|
+
function objectRecord(value) {
|
|
2
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value)
|
|
3
|
+
? value
|
|
4
|
+
: undefined;
|
|
5
|
+
}
|
|
1
6
|
function boundedAttachmentId(block) {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
7
|
+
const attachment = objectRecord(objectRecord(block)?.attachment);
|
|
8
|
+
const raw = attachment?.attachmentId ?? attachment?.id;
|
|
9
|
+
if (typeof raw !== 'string' || raw.trim() === '')
|
|
10
|
+
return undefined;
|
|
11
|
+
return raw.trim().slice(0, 256);
|
|
6
12
|
}
|
|
7
|
-
|
|
8
13
|
/** DSH 0.1.6+ marks durable request-limit omissions on the ImageBlock itself. */
|
|
9
14
|
export function isOffloadedImageBlock(block) {
|
|
10
|
-
|
|
15
|
+
const value = objectRecord(block);
|
|
16
|
+
return value?.type === 'image' && value.offloaded === true;
|
|
11
17
|
}
|
|
12
|
-
|
|
13
18
|
export function blocksHaveRetainedImage(content) {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
if (!Array.isArray(content))
|
|
20
|
+
return false;
|
|
21
|
+
for (const block of content) {
|
|
22
|
+
const value = objectRecord(block);
|
|
23
|
+
if (value === undefined)
|
|
24
|
+
continue;
|
|
25
|
+
if (value.type === 'image' && value.offloaded !== true)
|
|
26
|
+
return true;
|
|
27
|
+
if (Array.isArray(value.content) && blocksHaveRetainedImage(value.content))
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
return false;
|
|
21
31
|
}
|
|
22
|
-
|
|
23
32
|
/**
|
|
24
33
|
* Dependency-free model-facing placeholder for an image DSH already offloaded.
|
|
25
34
|
*
|
|
@@ -29,10 +38,10 @@ export function blocksHaveRetainedImage(content) {
|
|
|
29
38
|
* read or resend its bytes once the Host projection marks this occurrence.
|
|
30
39
|
*/
|
|
31
40
|
export function offloadedImagePlaceholder(block) {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
41
|
+
const id = boundedAttachmentId(block);
|
|
42
|
+
if (id === undefined) {
|
|
43
|
+
return '[image omitted to fit request image limits; attachment id unavailable]';
|
|
44
|
+
}
|
|
45
|
+
return `[image omitted to fit request image limits; attachment ${id}. ` +
|
|
46
|
+
`To inspect it again, call vision_describe with attachmentIds: [${JSON.stringify(id)}].]`;
|
|
38
47
|
}
|