dsh-vision-router 2.2.8 → 2.3.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 (49) hide show
  1. package/README.md +7 -7
  2. package/README.zh.md +7 -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/compat-inventory.md +26 -34
  8. package/docs/architecture/dsh-compatibility-matrix.md +65 -54
  9. package/docs/architecture/dsh-support-window.md +27 -21
  10. package/docs/architecture/final-closure-audit.md +2 -0
  11. package/docs/releases/v2.3.0.md +53 -0
  12. package/entry.js +8 -56
  13. package/index.js +161 -691
  14. package/lib/catalog-corrections.js +1 -2
  15. package/lib/client-maintenance-actions.js +145 -0
  16. package/lib/client-presentation-boundary-main.js +75 -17
  17. package/lib/client-presentation-boundary.js +53 -28
  18. package/lib/client.js +246 -144
  19. package/lib/core-apply-lifecycle.js +47 -0
  20. package/lib/core-primitives.js +88 -135
  21. package/lib/desktop-screenshot-tool.js +262 -0
  22. package/lib/dsh-contract-compat.js +11 -26
  23. package/lib/dsh-support-window.js +3 -3
  24. package/lib/http-compat.js +1 -2
  25. package/lib/local-remote-settings-permission.js +33 -7
  26. package/lib/maintenance-routes.js +136 -0
  27. package/lib/public-config.js +57 -0
  28. package/lib/public-entry.js +2 -7
  29. package/lib/remote-settings-bridge.js +11 -0
  30. package/lib/remote-settings-risk-client-boundary.js +155 -0
  31. package/lib/remote-settings-risk-confirmation.js +17 -105
  32. package/lib/runtime-composition.js +105 -30
  33. package/lib/runtime-i18n-boundary.js +10 -11
  34. package/lib/session-message-source-compat.js +7 -3
  35. package/lib/session-vision-index.js +154 -9
  36. package/lib/settings-client-loader-lifecycle.js +129 -0
  37. package/lib/sharp-runtime.js +0 -7
  38. package/lib/structured-flow-hardening.js +3 -2
  39. package/lib/vision-backend-runtime-policy.js +3 -2
  40. package/lib/vision-capability-benchmark-service.js +1 -0
  41. package/lib/vision-diagnostics-routes.js +216 -0
  42. package/lib/vision-evidence-guidance.js +3 -1
  43. package/lib/vision-provider-transport.js +0 -29
  44. package/lib/vision-toggle-root-hardening.js +22 -5
  45. package/lib/vision-tool-runtime-boundary.js +5 -1
  46. package/lib/web/remote-settings-client.js +2 -2
  47. package/package.json +23 -13
  48. package/lib/android-attachment-compat.js +0 -244
  49. package/lib/settings-client-rc8-lifecycle.js +0 -397
@@ -0,0 +1,129 @@
1
+ import { injectClientCarrierHtml, installClientScriptCarriers } from './client-script-carrier.js'
2
+ import {
3
+ LOCAL_PERMISSION_CLIENT_BOUNDARY_SOURCE,
4
+ LOCAL_REMOTE_SETTINGS_PERMISSION_FIELD,
5
+ LOCAL_REMOTE_SETTINGS_PERMISSION_PATH,
6
+ } from './local-remote-settings-permission.js'
7
+ import { REMOTE_SETTINGS_RISK_CLIENT_BOUNDARY_SOURCE } from './remote-settings-risk-client-boundary.js'
8
+
9
+ // Keep the legacy marker value so mixed-generation/HMR pages do not install
10
+ // a second lifecycle wrapper after the source/API rename.
11
+ const SETTINGS_CLIENT_LOADER_LIFECYCLE_MARK = 'data-vision-router-settings-rc8-lifecycle'
12
+
13
+ /**
14
+ * Supported Hosts can replace ModuleLoader.load() inside loader.create(). This
15
+ * compatibility owner only re-attaches the current local-permission and
16
+ * remote-risk product boundaries after that queue-to-live transition; product
17
+ * semantics stay in their capability owners.
18
+ */
19
+ export const SETTINGS_CLIENT_LOADER_LIFECYCLE_PRELUDE = String.raw`(function(){
20
+ 'use strict';
21
+ var TARGET = 'dsh-vision-router';
22
+ ${LOCAL_PERMISSION_CLIENT_BOUNDARY_SOURCE}
23
+ var localPermissionBoundary = createLocalRemoteSettingsPermissionClientBoundary({
24
+ endpoint: '${LOCAL_REMOTE_SETTINGS_PERMISSION_PATH}',
25
+ field: '${LOCAL_REMOTE_SETTINGS_PERMISSION_FIELD}',
26
+ fetchImpl: typeof fetch === 'function'
27
+ ? function(url, options) { return fetch(url, options); }
28
+ : undefined
29
+ });
30
+
31
+ ${REMOTE_SETTINGS_RISK_CLIENT_BOUNDARY_SOURCE}
32
+ var riskBoundary = createRemoteSettingsRiskClientBoundary({
33
+ confirmImpl: function(message) {
34
+ return typeof window.confirm === 'function' && window.confirm(message) === true;
35
+ },
36
+ alertImpl: function(message) {
37
+ try { if (typeof window.alert === 'function') window.alert(message); } catch (_) {}
38
+ },
39
+ locale: function() {
40
+ try {
41
+ return String((document && document.documentElement && document.documentElement.lang)
42
+ || (navigator && navigator.language) || '');
43
+ } catch (_) { return ''; }
44
+ },
45
+ location: function() {
46
+ try { return window && window.location; } catch (_) { return undefined; }
47
+ },
48
+ normalizeLoopback: true
49
+ });
50
+
51
+ function patchLiveLoader(loader) {
52
+ if (!loader || typeof loader.load !== 'function' || loader.load.__visionRouterSettingsRc8Lifecycle) return;
53
+ var original = loader.load;
54
+ function load(spec) {
55
+ if (spec && spec.id === TARGET && typeof spec.factory === 'function') {
56
+ var factory = spec.factory;
57
+ spec = Object.assign({}, spec, {
58
+ factory: function(require) {
59
+ var exports = factory(require);
60
+ if (exports && typeof exports.apply === 'function' && !exports.apply.__visionRouterSettingsRc8Lifecycle) {
61
+ var apply = exports.apply;
62
+ var wrappedApply = function(ctx) {
63
+ var rest = Array.prototype.slice.call(arguments, 1);
64
+ var nextCtx = riskBoundary.wrapContext(localPermissionBoundary.wrapContext(ctx));
65
+ return apply.apply(exports, [nextCtx].concat(rest));
66
+ };
67
+ Object.defineProperty(wrappedApply, '__visionRouterSettingsRc8Lifecycle', { value: true });
68
+ exports.apply = wrappedApply;
69
+ }
70
+ return exports;
71
+ }
72
+ });
73
+ }
74
+ return original.call(this, spec);
75
+ }
76
+ Object.defineProperty(load, '__visionRouterSettingsRc8Lifecycle', { value: true });
77
+ loader.load = load;
78
+ }
79
+
80
+ function patchCreate(loader) {
81
+ if (!loader || typeof loader.create !== 'function' || loader.create.__visionRouterSettingsRc8Lifecycle) return;
82
+ var originalCreate = loader.create;
83
+ function create() {
84
+ var result = originalCreate.apply(this, arguments);
85
+ patchLiveLoader(loader);
86
+ return result;
87
+ }
88
+ Object.defineProperty(create, '__visionRouterSettingsRc8Lifecycle', { value: true });
89
+ loader.create = create;
90
+ if (loader.mode === 'live') patchLiveLoader(loader);
91
+ }
92
+
93
+ function install() {
94
+ if (window.__ModuleLoader__) {
95
+ patchCreate(window.__ModuleLoader__);
96
+ return;
97
+ }
98
+ var descriptor = Object.getOwnPropertyDescriptor(window, '__ModuleLoader__');
99
+ if (descriptor && descriptor.configurable === false) return;
100
+ var previousGet = descriptor && descriptor.get;
101
+ var previousSet = descriptor && descriptor.set;
102
+ var stored = descriptor && Object.prototype.hasOwnProperty.call(descriptor, 'value') ? descriptor.value : undefined;
103
+ Object.defineProperty(window, '__ModuleLoader__', {
104
+ configurable: true,
105
+ enumerable: !descriptor || descriptor.enumerable !== false,
106
+ get: function(){ return previousGet ? previousGet.call(window) : stored; },
107
+ set: function(value) {
108
+ if (previousSet) previousSet.call(window, value); else stored = value;
109
+ try { patchCreate(previousGet ? previousGet.call(window) : value); } catch (_) {}
110
+ }
111
+ });
112
+ if (stored) patchCreate(stored);
113
+ }
114
+
115
+ try { install(); } catch (_) {}
116
+ })();`
117
+
118
+ export function injectSettingsClientLoaderLifecyclePrelude(html) {
119
+ return injectClientCarrierHtml(html, SETTINGS_CLIENT_LOADER_LIFECYCLE_MARK, SETTINGS_CLIENT_LOADER_LIFECYCLE_PRELUDE)
120
+ }
121
+
122
+ export function installSettingsClientLoaderLifecycle(ctx) {
123
+ installClientScriptCarriers(ctx, {
124
+ marker: SETTINGS_CLIENT_LOADER_LIFECYCLE_MARK,
125
+ prelude: SETTINGS_CLIENT_LOADER_LIFECYCLE_PRELUDE,
126
+ label: 'vision-router: settings client loader lifecycle',
127
+ injectHtml: injectSettingsClientLoaderLifecyclePrelude,
128
+ })
129
+ }
@@ -10,7 +10,6 @@ import { Worker } from 'node:worker_threads'
10
10
  import { createRequire } from 'node:module'
11
11
  import { pathToFileURL } from 'node:url'
12
12
  import { promisify } from 'node:util'
13
- import { appendPromptToImageOnlyMessage, fetchWithOpenAICompatibility } from './http-compat.js'
14
13
  import {
15
14
  directSessionAffinityHeaders,
16
15
  isOfficialOpenCodeGoUrl,
@@ -19,12 +18,6 @@ import {
19
18
  sessionIdentityOf,
20
19
  } from './session-affinity.js'
21
20
  import { runWithVisionSessionAffinity, streamWithVisionSessionAffinity } from './session-affinity-runtime.js'
22
- import {
23
- routingCorrectionFor,
24
- toAnthropicMessages,
25
- callAnthropicCompatible,
26
- anthropicMediaType,
27
- } from './catalog-corrections.js'
28
21
  import { createCachedUpdateChecker } from './update-check.js'
29
22
  import { probeLocalBackends } from './local-connection-probe.js'
30
23
  import { detectDshSelfUpdatePlan, runDshPluginUpdate } from './self-update.js'
@@ -4,6 +4,7 @@ import {
4
4
  } from './turn-budget-context.js'
5
5
  import { runWithDepthConfig } from './depth-guidance.js'
6
6
  import { runtimeLanguageFor } from './runtime-i18n.js'
7
+ import { visionRouterMessageSource } from './session-message-source-compat.js'
7
8
 
8
9
  const STRUCTURED_EVIDENCE_TOOLS = new Set([
9
10
  'vision_describe',
@@ -440,7 +441,7 @@ function appendGuardMessage(decision, payload, state, config) {
440
441
  ? '已达到本轮设置的深挖次数上限。不要再调用视觉工具;请基于已经获得的证据作答,并明确仍存在的不确定性。'
441
442
  : '连续多次后续视觉调用都没有产出可用证据。不要继续重复调用视觉工具;请基于已经获得的证据作答,并明确仍存在的不确定性。',
442
443
  }],
443
- source: { kind: 'plugin', plugin: 'dsh-vision-router' },
444
+ source: visionRouterMessageSource(payload?.agent?.session),
444
445
  }
445
446
  return appendSyntheticGuardOnce(decision, baseMessages, state, message, true)
446
447
  }
@@ -461,7 +462,7 @@ function appendGuardMessage(decision, payload, state, config) {
461
462
  ? 'The structured bootstrap is complete, but no usable task-directed visual evidence has been produced yet. Call at least one vision tool that can add or verify evidence, then answer after it succeeds.'
462
463
  : '结构化预识别已经完成,但目前还没有产出可用的任务定向视觉证据。请至少调用一个能新增或验证证据的视觉工具,成功后再作答。',
463
464
  }],
464
- source: { kind: 'plugin', plugin: 'dsh-vision-router' },
465
+ source: visionRouterMessageSource(payload?.agent?.session),
465
466
  }
466
467
  return appendSyntheticGuardOnce(decision, baseMessages, state, message)
467
468
  }
@@ -215,7 +215,7 @@ function failureStream(message, code = 'VISION_IMAGE_DELIVERY_UNAVAILABLE') {
215
215
  }
216
216
  }
217
217
 
218
- function directStream(ctx, core, plan, call, logger, source) {
218
+ function directStream(ctx, core, plan, call, logger, source, providerTransport) {
219
219
  return {
220
220
  async *[Symbol.asyncIterator]() {
221
221
  const started = Date.now()
@@ -252,6 +252,7 @@ function directStream(ctx, core, plan, call, logger, source) {
252
252
  maxTokens: call.maxTokens ?? 4096,
253
253
  signal: call.signal,
254
254
  sessionId: call.sessionId,
255
+ providerTransport,
255
256
  resolveCredential: async (ref) => ref === keyRef ? apiKey : undefined,
256
257
  },
257
258
  )
@@ -349,7 +350,7 @@ function llmWithRuntimePolicy(ctx, llm, options) {
349
350
  )
350
351
  return
351
352
  }
352
- yield* directStream(ctx, core, plan, call, logger, source)
353
+ yield* directStream(ctx, core, plan, call, logger, source, options.providerTransport)
353
354
  return
354
355
  }
355
356
 
@@ -307,6 +307,7 @@ export function createExactCapabilityInvoker(ctx, core, candidate, config, optio
307
307
  maxTokens: benchmarkMaxTokensForProvider(exactHttpProvider),
308
308
  signal: callSignal,
309
309
  affinityId: benchmarkAffinityId,
310
+ providerTransport: options.providerTransport,
310
311
  resolveCredential: (ref) => resolvedCredentialValue(ctx, ref),
311
312
  }),
312
313
  fixtureTimeoutMs,
@@ -0,0 +1,216 @@
1
+ import { probeLocalBackends } from './local-connection-probe.js'
2
+ import {
3
+ METADATA_RESPONSE_MAX_BYTES,
4
+ readResponseJsonBounded,
5
+ } from './http-body-limit.js'
6
+
7
+ export const VISION_TEST_CONNECTION_PATH = '/_dsh/vision-router/test-connection'
8
+ export const VISION_MODEL_CAPABILITIES_PATH = '/_dsh/vision-router/model-capabilities'
9
+
10
+ function adapterAvailable(llm, provider) {
11
+ if (!llm || typeof llm.registration !== 'function') return false
12
+ try {
13
+ llm.registration(provider)
14
+ return true
15
+ } catch {
16
+ return false
17
+ }
18
+ }
19
+
20
+ async function probeModels(fetchImpl, baseURL, expectedModel, startedAt) {
21
+ try {
22
+ const response = await fetchImpl(`${String(baseURL).replace(/\/$/, '')}/models`, {
23
+ method: 'GET',
24
+ signal: AbortSignal.timeout(8000),
25
+ })
26
+ const latencyMs = Date.now() - startedAt
27
+ if (!response.ok) {
28
+ return { ok: false, latencyMs, status: response.status, error: `HTTP ${response.status}` }
29
+ }
30
+ const data = await readResponseJsonBounded(
31
+ response,
32
+ METADATA_RESPONSE_MAX_BYTES,
33
+ { label: 'vision backend /models response' },
34
+ ).catch(() => undefined)
35
+ const models = data && Array.isArray(data.data) ? data.data : undefined
36
+ const count = models ? models.length : undefined
37
+ if (
38
+ typeof expectedModel === 'string' &&
39
+ expectedModel !== '' &&
40
+ models &&
41
+ !models.some((entry) => entry && String(entry.id) === expectedModel)
42
+ ) {
43
+ return {
44
+ ok: false,
45
+ latencyMs,
46
+ status: response.status,
47
+ models: count,
48
+ endpoint: baseURL,
49
+ error: `configured model "${expectedModel}" was not returned by /models`,
50
+ }
51
+ }
52
+ return { ok: true, latencyMs, status: response.status, models: count, endpoint: baseURL }
53
+ } catch (error) {
54
+ return {
55
+ ok: false,
56
+ latencyMs: Date.now() - startedAt,
57
+ error: error && error.message ? error.message : String(error),
58
+ }
59
+ }
60
+ }
61
+
62
+ function getArray(factory) {
63
+ const value = typeof factory === 'function' ? factory() : factory
64
+ return Array.isArray(value) ? value : []
65
+ }
66
+
67
+ /**
68
+ * Product diagnostics/settings-support routes kept outside visual Core.
69
+ *
70
+ * The caller supplies three coherent domain faces instead of a Core facade:
71
+ * connection candidates/probes, capability snapshot production, and the
72
+ * Host-ownership state rendered in the connection response. Authentication
73
+ * and remote/local capability policy stay outside this module in the existing
74
+ * scoped WebServer boundary.
75
+ */
76
+ export function installVisionDiagnosticsRoutes(ctx, options = {}) {
77
+ if (!ctx || typeof ctx.inject !== 'function') return ctx
78
+
79
+ const connection = options.connection ?? {}
80
+ const capabilities = options.capabilities ?? {}
81
+ const ownership = options.ownership ?? {}
82
+ const resolveCapability = connection.resolveCapability
83
+ const collectCapabilities = capabilities.collect
84
+ if (typeof resolveCapability !== 'function') {
85
+ throw new TypeError('vision diagnostics routes require a connection capability resolver')
86
+ }
87
+ if (typeof collectCapabilities !== 'function') {
88
+ throw new TypeError('vision diagnostics routes require a capability collector')
89
+ }
90
+
91
+ const llm = ctx.llm
92
+ const httpRoute = typeof connection.httpRoute === 'string' && connection.httpRoute !== ''
93
+ ? connection.httpRoute
94
+ : 'vision-http'
95
+ const fetchImpl = typeof options.fetchImpl === 'function' ? options.fetchImpl : globalThis.fetch
96
+
97
+ const probeConnection = async () => {
98
+ const started = Date.now()
99
+ let first
100
+ for (const pair of getArray(connection.candidatePairs)) {
101
+ if (!pair) continue
102
+ if (pair.provider !== httpRoute && !adapterAvailable(llm, pair.provider)) continue
103
+ const capability = await resolveCapability(pair.provider, pair.model)
104
+ if (capability?.attemptable !== false) {
105
+ first = pair
106
+ break
107
+ }
108
+ }
109
+
110
+ const localProbe = await probeLocalBackends(
111
+ getArray(connection.localBackends),
112
+ (provider) => probeModels(fetchImpl, provider.baseURL, provider.model, started),
113
+ started,
114
+ )
115
+ if (localProbe !== undefined) return localProbe
116
+
117
+ const httpBackends = getArray(connection.httpBackends)
118
+ if (first !== undefined && first.provider === httpRoute) {
119
+ const entry = httpBackends.find((provider) => `${provider.name}/${provider.model}` === first.model)
120
+ if (entry !== undefined) return probeModels(fetchImpl, entry.baseURL, entry.model, started)
121
+ }
122
+
123
+ if (first !== undefined) {
124
+ try {
125
+ await llm.resolveModelInfo(first.provider, first.model)
126
+ return {
127
+ ok: true,
128
+ latencyMs: Date.now() - started,
129
+ detail: `${first.provider}/${first.model} metadata resolved (no network call)`,
130
+ }
131
+ } catch (error) {
132
+ return {
133
+ ok: false,
134
+ latencyMs: Date.now() - started,
135
+ error: error && error.message ? error.message : String(error),
136
+ }
137
+ }
138
+ }
139
+
140
+ const httpFirst = httpBackends[0]
141
+ if (httpFirst !== undefined) return probeModels(fetchImpl, httpFirst.baseURL, httpFirst.model, started)
142
+ return { ok: false, error: 'no usable vision provider configured' }
143
+ }
144
+
145
+ ctx.inject(['webServer'], (webCtx) => {
146
+ webCtx.effect(() => {
147
+ return webCtx.webServer.register({
148
+ kind: 'exact',
149
+ path: VISION_TEST_CONNECTION_PATH,
150
+ handler: async (req, res) => {
151
+ if (req.method !== 'GET') {
152
+ res.setHeader('Allow', 'GET')
153
+ res.writeHead(405)
154
+ res.end()
155
+ return
156
+ }
157
+ try {
158
+ const result = await probeConnection()
159
+ const officialRouteAvailable = adapterAvailable(llm, 'deepseek-official')
160
+ result.stealth = {
161
+ configured: ownership.stealthConfigured,
162
+ active: false,
163
+ reason: ownership.hostOwnsOfficialDeepSeek && !officialRouteAvailable
164
+ ? 'host-owned-official-unavailable'
165
+ : undefined,
166
+ hostOwned: ownership.hostOwnsOfficialDeepSeek,
167
+ }
168
+ res.writeHead(result.ok ? 200 : 502, { 'content-type': 'application/json' })
169
+ res.end(JSON.stringify(result))
170
+ } catch (error) {
171
+ res.writeHead(500, { 'content-type': 'application/json' })
172
+ res.end(JSON.stringify({
173
+ ok: false,
174
+ error: error && error.message ? error.message : String(error),
175
+ }))
176
+ }
177
+ },
178
+ })
179
+ }, 'vision-router: test-connection route')
180
+ })
181
+
182
+ ctx.inject(['webServer'], (webCtx) => {
183
+ webCtx.effect(
184
+ () => webCtx.webServer.register({
185
+ kind: 'exact',
186
+ path: VISION_MODEL_CAPABILITIES_PATH,
187
+ handler: async (req, res) => {
188
+ if (req.method !== 'GET') {
189
+ res.setHeader('Allow', 'GET')
190
+ res.writeHead(405)
191
+ res.end()
192
+ return
193
+ }
194
+ try {
195
+ const snapshot = await collectCapabilities()
196
+ res.writeHead(200, { 'content-type': 'application/json' })
197
+ res.end(JSON.stringify({
198
+ capabilities: snapshot,
199
+ builtinFallback: getArray(capabilities.builtinFallback),
200
+ anonymousRpmPerModel: 2,
201
+ }))
202
+ } catch (error) {
203
+ res.writeHead(500, { 'content-type': 'application/json' })
204
+ res.end(JSON.stringify({
205
+ capabilities: {},
206
+ error: error && error.message ? error.message : String(error),
207
+ }))
208
+ }
209
+ },
210
+ }),
211
+ 'vision-router: model capabilities route',
212
+ )
213
+ })
214
+
215
+ return ctx
216
+ }
@@ -1,3 +1,5 @@
1
+ import { visionRouterMessageSource } from './session-message-source-compat.js'
2
+
1
3
  export const VISION_SUCCESS_GUIDANCE =
2
4
  'Vision Router trusted execution metadata: the preceding vision_describe call SUCCEEDED. ' +
3
5
  'This metadata is not image content. If that successful evidence resolves the visual fact needed for the current task, stop making additional vision calls for that fact and continue the remaining user task from this evidence. ' +
@@ -34,6 +36,6 @@ export function visionDescribeSuccessContext(exec, result) {
34
36
  role: 'user',
35
37
  id: `vision-router-evidence-success-${String(exec.callId)}`,
36
38
  content: [{ type: 'text', text: VISION_SUCCESS_GUIDANCE }],
37
- source: { kind: 'plugin', plugin: 'dsh-vision-router' },
39
+ source: visionRouterMessageSource(exec.agent.session),
38
40
  }
39
41
  }
@@ -33,8 +33,6 @@ const moduleFetch =
33
33
  ? globalThis.fetch.bind(globalThis)
34
34
  : undefined
35
35
 
36
- const installed = []
37
-
38
36
  function live(value) {
39
37
  return typeof value === 'function' ? value() : value
40
38
  }
@@ -175,30 +173,3 @@ export function createVisionProviderTransport({
175
173
  },
176
174
  })
177
175
  }
178
-
179
- /**
180
- * Scoped process/profile carrier for Router-owned compatibility calls whose
181
- * mature signatures do not yet accept an explicit VisionProviderTransport.
182
- * The registry never patches global fetch and is released with the public
183
- * plugin lifecycle. Remove it only after every production compatibility caller
184
- * receives the transport explicitly and no production code reads
185
- * currentVisionProviderTransport().
186
- */
187
- export function installVisionProviderTransport(transport) {
188
- if (!transport || typeof transport.fetch !== 'function') {
189
- throw new TypeError('invalid vision provider transport')
190
- }
191
- const token = Object.freeze({ transport })
192
- installed.push(token)
193
- let active = true
194
- return () => {
195
- if (!active) return
196
- active = false
197
- const index = installed.indexOf(token)
198
- if (index >= 0) installed.splice(index, 1)
199
- }
200
- }
201
-
202
- export function currentVisionProviderTransport() {
203
- return installed[installed.length - 1]?.transport
204
- }
@@ -251,6 +251,7 @@ const CLIENT_PRELUDE = String.raw`(function(){
251
251
  var listeners = new Set();
252
252
  var pending = new WeakMap();
253
253
  var recoveries = new WeakMap();
254
+ var selectionGenerations = new WeakMap();
254
255
  var inFlight = null;
255
256
  var refreshedAt = 0;
256
257
  function emit(){ listeners.forEach(function(fn){ try { fn(); } catch (_) {} }); }
@@ -302,13 +303,29 @@ const CLIENT_PRELUDE = String.raw`(function(){
302
303
  var key = JSON.stringify(selection || {});
303
304
  var held = pending.get(directory);
304
305
  if (held && held.key === key) return held.promise;
306
+ var generation = (selectionGenerations.get(directory) || 0) + 1;
307
+ selectionGenerations.set(directory, generation);
308
+ var current = function(){ return selectionGenerations.get(directory) === generation; };
305
309
  var recovery = recoveryFor(directory);
306
310
  recovery.clear();
307
311
  var promise = Promise.resolve().then(function(){ return directory.select(selection); }).then(
308
- function(value){ recovery.clear(); return value; },
309
- function(error){ recovery.fail(error); throw error; }
312
+ function(value){
313
+ if (!current()) return value;
314
+ if (value && value.ok === false) {
315
+ var failure = value.error;
316
+ var message = failure && failure.message
317
+ ? String(failure.message)
318
+ : String(failure || 'model operation failed');
319
+ if (failure && failure.code) message = String(failure.code) + ': ' + message;
320
+ recovery.fail(new Error(message));
321
+ return value;
322
+ }
323
+ recovery.clear();
324
+ return value;
325
+ },
326
+ function(error){ if (current()) recovery.fail(error); throw error; }
310
327
  );
311
- pending.set(directory, { key: key, promise: promise });
328
+ pending.set(directory, { key: key, promise: promise, generation: generation });
312
329
  promise.finally(function(){
313
330
  var latest = pending.get(directory);
314
331
  if (latest && latest.promise === promise) pending.delete(directory);
@@ -391,8 +408,8 @@ export function hardenVisionClientSource(source) {
391
408
 
392
409
  next = replaceAll(
393
410
  next,
394
- ` try {\n return Promise.resolve(directory.select(selection)).then(\n function(){ return true; },\n function(){ return false; }\n );\n } catch (_) {\n return Promise.resolve(false);\n }`,
395
- ` var hardening = window.__dshVisionRouterRootHardening;\n var operation;\n try { operation = hardening ? hardening.select(directory, selection) : Promise.resolve(directory.select(selection)); }\n catch (_) { return Promise.resolve(false); }\n return Promise.resolve(operation).then(function(){ return true; }, function(){ return false; });`,
411
+ ` try {\n return Promise.resolve(directory.select(selection)).then(\n function(result){ return !result || result.ok !== false; },\n function(){ return false; }\n );\n } catch (_) {\n return Promise.resolve(false);\n }`,
412
+ ` var hardening = window.__dshVisionRouterRootHardening;\n var operation;\n try { operation = hardening ? hardening.select(directory, selection) : Promise.resolve(directory.select(selection)); }\n catch (_) { return Promise.resolve(false); }\n return Promise.resolve(operation).then(function(result){ return !result || result.ok !== false; }, function(){ return false; });`,
396
413
  )
397
414
 
398
415
  next = replaceAll(
@@ -540,7 +540,11 @@ function wrapLlm(llm, runtimeState) {
540
540
  : state?.callConfigAuthority === 'transparent'
541
541
  ? projectTransparentCallConfig(options, state, runtimeState)
542
542
  : options
543
- return stream.call(target, projected)
543
+ const signal = combineSignals(projected?.signal, state?.signal)
544
+ const call = signal === undefined || signal === projected?.signal
545
+ ? projected
546
+ : { ...(projected ?? {}), signal }
547
+ return stream.call(target, call)
544
548
  }
545
549
  }
546
550
  if (property === 'registerAdapter') {
@@ -1,6 +1,6 @@
1
1
  import { injectClientCarrierHtml, installClientScriptCarriers } from '../client-script-carrier.js'
2
2
  import { installVisionRouterRemoteSettingsBridge } from '../remote-settings-bridge.js'
3
- import { installSettingsRc8ClientLifecycle } from '../settings-client-rc8-lifecycle.js'
3
+ import { installSettingsClientLoaderLifecycle } from '../settings-client-loader-lifecycle.js'
4
4
 
5
5
  const SETTINGS_CONFIG_FORMS_MARK = 'data-vision-router-settings-configforms-compat'
6
6
 
@@ -202,6 +202,6 @@ export function installSettingsConfigFormsCompat(ctx) {
202
202
  export function installVisionRemoteSettingsClient(ctx, logger) {
203
203
  installVisionRouterRemoteSettingsBridge(ctx, logger)
204
204
  installSettingsConfigFormsCompat(ctx)
205
- installSettingsRc8ClientLifecycle(ctx)
205
+ installSettingsClientLoaderLifecycle(ctx)
206
206
  return ctx
207
207
  }