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.
Files changed (105) hide show
  1. package/README.md +18 -3
  2. package/README.zh.md +16 -3
  3. package/docs/architecture/3.0-architecture-plan.md +252 -0
  4. package/docs/architecture/3.0-r0-contract-ledger.md +137 -0
  5. package/docs/architecture/3.0-r1-source-artifact-boundary.md +98 -0
  6. package/docs/architecture/3.0-r10-final-correctness-closure.md +207 -0
  7. package/docs/architecture/3.0-r11-adversarial-review.md +447 -0
  8. package/docs/architecture/3.0-r5-browser-go-no-go.md +165 -0
  9. package/docs/architecture/3.0-r5-browser-typescript-gate.md +105 -0
  10. package/docs/architecture/3.0-r6-compat-retirement.md +203 -0
  11. package/docs/architecture/3.0-r7-public-api.md +151 -0
  12. package/docs/architecture/3.0-r8-ci-release.md +248 -0
  13. package/docs/architecture/3.0-r9-final-audit.md +370 -0
  14. package/docs/architecture/dsh-support-window.md +13 -3
  15. package/docs/releases/v3.0.0.md +85 -0
  16. package/docs/releases/v3.0.1.md +77 -0
  17. package/entry.js +1 -1
  18. package/index.js +110 -71
  19. package/lib/agent-request-route-authority.js +1 -20
  20. package/lib/core-apply-lifecycle.js +37 -43
  21. package/lib/core-cache.js +87 -0
  22. package/lib/core-primitives.js +11 -144
  23. package/lib/core-vision-surface-domain.js +63 -0
  24. package/lib/core-vision-surface.js +11 -77
  25. package/lib/delegated-call-config.js +15 -24
  26. package/lib/doctor-cli.js +0 -0
  27. package/lib/doctor-runtime.js +4 -1
  28. package/lib/dsh-contract-compat.js +26 -230
  29. package/lib/dsh-host-capabilities.js +3 -9
  30. package/lib/dsh-settings-017-compat.js +17 -10
  31. package/lib/file-logger.js +8 -3
  32. package/lib/grounding-coordinate-runtime.js +1 -2
  33. package/lib/http-body-limit.js +97 -79
  34. package/lib/image-offload-compat.js +30 -21
  35. package/lib/image-resource-governor.js +210 -228
  36. package/lib/legacy-global-proxy-boundary.js +46 -3
  37. package/lib/live-model-client-prelude.js +12 -4
  38. package/lib/live-model-discovery.js +134 -43
  39. package/lib/local-vision-stabilizer.js +33 -18
  40. package/lib/maintenance-routes.js +1 -3
  41. package/lib/native-image-coexistence.js +15 -3
  42. package/lib/ollama-cold-start.js +87 -23
  43. package/lib/pi-ai-bridge-wire-compat.js +3 -2
  44. package/lib/proxy-dispatcher-pool.js +151 -92
  45. package/lib/proxy-routing.js +75 -51
  46. package/lib/proxy-url-compat.js +3 -10
  47. package/lib/public-config.js +23 -44
  48. package/lib/public-entry.d.ts +148 -0
  49. package/lib/public-entry.js +17 -4
  50. package/lib/replay-delegation.js +12 -7
  51. package/lib/replay-envelope-v2-compat.js +12 -7
  52. package/lib/runtime-composition.js +30 -10
  53. package/lib/screenshot-source-boundary.js +2 -2
  54. package/lib/session-affinity-runtime.js +113 -97
  55. package/lib/session-affinity.js +89 -76
  56. package/lib/session-event-history-compat.js +30 -0
  57. package/lib/session-query-readers.js +263 -0
  58. package/lib/session-surface-compat.js +30 -31
  59. package/lib/session-surface-policy-domain.js +85 -0
  60. package/lib/session-surface-policy.js +2 -111
  61. package/lib/session-turn-resolver.js +2 -6
  62. package/lib/session-vision-attachment-index.js +143 -0
  63. package/lib/session-vision-delegation-boundary.js +34 -0
  64. package/lib/session-vision-delegation.js +22 -0
  65. package/lib/session-vision-index.js +25 -107
  66. package/lib/session-vision-mode-authority.js +229 -192
  67. package/lib/session-vision-mode-boundary.js +65 -22
  68. package/lib/session-vision-policy-domain.js +304 -0
  69. package/lib/session-vision-policy.js +104 -0
  70. package/lib/session-vision-state.js +368 -378
  71. package/lib/settings-client-017-compat.js +16 -10
  72. package/lib/structured-flow-hardening.js +22 -4
  73. package/lib/tesseract-exec-compat.js +9 -1
  74. package/lib/turn-budget-context.js +47 -41
  75. package/lib/update-check.js +11 -2
  76. package/lib/vision-attachment-handle-runtime.js +176 -156
  77. package/lib/vision-backend-runtime-policy.js +16 -0
  78. package/lib/vision-background-benchmark.js +55 -6
  79. package/lib/vision-background-failure-policy.js +41 -41
  80. package/lib/vision-background-stop-store-core.js +217 -0
  81. package/lib/vision-background-stop-store.js +8 -179
  82. package/lib/vision-capability-benchmark.js +11 -61
  83. package/lib/vision-capability-evidence.js +35 -0
  84. package/lib/vision-capability-fingerprint.js +72 -0
  85. package/lib/vision-capability-identity.js +116 -61
  86. package/lib/vision-capability-router.js +13 -33
  87. package/lib/vision-chunk-assembler.js +95 -0
  88. package/lib/vision-diagnostics-routes.js +42 -4
  89. package/lib/vision-execution-order-apply.js +45 -55
  90. package/lib/vision-execution-order.js +19 -19
  91. package/lib/vision-execution-policy.js +12 -7
  92. package/lib/vision-model-registry.js +30 -17
  93. package/lib/vision-model-visibility-boundary-main.js +15 -16
  94. package/lib/vision-provider-transport.js +137 -147
  95. package/lib/vision-routing-authority.js +47 -48
  96. package/lib/vision-routing-product.js +26 -29
  97. package/lib/vision-routing-settings-prelude.js +8 -3
  98. package/lib/vision-runtime-performance-store.js +166 -0
  99. package/lib/vision-runtime-performance.js +13 -142
  100. package/lib/vision-tool-runtime-boundary.js +99 -30
  101. package/lib/web/dsh-settings-017-root-transport.js +27 -12
  102. package/lib/web/remote-settings-client.js +4 -7
  103. package/lib/web-capability-boundary.js +17 -7
  104. package/lib/wrapper-directory.js +14 -23
  105. package/package.json +37 -11
@@ -1,98 +1,116 @@
1
- const DEFAULT_FALLBACK_MAX_BYTES = 4 * 1024 * 1024
2
-
3
- export const MODEL_RESPONSE_MAX_BYTES = 4 * 1024 * 1024
4
- export const METADATA_RESPONSE_MAX_BYTES = 512 * 1024
5
- export const ERROR_RESPONSE_MAX_BYTES = 64 * 1024
6
- export const DOCTOR_RESPONSE_MAX_BYTES = 16 * 1024
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
- const number = Number(value)
10
- return Number.isSafeInteger(number) && number > 0 ? number : DEFAULT_FALLBACK_MAX_BYTES
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
- const suffix = Number.isFinite(observed) ? ` (observed ${observed} bytes)` : ''
15
- const error = new Error(`${label} exceeds the ${limit}-byte response limit${suffix}`)
16
- error.code = 'HTTP_RESPONSE_TOO_LARGE'
17
- error.maxBytes = limit
18
- if (Number.isFinite(observed)) error.observedBytes = observed
19
- return error
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
- const raw = response?.headers?.get?.('content-length')
24
- if (raw === null || raw === undefined || raw === '') return undefined
25
- const value = Number(raw)
26
- return Number.isFinite(value) && value >= 0 ? value : undefined
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
- const limit = normalizedLimit(maxBytes)
36
- const label = typeof options.label === 'string' && options.label !== '' ? options.label : 'HTTP response'
37
- const declared = declaredLength(response)
38
- if (declared !== undefined && declared > limit) {
39
- try { await response?.body?.cancel?.('response body limit exceeded') } catch { /* best effort */ }
40
- throw responseTooLarge(label, limit, declared)
41
- }
42
-
43
- const body = response?.body
44
- if (body && typeof body.getReader === 'function') {
45
- const reader = body.getReader()
46
- const chunks = []
47
- let total = 0
48
- try {
49
- while (true) {
50
- const { done, value } = await reader.read()
51
- if (done) break
52
- const bytes = value instanceof Uint8Array ? value : new Uint8Array(value ?? [])
53
- if (total + bytes.byteLength > limit) {
54
- try { await reader.cancel('response body limit exceeded') } catch { /* best effort */ }
55
- throw responseTooLarge(label, limit, total + bytes.byteLength)
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
- // Synthetic test doubles may expose text()/arrayBuffer() without a WHATWG
67
- // body stream. Real Node fetch responses take the streaming path above, so
68
- // this compatibility fallback is not the production admission boundary.
69
- if (typeof response?.arrayBuffer === 'function') {
70
- const bytes = Buffer.from(await response.arrayBuffer())
71
- if (bytes.length > limit) throw responseTooLarge(label, limit, bytes.length)
72
- return bytes
73
- }
74
- if (typeof response?.text === 'function') {
75
- const text = await response.text()
76
- const bytes = Buffer.from(String(text ?? ''), 'utf8')
77
- if (bytes.length > limit) throw responseTooLarge(label, limit, bytes.length)
78
- return bytes
79
- }
80
- throw new Error(`${label} has no readable response body`)
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
- return (await readResponseBytesBounded(response, maxBytes, options)).toString('utf8')
102
+ return (await readResponseBytesBounded(response, maxBytes, options)).toString('utf8');
85
103
  }
86
-
87
104
  export async function readResponseJsonBounded(response, maxBytes, options = {}) {
88
- const label = typeof options.label === 'string' && options.label !== '' ? options.label : 'HTTP response'
89
- const text = await readResponseTextBounded(response, maxBytes, options)
90
- try {
91
- return JSON.parse(text)
92
- } catch (cause) {
93
- const error = new Error(`${label} returned invalid JSON`)
94
- error.code = 'HTTP_RESPONSE_INVALID_JSON'
95
- error.cause = cause
96
- throw error
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
- const attachment = block && typeof block === 'object' ? block.attachment : undefined
3
- const raw = attachment && (attachment.attachmentId ?? attachment.id)
4
- if (typeof raw !== 'string' || raw.trim() === '') return undefined
5
- return raw.trim().slice(0, 256)
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
- return !!block && block.type === 'image' && block.offloaded === true
15
+ const value = objectRecord(block);
16
+ return value?.type === 'image' && value.offloaded === true;
11
17
  }
12
-
13
18
  export function blocksHaveRetainedImage(content) {
14
- if (!Array.isArray(content)) return false
15
- for (const block of content) {
16
- if (!block || typeof block !== 'object') continue
17
- if (block.type === 'image' && block.offloaded !== true) return true
18
- if (Array.isArray(block.content) && blocksHaveRetainedImage(block.content)) return true
19
- }
20
- return false
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
- const id = boundedAttachmentId(block)
33
- if (id === undefined) {
34
- return '[image omitted to fit request image limits; attachment id unavailable]'
35
- }
36
- return `[image omitted to fit request image limits; attachment ${id}. ` +
37
- `To inspect it again, call vision_describe with attachmentIds: [${JSON.stringify(id)}].]`
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
  }