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.
- package/README.md +7 -7
- package/README.zh.md +7 -7
- package/docs/architecture/2.3.0-compat-retirement.md +147 -0
- package/docs/architecture/2.3.0-execution-plan.md +410 -0
- package/docs/architecture/2.3.0-final-audit.md +279 -0
- package/docs/architecture/2.3.0-reopened-plan.md +720 -0
- package/docs/architecture/compat-inventory.md +26 -34
- package/docs/architecture/dsh-compatibility-matrix.md +65 -54
- package/docs/architecture/dsh-support-window.md +27 -21
- package/docs/architecture/final-closure-audit.md +2 -0
- package/docs/releases/v2.3.0.md +53 -0
- package/entry.js +8 -56
- package/index.js +161 -691
- package/lib/catalog-corrections.js +1 -2
- package/lib/client-maintenance-actions.js +145 -0
- package/lib/client-presentation-boundary-main.js +75 -17
- package/lib/client-presentation-boundary.js +53 -28
- package/lib/client.js +246 -144
- package/lib/core-apply-lifecycle.js +47 -0
- package/lib/core-primitives.js +88 -135
- package/lib/desktop-screenshot-tool.js +262 -0
- package/lib/dsh-contract-compat.js +11 -26
- package/lib/dsh-support-window.js +3 -3
- package/lib/http-compat.js +1 -2
- package/lib/local-remote-settings-permission.js +33 -7
- package/lib/maintenance-routes.js +136 -0
- package/lib/public-config.js +57 -0
- package/lib/public-entry.js +2 -7
- package/lib/remote-settings-bridge.js +11 -0
- package/lib/remote-settings-risk-client-boundary.js +155 -0
- package/lib/remote-settings-risk-confirmation.js +17 -105
- package/lib/runtime-composition.js +105 -30
- package/lib/runtime-i18n-boundary.js +10 -11
- package/lib/session-message-source-compat.js +7 -3
- package/lib/session-vision-index.js +154 -9
- package/lib/settings-client-loader-lifecycle.js +129 -0
- package/lib/sharp-runtime.js +0 -7
- package/lib/structured-flow-hardening.js +3 -2
- package/lib/vision-backend-runtime-policy.js +3 -2
- package/lib/vision-capability-benchmark-service.js +1 -0
- package/lib/vision-diagnostics-routes.js +216 -0
- package/lib/vision-evidence-guidance.js +3 -1
- package/lib/vision-provider-transport.js +0 -29
- package/lib/vision-toggle-root-hardening.js +22 -5
- package/lib/vision-tool-runtime-boundary.js +5 -1
- package/lib/web/remote-settings-client.js +2 -2
- package/package.json +23 -13
- package/lib/android-attachment-compat.js +0 -244
- 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
|
+
}
|
package/lib/sharp-runtime.js
CHANGED
|
@@ -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:
|
|
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:
|
|
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:
|
|
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){
|
|
309
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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 {
|
|
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
|
-
|
|
205
|
+
installSettingsClientLoaderLifecycle(ctx)
|
|
206
206
|
return ctx
|
|
207
207
|
}
|