@anionex/dsh-vision-toolkit 0.1.7 → 0.1.9
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.i18n.yaml +2 -2
- package/README.md +34 -17
- package/README.zh.md +32 -7
- package/assets/community-group-qr.png +0 -0
- package/assets/vision-model-test.png +0 -0
- package/docs/requirements-traceability/README.i18n.yaml +2 -2
- package/docs/requirements-traceability/README.md +2 -2
- package/docs/requirements-traceability/README.zh.md +2 -2
- package/lib/client.js +361 -28
- package/lib/client.js.map +1 -1
- package/lib/config.js +14 -0
- package/lib/config.js.map +1 -1
- package/lib/image-input-variants.js +615 -0
- package/lib/image-input-variants.js.map +1 -0
- package/lib/index.js +8 -1
- package/lib/index.js.map +1 -1
- package/lib/paste-images.js +6 -0
- package/lib/paste-images.js.map +1 -1
- package/lib/runtime.js +37 -3
- package/lib/runtime.js.map +1 -1
- package/lib/types/client/index.d.ts +16 -5
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/paste-images.d.ts +69 -0
- package/lib/types/client/paste-images.d.ts.map +1 -1
- package/lib/types/config.d.ts +26 -0
- package/lib/types/config.d.ts.map +1 -1
- package/lib/types/image-input-variants.d.ts +153 -0
- package/lib/types/image-input-variants.d.ts.map +1 -0
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/paste-images.d.ts +35 -0
- package/lib/types/paste-images.d.ts.map +1 -1
- package/lib/types/runtime.d.ts +5 -2
- package/lib/types/runtime.d.ts.map +1 -1
- package/lib/types/web-request.d.ts +7 -0
- package/lib/types/web-request.d.ts.map +1 -1
- package/lib/types/web.d.ts +17 -2
- package/lib/types/web.d.ts.map +1 -1
- package/lib/web-request.js +11 -2
- package/lib/web-request.js.map +1 -1
- package/lib/web.js +88 -5
- package/lib/web.js.map +1 -1
- package/package.json +2 -1
- package/src/client/index.tsx +53 -14
- package/src/client/paste-images.tsx +331 -15
- package/src/config.ts +40 -0
- package/src/image-input-variants.ts +688 -0
- package/src/index.ts +18 -1
- package/src/paste-images.ts +39 -0
- package/src/runtime.ts +42 -3
- package/src/web-request.ts +12 -2
- package/src/web.ts +90 -4
package/src/index.ts
CHANGED
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
type VisionToolkitConfig,
|
|
22
22
|
} from './config.ts'
|
|
23
23
|
import { VisionToolExposure } from './exposure.ts'
|
|
24
|
+
import { createPasteTakeoverResolver, installImageInputVariants } from './image-input-variants.ts'
|
|
24
25
|
import { VisionToolkitRuntimeManager } from './runtime-manager.ts'
|
|
25
26
|
import { VISION_TOOLS_SKILL } from './skill.ts'
|
|
26
27
|
import { createVisionTools } from './tools.ts'
|
|
@@ -98,11 +99,27 @@ export async function apply(ctx: Context, config: VisionToolkitConfig = {}): Pro
|
|
|
98
99
|
const pastedImages = new PastedImageBackend(ctx, {
|
|
99
100
|
maxImageBytes: () => manager.status().activeConfig?.maxImageBytes ?? resolveConfig(settings.get()).maxImageBytes,
|
|
100
101
|
})
|
|
101
|
-
|
|
102
|
+
// Image-input variants register asynchronously once eligible routes exist;
|
|
103
|
+
// the runtime getter stays lazy so variants appear even when the runtime
|
|
104
|
+
// becomes ready after the first sweep.
|
|
105
|
+
const variants = installImageInputVariants(
|
|
106
|
+
ctx,
|
|
107
|
+
() => resolveConfig(settings.get()),
|
|
108
|
+
() => manager.ready ? manager.current() : undefined,
|
|
109
|
+
)
|
|
110
|
+
installVisionToolkitWeb(
|
|
111
|
+
ctx,
|
|
112
|
+
backend,
|
|
113
|
+
artifacts,
|
|
114
|
+
pastedImages,
|
|
115
|
+
createPasteTakeoverResolver(ctx, () => resolveConfig(settings.get())),
|
|
116
|
+
)
|
|
117
|
+
disposers.push(variants.dispose)
|
|
102
118
|
disposers.push(settings.watch(async (next) => {
|
|
103
119
|
try {
|
|
104
120
|
await manager.reconfigure(next)
|
|
105
121
|
ensureOperational()
|
|
122
|
+
variants.reconcile()
|
|
106
123
|
} catch (error) {
|
|
107
124
|
const message = error instanceof Error ? error.message : String(error)
|
|
108
125
|
ctx.logger.error('dsh-vision-toolkit: keeping the previous runtime after a refused Settings generation. %s', message)
|
package/src/paste-images.ts
CHANGED
|
@@ -11,6 +11,45 @@ import { sameOriginPost } from './web-request.ts'
|
|
|
11
11
|
/** Exact route used by the browser paste integration. */
|
|
12
12
|
export const PASTE_IMAGES_ROUTE = '/_dsh/vision-toolkit/paste-images'
|
|
13
13
|
|
|
14
|
+
/**
|
|
15
|
+
* Exact route the browser paste integration asks before taking a paste over:
|
|
16
|
+
* `GET ?sessionId=&model=&provider=&modelId=` answers the verdict from the
|
|
17
|
+
* live Session's model route.
|
|
18
|
+
*/
|
|
19
|
+
export const PASTE_POLICY_ROUTE = '/_dsh/vision-toolkit/paste-policy'
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* One exact model route the browser should switch the Session to before the
|
|
23
|
+
* native attachment flow: the image-input variant of the current text-only
|
|
24
|
+
* model. The variant declares image input, so the paste keeps the composer
|
|
25
|
+
* thumbnail and the durable session image.
|
|
26
|
+
*/
|
|
27
|
+
export interface PasteSwitchRoute {
|
|
28
|
+
/** The variant provider route (`vision-toolkit-` + upstream provider id). */
|
|
29
|
+
provider: string
|
|
30
|
+
/** The model id, identical to the upstream text-only model's id. */
|
|
31
|
+
model: string
|
|
32
|
+
/** The variant's selector display name (upstream name + variant suffix). */
|
|
33
|
+
label: string
|
|
34
|
+
/** The upstream reasoning effort, when the selection carries one. */
|
|
35
|
+
reasoningEffort?: string
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** The exact model route the browser read from the live model catalog. */
|
|
39
|
+
export interface PasteSelectionQuery {
|
|
40
|
+
provider: string
|
|
41
|
+
model: string
|
|
42
|
+
reasoningEffort?: string
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** The paste-policy answer for one Session and model route. */
|
|
46
|
+
export interface PasteVerdict {
|
|
47
|
+
/** Whether the browser should turn the paste into workspace paths instead of attachments. */
|
|
48
|
+
takeOver: boolean
|
|
49
|
+
/** When present, the browser switches to this route first, then lets the paste flow natively. */
|
|
50
|
+
autoSwitch?: PasteSwitchRoute
|
|
51
|
+
}
|
|
52
|
+
|
|
14
53
|
const MAX_NAME_BYTES = 180
|
|
15
54
|
|
|
16
55
|
interface PasteImageResponse {
|
package/src/runtime.ts
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
import { createHash, randomUUID } from 'node:crypto'
|
|
10
10
|
import { readFile, rm, stat, writeFile } from 'node:fs/promises'
|
|
11
11
|
import { basename, extname, join } from 'node:path'
|
|
12
|
+
import { fileURLToPath } from 'node:url'
|
|
12
13
|
import type { Context } from '@deepseek-ai/cordis'
|
|
13
14
|
import type { ResolvedCredential } from '@deepseek-ai/dsh-credentials'
|
|
14
15
|
import { SaxesParser } from 'saxes'
|
|
@@ -49,6 +50,8 @@ import {
|
|
|
49
50
|
import { PLUGIN_VERSION } from './version.ts'
|
|
50
51
|
|
|
51
52
|
const SVG_NAMESPACE = 'http://www.w3.org/2000/svg'
|
|
53
|
+
const VISION_MODEL_TEST_IMAGE = fileURLToPath(new URL('../assets/vision-model-test.png', import.meta.url))
|
|
54
|
+
const VISION_MODEL_TEST_PROMPT = 'This is an explicit service readiness test. Reply with one short sentence confirming that you received the image.'
|
|
52
55
|
|
|
53
56
|
function svgDocumentPathCount(svg: string): number | undefined {
|
|
54
57
|
const parser = new SaxesParser({ xmlns: true })
|
|
@@ -444,9 +447,11 @@ export interface VisionToolkitHealthResult {
|
|
|
444
447
|
artifactDirectory: HealthCheck
|
|
445
448
|
tempDirectory: HealthCheck
|
|
446
449
|
service: HealthCheck
|
|
450
|
+
model: HealthCheck
|
|
447
451
|
}
|
|
448
452
|
healthy: boolean
|
|
449
453
|
connectionTested: boolean
|
|
454
|
+
modelTested: boolean
|
|
450
455
|
}
|
|
451
456
|
|
|
452
457
|
/** Shared per-call execution options. */
|
|
@@ -764,6 +769,10 @@ export class VisionToolkitRuntime {
|
|
|
764
769
|
`credential ${this.config.provider.credential} is not configured; set it through DSH credentials`,
|
|
765
770
|
)
|
|
766
771
|
}
|
|
772
|
+
return this.visionEnv(resolved)
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
private visionEnv(resolved: ResolvedCredential): UpstreamEnvironment {
|
|
767
776
|
return {
|
|
768
777
|
VISION_API_KEY: resolved.value,
|
|
769
778
|
VISION_BASE_URL: this.config.provider.baseUrl,
|
|
@@ -1690,8 +1699,8 @@ export class VisionToolkitRuntime {
|
|
|
1690
1699
|
}
|
|
1691
1700
|
}
|
|
1692
1701
|
|
|
1693
|
-
/**
|
|
1694
|
-
async health(testConnection: boolean, options: ToolCallOptions): Promise<VisionToolkitHealthResult> {
|
|
1702
|
+
/** Health: inspect local readiness, optionally probe `/models`, and explicitly test one real multimodal request. */
|
|
1703
|
+
async health(testConnection: boolean, options: ToolCallOptions, testModel = false): Promise<VisionToolkitHealthResult> {
|
|
1695
1704
|
return this.runOperation('vision_toolkit_health', options, async (operation) => {
|
|
1696
1705
|
const info = this.upstreamVersion
|
|
1697
1706
|
const python: HealthCheck = { status: 'ok', detail: `${info.pythonVersion} via ${info.python}` }
|
|
@@ -1733,6 +1742,10 @@ export class VisionToolkitRuntime {
|
|
|
1733
1742
|
status: 'not_tested',
|
|
1734
1743
|
detail: 'Connection was not tested; pass testConnection=true to query the configured /models endpoint',
|
|
1735
1744
|
}
|
|
1745
|
+
let model: HealthCheck = {
|
|
1746
|
+
status: 'not_tested',
|
|
1747
|
+
detail: 'Vision model was not tested; run an explicit model test to send the bundled diagnostic image',
|
|
1748
|
+
}
|
|
1736
1749
|
if (testConnection) {
|
|
1737
1750
|
if (resolvedCredential === undefined) {
|
|
1738
1751
|
service = { status: 'error', detail: 'Connection test skipped because the configured credential is unavailable' }
|
|
@@ -1775,7 +1788,32 @@ export class VisionToolkitRuntime {
|
|
|
1775
1788
|
}
|
|
1776
1789
|
}
|
|
1777
1790
|
}
|
|
1778
|
-
|
|
1791
|
+
if (testModel) {
|
|
1792
|
+
if (resolvedCredential === undefined) {
|
|
1793
|
+
model = { status: 'error', detail: 'Vision model test skipped because the configured credential is unavailable' }
|
|
1794
|
+
} else {
|
|
1795
|
+
try {
|
|
1796
|
+
const result = await this.runUpstream(
|
|
1797
|
+
'glance',
|
|
1798
|
+
[VISION_MODEL_TEST_IMAGE, '-q', VISION_MODEL_TEST_PROMPT],
|
|
1799
|
+
operation,
|
|
1800
|
+
this.visionEnv(resolvedCredential),
|
|
1801
|
+
)
|
|
1802
|
+
if (result.stdout.trim().length === 0) {
|
|
1803
|
+
throw new VisionToolkitError('output', 'glance: vision API returned an empty description')
|
|
1804
|
+
}
|
|
1805
|
+
model = {
|
|
1806
|
+
status: 'ok',
|
|
1807
|
+
detail: `Vision model ${this.config.provider.model} completed a multimodal request`,
|
|
1808
|
+
}
|
|
1809
|
+
} catch (error) {
|
|
1810
|
+
if (operation.signal.aborted) throw error
|
|
1811
|
+
const detail = error instanceof Error ? error.message : String(error)
|
|
1812
|
+
model = { status: 'error', detail: `Vision model test failed: ${detail.slice(0, 600)}` }
|
|
1813
|
+
}
|
|
1814
|
+
}
|
|
1815
|
+
}
|
|
1816
|
+
const checks = { python, dependencies, chrome, credential, artifactDirectory, tempDirectory, service, model }
|
|
1779
1817
|
const healthy = Object.values(checks).every(check => check.status !== 'error')
|
|
1780
1818
|
return {
|
|
1781
1819
|
pluginVersion: PLUGIN_VERSION,
|
|
@@ -1783,6 +1821,7 @@ export class VisionToolkitRuntime {
|
|
|
1783
1821
|
checks,
|
|
1784
1822
|
healthy,
|
|
1785
1823
|
connectionTested: testConnection,
|
|
1824
|
+
modelTested: testModel,
|
|
1786
1825
|
}
|
|
1787
1826
|
})
|
|
1788
1827
|
}
|
package/src/web-request.ts
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import type { IncomingMessage } from 'node:http'
|
|
2
2
|
|
|
3
|
-
/**
|
|
4
|
-
|
|
3
|
+
/**
|
|
4
|
+
* Accept a request only from the DSH Web application's origin. Method-agnostic:
|
|
5
|
+
* the same fence guards state-changing POSTs and policy GETs.
|
|
6
|
+
* @param req - the incoming request whose headers carry the origin evidence.
|
|
7
|
+
* @returns whether the request may be answered.
|
|
8
|
+
*/
|
|
9
|
+
export function sameOriginRequest(req: IncomingMessage): boolean {
|
|
5
10
|
const fetchSite = req.headers['sec-fetch-site']
|
|
6
11
|
if (fetchSite === 'cross-site') return false
|
|
7
12
|
const origin = req.headers.origin
|
|
@@ -15,3 +20,8 @@ export function sameOriginPost(req: IncomingMessage): boolean {
|
|
|
15
20
|
return false
|
|
16
21
|
}
|
|
17
22
|
}
|
|
23
|
+
|
|
24
|
+
/** Accept state-changing requests only from the DSH Web application's origin. */
|
|
25
|
+
export function sameOriginPost(req: IncomingMessage): boolean {
|
|
26
|
+
return sameOriginRequest(req)
|
|
27
|
+
}
|
package/src/web.ts
CHANGED
|
@@ -13,7 +13,13 @@ import { SettingsConflictError, type SettingsDescriptor } from '@deepseek-ai/dsh
|
|
|
13
13
|
// Type-only import activates the optional webServer Context declaration.
|
|
14
14
|
import type {} from '@deepseek-ai/dsh-host-webserver'
|
|
15
15
|
import { ArtifactAccessController, ARTIFACT_ROUTE_PREFIX } from './artifact-access.ts'
|
|
16
|
-
import {
|
|
16
|
+
import {
|
|
17
|
+
PastedImageBackend,
|
|
18
|
+
PASTE_IMAGES_ROUTE,
|
|
19
|
+
PASTE_POLICY_ROUTE,
|
|
20
|
+
type PasteSelectionQuery,
|
|
21
|
+
type PasteVerdict,
|
|
22
|
+
} from './paste-images.ts'
|
|
17
23
|
import {
|
|
18
24
|
resolveConfig,
|
|
19
25
|
VISION_TOOLKIT_SETTINGS_NAMESPACE,
|
|
@@ -27,7 +33,7 @@ import {
|
|
|
27
33
|
type RuntimeManagerStatus,
|
|
28
34
|
} from './runtime-manager.ts'
|
|
29
35
|
import { PLUGIN_VERSION, UPSTREAM_COMMIT, UPSTREAM_REPOSITORY, UPSTREAM_VERSION } from './version.ts'
|
|
30
|
-
import { sameOriginPost } from './web-request.ts'
|
|
36
|
+
import { sameOriginPost, sameOriginRequest } from './web-request.ts'
|
|
31
37
|
|
|
32
38
|
/** Exact route used by the browser Settings page. */
|
|
33
39
|
export const SETTINGS_ROUTE = '/_dsh/vision-toolkit/settings'
|
|
@@ -68,6 +74,7 @@ interface SaveRequest {
|
|
|
68
74
|
interface HealthRequest {
|
|
69
75
|
action: 'health'
|
|
70
76
|
testConnection: boolean
|
|
77
|
+
testModel: boolean
|
|
71
78
|
}
|
|
72
79
|
|
|
73
80
|
interface CredentialRequest {
|
|
@@ -150,7 +157,10 @@ function parseRequest(value: unknown): SettingsRequest {
|
|
|
150
157
|
if (!isRecord(value) || typeof value.action !== 'string') throw new TypeError('request action is required')
|
|
151
158
|
if (value.action === 'health') {
|
|
152
159
|
if (typeof value.testConnection !== 'boolean') throw new TypeError('health.testConnection must be boolean')
|
|
153
|
-
|
|
160
|
+
const testModel = value.testModel === undefined ? false : value.testModel
|
|
161
|
+
if (typeof testModel !== 'boolean') throw new TypeError('health.testModel must be boolean')
|
|
162
|
+
if (testModel && !value.testConnection) throw new TypeError('health.testModel requires health.testConnection')
|
|
163
|
+
return { action: 'health', testConnection: value.testConnection, testModel }
|
|
154
164
|
}
|
|
155
165
|
if (value.action === 'save') {
|
|
156
166
|
if (!Number.isSafeInteger(value.expectedRevision) || (value.expectedRevision as number) < 0) {
|
|
@@ -288,7 +298,7 @@ export class VisionToolkitWebBackend {
|
|
|
288
298
|
signal: controller.signal,
|
|
289
299
|
workspace: process.cwd(),
|
|
290
300
|
sessionId: 'vision-toolkit-settings',
|
|
291
|
-
})
|
|
301
|
+
}, request.testModel)
|
|
292
302
|
} finally {
|
|
293
303
|
req.off('aborted', abort)
|
|
294
304
|
req.socket.off('close', abort)
|
|
@@ -353,17 +363,87 @@ export class VisionToolkitWebBackend {
|
|
|
353
363
|
}
|
|
354
364
|
}
|
|
355
365
|
|
|
366
|
+
/**
|
|
367
|
+
* Same-origin policy handler for the paste route: whether the browser should
|
|
368
|
+
* take a paste over into workspace paths, or let it flow natively after an
|
|
369
|
+
* optional automatic switch to the image-input variant. The optional `model`
|
|
370
|
+
* query carries the model-selector label the client currently shows; the
|
|
371
|
+
* optional `provider`/`modelId`/`reasoningEffort` queries carry the exact
|
|
372
|
+
* route the client read from the live model catalog, which the resolver
|
|
373
|
+
* prefers (a label alone cannot pick a provider). Unresolvable routes answer
|
|
374
|
+
* native — the safe default.
|
|
375
|
+
* @param resolve - resolves one live Session's paste verdict.
|
|
376
|
+
* @returns the HTTP handler.
|
|
377
|
+
*/
|
|
378
|
+
export function createPastePolicyHandler(
|
|
379
|
+
resolve: (sessionId: string, selection?: PasteSelectionQuery, modelLabel?: string) => Promise<PasteVerdict>,
|
|
380
|
+
): (req: IncomingMessage, res: ServerResponse) => void {
|
|
381
|
+
return (req, res) => {
|
|
382
|
+
void (async () => {
|
|
383
|
+
try {
|
|
384
|
+
if (req.method !== 'GET') {
|
|
385
|
+
requestError(res, 405, 'method-not-allowed', 'Use GET')
|
|
386
|
+
return
|
|
387
|
+
}
|
|
388
|
+
if (!sameOriginRequest(req)) {
|
|
389
|
+
requestError(res, 403, 'origin-rejected', 'The request must originate from this DSH Web application')
|
|
390
|
+
return
|
|
391
|
+
}
|
|
392
|
+
let sessionId: string
|
|
393
|
+
let modelLabel: string | undefined
|
|
394
|
+
let selection: PasteSelectionQuery | undefined
|
|
395
|
+
try {
|
|
396
|
+
const url = new URL(req.url ?? PASTE_POLICY_ROUTE, 'http://dsh.internal')
|
|
397
|
+
const sessions = url.searchParams.getAll('sessionId')
|
|
398
|
+
if (sessions.length !== 1 || sessions[0] === undefined || sessions[0] === '') {
|
|
399
|
+
throw new TypeError('sessionId is required exactly once')
|
|
400
|
+
}
|
|
401
|
+
sessionId = sessions[0]!
|
|
402
|
+
const models = url.searchParams.getAll('model')
|
|
403
|
+
if (models.length > 1) throw new TypeError('model may be given at most once')
|
|
404
|
+
modelLabel = models[0]
|
|
405
|
+
const providers = url.searchParams.getAll('provider')
|
|
406
|
+
if (providers.length > 1) throw new TypeError('provider may be given at most once')
|
|
407
|
+
const modelIds = url.searchParams.getAll('modelId')
|
|
408
|
+
if (modelIds.length > 1) throw new TypeError('modelId may be given at most once')
|
|
409
|
+
const efforts = url.searchParams.getAll('reasoningEffort')
|
|
410
|
+
if (efforts.length > 1) throw new TypeError('reasoningEffort may be given at most once')
|
|
411
|
+
const provider = providers[0]
|
|
412
|
+
const modelId = modelIds[0]
|
|
413
|
+
if (provider !== undefined && modelId !== undefined && provider !== '' && modelId !== '') {
|
|
414
|
+
selection = {
|
|
415
|
+
provider,
|
|
416
|
+
model: modelId,
|
|
417
|
+
...(efforts[0] === undefined || efforts[0] === '' ? {} : { reasoningEffort: efforts[0] }),
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
} catch (error) {
|
|
421
|
+
requestError(res, 400, 'invalid-request', publicMessage(error))
|
|
422
|
+
return
|
|
423
|
+
}
|
|
424
|
+
const verdict = await resolve(sessionId, selection, modelLabel)
|
|
425
|
+
responseJson(res, 200, { ok: true, value: verdict })
|
|
426
|
+
} catch (error) {
|
|
427
|
+
requestError(res, 500, 'policy-failed', publicMessage(error))
|
|
428
|
+
}
|
|
429
|
+
})()
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
356
433
|
/**
|
|
357
434
|
* Attach optional Web routes whenever a webServer service is present.
|
|
358
435
|
* @param ctx - plugin context owning route effects.
|
|
359
436
|
* @param backend - Settings handler.
|
|
360
437
|
* @param artifacts - signed Artifact handler.
|
|
438
|
+
* @param pastedImages - pasted-image workspace handler.
|
|
439
|
+
* @param pastePolicy - paste-policy verdict resolver (sessionId, selection, modelLabel).
|
|
361
440
|
*/
|
|
362
441
|
export function installVisionToolkitWeb(
|
|
363
442
|
ctx: Context,
|
|
364
443
|
backend: VisionToolkitWebBackend,
|
|
365
444
|
artifacts: ArtifactAccessController,
|
|
366
445
|
pastedImages: PastedImageBackend,
|
|
446
|
+
pastePolicy: (sessionId: string, selection?: PasteSelectionQuery, modelLabel?: string) => Promise<PasteVerdict>,
|
|
367
447
|
): void {
|
|
368
448
|
ctx.inject(['webServer'], (webCtx) => {
|
|
369
449
|
webCtx.effect(() => {
|
|
@@ -383,7 +463,13 @@ export function installVisionToolkitWeb(
|
|
|
383
463
|
path: PASTE_IMAGES_ROUTE,
|
|
384
464
|
handler: (req, res) => pastedImages.handle(req, res),
|
|
385
465
|
})
|
|
466
|
+
const disposePastePolicy = webCtx.webServer.register({
|
|
467
|
+
kind: 'exact',
|
|
468
|
+
path: PASTE_POLICY_ROUTE,
|
|
469
|
+
handler: createPastePolicyHandler(pastePolicy),
|
|
470
|
+
})
|
|
386
471
|
return () => {
|
|
472
|
+
disposePastePolicy()
|
|
387
473
|
disposePasteImages()
|
|
388
474
|
disposeSettings()
|
|
389
475
|
disposeArtifact()
|