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.
Files changed (132) hide show
  1. package/README.md +16 -7
  2. package/README.zh.md +16 -7
  3. package/docs/architecture/2.3.0-compat-retirement.md +147 -0
  4. package/docs/architecture/2.3.0-execution-plan.md +410 -0
  5. package/docs/architecture/2.3.0-final-audit.md +279 -0
  6. package/docs/architecture/2.3.0-reopened-plan.md +720 -0
  7. package/docs/architecture/3.0-architecture-plan.md +252 -0
  8. package/docs/architecture/3.0-r0-contract-ledger.md +137 -0
  9. package/docs/architecture/3.0-r1-source-artifact-boundary.md +98 -0
  10. package/docs/architecture/3.0-r10-final-correctness-closure.md +207 -0
  11. package/docs/architecture/3.0-r11-adversarial-review.md +441 -0
  12. package/docs/architecture/3.0-r5-browser-go-no-go.md +165 -0
  13. package/docs/architecture/3.0-r5-browser-typescript-gate.md +105 -0
  14. package/docs/architecture/3.0-r6-compat-retirement.md +203 -0
  15. package/docs/architecture/3.0-r7-public-api.md +151 -0
  16. package/docs/architecture/3.0-r8-ci-release.md +248 -0
  17. package/docs/architecture/3.0-r9-final-audit.md +370 -0
  18. package/docs/architecture/compat-inventory.md +26 -34
  19. package/docs/architecture/dsh-compatibility-matrix.md +65 -54
  20. package/docs/architecture/dsh-support-window.md +30 -22
  21. package/docs/architecture/final-closure-audit.md +2 -0
  22. package/docs/releases/v2.3.0.md +53 -0
  23. package/docs/releases/v3.0.0.md +85 -0
  24. package/entry.js +9 -57
  25. package/index.js +268 -761
  26. package/lib/agent-request-route-authority.js +1 -20
  27. package/lib/catalog-corrections.js +1 -2
  28. package/lib/client-maintenance-actions.js +145 -0
  29. package/lib/client-presentation-boundary-main.js +75 -17
  30. package/lib/client-presentation-boundary.js +53 -28
  31. package/lib/client.js +246 -144
  32. package/lib/core-apply-lifecycle.js +41 -0
  33. package/lib/core-cache.js +87 -0
  34. package/lib/core-primitives.js +91 -278
  35. package/lib/core-vision-surface-domain.js +63 -0
  36. package/lib/core-vision-surface.js +11 -77
  37. package/lib/delegated-call-config.js +15 -24
  38. package/lib/desktop-screenshot-tool.js +262 -0
  39. package/lib/doctor-cli.js +0 -0
  40. package/lib/doctor-runtime.js +4 -1
  41. package/lib/dsh-contract-compat.js +37 -256
  42. package/lib/dsh-host-capabilities.js +3 -9
  43. package/lib/dsh-settings-017-compat.js +17 -10
  44. package/lib/dsh-support-window.js +3 -3
  45. package/lib/file-logger.js +8 -3
  46. package/lib/grounding-coordinate-runtime.js +1 -2
  47. package/lib/http-body-limit.js +97 -79
  48. package/lib/http-compat.js +1 -2
  49. package/lib/image-offload-compat.js +30 -21
  50. package/lib/image-resource-governor.js +210 -228
  51. package/lib/legacy-global-proxy-boundary.js +46 -3
  52. package/lib/live-model-client-prelude.js +12 -4
  53. package/lib/live-model-discovery.js +134 -43
  54. package/lib/local-remote-settings-permission.js +33 -7
  55. package/lib/local-vision-stabilizer.js +31 -17
  56. package/lib/maintenance-routes.js +134 -0
  57. package/lib/native-image-coexistence.js +15 -3
  58. package/lib/ollama-cold-start.js +87 -23
  59. package/lib/pi-ai-bridge-wire-compat.js +3 -2
  60. package/lib/proxy-dispatcher-pool.js +151 -92
  61. package/lib/proxy-routing.js +75 -51
  62. package/lib/proxy-url-compat.js +3 -10
  63. package/lib/public-config.js +36 -0
  64. package/lib/public-entry.d.ts +148 -0
  65. package/lib/public-entry.js +19 -11
  66. package/lib/remote-settings-bridge.js +11 -0
  67. package/lib/remote-settings-risk-client-boundary.js +155 -0
  68. package/lib/remote-settings-risk-confirmation.js +17 -105
  69. package/lib/replay-delegation.js +12 -7
  70. package/lib/replay-envelope-v2-compat.js +12 -7
  71. package/lib/runtime-composition.js +127 -34
  72. package/lib/runtime-i18n-boundary.js +10 -11
  73. package/lib/screenshot-source-boundary.js +2 -2
  74. package/lib/session-affinity-runtime.js +113 -97
  75. package/lib/session-affinity.js +89 -76
  76. package/lib/session-event-history-compat.js +30 -0
  77. package/lib/session-message-source-compat.js +7 -3
  78. package/lib/session-query-readers.js +263 -0
  79. package/lib/session-surface-compat.js +30 -31
  80. package/lib/session-surface-policy-domain.js +85 -0
  81. package/lib/session-surface-policy.js +2 -111
  82. package/lib/session-turn-resolver.js +2 -6
  83. package/lib/session-vision-attachment-index.js +143 -0
  84. package/lib/session-vision-delegation-boundary.js +34 -0
  85. package/lib/session-vision-delegation.js +22 -0
  86. package/lib/session-vision-index.js +179 -116
  87. package/lib/session-vision-mode-authority.js +229 -192
  88. package/lib/session-vision-mode-boundary.js +65 -22
  89. package/lib/session-vision-policy-domain.js +304 -0
  90. package/lib/session-vision-policy.js +104 -0
  91. package/lib/session-vision-state.js +368 -378
  92. package/lib/settings-client-017-compat.js +9 -7
  93. package/lib/settings-client-loader-lifecycle.js +129 -0
  94. package/lib/sharp-runtime.js +0 -7
  95. package/lib/structured-flow-hardening.js +25 -6
  96. package/lib/tesseract-exec-compat.js +9 -1
  97. package/lib/turn-budget-context.js +47 -41
  98. package/lib/update-check.js +11 -2
  99. package/lib/vision-attachment-handle-runtime.js +176 -156
  100. package/lib/vision-backend-runtime-policy.js +19 -2
  101. package/lib/vision-background-benchmark.js +41 -4
  102. package/lib/vision-background-failure-policy.js +41 -41
  103. package/lib/vision-background-stop-store-core.js +217 -0
  104. package/lib/vision-background-stop-store.js +8 -179
  105. package/lib/vision-capability-benchmark-service.js +1 -0
  106. package/lib/vision-capability-benchmark.js +11 -61
  107. package/lib/vision-capability-evidence.js +35 -0
  108. package/lib/vision-capability-fingerprint.js +72 -0
  109. package/lib/vision-capability-identity.js +116 -61
  110. package/lib/vision-capability-router.js +13 -33
  111. package/lib/vision-chunk-assembler.js +95 -0
  112. package/lib/vision-diagnostics-routes.js +246 -0
  113. package/lib/vision-evidence-guidance.js +3 -1
  114. package/lib/vision-execution-order-apply.js +45 -55
  115. package/lib/vision-execution-order.js +19 -19
  116. package/lib/vision-execution-policy.js +12 -7
  117. package/lib/vision-model-registry.js +30 -17
  118. package/lib/vision-provider-transport.js +137 -176
  119. package/lib/vision-routing-authority.js +47 -48
  120. package/lib/vision-routing-product.js +26 -29
  121. package/lib/vision-routing-settings-prelude.js +8 -3
  122. package/lib/vision-runtime-performance-store.js +166 -0
  123. package/lib/vision-runtime-performance.js +13 -142
  124. package/lib/vision-toggle-root-hardening.js +22 -5
  125. package/lib/vision-tool-runtime-boundary.js +104 -31
  126. package/lib/web/dsh-settings-017-root-transport.js +27 -12
  127. package/lib/web/remote-settings-client.js +6 -9
  128. package/lib/web-capability-boundary.js +17 -7
  129. package/lib/wrapper-directory.js +14 -23
  130. package/package.json +56 -20
  131. package/lib/android-attachment-compat.js +0 -244
  132. package/lib/settings-client-rc8-lifecycle.js +0 -397
@@ -14,8 +14,12 @@ import { contextWithNativeImageCoexistence } from './native-image-coexistence.js
14
14
  import { createCoreVisionSurfaceRuntime } from './core-vision-surface.js'
15
15
  import { installSessionVisionIndexBoundary } from './session-vision-index.js'
16
16
  import { createSessionVisionRuntime } from './session-vision-runtime.js'
17
+ import { runCoreApplyLifecycle } from './core-apply-lifecycle.js'
17
18
  import { installLegacyCoreVisionPolicyBridge } from './legacy-core-vision-policy-bridge.js'
18
19
  import { installSessionVisionModeBoundary } from './session-vision-mode-boundary.js'
20
+ import { resolveSessionVisionModeAuthority } from './session-vision-mode-authority.js'
21
+ import { createSessionVisionPolicyRuntimeStore } from './session-vision-policy-domain.js'
22
+ import { installSessionVisionDelegationBoundary } from './session-vision-delegation-boundary.js'
19
23
  import { installPiAiBridgeWireCompat } from './pi-ai-bridge-wire-compat.js'
20
24
  import { installLiveModelDiscovery } from './live-model-discovery.js'
21
25
  import { installVisionModelRegistry } from './vision-model-registry.js'
@@ -23,14 +27,12 @@ import { installAdversarialHardening } from './adversarial-hardening.js'
23
27
  import { installOllamaColdStartGuard } from './ollama-cold-start.js'
24
28
  import { installLocalVisionStabilizer } from './local-vision-stabilizer.js'
25
29
  import { installWrapperDirectoryAlias } from './wrapper-directory.js'
26
- import { installAndroidAttachmentCompat } from './android-attachment-compat.js'
27
30
  import { contextWithCoalescedAdapterUpdates } from './adapter-update-coalescer.js'
28
31
  import { installTesseractExecFileCompat } from './tesseract-exec-compat.js'
29
32
  import { installLocalMutationRouteBoundary } from './web-capability-boundary.js'
30
33
  import { installScreenshotSourceBoundary } from './screenshot-source-boundary.js'
31
34
  import { installVisionToolRuntimeBoundary } from './vision-tool-runtime-boundary.js'
32
35
  import {
33
- configureAgentRequestRouteAuthority,
34
36
  contextWithAgentRequestRouteAuthority,
35
37
  } from './agent-request-route-authority.js'
36
38
  import { contextWithGroundingCoordinateFrame } from './grounding-coordinate-runtime.js'
@@ -57,7 +59,7 @@ import {
57
59
  } from './structured-flow-hardening.js'
58
60
  import { installVisionLimitDiagnostics } from './vision-limit-diagnostics.js'
59
61
  import {
60
- attachmentContextForContract,
62
+ installDshHostCapabilityDiagnostics,
61
63
  createSessionEventReader,
62
64
  createSessionEventTailReader,
63
65
  createSessionLogReader,
@@ -73,6 +75,7 @@ import {
73
75
  } from './web/index.js'
74
76
  import { createRuntimeI18nCoreFacade } from './runtime-i18n-core.js'
75
77
  import { createRuntimeI18nCoreScope } from './runtime-i18n-core-scope.js'
78
+ import { installVisionMaintenanceRoutes } from './maintenance-routes.js'
76
79
 
77
80
  function liveVisionSettings(ctx, fallback) {
78
81
  try {
@@ -95,7 +98,7 @@ function legacyCoreVisionSettings(ctx, fallback) {
95
98
  return { ...current, autoActivateOnImage: false }
96
99
  }
97
100
 
98
- export function applyVisionRuntimeComposition(ctx, config = {}, core) {
101
+ function installHostAndSecurityBoundaries(ctx, config, core) {
99
102
  // SessionVisionIndex calls planGuardStopShadows before Core.apply. Keep that
100
103
  // one helper behind the live-locale facade; the final Core itself still gets
101
104
  // the ordinary composition identity and is localized by the scoped context.
@@ -134,7 +137,9 @@ export function applyVisionRuntimeComposition(ctx, config = {}, core) {
134
137
  config,
135
138
  core,
136
139
  )
137
- const ollamaColdStartCtx = installOllamaColdStartGuard(hardenedCtx, hardenedConfig, core)
140
+ const ollamaColdStartCtx = installOllamaColdStartGuard(hardenedCtx, hardenedConfig, core, {
141
+ logger: logging.logger,
142
+ })
138
143
  const { ctx: stabilizedCtx, bootConfig } = installLocalVisionStabilizer(
139
144
  ollamaColdStartCtx,
140
145
  hardenedConfig,
@@ -174,10 +179,29 @@ export function applyVisionRuntimeComposition(ctx, config = {}, core) {
174
179
  Config: core.Config,
175
180
  })
176
181
  : ownershipCtx
177
- const attachmentCompatCtx = attachmentContextForContract(settingsCtx, logging.logger, {
178
- installAndroidAttachmentCompat,
179
- })
182
+ // DVR 2.3 supports DSH from the 0.1.5 train, whose attachment
183
+ // contract already owns batch persistence. The old no-batch Android wrapper
184
+ // is therefore unreachable; keep capability diagnostics as an explicit
185
+ // composition responsibility instead of hiding that side effect in a shim.
186
+ installDshHostCapabilityDiagnostics(settingsCtx)
187
+ const attachmentCompatCtx = settingsCtx
180
188
 
189
+ return {
190
+ attachmentCompatCtx,
191
+ batchAttachmentHost,
192
+ capabilityStore,
193
+ hardenedConfig,
194
+ hostOwnsOfficialDeepSeek,
195
+ logging,
196
+ runtimeConfig,
197
+ runtimeI18nCore,
198
+ runtimeI18nCoreScope,
199
+ runtimePerformanceStore,
200
+ }
201
+ }
202
+
203
+ function createRuntimeOwners(host, core) {
204
+ const { attachmentCompatCtx, logging, runtimeConfig, runtimeI18nCore } = host
181
205
  const toolRuntimeCtx = installVisionToolRuntimeBoundary(attachmentCompatCtx, runtimeConfig)
182
206
  const nativeImageCompat = contextWithNativeImageCoexistence(toolRuntimeCtx, runtimeConfig)
183
207
  const coreVisionSurfaceRuntime = createCoreVisionSurfaceRuntime({
@@ -208,11 +232,37 @@ export function applyVisionRuntimeComposition(ctx, config = {}, core) {
208
232
  nativeImageCompat.config,
209
233
  { rewriteHistoryImages: core.rewriteHistoryImages },
210
234
  )
235
+ const sessionVisionPolicyStore = createSessionVisionPolicyRuntimeStore(legacyCoreCompat.ctx)
236
+ legacyCoreCompat.ctx.inject?.(['agents'], (agentsCtx) => {
237
+ installSessionVisionDelegationBoundary(
238
+ agentsCtx,
239
+ sessionVisionPolicyStore,
240
+ (parent) => resolveSessionVisionModeAuthority(
241
+ agentsCtx,
242
+ parent,
243
+ legacyCoreCompat.config,
244
+ { sessionPolicy: sessionVisionPolicyStore.get(String(parent.id)) },
245
+ ),
246
+ )
247
+ })
211
248
  const sessionVisionModeCompat = installSessionVisionModeBoundary(
212
249
  legacyCoreCompat.ctx,
213
250
  legacyCoreCompat.config,
251
+ { sessionVisionPolicyStore },
214
252
  )
215
253
 
254
+ return {
255
+ coreVisionSurfaceRuntime,
256
+ nativeImageCompat,
257
+ sessionVisionModeCompat,
258
+ sessionVisionPolicyStore,
259
+ sessionVisionRuntime,
260
+ }
261
+ }
262
+
263
+ function installRoutingAndHostProducts(host, owners, core) {
264
+ const { capabilityStore, logging, runtimeConfig, runtimePerformanceStore } = host
265
+ const { sessionVisionModeCompat } = owners
216
266
  // Final structured-flow guard sits closest to core.apply so it sees the
217
267
  // actual tool registrations and pre-step listener. The diagnostic observer
218
268
  // remains immediately inside it so existing timeout/budget semantics stay
@@ -266,6 +316,19 @@ export function applyVisionRuntimeComposition(ctx, config = {}, core) {
266
316
  })
267
317
  installPiAiBridgeWireCompat(reconciledCtx, logging.logger)
268
318
 
319
+ return {
320
+ breakerShadowHealth,
321
+ liveDiscovery,
322
+ reconciledCtx,
323
+ sessionEventTailReader,
324
+ sessionTurnResolver,
325
+ }
326
+ }
327
+
328
+ function installExecutionBoundaries(host, owners, routing, core, providerTransport) {
329
+ const { capabilityStore, logging, runtimeConfig, runtimeI18nCoreScope, runtimePerformanceStore } = host
330
+ const { sessionVisionRuntime } = owners
331
+ const { breakerShadowHealth, liveDiscovery, reconciledCtx } = routing
269
332
  const executionCtx = contextWithVisionExecutionPolicy(reconciledCtx, {
270
333
  isBridgeEvidence: (provider, model) => liveDiscovery.hasModel(provider, model),
271
334
  evidenceSource: (provider, model) => liveDiscovery.evidenceSource?.(provider, model),
@@ -314,11 +377,13 @@ export function applyVisionRuntimeComposition(ctx, config = {}, core) {
314
377
  core,
315
378
  evidenceSource: (provider, model) => liveDiscovery.evidenceSource?.(provider, model),
316
379
  logger: logging.logger,
380
+ providerTransport,
317
381
  finalContextDecorator: runtimeI18nCoreScope.decorate,
318
382
  })
319
383
  const capabilityBenchmark = installCapabilityBenchmarkService(backendRuntimeCtx, runtimeConfig, core, {
320
384
  logger: logging.logger,
321
385
  store: capabilityStore,
386
+ providerTransport,
322
387
  })
323
388
  attachCapabilityBenchmarkPresentation(capabilityBenchmark, {
324
389
  ctx: backendRuntimeCtx,
@@ -331,13 +396,28 @@ export function applyVisionRuntimeComposition(ctx, config = {}, core) {
331
396
  // Core owns exactly one agent/request routing hook. Protect the completed
332
397
  // provider/model handoff at that event boundary so future route-switch logic
333
398
  // cannot accidentally carry source-model call defaults into the target.
334
- configureAgentRequestRouteAuthority(backendRuntimeCtx, {
399
+ const coreRequestAuthorityCtx = contextWithAgentRequestRouteAuthority(backendRuntimeCtx, {
335
400
  wrapperRoute: runtimeConfig.wrapperRoute,
336
401
  chainRoute: runtimeConfig.chainRoute,
337
402
  logger: logging.logger,
338
403
  })
339
- const coreRequestAuthorityCtx = contextWithAgentRequestRouteAuthority(backendRuntimeCtx)
340
404
 
405
+ return { coreRequestAuthorityCtx }
406
+ }
407
+
408
+ function applyMatureCoreBridge(host, owners, routing, execution, core, runtime) {
409
+ const {
410
+ attachmentCompatCtx,
411
+ batchAttachmentHost,
412
+ hardenedConfig,
413
+ hostOwnsOfficialDeepSeek,
414
+ logging,
415
+ runtimeConfig,
416
+ runtimeI18nCoreScope,
417
+ } = host
418
+ const { coreVisionSurfaceRuntime, sessionVisionModeCompat, sessionVisionRuntime } = owners
419
+ const { breakerShadowHealth, sessionEventTailReader, sessionTurnResolver } = routing
420
+ const { coreRequestAuthorityCtx } = execution
341
421
  try {
342
422
  const c = hardenedConfig && typeof hardenedConfig === 'object' ? hardenedConfig : {}
343
423
  const local = c.localOllama && typeof c.localOllama === 'object' ? c.localOllama : {}
@@ -354,8 +434,27 @@ export function applyVisionRuntimeComposition(ctx, config = {}, core) {
354
434
  /* diagnostics must never break apply */
355
435
  }
356
436
 
357
- try {
358
- const result = runtimeI18nCoreScope.run(() =>
437
+ let schemaBootstrapFinished = false
438
+ const finishSchemaBootstrapOnce = () => {
439
+ if (schemaBootstrapFinished) return
440
+ schemaBootstrapFinished = true
441
+ coreVisionSurfaceRuntime.finishSchemaBootstrap()
442
+ }
443
+ const installPostCoreRuntime = () => {
444
+ installVisionMaintenanceRoutes(coreRequestAuthorityCtx, { logger: logging.logger })
445
+ finishSchemaBootstrapOnce()
446
+ installWrapperDirectoryAlias(attachmentCompatCtx, runtimeConfig, logging.logger)
447
+ }
448
+ const failCoreApply = (error) => {
449
+ finishSchemaBootstrapOnce()
450
+ logging.logger.error(
451
+ 'vision-router: plugin apply failed: %s',
452
+ error && error.stack ? error.stack : error && error.message ? error.message : String(error),
453
+ )
454
+ }
455
+
456
+ return runCoreApplyLifecycle(
457
+ () => runtimeI18nCoreScope.run(() =>
359
458
  withVisionCircuitBreakerObserver(
360
459
  breakerShadowHealth.capture,
361
460
  () => core.apply(
@@ -367,28 +466,22 @@ export function applyVisionRuntimeComposition(ctx, config = {}, core) {
367
466
  sessionTurnResolver,
368
467
  sessionEventTailReader,
369
468
  hostOwnsOfficialDeepSeek,
469
+ providerTransport: runtime?.providerTransport,
370
470
  },
371
471
  ),
372
472
  ),
373
- )
374
- coreVisionSurfaceRuntime.finishSchemaBootstrap()
375
- installWrapperDirectoryAlias(attachmentCompatCtx, runtimeConfig, logging.logger)
376
- if (result && typeof result.then === 'function') {
377
- return result.catch((error) => {
378
- logging.logger.error(
379
- 'vision-router: plugin apply failed: %s',
380
- error && error.stack ? error.stack : error && error.message ? error.message : String(error),
381
- )
382
- throw error
383
- })
384
- }
385
- return result
386
- } catch (error) {
387
- coreVisionSurfaceRuntime.finishSchemaBootstrap()
388
- logging.logger.error(
389
- 'vision-router: plugin apply failed: %s',
390
- error && error.stack ? error.stack : error && error.message ? error.message : String(error),
391
- )
392
- throw error
393
- }
394
- }
473
+ ),
474
+ {
475
+ onSuccess: installPostCoreRuntime,
476
+ onFailure: failCoreApply,
477
+ },
478
+ )
479
+ }
480
+
481
+ export function applyVisionRuntimeComposition(ctx, config = {}, core, runtime = {}) {
482
+ const host = installHostAndSecurityBoundaries(ctx, config, core)
483
+ const owners = createRuntimeOwners(host, core)
484
+ const routing = installRoutingAndHostProducts(host, owners, core)
485
+ const execution = installExecutionBoundaries(host, owners, routing, core, runtime?.providerTransport)
486
+ return applyMatureCoreBridge(host, owners, routing, execution, core, runtime)
487
+ }
@@ -1,6 +1,8 @@
1
1
  import { createRuntimeI18n, DEEP_TOOL_MOUNT_STATE } from './runtime-i18n.js'
2
2
  import { depthCopyFor } from './depth-guidance.js'
3
3
  import { blocksHaveRetainedImage } from './image-offload-compat.js'
4
+ import { captureFetchDelegate, installFetchWrapper } from './fetch-wrapper-lifecycle.js'
5
+ import { visionRouterMessageSource } from './session-message-source-compat.js'
4
6
 
5
7
  const PLUGIN_NAME = 'dsh-vision-router'
6
8
  const DEEP_TOOL_NAMES = new Set([
@@ -432,7 +434,7 @@ function addAutoMountReminder(decision, payload, i18n) {
432
434
  : `${Date.now()}-${Math.floor(Math.random() * 1e9)}`
433
435
  }`,
434
436
  content: [{ type: 'text', text: i18n.t('autoMountReminder') }],
435
- source: { kind: 'plugin', plugin: PLUGIN_NAME },
437
+ source: visionRouterMessageSource(payload?.agent?.session),
436
438
  }
437
439
  const structuredIndex = base.findIndex((message) =>
438
440
  typeof message?.id === 'string' && message.id.startsWith('vision-router-structured-'),
@@ -520,9 +522,9 @@ function localizeRequestBody(value, i18n) {
520
522
  function installPromptFetchBoundary(ctx, i18n) {
521
523
  if (typeof ctx?.effect !== 'function' || typeof globalThis.fetch !== 'function') return
522
524
  ctx.effect(() => {
523
- const originalFetch = globalThis.fetch
525
+ const delegateFetch = captureFetchDelegate()
524
526
  const localizedFetch = (input, init) => {
525
- if (i18n.language() !== 'en' || typeof init?.body !== 'string') return originalFetch(input, init)
527
+ if (i18n.language() !== 'en' || typeof init?.body !== 'string') return delegateFetch(input, init)
526
528
  const body = init.body
527
529
  if (
528
530
  !body.includes('请按以下结构识别这张图片') &&
@@ -530,21 +532,18 @@ function installPromptFetchBoundary(ctx, i18n) {
530
532
  !body.includes(OCR_FALLBACK_PROMPT_ZH) &&
531
533
  !body.includes(LONG_SCREENSHOT_OCR_PROMPT_ZH)
532
534
  ) {
533
- return originalFetch(input, init)
535
+ return delegateFetch(input, init)
534
536
  }
535
537
  try {
536
538
  const parsed = JSON.parse(body)
537
539
  const localized = localizeRequestBody(parsed, i18n)
538
- if (localized === parsed) return originalFetch(input, init)
539
- return originalFetch(input, { ...init, body: JSON.stringify(localized) })
540
+ if (localized === parsed) return delegateFetch(input, init)
541
+ return delegateFetch(input, { ...init, body: JSON.stringify(localized) })
540
542
  } catch {
541
- return originalFetch(input, init)
543
+ return delegateFetch(input, init)
542
544
  }
543
545
  }
544
- globalThis.fetch = localizedFetch
545
- return () => {
546
- if (globalThis.fetch === localizedFetch) globalThis.fetch = originalFetch
547
- }
546
+ return installFetchWrapper(localizedFetch)
548
547
  }, 'vision-router: runtime i18n prompt boundary')
549
548
  }
550
549
 
@@ -110,7 +110,7 @@ function wrapTools(tools, ctx, core) {
110
110
  export function installScreenshotSourceBoundary(ctx, core) {
111
111
  if (!ctx || (typeof ctx !== 'object' && typeof ctx !== 'function')) return ctx
112
112
  const cached = wrappedContexts.get(ctx)
113
- if (cached) return cached
113
+ if (cached?.core === core) return cached.wrapped
114
114
  let wrapped
115
115
  wrapped = new Proxy(ctx, {
116
116
  get(target, property) {
@@ -119,6 +119,6 @@ export function installScreenshotSourceBoundary(ctx, core) {
119
119
  return typeof value === 'function' ? value.bind(target) : value
120
120
  },
121
121
  })
122
- wrappedContexts.set(ctx, wrapped)
122
+ wrappedContexts.set(ctx, { core, wrapped })
123
123
  return wrapped
124
124
  }
@@ -1,104 +1,120 @@
1
- import { AsyncLocalStorage } from 'node:async_hooks'
2
- import { rawSessionIdentity } from './session-affinity.js'
3
-
4
- const affinityRuntime = new AsyncLocalStorage()
5
-
1
+ import { AsyncLocalStorage } from 'node:async_hooks';
2
+ import { rawSessionIdentity } from './session-affinity.js';
3
+ const affinityRuntime = new AsyncLocalStorage();
4
+ function propertyBag(value) {
5
+ return value !== null && (typeof value === 'object' || typeof value === 'function')
6
+ ? value
7
+ : undefined;
8
+ }
9
+ function isPromiseLike(value) {
10
+ return typeof propertyBag(value)?.then === 'function';
11
+ }
6
12
  export function currentVisionSessionAffinityId() {
7
- const state = affinityRuntime.getStore()
8
- return state?.active === true ? state.affinityId : undefined
13
+ const state = affinityRuntime.getStore();
14
+ return state?.active === true ? state.affinityId : undefined;
9
15
  }
10
-
11
16
  export function runWithVisionSessionAffinity(affinityId, callback) {
12
- const raw = rawSessionIdentity(affinityId)
13
- if (raw === undefined) return callback()
14
- const state = { affinityId: raw, active: true }
15
- let result
16
- try {
17
- result = affinityRuntime.run(state, callback)
18
- } catch (error) {
19
- state.active = false
20
- throw error
21
- }
22
- if (result && typeof result.then === 'function') {
23
- return Promise.resolve(result).finally(() => { state.active = false })
24
- }
25
- state.active = false
26
- return result
17
+ const raw = rawSessionIdentity(affinityId);
18
+ if (raw === undefined)
19
+ return callback();
20
+ const state = {
21
+ affinityId: raw,
22
+ active: true,
23
+ };
24
+ let result;
25
+ try {
26
+ result = affinityRuntime.run(state, callback);
27
+ }
28
+ catch (error) {
29
+ state.active = false;
30
+ throw error;
31
+ }
32
+ if (isPromiseLike(result)) {
33
+ return Promise.resolve(result).finally(() => {
34
+ state.active = false;
35
+ });
36
+ }
37
+ state.active = false;
38
+ return result;
27
39
  }
28
-
29
- /**
30
- * Keep AsyncLocalStorage active across lazy AsyncIterable creation and every
31
- * iterator operation. Adapter/network work often begins on next(), not when
32
- * ctx.llm.stream() returns the iterable. Promise-returning test/compat streams
33
- * are supported too because some older seams resolve the iterable lazily.
34
- * One mutable state object is retired at stream completion so async work that
35
- * outlives the model request cannot keep projecting the conversation id.
36
- */
37
40
  export function streamWithVisionSessionAffinity(affinityId, streamFactory) {
38
- const raw = rawSessionIdentity(affinityId)
39
- if (raw === undefined) return streamFactory()
40
- return {
41
- [Symbol.asyncIterator]() {
42
- const state = { affinityId: raw, active: true }
43
- let iteratorPromise
44
- const retire = () => { state.active = false }
45
- const ensure = () => {
46
- if (iteratorPromise === undefined) {
47
- iteratorPromise = affinityRuntime.run(state, async () => {
48
- const stream = await streamFactory()
49
- if (!stream || typeof stream[Symbol.asyncIterator] !== 'function') {
50
- throw new TypeError('scoped vision stream is not async iterable')
51
- }
52
- return stream[Symbol.asyncIterator]()
53
- })
54
- }
55
- return iteratorPromise
56
- }
57
- return {
58
- next(value) {
59
- return affinityRuntime.run(state, async () => {
60
- try {
61
- const result = await (await ensure()).next(value)
62
- if (result?.done === true) retire()
63
- return result
64
- } catch (error) {
65
- retire()
66
- throw error
67
- }
68
- })
69
- },
70
- return(value) {
71
- if (iteratorPromise === undefined) {
72
- retire()
73
- return Promise.resolve({ done: true, value })
74
- }
75
- return affinityRuntime.run(state, async () => {
76
- try {
77
- const active = await iteratorPromise
78
- return typeof active.return === 'function'
79
- ? await active.return(value)
80
- : { done: true, value }
81
- } finally {
82
- retire()
83
- }
84
- })
85
- },
86
- throw(error) {
87
- if (iteratorPromise === undefined) {
88
- retire()
89
- return Promise.reject(error)
90
- }
91
- return affinityRuntime.run(state, async () => {
92
- try {
93
- const active = await iteratorPromise
94
- if (typeof active.throw === 'function') return await active.throw(error)
95
- throw error
96
- } finally {
97
- retire()
98
- }
99
- })
41
+ const raw = rawSessionIdentity(affinityId);
42
+ if (raw === undefined)
43
+ return streamFactory();
44
+ return {
45
+ [Symbol.asyncIterator]() {
46
+ const state = {
47
+ affinityId: raw,
48
+ active: true,
49
+ };
50
+ let iteratorPromise;
51
+ const retire = () => {
52
+ state.active = false;
53
+ };
54
+ const ensure = () => {
55
+ if (iteratorPromise === undefined) {
56
+ iteratorPromise = affinityRuntime.run(state, async () => {
57
+ const stream = await streamFactory();
58
+ if (stream === null
59
+ || (typeof stream !== 'object' && typeof stream !== 'function')
60
+ || typeof stream[Symbol.asyncIterator] !== 'function') {
61
+ throw new TypeError('scoped vision stream is not async iterable');
62
+ }
63
+ return stream[Symbol.asyncIterator]();
64
+ });
65
+ }
66
+ return iteratorPromise;
67
+ };
68
+ return {
69
+ next(value) {
70
+ return affinityRuntime.run(state, async () => {
71
+ try {
72
+ const result = await (await ensure()).next(value);
73
+ if (result?.done === true)
74
+ retire();
75
+ return result;
76
+ }
77
+ catch (error) {
78
+ retire();
79
+ throw error;
80
+ }
81
+ });
82
+ },
83
+ return(value) {
84
+ if (iteratorPromise === undefined) {
85
+ retire();
86
+ return Promise.resolve({ done: true, value });
87
+ }
88
+ return affinityRuntime.run(state, async () => {
89
+ try {
90
+ const active = await ensure();
91
+ return typeof active.return === 'function'
92
+ ? await active.return(value)
93
+ : { done: true, value };
94
+ }
95
+ finally {
96
+ retire();
97
+ }
98
+ });
99
+ },
100
+ throw(error) {
101
+ if (iteratorPromise === undefined) {
102
+ retire();
103
+ return Promise.reject(error);
104
+ }
105
+ return affinityRuntime.run(state, async () => {
106
+ try {
107
+ const active = await ensure();
108
+ if (typeof active.throw === 'function')
109
+ return await active.throw(error);
110
+ throw error;
111
+ }
112
+ finally {
113
+ retire();
114
+ }
115
+ });
116
+ },
117
+ };
100
118
  },
101
- }
102
- },
103
- }
119
+ };
104
120
  }