@mengruo/dsh-vision-toolkit 0.1.3 → 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/README.i18n.yaml +2 -2
- package/README.md +15 -70
- package/README.zh.md +15 -69
- package/docs/requirements-traceability/README.i18n.yaml +2 -2
- package/docs/requirements-traceability/README.md +1 -1
- package/docs/requirements-traceability/README.zh.md +1 -1
- package/lib/artifact-access.js +20 -2
- package/lib/artifact-access.js.map +1 -1
- package/lib/client.js +128 -12
- package/lib/client.js.map +1 -1
- package/lib/config.js +69 -0
- package/lib/config.js.map +1 -1
- package/lib/errors.js +25 -2
- package/lib/errors.js.map +1 -1
- package/lib/evidence-cache.js +2 -1
- package/lib/evidence-cache.js.map +1 -1
- package/lib/exposure.js +14 -1
- package/lib/exposure.js.map +1 -1
- package/lib/image-input-variants.js +22 -12
- package/lib/image-input-variants.js.map +1 -1
- package/lib/index.js +53 -6
- package/lib/index.js.map +1 -1
- package/lib/object-storage.js +141 -0
- package/lib/object-storage.js.map +1 -0
- package/lib/paste-images.js +67 -19
- package/lib/paste-images.js.map +1 -1
- package/lib/paths.js +214 -28
- package/lib/paths.js.map +1 -1
- package/lib/runtime-manager.js +76 -10
- package/lib/runtime-manager.js.map +1 -1
- package/lib/runtime.js +219 -38
- package/lib/runtime.js.map +1 -1
- package/lib/storage-history.js +154 -0
- package/lib/storage-history.js.map +1 -0
- package/lib/types/artifact-access.d.ts.map +1 -1
- package/lib/types/client/index.d.ts +45 -2
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/paste-images.d.ts +2 -0
- package/lib/types/client/paste-images.d.ts.map +1 -1
- package/lib/types/config.d.ts +55 -0
- package/lib/types/config.d.ts.map +1 -1
- package/lib/types/errors.d.ts +18 -2
- package/lib/types/errors.d.ts.map +1 -1
- package/lib/types/evidence-cache.d.ts +1 -1
- package/lib/types/evidence-cache.d.ts.map +1 -1
- package/lib/types/exposure.d.ts.map +1 -1
- package/lib/types/image-input-variants.d.ts +5 -3
- package/lib/types/image-input-variants.d.ts.map +1 -1
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/object-storage.d.ts +54 -0
- package/lib/types/object-storage.d.ts.map +1 -0
- package/lib/types/paste-images.d.ts +12 -4
- package/lib/types/paste-images.d.ts.map +1 -1
- package/lib/types/paths.d.ts +31 -5
- package/lib/types/paths.d.ts.map +1 -1
- package/lib/types/runtime-manager.d.ts +28 -4
- package/lib/types/runtime-manager.d.ts.map +1 -1
- package/lib/types/runtime.d.ts +31 -1
- package/lib/types/runtime.d.ts.map +1 -1
- package/lib/types/storage-history.d.ts +63 -0
- package/lib/types/storage-history.d.ts.map +1 -0
- package/lib/types/upstream.d.ts +1 -0
- package/lib/types/upstream.d.ts.map +1 -1
- package/lib/types/web.d.ts +7 -0
- package/lib/types/web.d.ts.map +1 -1
- package/lib/upstream.js +34 -8
- package/lib/upstream.js.map +1 -1
- package/lib/web.js +44 -9
- package/lib/web.js.map +1 -1
- package/package.json +3 -1
- package/src/artifact-access.ts +22 -2
- package/src/client/index.tsx +169 -9
- package/src/client/paste-images.tsx +14 -4
- package/src/config.ts +128 -0
- package/src/errors.ts +25 -2
- package/src/evidence-cache.ts +2 -1
- package/src/exposure.ts +16 -2
- package/src/image-input-variants.ts +21 -6
- package/src/index.ts +65 -6
- package/src/object-storage.ts +174 -0
- package/src/paste-images.ts +81 -19
- package/src/paths.ts +249 -28
- package/src/runtime-manager.ts +93 -10
- package/src/runtime.ts +215 -36
- package/src/storage-history.ts +172 -0
- package/src/upstream.ts +36 -7
- package/src/web.ts +54 -9
- package/vendor/agent-vision-toolkit/UPSTREAM_MANIFEST.json +11 -11
- package/vendor/agent-vision-toolkit/__pycache__/detect.cpython-314.pyc +0 -0
- package/vendor/agent-vision-toolkit/__pycache__/ground.cpython-314.pyc +0 -0
- package/vendor/agent-vision-toolkit/__pycache__/vision_client.cpython-314.pyc +0 -0
- package/vendor/agent-vision-toolkit/bin/__pycache__/glancecpython-314.pyc +0 -0
- package/vendor/agent-vision-toolkit/bin/glance +8 -1
- package/vendor/agent-vision-toolkit/detect.py +13 -7
- package/vendor/agent-vision-toolkit/ground.py +43 -18
- package/vendor/agent-vision-toolkit/tests/test_vision_client.py +88 -0
- package/vendor/agent-vision-toolkit/vision_client.py +84 -6
- package/assets/community-group-qr.png +0 -0
- package/assets/logo_aihubmix.png +0 -0
- package/assets/logo_eapi_dark.png +0 -0
- package/assets/wechat-reward.png +0 -0
package/src/config.ts
CHANGED
|
@@ -77,6 +77,10 @@ export interface VisionProviderConfig {
|
|
|
77
77
|
anthropicThinking?: 'omit' | 'disabled' | 'adaptive'
|
|
78
78
|
/** Outbound User-Agent for provider requests and connection tests. */
|
|
79
79
|
userAgent?: string
|
|
80
|
+
/** Whether to request a streamed (SSE) completion instead of one JSON response (default false). */
|
|
81
|
+
stream?: boolean
|
|
82
|
+
/** Whether to upload images to object storage and send the model a URL instead of base64 (default false). */
|
|
83
|
+
uploadViaUrl?: boolean
|
|
80
84
|
/** t1: per-request hedge threshold in seconds. A single request exceeding t1 keeps running while the next provider starts in parallel. */
|
|
81
85
|
t1Seconds?: number
|
|
82
86
|
/** t2: per-provider cumulative cutoff in seconds. Total accumulated request time reaching t2 terminates the provider. */
|
|
@@ -106,6 +110,10 @@ export interface VisionToolkitConfig {
|
|
|
106
110
|
anthropicThinking?: 'omit' | 'disabled' | 'adaptive'
|
|
107
111
|
/** Outbound User-Agent for provider requests and connection tests. */
|
|
108
112
|
userAgent?: string
|
|
113
|
+
/** Whether to request a streamed (SSE) completion instead of one JSON response (default false). */
|
|
114
|
+
stream?: boolean
|
|
115
|
+
/** Whether to upload images to object storage and send the model a URL instead of base64 (default false). */
|
|
116
|
+
uploadViaUrl?: boolean
|
|
109
117
|
}
|
|
110
118
|
/** Ordered online vision providers; array order is the failover priority. */
|
|
111
119
|
providers?: VisionProviderConfig[]
|
|
@@ -123,6 +131,21 @@ export interface VisionToolkitConfig {
|
|
|
123
131
|
maxImagePixels?: number
|
|
124
132
|
/** Default per-model in-flight request cap inherited by providers that do not set their own. */
|
|
125
133
|
concurrency?: number
|
|
134
|
+
/**
|
|
135
|
+
* Optional S3-compatible object storage used by the URL image-transfer path.
|
|
136
|
+
* `endpoint`, `bucket`, and `credential` are required to enable URL transfer;
|
|
137
|
+
* `publicBase` is optional and falls back to presigned URLs when unset.
|
|
138
|
+
*/
|
|
139
|
+
objectStorage?: {
|
|
140
|
+
/** S3-compatible API endpoint (e.g. R2, MinIO, Tencent COS). */
|
|
141
|
+
endpoint?: string
|
|
142
|
+
/** Bucket name. */
|
|
143
|
+
bucket?: string
|
|
144
|
+
/** DSH Credential reference holding "accessKeyId:secretAccessKey". */
|
|
145
|
+
credential?: string
|
|
146
|
+
/** Public base URL (custom domain / r2.dev); when unset, presigned URLs are used. */
|
|
147
|
+
publicBase?: string
|
|
148
|
+
}
|
|
126
149
|
runtime?: {
|
|
127
150
|
/** `managed` uses the packaged snapshot and isolated venv; `external` uses a clean pinned checkout. */
|
|
128
151
|
mode?: 'managed' | 'external'
|
|
@@ -131,6 +154,14 @@ export interface VisionToolkitConfig {
|
|
|
131
154
|
/** Optional Python 3.11+ bootstrap/interpreter override. */
|
|
132
155
|
python?: string
|
|
133
156
|
}
|
|
157
|
+
/**
|
|
158
|
+
* Optional shared storage root. When set, every workspace gets an isolated,
|
|
159
|
+
* automatically generated child directory below this root instead of writing
|
|
160
|
+
* `.dsh-vision-toolkit` into the workspace.
|
|
161
|
+
*/
|
|
162
|
+
storageDir?: string
|
|
163
|
+
/** Internal read-only history used to keep persisted paths valid after storage moves. */
|
|
164
|
+
storageHistory?: string[]
|
|
134
165
|
/** Extra directories (besides the workspace) inputs may come from. */
|
|
135
166
|
allowedDirs?: string[]
|
|
136
167
|
/**
|
|
@@ -174,6 +205,7 @@ export const Config: Schema<VisionToolkitConfig> = z.object({
|
|
|
174
205
|
protocol: z.union(['openai', 'anthropic'] as const).default('openai'),
|
|
175
206
|
anthropicThinking: z.union(['omit', 'disabled', 'adaptive'] as const).default('omit'),
|
|
176
207
|
userAgent: z.string().default(DEFAULT_VISION_USER_AGENT),
|
|
208
|
+
stream: z.boolean().default(false),
|
|
177
209
|
}),
|
|
178
210
|
providers: z.array(z.object({
|
|
179
211
|
name: z.string(),
|
|
@@ -185,6 +217,8 @@ export const Config: Schema<VisionToolkitConfig> = z.object({
|
|
|
185
217
|
protocol: z.union(['openai', 'anthropic'] as const).default('openai'),
|
|
186
218
|
anthropicThinking: z.union(['omit', 'disabled', 'adaptive'] as const).default('omit'),
|
|
187
219
|
userAgent: z.string(),
|
|
220
|
+
stream: z.boolean().default(false),
|
|
221
|
+
uploadViaUrl: z.boolean().default(false),
|
|
188
222
|
t1Seconds: z.number(),
|
|
189
223
|
t2Seconds: z.number(),
|
|
190
224
|
maxImageBytes: z.number(),
|
|
@@ -199,11 +233,19 @@ export const Config: Schema<VisionToolkitConfig> = z.object({
|
|
|
199
233
|
maxImageBytes: z.number().default(4194304),
|
|
200
234
|
maxImagePixels: z.number().default(20000000),
|
|
201
235
|
concurrency: z.number().default(4),
|
|
236
|
+
objectStorage: z.object({
|
|
237
|
+
endpoint: z.string().default(''),
|
|
238
|
+
bucket: z.string().default(''),
|
|
239
|
+
credential: z.string().default(''),
|
|
240
|
+
publicBase: z.string().default(''),
|
|
241
|
+
}),
|
|
202
242
|
runtime: z.object({
|
|
203
243
|
mode: z.union(['managed', 'external'] as const).default('managed'),
|
|
204
244
|
agentVisionToolkitPath: z.string(),
|
|
205
245
|
python: z.string(),
|
|
206
246
|
}),
|
|
247
|
+
storageDir: z.string(),
|
|
248
|
+
storageHistory: z.array(z.string()).default([]),
|
|
207
249
|
allowedDirs: z.array(z.string()).default([]),
|
|
208
250
|
imageInputVariants: z.object({
|
|
209
251
|
enabled: z.boolean().default(true),
|
|
@@ -225,6 +267,8 @@ export interface ResolvedProvider {
|
|
|
225
267
|
protocol: 'openai' | 'anthropic'
|
|
226
268
|
anthropicThinking: 'omit' | 'disabled' | 'adaptive'
|
|
227
269
|
userAgent: string
|
|
270
|
+
stream: boolean
|
|
271
|
+
uploadViaUrl: boolean
|
|
228
272
|
t1Seconds: number
|
|
229
273
|
t2Seconds: number
|
|
230
274
|
maxImageBytes: number
|
|
@@ -242,6 +286,8 @@ export interface ResolvedVisionToolkitConfig {
|
|
|
242
286
|
protocol: 'openai' | 'anthropic'
|
|
243
287
|
anthropicThinking: 'omit' | 'disabled' | 'adaptive'
|
|
244
288
|
userAgent: string
|
|
289
|
+
stream: boolean
|
|
290
|
+
uploadViaUrl: boolean
|
|
245
291
|
}
|
|
246
292
|
/** Ordered failover pool; array order is the priority, highest first. */
|
|
247
293
|
providers: ResolvedProvider[]
|
|
@@ -252,11 +298,19 @@ export interface ResolvedVisionToolkitConfig {
|
|
|
252
298
|
maxImageBytes: number
|
|
253
299
|
maxImagePixels: number
|
|
254
300
|
concurrency: number
|
|
301
|
+
objectStorage: {
|
|
302
|
+
endpoint: string
|
|
303
|
+
bucket: string
|
|
304
|
+
credential?: CredentialRef
|
|
305
|
+
publicBase?: string
|
|
306
|
+
}
|
|
255
307
|
runtime: {
|
|
256
308
|
mode: 'managed' | 'external'
|
|
257
309
|
agentVisionToolkitPath?: string
|
|
258
310
|
python?: string
|
|
259
311
|
}
|
|
312
|
+
storageDir?: string
|
|
313
|
+
storageHistory: string[]
|
|
260
314
|
allowedDirs: string[]
|
|
261
315
|
imageInputVariants: {
|
|
262
316
|
enabled: boolean
|
|
@@ -354,6 +408,8 @@ function resolveProvider(
|
|
|
354
408
|
if (userAgent.length === 0) {
|
|
355
409
|
throw new VisionToolkitError('config', `${label}.userAgent must not be empty`)
|
|
356
410
|
}
|
|
411
|
+
const stream = input.stream === true
|
|
412
|
+
const uploadViaUrl = input.uploadViaUrl === true
|
|
357
413
|
const t1Seconds = input.t1Seconds ?? 90
|
|
358
414
|
if (!Number.isInteger(t1Seconds) || t1Seconds < 1 || t1Seconds > MAX_TIMEOUT_SECONDS) {
|
|
359
415
|
throw new VisionToolkitError('config', `${label}.t1Seconds must be an integer between 1 and ${MAX_TIMEOUT_SECONDS}`)
|
|
@@ -390,6 +446,8 @@ function resolveProvider(
|
|
|
390
446
|
protocol,
|
|
391
447
|
anthropicThinking,
|
|
392
448
|
userAgent,
|
|
449
|
+
stream,
|
|
450
|
+
uploadViaUrl,
|
|
393
451
|
t1Seconds,
|
|
394
452
|
t2Seconds,
|
|
395
453
|
maxImageBytes,
|
|
@@ -455,7 +513,24 @@ export function resolveConfig(config: VisionToolkitConfig = {}): ResolvedVisionT
|
|
|
455
513
|
if (python !== undefined && python.length === 0) {
|
|
456
514
|
throw new VisionToolkitError('config', 'runtime.python must not be empty')
|
|
457
515
|
}
|
|
516
|
+
const storageDir = config.storageDir?.trim()
|
|
517
|
+
const storageHistory = [...new Set((config.storageHistory ?? [])
|
|
518
|
+
.map(dir => dir.trim())
|
|
519
|
+
.filter(dir => dir.length > 0 && dir !== storageDir))]
|
|
458
520
|
const allowedDirs = (config.allowedDirs ?? []).map(dir => dir.trim()).filter(dir => dir.length > 0)
|
|
521
|
+
const objectStorageInput = config.objectStorage ?? {}
|
|
522
|
+
const objectStorageEndpoint = objectStorageInput.endpoint?.trim() ?? ''
|
|
523
|
+
const objectStorageBucket = objectStorageInput.bucket?.trim() ?? ''
|
|
524
|
+
const objectStoragePublicBase = objectStorageInput.publicBase?.trim()
|
|
525
|
+
let objectStorageCredential: CredentialRef | undefined
|
|
526
|
+
const objectStorageCredentialSource = objectStorageInput.credential?.trim()
|
|
527
|
+
if (objectStorageCredentialSource !== undefined && objectStorageCredentialSource.length > 0) {
|
|
528
|
+
try {
|
|
529
|
+
objectStorageCredential = credentialRef(objectStorageCredentialSource)
|
|
530
|
+
} catch (error) {
|
|
531
|
+
throw new VisionToolkitError('config', `objectStorage.credential "${objectStorageCredentialSource}" is not a valid credential reference`, { cause: error })
|
|
532
|
+
}
|
|
533
|
+
}
|
|
459
534
|
const imageInputVariants = config.imageInputVariants ?? {}
|
|
460
535
|
const variantProviders = (imageInputVariants.providers ?? [])
|
|
461
536
|
.map(provider => provider.trim())
|
|
@@ -478,6 +553,8 @@ export function resolveConfig(config: VisionToolkitConfig = {}): ResolvedVisionT
|
|
|
478
553
|
protocol: primary.protocol,
|
|
479
554
|
anthropicThinking: primary.anthropicThinking,
|
|
480
555
|
userAgent: primary.userAgent,
|
|
556
|
+
stream: primary.stream,
|
|
557
|
+
uploadViaUrl: primary.uploadViaUrl,
|
|
481
558
|
},
|
|
482
559
|
providers,
|
|
483
560
|
language,
|
|
@@ -487,11 +564,19 @@ export function resolveConfig(config: VisionToolkitConfig = {}): ResolvedVisionT
|
|
|
487
564
|
maxImageBytes,
|
|
488
565
|
maxImagePixels,
|
|
489
566
|
concurrency,
|
|
567
|
+
objectStorage: {
|
|
568
|
+
endpoint: objectStorageEndpoint,
|
|
569
|
+
bucket: objectStorageBucket,
|
|
570
|
+
...(objectStorageCredential === undefined ? {} : { credential: objectStorageCredential }),
|
|
571
|
+
...(objectStoragePublicBase === undefined || objectStoragePublicBase.length === 0 ? {} : { publicBase: objectStoragePublicBase }),
|
|
572
|
+
},
|
|
490
573
|
runtime: {
|
|
491
574
|
mode,
|
|
492
575
|
...(toolkitPath !== undefined ? { agentVisionToolkitPath: toolkitPath } : {}),
|
|
493
576
|
...(python !== undefined ? { python } : {}),
|
|
494
577
|
},
|
|
578
|
+
...(storageDir === undefined || storageDir.length === 0 ? {} : { storageDir }),
|
|
579
|
+
storageHistory,
|
|
495
580
|
allowedDirs,
|
|
496
581
|
imageInputVariants: {
|
|
497
582
|
enabled: imageInputVariants.enabled ?? true,
|
|
@@ -502,6 +587,49 @@ export function resolveConfig(config: VisionToolkitConfig = {}): ResolvedVisionT
|
|
|
502
587
|
}
|
|
503
588
|
}
|
|
504
589
|
|
|
590
|
+
/** Merge prior storage roots into the next resolved generation's read-only history. */
|
|
591
|
+
export function retainedStorageHistory(
|
|
592
|
+
next: VisionToolkitConfig,
|
|
593
|
+
previous: VisionToolkitConfig,
|
|
594
|
+
): string[] {
|
|
595
|
+
const resolvedNext = resolveConfig(next)
|
|
596
|
+
const resolvedPrevious = resolveConfig(previous)
|
|
597
|
+
return [...new Set([
|
|
598
|
+
...resolvedPrevious.storageHistory,
|
|
599
|
+
...resolvedNext.storageHistory,
|
|
600
|
+
...(resolvedPrevious.storageDir === undefined ? [] : [resolvedPrevious.storageDir]),
|
|
601
|
+
])].filter(storageDir => storageDir !== resolvedNext.storageDir)
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
export interface WatchedSettingsGeneration {
|
|
605
|
+
/** Configuration to activate now; omitted after a successful history writeback. */
|
|
606
|
+
config?: VisionToolkitConfig
|
|
607
|
+
/** Whether the derived history still needs plugin-owned durable persistence. */
|
|
608
|
+
requiresDurableStorageHistory?: boolean
|
|
609
|
+
/** Non-fatal internal-history persistence error. */
|
|
610
|
+
persistenceError?: unknown
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
/** Prepare one live Settings generation without letting internal history writeback block activation. */
|
|
614
|
+
export async function prepareWatchedSettingsGeneration(
|
|
615
|
+
next: VisionToolkitConfig,
|
|
616
|
+
previous: VisionToolkitConfig,
|
|
617
|
+
writable: boolean,
|
|
618
|
+
persistStorageHistory: (storageHistory: string[]) => Promise<void>,
|
|
619
|
+
): Promise<WatchedSettingsGeneration> {
|
|
620
|
+
const storageHistory = retainedStorageHistory(next, previous)
|
|
621
|
+
if (JSON.stringify(storageHistory) === JSON.stringify(resolveConfig(next).storageHistory)) return { config: next }
|
|
622
|
+
|
|
623
|
+
const config = { ...next, storageHistory }
|
|
624
|
+
if (!writable) return { config, requiresDurableStorageHistory: true }
|
|
625
|
+
try {
|
|
626
|
+
await persistStorageHistory(storageHistory)
|
|
627
|
+
return {}
|
|
628
|
+
} catch (persistenceError) {
|
|
629
|
+
return { config, requiresDurableStorageHistory: true, persistenceError }
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
|
|
505
633
|
/** Whether a resolved provider should use the bundled public key instead of DSH credentials. */
|
|
506
634
|
export function isBuiltInFreeVisionProvider(provider: ResolvedVisionToolkitConfig['provider']): boolean {
|
|
507
635
|
return String(provider.credential) === BUILT_IN_FREE_VISION_CREDENTIAL
|
package/src/errors.ts
CHANGED
|
@@ -5,18 +5,41 @@
|
|
|
5
5
|
* @module dsh-vision-toolkit/errors
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
/**
|
|
8
|
+
/**
|
|
9
|
+
* Discriminant tag for every Vision Toolkit failure.
|
|
10
|
+
*
|
|
11
|
+
* The remote vision-provider failures are split by a machine-routable taxonomy
|
|
12
|
+
* so the failover loop can decide, per error, whether to retry the SAME
|
|
13
|
+
* provider or advance to the next one:
|
|
14
|
+
*
|
|
15
|
+
* - `auth` 401/403 — credential is wrong or unauthorized. Never retry.
|
|
16
|
+
* - `quota` 402 — account out of quota/unpaid. Never retry.
|
|
17
|
+
* - `rate_limit` 429 — throttled. Park and revisit, never retry in place.
|
|
18
|
+
* - `server` 5xx — transient provider fault. Worth a same-provider retry.
|
|
19
|
+
* - `network` connection refused / DNS / socket — transient. Worth a retry.
|
|
20
|
+
* - `region` provider unavailable in this region. Never retry.
|
|
21
|
+
* - `tos` rejected by content/safety policy. Never retry.
|
|
22
|
+
* - `invalid_request` 400/404/422 — bad request or unknown model. Never retry.
|
|
23
|
+
* - `service` fallback for unclassifiable remote failures. Never retry.
|
|
24
|
+
*/
|
|
9
25
|
export const VISION_TOOLKIT_ERROR_CODES = [
|
|
10
26
|
'config',
|
|
11
27
|
'input',
|
|
12
28
|
'capacity',
|
|
29
|
+
'auth',
|
|
30
|
+
'quota',
|
|
31
|
+
'rate_limit',
|
|
32
|
+
'server',
|
|
33
|
+
'network',
|
|
34
|
+
'region',
|
|
35
|
+
'tos',
|
|
36
|
+
'invalid_request',
|
|
13
37
|
'service',
|
|
14
38
|
'runtime',
|
|
15
39
|
'output',
|
|
16
40
|
'timeout',
|
|
17
41
|
'cancelled',
|
|
18
42
|
'path',
|
|
19
|
-
'rate_limit',
|
|
20
43
|
] as const
|
|
21
44
|
|
|
22
45
|
/** Stable machine-readable error category. */
|
package/src/evidence-cache.ts
CHANGED
|
@@ -73,7 +73,7 @@ function hash(value: string): string {
|
|
|
73
73
|
return createHash('sha256').update(value).digest('hex')
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
-
/** Fingerprint every runtime setting that can change
|
|
76
|
+
/** Fingerprint every runtime setting that can change generated evidence or its embedded path. */
|
|
77
77
|
export function evidenceRuntimeFingerprint(
|
|
78
78
|
config: ResolvedVisionToolkitConfig,
|
|
79
79
|
credentialSha256?: string,
|
|
@@ -115,6 +115,7 @@ export function evidenceRuntimeFingerprint(
|
|
|
115
115
|
maxImageBytes: config.maxImageBytes,
|
|
116
116
|
maxImagePixels: config.maxImagePixels,
|
|
117
117
|
runtime: config.runtime,
|
|
118
|
+
storageDir: config.storageDir ?? null,
|
|
118
119
|
}))
|
|
119
120
|
}
|
|
120
121
|
|
package/src/exposure.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
import type { Agent } from '@deepseek-ai/dsh-agent'
|
|
10
10
|
import type { ContentBlock } from '@deepseek-ai/dsh-llm'
|
|
11
|
-
import type { Session } from '@deepseek-ai/dsh-session'
|
|
11
|
+
import type { Session, SessionEvent } from '@deepseek-ai/dsh-session'
|
|
12
12
|
import { defineTool, type ToolDefinition } from '@deepseek-ai/dsh-tools'
|
|
13
13
|
import type { Context } from '@deepseek-ai/cordis'
|
|
14
14
|
import { VISION_SKILLS_CONTENT, VISION_SKILLS_NAME } from './skill.ts'
|
|
@@ -78,10 +78,24 @@ function isBundledSkillResult(value: unknown): boolean {
|
|
|
78
78
|
&& isBundledSkillContent(value.content)
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
+
/**
|
|
82
|
+
* Iterate a Session's durable event log across runtime lines. dsh 0.1.2-alpha
|
|
83
|
+
* replaced the `events` getter with `snapshotEvents()` (session-log-read-intent):
|
|
84
|
+
* iterating the removed `session.events` throws `session.events is not iterable`
|
|
85
|
+
* and takes down `agent/created` → session restore. Prefer the snapshot accessor
|
|
86
|
+
* and fall back to the legacy rc-line `events` getter so both lines work.
|
|
87
|
+
*/
|
|
88
|
+
function sessionEventLog(session: Session): readonly SessionEvent[] {
|
|
89
|
+
const snapshot = (session as { snapshotEvents?: () => readonly SessionEvent[] }).snapshotEvents
|
|
90
|
+
return typeof snapshot === 'function'
|
|
91
|
+
? snapshot.call(session)
|
|
92
|
+
: (session as { events: readonly SessionEvent[] }).events
|
|
93
|
+
}
|
|
94
|
+
|
|
81
95
|
/** Whether durable history proves that this Session loaded the bundled Skill. */
|
|
82
96
|
function hasLoadedVisionSkill(session: Session): boolean {
|
|
83
97
|
const nativeCalls = new Set<string>()
|
|
84
|
-
for (const event of session
|
|
98
|
+
for (const event of sessionEventLog(session)) {
|
|
85
99
|
if (event.type === 'user/message') {
|
|
86
100
|
const source = event.data.source
|
|
87
101
|
if (source.kind === 'skill-invocation'
|
|
@@ -142,18 +142,21 @@ async function materializeImage(
|
|
|
142
142
|
data: Uint8Array,
|
|
143
143
|
extension: string,
|
|
144
144
|
sessionId: string | undefined,
|
|
145
|
+
storageDir: string | undefined,
|
|
145
146
|
): Promise<MaterializedImage> {
|
|
146
147
|
const session = sessionId === undefined ? undefined : ctx.sessions.get(sessionId as never)
|
|
147
148
|
const cwd = session?.header.cwd
|
|
148
149
|
if (sessionId !== undefined && cwd !== undefined && isAbsolute(cwd)) {
|
|
149
|
-
const root = await sessionPasteRoot(ctx, sessionId)
|
|
150
|
+
const root = await sessionPasteRoot(ctx, sessionId, storageDir)
|
|
150
151
|
const identity = createHash('sha256')
|
|
151
152
|
.update(`${sessionId}\u0000${String(block.attachment.attachmentId)}`)
|
|
152
153
|
.digest('hex')
|
|
153
154
|
.slice(0, 32)
|
|
154
|
-
const
|
|
155
|
+
const filename = `attachment-${identity}${extension}`
|
|
156
|
+
const writePath = join(root.writeRoot, filename)
|
|
157
|
+
const file = join(root.visibleRoot, filename)
|
|
155
158
|
try {
|
|
156
|
-
await writeFile(
|
|
159
|
+
await writeFile(writePath, Buffer.from(data), { mode: 0o600, flag: 'wx' })
|
|
157
160
|
} catch (error) {
|
|
158
161
|
if (!(error instanceof Error && 'code' in error && error.code === 'EEXIST')) throw error
|
|
159
162
|
}
|
|
@@ -357,6 +360,7 @@ async function readImageBlock(
|
|
|
357
360
|
block: ImageBlock,
|
|
358
361
|
query: string,
|
|
359
362
|
sessionId?: string,
|
|
363
|
+
storageDir?: string,
|
|
360
364
|
): Promise<ContentBlock> {
|
|
361
365
|
const attachments = ctx.get('attachments')
|
|
362
366
|
const current = runtime()
|
|
@@ -371,7 +375,7 @@ async function readImageBlock(
|
|
|
371
375
|
let pathEvidence = ''
|
|
372
376
|
try {
|
|
373
377
|
const stored = await attachments.readImage(block.attachment)
|
|
374
|
-
const materialized = await materializeImage(ctx, block, stored.data, extension, sessionId)
|
|
378
|
+
const materialized = await materializeImage(ctx, block, stored.data, extension, sessionId, storageDir)
|
|
375
379
|
temporaryDirectory = materialized.temporaryDirectory
|
|
376
380
|
if (materialized.persistent) pathEvidence = imagePathEvidence(materialized.file)
|
|
377
381
|
|
|
@@ -414,6 +418,7 @@ async function readImageBlock(
|
|
|
414
418
|
* @param signal - the caller's cancellation for this conversion pass.
|
|
415
419
|
* @param sessionId - the live Session identity, when available.
|
|
416
420
|
* @param runtimeHash - stable fingerprint of the vision provider and evidence runtime.
|
|
421
|
+
* @param storageDir - optional shared plugin storage root.
|
|
417
422
|
* @returns the rewritten message list.
|
|
418
423
|
*/
|
|
419
424
|
export async function convertImagesToEvidence(
|
|
@@ -424,6 +429,7 @@ export async function convertImagesToEvidence(
|
|
|
424
429
|
signal?: AbortSignal,
|
|
425
430
|
sessionId?: string,
|
|
426
431
|
runtimeHash = 'process-only-runtime',
|
|
432
|
+
storageDir?: string,
|
|
427
433
|
): Promise<Message[]> {
|
|
428
434
|
const session = sessionId === undefined ? undefined : ctx.sessions.get(sessionId as never)
|
|
429
435
|
const sessionIdentity = session === undefined
|
|
@@ -480,7 +486,7 @@ export async function convertImagesToEvidence(
|
|
|
480
486
|
prompt: query,
|
|
481
487
|
runtimeHash,
|
|
482
488
|
}), () =>
|
|
483
|
-
readImageBlock(ctx, runtime, block, query, sessionId)),
|
|
489
|
+
readImageBlock(ctx, runtime, block, query, sessionId, storageDir)),
|
|
484
490
|
signal,
|
|
485
491
|
),
|
|
486
492
|
signal,
|
|
@@ -517,6 +523,7 @@ export class ImageInputVariantAdapter extends LlmAdapter {
|
|
|
517
523
|
private readonly runtime: () => VisionToolkitRuntime | undefined,
|
|
518
524
|
private readonly cache: EvidenceCache,
|
|
519
525
|
private readonly hidden: () => boolean = () => false,
|
|
526
|
+
private readonly startupStorageDirectory: () => string | undefined = () => undefined,
|
|
520
527
|
) {
|
|
521
528
|
super()
|
|
522
529
|
}
|
|
@@ -589,6 +596,7 @@ export class ImageInputVariantAdapter extends LlmAdapter {
|
|
|
589
596
|
|
|
590
597
|
override async *stream(options: GenerateOptions): AsyncGenerator<StreamChunk> {
|
|
591
598
|
const current = this.runtime()
|
|
599
|
+
const storageDir = current === undefined ? this.startupStorageDirectory() : current.storageDirectory
|
|
592
600
|
let captured: CapturedEvidenceRuntime | undefined
|
|
593
601
|
if (current !== undefined && options.messages.some(message => contentHasImage(message.content))) {
|
|
594
602
|
try {
|
|
@@ -610,7 +618,12 @@ export class ImageInputVariantAdapter extends LlmAdapter {
|
|
|
610
618
|
options.messages,
|
|
611
619
|
options.signal,
|
|
612
620
|
options.sessionId === undefined ? undefined : String(options.sessionId),
|
|
613
|
-
captured?.evidenceFingerprint ?? '
|
|
621
|
+
captured?.evidenceFingerprint ?? createHash('sha256')
|
|
622
|
+
.update('process-only-runtime')
|
|
623
|
+
.update('\0')
|
|
624
|
+
.update(storageDir ?? '')
|
|
625
|
+
.digest('hex'),
|
|
626
|
+
storageDir,
|
|
614
627
|
)
|
|
615
628
|
// Delegate through the host service under the upstream route: the variant
|
|
616
629
|
// is a wire-only facade, and the upstream route owns retry and replay.
|
|
@@ -846,6 +859,7 @@ export function installImageInputVariants(
|
|
|
846
859
|
ctx: Context,
|
|
847
860
|
getConfig: () => ResolvedVisionToolkitConfig,
|
|
848
861
|
getRuntime: () => VisionToolkitRuntime | undefined,
|
|
862
|
+
getStartupStorageDirectory: () => string | undefined = () => undefined,
|
|
849
863
|
): { dispose: () => void; reconcile: () => void } {
|
|
850
864
|
const evidenceStore = new SessionEvidenceStore(ctx)
|
|
851
865
|
const evidenceCache = new EvidenceCache(EVIDENCE_CACHE_LIMIT, evidenceStore)
|
|
@@ -991,6 +1005,7 @@ export function installImageInputVariants(
|
|
|
991
1005
|
getRuntime,
|
|
992
1006
|
evidenceCache,
|
|
993
1007
|
() => getConfig().imageInputVariants.hidden,
|
|
1008
|
+
getStartupStorageDirectory,
|
|
994
1009
|
),
|
|
995
1010
|
)
|
|
996
1011
|
registrations.set(upstream, { dispose, retryPolicyKey: upstreamRetryPolicyKey })
|
package/src/index.ts
CHANGED
|
@@ -18,13 +18,16 @@ import { ArtifactAccessController, prepareArtifactAccessKey } from './artifact-a
|
|
|
18
18
|
import {
|
|
19
19
|
Config,
|
|
20
20
|
VISION_TOOLKIT_SETTINGS_NAMESPACE,
|
|
21
|
+
prepareWatchedSettingsGeneration,
|
|
21
22
|
resolveConfig,
|
|
23
|
+
type ResolvedVisionToolkitConfig,
|
|
22
24
|
type VisionToolkitConfig,
|
|
23
25
|
} from './config.ts'
|
|
24
26
|
import { VisionToolExposure } from './exposure.ts'
|
|
25
27
|
import { createPasteTakeoverResolver, installImageInputVariants } from './image-input-variants.ts'
|
|
26
28
|
import { VisionToolkitRuntimeManager } from './runtime-manager.ts'
|
|
27
29
|
import { VISION_SKILLS_SKILL } from './skill.ts'
|
|
30
|
+
import { StorageHistoryStore } from './storage-history.ts'
|
|
28
31
|
import { createVisionTools } from './tools.ts'
|
|
29
32
|
import { PLUGIN_VERSION } from './version.ts'
|
|
30
33
|
import { installVisionToolkitWeb, VisionToolkitWebBackend } from './web.ts'
|
|
@@ -51,8 +54,33 @@ export async function apply(ctx: Context, config: VisionToolkitConfig = {}): Pro
|
|
|
51
54
|
const artifacts = new ArtifactAccessController(await prepareArtifactAccessKey())
|
|
52
55
|
const lifecycle = new AbortController()
|
|
53
56
|
const disposers: Array<() => void> = []
|
|
57
|
+
const storageHistory = new StorageHistoryStore(ctx)
|
|
58
|
+
disposers.push(() => { storageHistory.dispose() })
|
|
59
|
+
let storageHistoryWarningReported = false
|
|
54
60
|
let operationalDisposers: { activationTool: () => void; exposure: () => void; skill: () => void } | undefined
|
|
55
61
|
|
|
62
|
+
const persistStorageHistory = async (candidate: VisionToolkitConfig, required: boolean): Promise<void> => {
|
|
63
|
+
try {
|
|
64
|
+
const persisted = await storageHistory.persist(candidate)
|
|
65
|
+
if (persisted) return
|
|
66
|
+
const error = new Error(
|
|
67
|
+
'configured storage history requires @deepseek-ai/dsh-storage-domain when Settings cannot persist it',
|
|
68
|
+
)
|
|
69
|
+
if (required) throw error
|
|
70
|
+
if (!storageHistoryWarningReported) {
|
|
71
|
+
storageHistoryWarningReported = true
|
|
72
|
+
ctx.logger.warn('dsh-vision-toolkit: %s', error.message)
|
|
73
|
+
}
|
|
74
|
+
} catch (error) {
|
|
75
|
+
if (required) throw error
|
|
76
|
+
if (!storageHistoryWarningReported) {
|
|
77
|
+
storageHistoryWarningReported = true
|
|
78
|
+
const message = error instanceof Error ? error.message : String(error)
|
|
79
|
+
ctx.logger.warn('dsh-vision-toolkit: configured storage history was not persisted. %s', message)
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
56
84
|
const ensureOperational = (): void => {
|
|
57
85
|
if (!manager.ready || operationalDisposers !== undefined) return
|
|
58
86
|
const exposure = new VisionToolExposure(ctx, () => createVisionTools(
|
|
@@ -84,10 +112,16 @@ export async function apply(ctx: Context, config: VisionToolkitConfig = {}): Pro
|
|
|
84
112
|
}
|
|
85
113
|
}
|
|
86
114
|
|
|
115
|
+
const initialConfig = await storageHistory.restore(settings.get())
|
|
87
116
|
try {
|
|
88
|
-
await manager.initialize(
|
|
117
|
+
await manager.initialize(initialConfig, candidate => persistStorageHistory(candidate.config, false))
|
|
89
118
|
ensureOperational()
|
|
90
119
|
} catch (error) {
|
|
120
|
+
const resolvedInitial = resolveConfig(initialConfig)
|
|
121
|
+
if (resolvedInitial.storageDir === undefined
|
|
122
|
+
|| manager.validatedStorageDirectory() === resolvedInitial.storageDir) {
|
|
123
|
+
await persistStorageHistory(initialConfig, false)
|
|
124
|
+
}
|
|
91
125
|
const message = error instanceof Error ? error.message : String(error)
|
|
92
126
|
ctx.logger.error(
|
|
93
127
|
'dsh-vision-toolkit %s: runtime not ready; the vision-skills skill, activation bootstrap, and Agent-scoped visual tools are NOT registered. Settings remain available for repair. %s',
|
|
@@ -97,29 +131,54 @@ export async function apply(ctx: Context, config: VisionToolkitConfig = {}): Pro
|
|
|
97
131
|
}
|
|
98
132
|
|
|
99
133
|
const backend = new VisionToolkitWebBackend(ctx, manager, artifacts, ensureOperational)
|
|
134
|
+
const currentConfig = (): ResolvedVisionToolkitConfig => manager.ready
|
|
135
|
+
? manager.currentConfig()
|
|
136
|
+
: resolveConfig(settings.get())
|
|
100
137
|
const pastedImages = new PastedImageBackend(ctx, {
|
|
101
138
|
maxUploadBytes: () => MAX_PASTE_IMAGE_BYTES,
|
|
139
|
+
storageGeneration: () => manager.storageGeneration(),
|
|
102
140
|
})
|
|
103
141
|
// Image-input variants register asynchronously once eligible routes exist;
|
|
104
142
|
// the runtime getter stays lazy so variants appear even when the runtime
|
|
105
143
|
// becomes ready after the first sweep.
|
|
106
144
|
const variants = installImageInputVariants(
|
|
107
145
|
ctx,
|
|
108
|
-
|
|
146
|
+
currentConfig,
|
|
109
147
|
() => manager.ready ? manager.current() : undefined,
|
|
148
|
+
() => manager.validatedStorageDirectory(),
|
|
110
149
|
)
|
|
111
150
|
installVisionToolkitWeb(
|
|
112
151
|
ctx,
|
|
113
152
|
backend,
|
|
114
153
|
artifacts,
|
|
115
154
|
pastedImages,
|
|
116
|
-
createPasteTakeoverResolver(ctx,
|
|
117
|
-
() => ({ hidden:
|
|
155
|
+
createPasteTakeoverResolver(ctx, currentConfig),
|
|
156
|
+
() => ({ hidden: currentConfig().imageInputVariants.hidden }),
|
|
118
157
|
)
|
|
119
158
|
disposers.push(variants.dispose)
|
|
120
|
-
disposers.push(settings.watch(async (next) => {
|
|
159
|
+
disposers.push(settings.watch(async (next, previous) => {
|
|
121
160
|
try {
|
|
122
|
-
await
|
|
161
|
+
const prepared = await prepareWatchedSettingsGeneration(
|
|
162
|
+
next,
|
|
163
|
+
previous,
|
|
164
|
+
ctx.settings.writable,
|
|
165
|
+
storageHistory => settings.update({ storageHistory }),
|
|
166
|
+
)
|
|
167
|
+
if (prepared.persistenceError !== undefined) {
|
|
168
|
+
const message = prepared.persistenceError instanceof Error
|
|
169
|
+
? prepared.persistenceError.message
|
|
170
|
+
: String(prepared.persistenceError)
|
|
171
|
+
ctx.logger.warn('dsh-vision-toolkit: activating Settings without persisting internal storage history. %s', message)
|
|
172
|
+
}
|
|
173
|
+
if (prepared.config === undefined) return
|
|
174
|
+
const candidate = await storageHistory.restore(prepared.config)
|
|
175
|
+
await manager.reconfigure(
|
|
176
|
+
candidate,
|
|
177
|
+
generation => persistStorageHistory(
|
|
178
|
+
generation.config,
|
|
179
|
+
prepared.requiresDurableStorageHistory === true,
|
|
180
|
+
),
|
|
181
|
+
)
|
|
123
182
|
ensureOperational()
|
|
124
183
|
variants.reconcile()
|
|
125
184
|
} catch (error) {
|