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.
- package/README.md +16 -7
- package/README.zh.md +16 -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/3.0-architecture-plan.md +252 -0
- package/docs/architecture/3.0-r0-contract-ledger.md +137 -0
- package/docs/architecture/3.0-r1-source-artifact-boundary.md +98 -0
- package/docs/architecture/3.0-r10-final-correctness-closure.md +207 -0
- package/docs/architecture/3.0-r11-adversarial-review.md +441 -0
- package/docs/architecture/3.0-r5-browser-go-no-go.md +165 -0
- package/docs/architecture/3.0-r5-browser-typescript-gate.md +105 -0
- package/docs/architecture/3.0-r6-compat-retirement.md +203 -0
- package/docs/architecture/3.0-r7-public-api.md +151 -0
- package/docs/architecture/3.0-r8-ci-release.md +248 -0
- package/docs/architecture/3.0-r9-final-audit.md +370 -0
- package/docs/architecture/compat-inventory.md +26 -34
- package/docs/architecture/dsh-compatibility-matrix.md +65 -54
- package/docs/architecture/dsh-support-window.md +30 -22
- package/docs/architecture/final-closure-audit.md +2 -0
- package/docs/releases/v2.3.0.md +53 -0
- package/docs/releases/v3.0.0.md +85 -0
- package/entry.js +9 -57
- package/index.js +268 -761
- package/lib/agent-request-route-authority.js +1 -20
- 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 +41 -0
- package/lib/core-cache.js +87 -0
- package/lib/core-primitives.js +91 -278
- package/lib/core-vision-surface-domain.js +63 -0
- package/lib/core-vision-surface.js +11 -77
- package/lib/delegated-call-config.js +15 -24
- package/lib/desktop-screenshot-tool.js +262 -0
- package/lib/doctor-cli.js +0 -0
- package/lib/doctor-runtime.js +4 -1
- package/lib/dsh-contract-compat.js +37 -256
- package/lib/dsh-host-capabilities.js +3 -9
- package/lib/dsh-settings-017-compat.js +17 -10
- package/lib/dsh-support-window.js +3 -3
- package/lib/file-logger.js +8 -3
- package/lib/grounding-coordinate-runtime.js +1 -2
- package/lib/http-body-limit.js +97 -79
- package/lib/http-compat.js +1 -2
- package/lib/image-offload-compat.js +30 -21
- package/lib/image-resource-governor.js +210 -228
- package/lib/legacy-global-proxy-boundary.js +46 -3
- package/lib/live-model-client-prelude.js +12 -4
- package/lib/live-model-discovery.js +134 -43
- package/lib/local-remote-settings-permission.js +33 -7
- package/lib/local-vision-stabilizer.js +31 -17
- package/lib/maintenance-routes.js +134 -0
- package/lib/native-image-coexistence.js +15 -3
- package/lib/ollama-cold-start.js +87 -23
- package/lib/pi-ai-bridge-wire-compat.js +3 -2
- package/lib/proxy-dispatcher-pool.js +151 -92
- package/lib/proxy-routing.js +75 -51
- package/lib/proxy-url-compat.js +3 -10
- package/lib/public-config.js +36 -0
- package/lib/public-entry.d.ts +148 -0
- package/lib/public-entry.js +19 -11
- 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/replay-delegation.js +12 -7
- package/lib/replay-envelope-v2-compat.js +12 -7
- package/lib/runtime-composition.js +127 -34
- package/lib/runtime-i18n-boundary.js +10 -11
- package/lib/screenshot-source-boundary.js +2 -2
- package/lib/session-affinity-runtime.js +113 -97
- package/lib/session-affinity.js +89 -76
- package/lib/session-event-history-compat.js +30 -0
- package/lib/session-message-source-compat.js +7 -3
- package/lib/session-query-readers.js +263 -0
- package/lib/session-surface-compat.js +30 -31
- package/lib/session-surface-policy-domain.js +85 -0
- package/lib/session-surface-policy.js +2 -111
- package/lib/session-turn-resolver.js +2 -6
- package/lib/session-vision-attachment-index.js +143 -0
- package/lib/session-vision-delegation-boundary.js +34 -0
- package/lib/session-vision-delegation.js +22 -0
- package/lib/session-vision-index.js +179 -116
- package/lib/session-vision-mode-authority.js +229 -192
- package/lib/session-vision-mode-boundary.js +65 -22
- package/lib/session-vision-policy-domain.js +304 -0
- package/lib/session-vision-policy.js +104 -0
- package/lib/session-vision-state.js +368 -378
- package/lib/settings-client-017-compat.js +9 -7
- package/lib/settings-client-loader-lifecycle.js +129 -0
- package/lib/sharp-runtime.js +0 -7
- package/lib/structured-flow-hardening.js +25 -6
- package/lib/tesseract-exec-compat.js +9 -1
- package/lib/turn-budget-context.js +47 -41
- package/lib/update-check.js +11 -2
- package/lib/vision-attachment-handle-runtime.js +176 -156
- package/lib/vision-backend-runtime-policy.js +19 -2
- package/lib/vision-background-benchmark.js +41 -4
- package/lib/vision-background-failure-policy.js +41 -41
- package/lib/vision-background-stop-store-core.js +217 -0
- package/lib/vision-background-stop-store.js +8 -179
- package/lib/vision-capability-benchmark-service.js +1 -0
- package/lib/vision-capability-benchmark.js +11 -61
- package/lib/vision-capability-evidence.js +35 -0
- package/lib/vision-capability-fingerprint.js +72 -0
- package/lib/vision-capability-identity.js +116 -61
- package/lib/vision-capability-router.js +13 -33
- package/lib/vision-chunk-assembler.js +95 -0
- package/lib/vision-diagnostics-routes.js +246 -0
- package/lib/vision-evidence-guidance.js +3 -1
- package/lib/vision-execution-order-apply.js +45 -55
- package/lib/vision-execution-order.js +19 -19
- package/lib/vision-execution-policy.js +12 -7
- package/lib/vision-model-registry.js +30 -17
- package/lib/vision-provider-transport.js +137 -176
- package/lib/vision-routing-authority.js +47 -48
- package/lib/vision-routing-product.js +26 -29
- package/lib/vision-routing-settings-prelude.js +8 -3
- package/lib/vision-runtime-performance-store.js +166 -0
- package/lib/vision-runtime-performance.js +13 -142
- package/lib/vision-toggle-root-hardening.js +22 -5
- package/lib/vision-tool-runtime-boundary.js +104 -31
- package/lib/web/dsh-settings-017-root-transport.js +27 -12
- package/lib/web/remote-settings-client.js +6 -9
- package/lib/web-capability-boundary.js +17 -7
- package/lib/wrapper-directory.js +14 -23
- package/package.json +56 -20
- package/lib/android-attachment-compat.js +0 -244
- package/lib/settings-client-rc8-lifecycle.js +0 -397
|
@@ -1,82 +1,20 @@
|
|
|
1
|
+
import { currentSessionSurfacePolicy } from './session-surface-policy.js'
|
|
1
2
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
CORE_VISION_SURFACE_KEYS,
|
|
4
|
+
projectCoreVisionSurface,
|
|
5
|
+
resolveCoreVisionSurface,
|
|
6
|
+
} from './core-vision-surface-domain.js'
|
|
5
7
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
'structuredVisionBootstrap',
|
|
12
|
-
])
|
|
8
|
+
export {
|
|
9
|
+
CORE_VISION_SURFACE_KEYS,
|
|
10
|
+
projectCoreVisionSurface,
|
|
11
|
+
resolveCoreVisionSurface,
|
|
12
|
+
} from './core-vision-surface-domain.js'
|
|
13
13
|
|
|
14
14
|
function isObject(value) {
|
|
15
15
|
return value !== null && typeof value === 'object' && !Array.isArray(value)
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
function own(object, key) {
|
|
19
|
-
return Object.prototype.hasOwnProperty.call(object, key)
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function coreFlags(values) {
|
|
23
|
-
return {
|
|
24
|
-
toolAvailable: values.tool !== false,
|
|
25
|
-
rewriteEnabled: values.rewriteImages !== false,
|
|
26
|
-
instantDescribe: values.instantDescribe === true,
|
|
27
|
-
autoActivateOnImage: values.autoActivateOnImage !== false,
|
|
28
|
-
structuredBootstrap: values.structuredVisionBootstrap === true,
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Explicit core-facing projection of the SessionSurfacePolicy.
|
|
34
|
-
*
|
|
35
|
-
* The returned object carries only the five config values that the historical
|
|
36
|
-
* Settings/config projection could override plus explicit booleans consumed by
|
|
37
|
-
* Core and the already-resolved image ownership facts. It deliberately cannot
|
|
38
|
-
* masquerade as a Settings service or a full plugin config object.
|
|
39
|
-
*/
|
|
40
|
-
export function projectCoreVisionSurface(config = {}, sessionPolicy) {
|
|
41
|
-
const value = isObject(config) ? config : {}
|
|
42
|
-
const policy = sessionPolicy && typeof sessionPolicy === 'object'
|
|
43
|
-
? sessionPolicy
|
|
44
|
-
: resolveSessionSurfacePolicy({ config: value })
|
|
45
|
-
const overrides = isObject(policy.legacyConfigOverrides)
|
|
46
|
-
? policy.legacyConfigOverrides
|
|
47
|
-
: {}
|
|
48
|
-
const projected = {}
|
|
49
|
-
|
|
50
|
-
for (const key of CORE_SURFACE_KEYS) {
|
|
51
|
-
projected[key] = own(overrides, key) ? overrides[key] : value[key]
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
return Object.freeze({
|
|
55
|
-
ownership: policy.ownership,
|
|
56
|
-
preserveRawImages: policy.preserveRawImages === true,
|
|
57
|
-
rewriteCurrentImages: policy.rewriteCurrentImages === true,
|
|
58
|
-
...coreFlags(projected),
|
|
59
|
-
values: Object.freeze(projected),
|
|
60
|
-
})
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/** Resolve an explicit core surface from an explicit ownership-policy input. */
|
|
64
|
-
export function resolveCoreVisionSurface({
|
|
65
|
-
visionPolicy,
|
|
66
|
-
config = {},
|
|
67
|
-
schemaBootstrapping = false,
|
|
68
|
-
} = {}) {
|
|
69
|
-
const value = isObject(config) ? config : {}
|
|
70
|
-
return projectCoreVisionSurface(
|
|
71
|
-
value,
|
|
72
|
-
resolveSessionSurfacePolicy({
|
|
73
|
-
visionPolicy,
|
|
74
|
-
config: value,
|
|
75
|
-
schemaBootstrapping,
|
|
76
|
-
}),
|
|
77
|
-
)
|
|
78
|
-
}
|
|
79
|
-
|
|
80
18
|
/** Resolve the current turn-local surface without exposing a Settings facade. */
|
|
81
19
|
export function currentCoreVisionSurface(config = {}, options = {}) {
|
|
82
20
|
const value = isObject(config) ? config : {}
|
|
@@ -92,9 +30,7 @@ export function currentCoreVisionSurface(config = {}, options = {}) {
|
|
|
92
30
|
* One explicit production seam for Core's five policy-derived switches.
|
|
93
31
|
*
|
|
94
32
|
* The config source stays live, but schema bootstrap is an internal lifecycle
|
|
95
|
-
* bit rather than a fake Settings/config value.
|
|
96
|
-
* their two-argument compatibility path; composition passes this runtime as
|
|
97
|
-
* the authoritative production surface.
|
|
33
|
+
* bit rather than a fake Settings/config value.
|
|
98
34
|
*/
|
|
99
35
|
export function createCoreVisionSurfaceRuntime({ config = {} } = {}) {
|
|
100
36
|
let schemaBootstrapping = true
|
|
@@ -120,5 +56,3 @@ export function createCoreVisionSurfaceRuntime({ config = {} } = {}) {
|
|
|
120
56
|
},
|
|
121
57
|
})
|
|
122
58
|
}
|
|
123
|
-
|
|
124
|
-
export const CORE_VISION_SURFACE_KEYS = CORE_SURFACE_KEYS
|
|
@@ -1,27 +1,18 @@
|
|
|
1
1
|
const ROUTE_OWNED_CALL_FIELDS = Object.freeze([
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
])
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Project an internal Vision Router delegation onto a different provider/model.
|
|
10
|
-
*
|
|
11
|
-
* The delegated route owns its own call defaults and compatibility. Carry the
|
|
12
|
-
* request payload and lifecycle through, but never leak source-route sampling,
|
|
13
|
-
* reasoning or output-limit state into the target adapter. DSH will materialize
|
|
14
|
-
* the target adapter's configured defaults when these fields are absent.
|
|
15
|
-
*
|
|
16
|
-
* Direct transports owned by Vision Router do not use this projection; their
|
|
17
|
-
* wire compatibility remains the Router's responsibility.
|
|
18
|
-
*/
|
|
2
|
+
'reasoningEffort',
|
|
3
|
+
'temperature',
|
|
4
|
+
'maxTokens',
|
|
5
|
+
'stop',
|
|
6
|
+
]);
|
|
19
7
|
export function projectDelegatedCallConfig(options) {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
8
|
+
if (!options || typeof options !== 'object' || Array.isArray(options))
|
|
9
|
+
return options;
|
|
10
|
+
const record = options;
|
|
11
|
+
if (!ROUTE_OWNED_CALL_FIELDS.some((field) => Object.hasOwn(record, field)))
|
|
12
|
+
return options;
|
|
13
|
+
const next = { ...record };
|
|
14
|
+
for (const field of ROUTE_OWNED_CALL_FIELDS)
|
|
15
|
+
delete next[field];
|
|
16
|
+
return next;
|
|
25
17
|
}
|
|
26
|
-
|
|
27
|
-
export const delegatedRouteOwnedCallFields = ROUTE_OWNED_CALL_FIELDS
|
|
18
|
+
export const delegatedRouteOwnedCallFields = ROUTE_OWNED_CALL_FIELDS;
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
import { execFile } from 'node:child_process'
|
|
2
|
+
import { existsSync } from 'node:fs'
|
|
3
|
+
import { readFile, unlink } from 'node:fs/promises'
|
|
4
|
+
import { tmpdir } from 'node:os'
|
|
5
|
+
import path from 'node:path'
|
|
6
|
+
import { promisify } from 'node:util'
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
callLocalBackend,
|
|
10
|
+
localDescribePrompt,
|
|
11
|
+
localProvidersOf,
|
|
12
|
+
toOpenAIContent,
|
|
13
|
+
} from './core-primitives.js'
|
|
14
|
+
import { loadSharp } from './sharp-runtime.js'
|
|
15
|
+
import { captureWindowsDesktop } from './windows-desktop-capture.js'
|
|
16
|
+
import { currentVisionTurnBudgetSignal } from './turn-budget-context.js'
|
|
17
|
+
import { combineSignals } from './vision-resilience.js'
|
|
18
|
+
|
|
19
|
+
function abortReason(signal, fallback = 'vision_screenshot aborted') {
|
|
20
|
+
if (signal?.reason instanceof Error) return signal.reason
|
|
21
|
+
const error = new Error(fallback)
|
|
22
|
+
error.name = 'AbortError'
|
|
23
|
+
error.code = 'ABORT_ERR'
|
|
24
|
+
return error
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function throwIfAborted(signal) {
|
|
28
|
+
if (signal?.aborted) throw abortReason(signal)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function isAbortFailure(error) {
|
|
32
|
+
return error?.name === 'AbortError' || error?.code === 'ABORT_ERR'
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export async function captureDesktopPng(target, timeoutMs, signal, options = {}) {
|
|
36
|
+
const platform = options.platform ?? process.platform
|
|
37
|
+
const run = options.execFileAsync ?? promisify(execFile)
|
|
38
|
+
const captureWindows = options.captureWindowsDesktop ?? captureWindowsDesktop
|
|
39
|
+
throwIfAborted(signal)
|
|
40
|
+
if (platform === 'win32') {
|
|
41
|
+
// Own DPI-aware capture at the implementation boundary rather than
|
|
42
|
+
// depending on a process-wide execFile argument rewrite.
|
|
43
|
+
await captureWindows(target, { timeoutMs, signal })
|
|
44
|
+
throwIfAborted(signal)
|
|
45
|
+
return platform
|
|
46
|
+
}
|
|
47
|
+
if (platform === 'darwin') {
|
|
48
|
+
// Without -m, screencapture writes one file per display while the tool owns
|
|
49
|
+
// exactly one artifact path, so request the main display explicitly.
|
|
50
|
+
await run('screencapture', ['-x', '-m', target], {
|
|
51
|
+
timeout: timeoutMs,
|
|
52
|
+
windowsHide: true,
|
|
53
|
+
...(signal === undefined ? {} : { signal }),
|
|
54
|
+
})
|
|
55
|
+
throwIfAborted(signal)
|
|
56
|
+
return platform
|
|
57
|
+
}
|
|
58
|
+
try {
|
|
59
|
+
await run('import', ['-window', 'root', target], {
|
|
60
|
+
timeout: timeoutMs,
|
|
61
|
+
...(signal === undefined ? {} : { signal }),
|
|
62
|
+
})
|
|
63
|
+
} catch (error) {
|
|
64
|
+
if (signal?.aborted || isAbortFailure(error)) throw error
|
|
65
|
+
await run('scrot', [target], {
|
|
66
|
+
timeout: timeoutMs,
|
|
67
|
+
...(signal === undefined ? {} : { signal }),
|
|
68
|
+
})
|
|
69
|
+
}
|
|
70
|
+
throwIfAborted(signal)
|
|
71
|
+
return platform
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async function downscaleForIdentification(data) {
|
|
75
|
+
try {
|
|
76
|
+
const sharp = await loadSharp()
|
|
77
|
+
if (!sharp) return data
|
|
78
|
+
const downscaled = await sharp(data, { failOn: 'none' })
|
|
79
|
+
.resize({ width: 1280, height: 1280, fit: 'inside', withoutEnlargement: true })
|
|
80
|
+
.png()
|
|
81
|
+
.toBuffer()
|
|
82
|
+
return downscaled.length > 0 && downscaled.length < data.length ? downscaled : data
|
|
83
|
+
} catch {
|
|
84
|
+
return data
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async function identifyDesktopCapture(data, {
|
|
89
|
+
current,
|
|
90
|
+
timeoutMs,
|
|
91
|
+
instantLocalStyle,
|
|
92
|
+
providerTransport,
|
|
93
|
+
signal,
|
|
94
|
+
} = {}) {
|
|
95
|
+
throwIfAborted(signal)
|
|
96
|
+
const locals = localProvidersOf(current())
|
|
97
|
+
if (locals.length === 0) {
|
|
98
|
+
return {
|
|
99
|
+
identifyError: 'no local vision backend enabled (localOllama / localLmStudio); enable one to use identify',
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const startedAt = Date.now()
|
|
104
|
+
const identifyBytes = await downscaleForIdentification(data)
|
|
105
|
+
throwIfAborted(signal)
|
|
106
|
+
const result = {}
|
|
107
|
+
if (identifyBytes !== data) {
|
|
108
|
+
result.identifyDownscaled = {
|
|
109
|
+
originalBytes: data.length,
|
|
110
|
+
sentBytes: identifyBytes.length,
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const content = toOpenAIContent(
|
|
115
|
+
[{ type: 'image', attachment: { mediaType: 'image/png', data: identifyBytes } }],
|
|
116
|
+
() => identifyBytes,
|
|
117
|
+
)
|
|
118
|
+
content.push({ type: 'text', text: localDescribePrompt(instantLocalStyle()) })
|
|
119
|
+
|
|
120
|
+
const deadlineAt = Date.now() + timeoutMs()
|
|
121
|
+
const errors = []
|
|
122
|
+
for (let index = 0; index < locals.length; index++) {
|
|
123
|
+
throwIfAborted(signal)
|
|
124
|
+
const local = locals[index]
|
|
125
|
+
const remainingMs = deadlineAt - Date.now()
|
|
126
|
+
if (remainingMs <= 0) break
|
|
127
|
+
// Reserve a fair share for later local backends. A connected but hung
|
|
128
|
+
// first backend must not consume the entire fallback budget.
|
|
129
|
+
const roundBudgetMs = Math.max(1, Math.floor(remainingMs / (locals.length - index)))
|
|
130
|
+
const controller = new AbortController()
|
|
131
|
+
const roundSignal = signal
|
|
132
|
+
? AbortSignal.any([signal, controller.signal])
|
|
133
|
+
: controller.signal
|
|
134
|
+
const timer = setTimeout(() => controller.abort(), roundBudgetMs)
|
|
135
|
+
try {
|
|
136
|
+
const identified = await callLocalBackend(
|
|
137
|
+
local,
|
|
138
|
+
[{ role: 'user', content }],
|
|
139
|
+
{
|
|
140
|
+
maxTokens: local.maxTokens ?? 2048,
|
|
141
|
+
signal: roundSignal,
|
|
142
|
+
providerTransport,
|
|
143
|
+
},
|
|
144
|
+
)
|
|
145
|
+
if (typeof identified === 'string' && identified.trim() !== '') {
|
|
146
|
+
result.identified = identified.trim()
|
|
147
|
+
result.identifiedBy = local.name
|
|
148
|
+
result.elapsedSec = Math.max(1, Math.round((Date.now() - startedAt) / 1000))
|
|
149
|
+
return result
|
|
150
|
+
}
|
|
151
|
+
errors.push(`${local.name}: empty response`)
|
|
152
|
+
} catch (error) {
|
|
153
|
+
if (signal?.aborted) throw abortReason(signal)
|
|
154
|
+
errors.push(`${local.name}: ${error && error.message ? error.message : String(error)}`)
|
|
155
|
+
} finally {
|
|
156
|
+
clearTimeout(timer)
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
result.identifyError = errors.length > 0
|
|
161
|
+
? errors.join('; ').slice(0, 1000)
|
|
162
|
+
: 'local vision identification timed out before a backend could respond'
|
|
163
|
+
return result
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Build the privacy-sensitive desktop screenshot tool definition.
|
|
168
|
+
*
|
|
169
|
+
* Tool registration/exposure remains owned by Core + LocalVisionStabilizer;
|
|
170
|
+
* this module owns only the platform capture and optional local-recognition
|
|
171
|
+
* implementation behind that existing permission boundary.
|
|
172
|
+
*/
|
|
173
|
+
export function createDesktopScreenshotTool({
|
|
174
|
+
current,
|
|
175
|
+
timeoutMs,
|
|
176
|
+
saveArtifact,
|
|
177
|
+
stringOutput,
|
|
178
|
+
instantLocalStyle,
|
|
179
|
+
providerTransport,
|
|
180
|
+
captureDesktop = captureDesktopPng,
|
|
181
|
+
} = {}) {
|
|
182
|
+
if (
|
|
183
|
+
typeof current !== 'function' ||
|
|
184
|
+
typeof timeoutMs !== 'function' ||
|
|
185
|
+
typeof saveArtifact !== 'function' ||
|
|
186
|
+
typeof captureDesktop !== 'function'
|
|
187
|
+
) {
|
|
188
|
+
throw new TypeError('desktop screenshot tool requires live config, timeout, artifact and capture callbacks')
|
|
189
|
+
}
|
|
190
|
+
if (typeof instantLocalStyle !== 'function') {
|
|
191
|
+
throw new TypeError('desktop screenshot tool requires a live local describe style')
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return {
|
|
195
|
+
name: 'vision_screenshot',
|
|
196
|
+
description:
|
|
197
|
+
'Capture the user\'s desktop screen as a PNG artifact (the virtual screen on Windows; the main display on macOS; the root display on Linux). ' +
|
|
198
|
+
'Windows: per-monitor-DPI-aware PowerShell capture; macOS: screencapture; Linux: ImageMagick import (falls back to scrot; either command must be installed). ' +
|
|
199
|
+
'This privacy-sensitive tool is disabled by default and works only after the user explicitly enables Desktop screenshot in Vision Router settings. ' +
|
|
200
|
+
'Use it when you need to see what is on the user\'s screen right now — e.g. their current GUI, an app, or a page outside this browser. ' +
|
|
201
|
+
'Optional identify=true also runs local recognition on the capture using the enabled local backends (Ollama, then LM Studio) and returns the description alongside the path.',
|
|
202
|
+
parameters: {
|
|
203
|
+
type: 'object',
|
|
204
|
+
properties: {
|
|
205
|
+
identify: {
|
|
206
|
+
type: 'boolean',
|
|
207
|
+
description:
|
|
208
|
+
'Also recognize the captured screen with enabled local vision backends (Ollama, then LM Studio) and return the description text with the path. Default false.',
|
|
209
|
+
},
|
|
210
|
+
},
|
|
211
|
+
additionalProperties: false,
|
|
212
|
+
},
|
|
213
|
+
output: stringOutput,
|
|
214
|
+
async execute(args, exec) {
|
|
215
|
+
if (current().desktopScreenshot !== true) {
|
|
216
|
+
throw new Error(
|
|
217
|
+
'vision_screenshot is disabled; enable Desktop screenshot explicitly in Vision Router settings before use',
|
|
218
|
+
)
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const tmp = path.join(
|
|
222
|
+
tmpdir(),
|
|
223
|
+
`vision-screenshot-${Date.now()}-${Math.floor(Math.random() * 1e9)}.png`,
|
|
224
|
+
)
|
|
225
|
+
try {
|
|
226
|
+
const signal = combineSignals(currentVisionTurnBudgetSignal(), exec?.signal)
|
|
227
|
+
throwIfAborted(signal)
|
|
228
|
+
const platform = await captureDesktop(tmp, timeoutMs(), signal)
|
|
229
|
+
throwIfAborted(signal)
|
|
230
|
+
if (!existsSync(tmp)) {
|
|
231
|
+
throw new Error(
|
|
232
|
+
`vision_screenshot: no output produced on ${platform} (is a screen available?)`,
|
|
233
|
+
)
|
|
234
|
+
}
|
|
235
|
+
const data = await readFile(tmp, signal === undefined ? undefined : { signal })
|
|
236
|
+
throwIfAborted(signal)
|
|
237
|
+
let identification
|
|
238
|
+
if (args.identify === true) {
|
|
239
|
+
identification = await identifyDesktopCapture(data, {
|
|
240
|
+
current,
|
|
241
|
+
timeoutMs,
|
|
242
|
+
instantLocalStyle,
|
|
243
|
+
providerTransport,
|
|
244
|
+
signal,
|
|
245
|
+
})
|
|
246
|
+
}
|
|
247
|
+
// Artifact publication is the final commit point. If optional local
|
|
248
|
+
// identification is cancelled, no successful screenshot result should
|
|
249
|
+
// already have escaped into the managed artifact store.
|
|
250
|
+
throwIfAborted(signal)
|
|
251
|
+
const target = await saveArtifact(exec, `screenshot-${Date.now()}.png`, data)
|
|
252
|
+
return JSON.stringify({
|
|
253
|
+
path: target,
|
|
254
|
+
bytes: data.length,
|
|
255
|
+
...(identification ?? {}),
|
|
256
|
+
})
|
|
257
|
+
} finally {
|
|
258
|
+
try { await unlink(tmp) } catch { /* best effort cleanup */ }
|
|
259
|
+
}
|
|
260
|
+
},
|
|
261
|
+
}
|
|
262
|
+
}
|
package/lib/doctor-cli.js
CHANGED
|
File without changes
|
package/lib/doctor-runtime.js
CHANGED
|
@@ -52,6 +52,10 @@ export async function probeRuntime({
|
|
|
52
52
|
dshHome,
|
|
53
53
|
applicableProfiles = [],
|
|
54
54
|
timeoutMs = 800,
|
|
55
|
+
// Doctor probes are a CLI surface: they never install a process fetch
|
|
56
|
+
// wrapper and must observe whatever transport the surrounding process
|
|
57
|
+
// currently provides.
|
|
58
|
+
// diagnostics-lint: sealed-process-global
|
|
55
59
|
fetchImpl = globalThis.fetch,
|
|
56
60
|
} = {}) {
|
|
57
61
|
if (typeof fetchImpl !== 'function') throw new TypeError('runtime probe requires fetch')
|
|
@@ -220,7 +224,6 @@ function chromiumCandidates(env = process.env) {
|
|
|
220
224
|
}
|
|
221
225
|
|
|
222
226
|
const HOST_PACKAGES = [
|
|
223
|
-
'@deepseek-ai/dsh-client-runtime',
|
|
224
227
|
'@deepseek-ai/dsh-client-ui-settings-plugins',
|
|
225
228
|
'@deepseek-ai/dsh-attachment-local',
|
|
226
229
|
'@deepseek-ai/dsh-llm-deepseek',
|