@anionex/dsh-vision-toolkit 0.1.5
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/LICENSE +21 -0
- package/README.i18n.yaml +6 -0
- package/README.md +383 -0
- package/README.zh.md +383 -0
- package/assets/dsh-conversation-artifact.png +0 -0
- package/assets/dsh-conversation-image-qa-top.png +0 -0
- package/assets/dsh-conversation-image-qa.png +0 -0
- package/assets/dsh-conversation-pixel-diff.png +0 -0
- package/assets/dsh-conversation-screenshot-debugging-top.png +0 -0
- package/assets/dsh-conversation-screenshot-debugging.png +0 -0
- package/assets/dsh-conversation-tool-call.png +0 -0
- package/assets/dsh-conversation-vision-trace.png +0 -0
- package/assets/hero.png +0 -0
- package/assets/social-preview.png +0 -0
- package/assets/upstream/README.md +16 -0
- package/assets/upstream/image-qa.webp +0 -0
- package/assets/upstream/infographic-reference.webp +0 -0
- package/assets/upstream/infographic-result.webp +0 -0
- package/assets/upstream/screenshot-debugging.webp +0 -0
- package/assets/upstream/ui-result.webp +0 -0
- package/assets/upstream/ui-sketch.webp +0 -0
- package/assets/vision-settings.png +0 -0
- package/cordis.patch.yml +6 -0
- package/docs/assets/vision-settings.png +0 -0
- package/docs/requirements-traceability/README.i18n.yaml +6 -0
- package/docs/requirements-traceability/README.md +75 -0
- package/docs/requirements-traceability/README.zh.md +75 -0
- package/examples/ui-restoration/README.i18n.yaml +6 -0
- package/examples/ui-restoration/README.md +70 -0
- package/examples/ui-restoration/README.zh.md +70 -0
- package/examples/ui-restoration/assets/final-heatmap.png +0 -0
- package/examples/ui-restoration/assets/final-report.json +83 -0
- package/examples/ui-restoration/assets/implementation.png +0 -0
- package/examples/ui-restoration/assets/initial-heatmap.png +0 -0
- package/examples/ui-restoration/assets/initial-report.json +83 -0
- package/examples/ui-restoration/assets/initial.png +0 -0
- package/examples/ui-restoration/assets/metrics.json +12 -0
- package/examples/ui-restoration/assets/reference.png +0 -0
- package/examples/ui-restoration/implementation.html +94 -0
- package/examples/ui-restoration/initial.html +57 -0
- package/lib/artifact-access.js +369 -0
- package/lib/artifact-access.js.map +1 -0
- package/lib/artifacts.js +56 -0
- package/lib/artifacts.js.map +1 -0
- package/lib/client.js +952 -0
- package/lib/client.js.map +1 -0
- package/lib/config.js +117 -0
- package/lib/config.js.map +1 -0
- package/lib/errors.js +56 -0
- package/lib/errors.js.map +1 -0
- package/lib/exposure.js +213 -0
- package/lib/exposure.js.map +1 -0
- package/lib/index.js +97 -0
- package/lib/index.js.map +1 -0
- package/lib/paste-images.js +199 -0
- package/lib/paste-images.js.map +1 -0
- package/lib/paths.js +325 -0
- package/lib/paths.js.map +1 -0
- package/lib/runtime-install.js +601 -0
- package/lib/runtime-install.js.map +1 -0
- package/lib/runtime-manager.js +126 -0
- package/lib/runtime-manager.js.map +1 -0
- package/lib/runtime.js +1344 -0
- package/lib/runtime.js.map +1 -0
- package/lib/skill.js +139 -0
- package/lib/skill.js.map +1 -0
- package/lib/tools.js +528 -0
- package/lib/tools.js.map +1 -0
- package/lib/types/artifact-access.d.ts +61 -0
- package/lib/types/artifact-access.d.ts.map +1 -0
- package/lib/types/artifacts.d.ts +42 -0
- package/lib/types/artifacts.d.ts.map +1 -0
- package/lib/types/client/index.d.ts +179 -0
- package/lib/types/client/index.d.ts.map +1 -0
- package/lib/types/client/paste-images.d.ts +57 -0
- package/lib/types/client/paste-images.d.ts.map +1 -0
- package/lib/types/config.d.ts +73 -0
- package/lib/types/config.d.ts.map +1 -0
- package/lib/types/errors.d.ts +35 -0
- package/lib/types/errors.d.ts.map +1 -0
- package/lib/types/exposure.d.ts +40 -0
- package/lib/types/exposure.d.ts.map +1 -0
- package/lib/types/index.d.ts +18 -0
- package/lib/types/index.d.ts.map +1 -0
- package/lib/types/paste-images.d.ts +21 -0
- package/lib/types/paste-images.d.ts.map +1 -0
- package/lib/types/paths.d.ts +107 -0
- package/lib/types/paths.d.ts.map +1 -0
- package/lib/types/runtime-install.d.ts +49 -0
- package/lib/types/runtime-install.d.ts.map +1 -0
- package/lib/types/runtime-manager.d.ts +60 -0
- package/lib/types/runtime-manager.d.ts.map +1 -0
- package/lib/types/runtime.d.ts +389 -0
- package/lib/types/runtime.d.ts.map +1 -0
- package/lib/types/skill.d.ts +15 -0
- package/lib/types/skill.d.ts.map +1 -0
- package/lib/types/tools.d.ts +22 -0
- package/lib/types/tools.d.ts.map +1 -0
- package/lib/types/upstream.d.ts +207 -0
- package/lib/types/upstream.d.ts.map +1 -0
- package/lib/types/version.d.ts +15 -0
- package/lib/types/version.d.ts.map +1 -0
- package/lib/types/web-request.d.ts +4 -0
- package/lib/types/web-request.d.ts.map +1 -0
- package/lib/types/web.d.ts +74 -0
- package/lib/types/web.d.ts.map +1 -0
- package/lib/upstream.js +675 -0
- package/lib/upstream.js.map +1 -0
- package/lib/version.js +18 -0
- package/lib/version.js.map +1 -0
- package/lib/web-request.js +20 -0
- package/lib/web-request.js.map +1 -0
- package/lib/web.js +244 -0
- package/lib/web.js.map +1 -0
- package/package.json +139 -0
- package/runtime/requirements.lock +3 -0
- package/src/artifact-access.ts +386 -0
- package/src/artifacts.ts +85 -0
- package/src/client/index.tsx +866 -0
- package/src/client/paste-images.tsx +426 -0
- package/src/config.ts +177 -0
- package/src/errors.ts +62 -0
- package/src/exposure.ts +227 -0
- package/src/index.ts +122 -0
- package/src/paste-images.ts +234 -0
- package/src/paths.ts +348 -0
- package/src/runtime-install.ts +723 -0
- package/src/runtime-manager.ts +166 -0
- package/src/runtime.ts +1783 -0
- package/src/skill.ts +143 -0
- package/src/tools.ts +668 -0
- package/src/upstream.ts +861 -0
- package/src/version.ts +37 -0
- package/src/web-request.ts +17 -0
- package/src/web.ts +329 -0
- package/vendor/agent-vision-toolkit/CHANGELOG.md +16 -0
- package/vendor/agent-vision-toolkit/LICENSE +21 -0
- package/vendor/agent-vision-toolkit/README.md +399 -0
- package/vendor/agent-vision-toolkit/UPSTREAM_MANIFEST.json +89 -0
- package/vendor/agent-vision-toolkit/bin/crop +90 -0
- package/vendor/agent-vision-toolkit/bin/detect +13 -0
- package/vendor/agent-vision-toolkit/bin/glance +93 -0
- package/vendor/agent-vision-toolkit/bin/ground +13 -0
- package/vendor/agent-vision-toolkit/bin/trace +129 -0
- package/vendor/agent-vision-toolkit/detect.py +56 -0
- package/vendor/agent-vision-toolkit/ground.py +216 -0
- package/vendor/agent-vision-toolkit/skills/vision-tools/scripts/dominant_colors.py +224 -0
- package/vendor/agent-vision-toolkit/skills/vision-tools/scripts/extract_fg.py +278 -0
- package/vendor/agent-vision-toolkit/skills/vision-tools/scripts/html_shot.py +108 -0
- package/vendor/agent-vision-toolkit/skills/vision-tools/scripts/long_screenshot_ocr.py +1245 -0
- package/vendor/agent-vision-toolkit/skills/vision-tools/scripts/pixel_diff.py +88 -0
- package/vendor/agent-vision-toolkit/vision_client.py +156 -0
package/src/version.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin and pinned upstream version facts. The upstream snapshot is fixed at
|
|
3
|
+
* build time and recorded in package.json's `dsh.visionToolkit` metadata so
|
|
4
|
+
* the runtime, tool results, and docs all report the same source commit.
|
|
5
|
+
* @module dsh-vision-toolkit/version
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { readFileSync } from 'node:fs'
|
|
9
|
+
|
|
10
|
+
interface VisionToolkitPackageMetadata {
|
|
11
|
+
version: string
|
|
12
|
+
dsh?: {
|
|
13
|
+
visionToolkit?: {
|
|
14
|
+
upstreamRepository?: string
|
|
15
|
+
upstreamVersion?: string
|
|
16
|
+
upstreamCommit?: string
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const metadata = JSON.parse(
|
|
22
|
+
readFileSync(new URL('../package.json', import.meta.url), 'utf8'),
|
|
23
|
+
) as VisionToolkitPackageMetadata
|
|
24
|
+
|
|
25
|
+
const visionToolkit = metadata.dsh?.visionToolkit
|
|
26
|
+
|
|
27
|
+
/** Plugin package version. */
|
|
28
|
+
export const PLUGIN_VERSION = metadata.version
|
|
29
|
+
|
|
30
|
+
/** Pinned upstream repository URL. */
|
|
31
|
+
export const UPSTREAM_REPOSITORY = visionToolkit?.upstreamRepository ?? 'https://github.com/Anionex/agent-vision-toolkit'
|
|
32
|
+
|
|
33
|
+
/** Pinned upstream release tag. */
|
|
34
|
+
export const UPSTREAM_VERSION = visionToolkit?.upstreamVersion ?? 'v0.1.0+snapshot.8ccf9d7'
|
|
35
|
+
|
|
36
|
+
/** Pinned upstream source commit. */
|
|
37
|
+
export const UPSTREAM_COMMIT = visionToolkit?.upstreamCommit ?? '8ccf9d7d89bc395ad7dd19dde840401a5b84623d'
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { IncomingMessage } from 'node:http'
|
|
2
|
+
|
|
3
|
+
/** Accept state-changing requests only from the DSH Web application's origin. */
|
|
4
|
+
export function sameOriginPost(req: IncomingMessage): boolean {
|
|
5
|
+
const fetchSite = req.headers['sec-fetch-site']
|
|
6
|
+
if (fetchSite === 'cross-site') return false
|
|
7
|
+
const origin = req.headers.origin
|
|
8
|
+
if (origin === undefined) return fetchSite === 'same-origin' || fetchSite === 'same-site' || fetchSite === 'none'
|
|
9
|
+
const host = req.headers.host
|
|
10
|
+
if (host === undefined) return false
|
|
11
|
+
try {
|
|
12
|
+
const parsed = new URL(origin)
|
|
13
|
+
return (parsed.protocol === 'http:' || parsed.protocol === 'https:') && parsed.host === host
|
|
14
|
+
} catch {
|
|
15
|
+
return false
|
|
16
|
+
}
|
|
17
|
+
}
|
package/src/web.ts
ADDED
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Optional Web-profile routes: signed Artifact delivery plus a same-origin
|
|
3
|
+
* Settings/health endpoint. The browser never receives credential values and
|
|
4
|
+
* connection tests run only after an explicit POST action.
|
|
5
|
+
* @module dsh-vision-toolkit/web
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { IncomingMessage, ServerResponse } from 'node:http'
|
|
9
|
+
import type { Context } from '@deepseek-ai/cordis'
|
|
10
|
+
import type { CredentialInfo } from '@deepseek-ai/dsh-credentials'
|
|
11
|
+
import { credentialRef } from '@deepseek-ai/dsh-credentials'
|
|
12
|
+
import { SettingsConflictError, type SettingsDescriptor } from '@deepseek-ai/dsh-settings'
|
|
13
|
+
// Type-only import activates the optional webServer Context declaration.
|
|
14
|
+
import type {} from '@deepseek-ai/dsh-host-webserver'
|
|
15
|
+
import { ArtifactAccessController, ARTIFACT_ROUTE_PREFIX } from './artifact-access.ts'
|
|
16
|
+
import { PastedImageBackend, PASTE_IMAGES_ROUTE } from './paste-images.ts'
|
|
17
|
+
import {
|
|
18
|
+
resolveConfig,
|
|
19
|
+
VISION_TOOLKIT_SETTINGS_NAMESPACE,
|
|
20
|
+
type ResolvedVisionToolkitConfig,
|
|
21
|
+
type VisionToolkitConfig,
|
|
22
|
+
} from './config.ts'
|
|
23
|
+
import type { VisionToolkitHealthResult } from './runtime.ts'
|
|
24
|
+
import {
|
|
25
|
+
VisionToolkitRuntimeManager,
|
|
26
|
+
type PreparedRuntimeGeneration,
|
|
27
|
+
type RuntimeManagerStatus,
|
|
28
|
+
} from './runtime-manager.ts'
|
|
29
|
+
import { PLUGIN_VERSION, UPSTREAM_COMMIT, UPSTREAM_REPOSITORY, UPSTREAM_VERSION } from './version.ts'
|
|
30
|
+
import { sameOriginPost } from './web-request.ts'
|
|
31
|
+
|
|
32
|
+
/** Exact route used by the browser Settings page. */
|
|
33
|
+
export const SETTINGS_ROUTE = '/_dsh/vision-toolkit/settings'
|
|
34
|
+
|
|
35
|
+
/** Public Settings snapshot; credential values are deliberately impossible here. */
|
|
36
|
+
export interface VisionToolkitSettingsSnapshot {
|
|
37
|
+
schemaVersion: 1
|
|
38
|
+
writable: boolean
|
|
39
|
+
settings: {
|
|
40
|
+
value: VisionToolkitConfig
|
|
41
|
+
user?: unknown
|
|
42
|
+
base?: unknown
|
|
43
|
+
revision: number
|
|
44
|
+
applies: 'live'
|
|
45
|
+
}
|
|
46
|
+
credential: {
|
|
47
|
+
ref: string
|
|
48
|
+
configured: boolean
|
|
49
|
+
source?: string
|
|
50
|
+
writable: boolean
|
|
51
|
+
}
|
|
52
|
+
runtime: RuntimeManagerStatus
|
|
53
|
+
release: {
|
|
54
|
+
pluginVersion: string
|
|
55
|
+
upstreamRepository: string
|
|
56
|
+
upstreamVersion: string
|
|
57
|
+
upstreamCommit: string
|
|
58
|
+
}
|
|
59
|
+
artifactRouteAvailable: boolean
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
interface SaveRequest {
|
|
63
|
+
action: 'save'
|
|
64
|
+
expectedRevision: number
|
|
65
|
+
value: VisionToolkitConfig
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
interface HealthRequest {
|
|
69
|
+
action: 'health'
|
|
70
|
+
testConnection: boolean
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
type SettingsRequest = SaveRequest | HealthRequest
|
|
74
|
+
|
|
75
|
+
interface JsonError {
|
|
76
|
+
ok: false
|
|
77
|
+
error: { code: string; message: string }
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
interface JsonSuccess<T> {
|
|
81
|
+
ok: true
|
|
82
|
+
value: T
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
type JsonResponse<T> = JsonSuccess<T> | JsonError
|
|
86
|
+
|
|
87
|
+
/** Minimal runtime-manager face used by the Web route and its tests. */
|
|
88
|
+
export interface WebRuntimeManager {
|
|
89
|
+
readonly ready: boolean
|
|
90
|
+
current(): ReturnType<VisionToolkitRuntimeManager['current']>
|
|
91
|
+
prepareCandidate(raw: VisionToolkitConfig): Promise<PreparedRuntimeGeneration>
|
|
92
|
+
activateCandidate(candidate: PreparedRuntimeGeneration): void
|
|
93
|
+
recordFailure(error: unknown): void
|
|
94
|
+
status(): RuntimeManagerStatus
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** Callback invoked when a Settings save makes the first runtime available. */
|
|
98
|
+
export type RuntimeActivated = () => void
|
|
99
|
+
|
|
100
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
101
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function descriptorOf(ctx: Context): SettingsDescriptor {
|
|
105
|
+
const descriptor = ctx.settings.describe().find(row => row.ns === VISION_TOOLKIT_SETTINGS_NAMESPACE)
|
|
106
|
+
if (descriptor === undefined) throw new Error('vision-toolkit Settings namespace is not registered')
|
|
107
|
+
return descriptor
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function responseJson<T>(res: ServerResponse, status: number, body: JsonResponse<T>): void {
|
|
111
|
+
const bytes = Buffer.from(JSON.stringify(body))
|
|
112
|
+
res.setHeader('Content-Type', 'application/json; charset=utf-8')
|
|
113
|
+
res.setHeader('Content-Length', String(bytes.length))
|
|
114
|
+
res.setHeader('Cache-Control', 'no-store')
|
|
115
|
+
res.setHeader('X-Content-Type-Options', 'nosniff')
|
|
116
|
+
res.setHeader('Content-Security-Policy', "default-src 'none'; frame-ancestors 'none'")
|
|
117
|
+
res.writeHead(status)
|
|
118
|
+
res.end(bytes)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function requestError(res: ServerResponse, status: number, code: string, message: string): void {
|
|
122
|
+
responseJson(res, status, { ok: false, error: { code, message } })
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async function readJson(req: IncomingMessage, maxBytes = 64 * 1024): Promise<unknown> {
|
|
126
|
+
const contentType = req.headers['content-type']?.split(';', 1)[0]?.trim().toLowerCase()
|
|
127
|
+
if (contentType !== 'application/json') throw new TypeError('Content-Type must be application/json')
|
|
128
|
+
const chunks: Buffer[] = []
|
|
129
|
+
let bytes = 0
|
|
130
|
+
for await (const chunk of req) {
|
|
131
|
+
const part = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)
|
|
132
|
+
bytes += part.length
|
|
133
|
+
if (bytes > maxBytes) throw new RangeError(`request body exceeds ${maxBytes} bytes`)
|
|
134
|
+
chunks.push(part)
|
|
135
|
+
}
|
|
136
|
+
if (chunks.length === 0) throw new TypeError('request body is empty')
|
|
137
|
+
return JSON.parse(Buffer.concat(chunks).toString('utf8')) as unknown
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function parseRequest(value: unknown): SettingsRequest {
|
|
141
|
+
if (!isRecord(value) || typeof value.action !== 'string') throw new TypeError('request action is required')
|
|
142
|
+
if (value.action === 'health') {
|
|
143
|
+
if (typeof value.testConnection !== 'boolean') throw new TypeError('health.testConnection must be boolean')
|
|
144
|
+
return { action: 'health', testConnection: value.testConnection }
|
|
145
|
+
}
|
|
146
|
+
if (value.action === 'save') {
|
|
147
|
+
if (!Number.isSafeInteger(value.expectedRevision) || (value.expectedRevision as number) < 0) {
|
|
148
|
+
throw new TypeError('save.expectedRevision must be a non-negative integer')
|
|
149
|
+
}
|
|
150
|
+
if (!isRecord(value.value)) throw new TypeError('save.value must be an object')
|
|
151
|
+
return {
|
|
152
|
+
action: 'save',
|
|
153
|
+
expectedRevision: value.expectedRevision as number,
|
|
154
|
+
value: value.value as VisionToolkitConfig,
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
throw new TypeError(`unsupported action: ${value.action}`)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function publicMessage(error: unknown): string {
|
|
161
|
+
if (error instanceof Error) return error.message
|
|
162
|
+
return String(error)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/** Same-origin Settings and health handler. */
|
|
166
|
+
export class VisionToolkitWebBackend {
|
|
167
|
+
constructor(
|
|
168
|
+
private readonly ctx: Context,
|
|
169
|
+
private readonly manager: WebRuntimeManager,
|
|
170
|
+
private readonly artifacts: ArtifactAccessController,
|
|
171
|
+
private readonly onRuntimeActivated: RuntimeActivated,
|
|
172
|
+
) {}
|
|
173
|
+
|
|
174
|
+
private async credential(config: ResolvedVisionToolkitConfig): Promise<CredentialInfo> {
|
|
175
|
+
return this.ctx.credentials.describe(credentialRef(String(config.provider.credential)))
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** Build the current settings/runtime/credential snapshot without secrets. */
|
|
179
|
+
async snapshot(): Promise<VisionToolkitSettingsSnapshot> {
|
|
180
|
+
const descriptor = descriptorOf(this.ctx)
|
|
181
|
+
const value = descriptor.value as VisionToolkitConfig
|
|
182
|
+
const resolved = resolveConfig(value)
|
|
183
|
+
const credential = await this.credential(resolved)
|
|
184
|
+
return {
|
|
185
|
+
schemaVersion: 1,
|
|
186
|
+
writable: this.ctx.settings.writable,
|
|
187
|
+
settings: {
|
|
188
|
+
value,
|
|
189
|
+
...(descriptor.user === undefined ? {} : { user: descriptor.user }),
|
|
190
|
+
...(descriptor.base === undefined ? {} : { base: descriptor.base }),
|
|
191
|
+
revision: descriptor.revision,
|
|
192
|
+
applies: 'live',
|
|
193
|
+
},
|
|
194
|
+
credential: {
|
|
195
|
+
ref: String(resolved.provider.credential),
|
|
196
|
+
configured: credential.configured,
|
|
197
|
+
...(credential.source === undefined ? {} : { source: credential.source }),
|
|
198
|
+
writable: credential.writable,
|
|
199
|
+
},
|
|
200
|
+
runtime: this.manager.status(),
|
|
201
|
+
release: {
|
|
202
|
+
pluginVersion: PLUGIN_VERSION,
|
|
203
|
+
upstreamRepository: UPSTREAM_REPOSITORY,
|
|
204
|
+
upstreamVersion: UPSTREAM_VERSION,
|
|
205
|
+
upstreamCommit: UPSTREAM_COMMIT,
|
|
206
|
+
},
|
|
207
|
+
artifactRouteAvailable: this.artifacts.routeAvailable,
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
private async save(request: SaveRequest): Promise<VisionToolkitSettingsSnapshot> {
|
|
212
|
+
if (!this.ctx.settings.writable) throw new Error('settings provider is read-only')
|
|
213
|
+
let candidate: PreparedRuntimeGeneration
|
|
214
|
+
try {
|
|
215
|
+
candidate = await this.manager.prepareCandidate(request.value)
|
|
216
|
+
} catch (error) {
|
|
217
|
+
this.manager.recordFailure(error)
|
|
218
|
+
throw error
|
|
219
|
+
}
|
|
220
|
+
await this.ctx.settings.replace(
|
|
221
|
+
VISION_TOOLKIT_SETTINGS_NAMESPACE,
|
|
222
|
+
request.value as object,
|
|
223
|
+
request.expectedRevision,
|
|
224
|
+
)
|
|
225
|
+
this.manager.activateCandidate(candidate)
|
|
226
|
+
this.onRuntimeActivated()
|
|
227
|
+
return this.snapshot()
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
private async health(request: HealthRequest, req: IncomingMessage): Promise<VisionToolkitHealthResult> {
|
|
231
|
+
if (!this.manager.ready) throw new Error('runtime is not ready; fix Settings and save a valid configuration first')
|
|
232
|
+
const controller = new AbortController()
|
|
233
|
+
const abort = (): void => { controller.abort() }
|
|
234
|
+
req.once('aborted', abort)
|
|
235
|
+
req.socket.once('close', abort)
|
|
236
|
+
try {
|
|
237
|
+
return await this.manager.current().health(request.testConnection, {
|
|
238
|
+
signal: controller.signal,
|
|
239
|
+
workspace: process.cwd(),
|
|
240
|
+
sessionId: 'vision-toolkit-settings',
|
|
241
|
+
})
|
|
242
|
+
} finally {
|
|
243
|
+
req.off('aborted', abort)
|
|
244
|
+
req.socket.off('close', abort)
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/** Handle the exact Settings route. */
|
|
249
|
+
async handle(req: IncomingMessage, res: ServerResponse): Promise<void> {
|
|
250
|
+
if (req.method === 'GET') {
|
|
251
|
+
try {
|
|
252
|
+
responseJson(res, 200, { ok: true, value: await this.snapshot() })
|
|
253
|
+
} catch (error) {
|
|
254
|
+
this.ctx.logger.warn('dsh-vision-toolkit Settings snapshot failed: %s', publicMessage(error))
|
|
255
|
+
requestError(res, 503, 'settings-unavailable', 'Vision Toolkit Settings are unavailable')
|
|
256
|
+
}
|
|
257
|
+
return
|
|
258
|
+
}
|
|
259
|
+
if (req.method !== 'POST') {
|
|
260
|
+
res.setHeader('Allow', 'GET, POST')
|
|
261
|
+
requestError(res, 405, 'method-not-allowed', 'Use GET or POST')
|
|
262
|
+
return
|
|
263
|
+
}
|
|
264
|
+
if (!sameOriginPost(req)) {
|
|
265
|
+
requestError(res, 403, 'origin-rejected', 'The request must originate from this DSH Web application')
|
|
266
|
+
return
|
|
267
|
+
}
|
|
268
|
+
let parsed: SettingsRequest
|
|
269
|
+
try {
|
|
270
|
+
parsed = parseRequest(await readJson(req))
|
|
271
|
+
} catch (error) {
|
|
272
|
+
requestError(res, error instanceof RangeError ? 413 : 400, 'invalid-request', publicMessage(error))
|
|
273
|
+
return
|
|
274
|
+
}
|
|
275
|
+
try {
|
|
276
|
+
if (parsed.action === 'health') {
|
|
277
|
+
responseJson(res, 200, { ok: true, value: await this.health(parsed, req) })
|
|
278
|
+
} else {
|
|
279
|
+
responseJson(res, 200, { ok: true, value: await this.save(parsed) })
|
|
280
|
+
}
|
|
281
|
+
} catch (error) {
|
|
282
|
+
const conflict = error instanceof SettingsConflictError
|
|
283
|
+
const code = conflict ? 'settings-conflict' : parsed.action === 'health' ? 'health-failed' : 'settings-rejected'
|
|
284
|
+
const status = conflict ? 409 : parsed.action === 'health' ? 503 : 400
|
|
285
|
+
this.ctx.logger.warn('dsh-vision-toolkit Web action=%s failed: %s', parsed.action, publicMessage(error))
|
|
286
|
+
requestError(res, status, code, publicMessage(error))
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Attach optional Web routes whenever an webServer service is present.
|
|
293
|
+
* @param ctx - plugin context owning route effects.
|
|
294
|
+
* @param backend - Settings handler.
|
|
295
|
+
* @param artifacts - signed Artifact handler.
|
|
296
|
+
*/
|
|
297
|
+
export function installVisionToolkitWeb(
|
|
298
|
+
ctx: Context,
|
|
299
|
+
backend: VisionToolkitWebBackend,
|
|
300
|
+
artifacts: ArtifactAccessController,
|
|
301
|
+
pastedImages: PastedImageBackend,
|
|
302
|
+
): void {
|
|
303
|
+
ctx.inject(['webServer'], (webCtx) => {
|
|
304
|
+
webCtx.effect(() => {
|
|
305
|
+
const detach = artifacts.attachRoute()
|
|
306
|
+
const disposeArtifact = webCtx.webServer.register({
|
|
307
|
+
kind: 'prefix',
|
|
308
|
+
path: ARTIFACT_ROUTE_PREFIX,
|
|
309
|
+
handler: (req, res) => artifacts.handle(req, res),
|
|
310
|
+
})
|
|
311
|
+
const disposeSettings = webCtx.webServer.register({
|
|
312
|
+
kind: 'exact',
|
|
313
|
+
path: SETTINGS_ROUTE,
|
|
314
|
+
handler: (req, res) => backend.handle(req, res),
|
|
315
|
+
})
|
|
316
|
+
const disposePasteImages = webCtx.webServer.register({
|
|
317
|
+
kind: 'exact',
|
|
318
|
+
path: PASTE_IMAGES_ROUTE,
|
|
319
|
+
handler: (req, res) => pastedImages.handle(req, res),
|
|
320
|
+
})
|
|
321
|
+
return () => {
|
|
322
|
+
disposePasteImages()
|
|
323
|
+
disposeSettings()
|
|
324
|
+
disposeArtifact()
|
|
325
|
+
detach()
|
|
326
|
+
}
|
|
327
|
+
}, 'dsh-vision-toolkit: Web routes')
|
|
328
|
+
})
|
|
329
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable user-facing changes to agent-vision-toolkit are documented in this file.
|
|
4
|
+
|
|
5
|
+
## [0.1.0] - 2026-08-07
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Five vision CLIs — `glance`, `ground`, `detect`, `trace`, and `crop` — plus the `vision-tools` agent skill.
|
|
10
|
+
- Optional seamless integration: a local proxy for Codex and Claude Code, and single-file native extensions for Pi, Oh My Pi, and OpenCode.
|
|
11
|
+
- Pasted-image and tool-fetched image support with task-aware focus hints, parallel multi-image descriptions, per-request caching, and honest failure notes.
|
|
12
|
+
- Vision playbooks for long-screenshot OCR, UI restoration, graphic restoration, structure recovery, and GUI operation.
|
|
13
|
+
- Community contribution, conduct, support, and security policies.
|
|
14
|
+
- Structured issue forms and a pull request template.
|
|
15
|
+
- GitHub funding configuration and continuous integration checks.
|
|
16
|
+
- A bilingual funding policy and sponsorship-use statement.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Anionex
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|