@mengruo/dsh-vision-toolkit 0.0.1 → 0.1.1
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 +17 -42
- package/README.zh.md +16 -41
- package/assets/logo_eapi_dark.png +0 -0
- package/docs/aihubmix-gemini-vision.i18n.yaml +2 -2
- package/docs/aihubmix-gemini-vision.md +2 -2
- package/docs/aihubmix-gemini-vision.zh.md +2 -2
- package/lib/client.js +246 -68
- package/lib/client.js.map +1 -1
- package/lib/config.js +143 -24
- package/lib/config.js.map +1 -1
- package/lib/evidence-cache.js +14 -0
- package/lib/evidence-cache.js.map +1 -1
- package/lib/image-input-variants.js +48 -24
- package/lib/image-input-variants.js.map +1 -1
- package/lib/runtime-install.js +204 -19
- package/lib/runtime-install.js.map +1 -1
- package/lib/runtime.js +310 -154
- package/lib/runtime.js.map +1 -1
- package/lib/tools.js +1 -0
- package/lib/tools.js.map +1 -1
- package/lib/types/client/index.d.ts +45 -3
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/config.d.ts +53 -0
- package/lib/types/config.d.ts.map +1 -1
- package/lib/types/evidence-cache.d.ts.map +1 -1
- package/lib/types/image-input-variants.d.ts +9 -4
- package/lib/types/image-input-variants.d.ts.map +1 -1
- package/lib/types/runtime-install.d.ts +21 -0
- package/lib/types/runtime-install.d.ts.map +1 -1
- package/lib/types/runtime.d.ts +45 -10
- package/lib/types/runtime.d.ts.map +1 -1
- package/lib/types/tools.d.ts.map +1 -1
- package/lib/types/upstream.d.ts +3 -0
- package/lib/types/upstream.d.ts.map +1 -1
- package/lib/types/web.d.ts +8 -0
- package/lib/types/web.d.ts.map +1 -1
- package/lib/upstream.js +11 -6
- package/lib/upstream.js.map +1 -1
- package/lib/web.js +50 -7
- package/lib/web.js.map +1 -1
- package/package.json +1 -1
- package/src/client/index.tsx +331 -88
- package/src/config.ts +210 -28
- package/src/evidence-cache.ts +14 -0
- package/src/image-input-variants.ts +54 -22
- package/src/runtime-install.ts +231 -20
- package/src/runtime.ts +333 -180
- package/src/tools.ts +1 -0
- package/src/upstream.ts +15 -8
- package/src/web.ts +67 -8
package/src/tools.ts
CHANGED
|
@@ -97,6 +97,7 @@ const imageInfoSchema = {
|
|
|
97
97
|
width: { type: 'integer', required: true },
|
|
98
98
|
height: { type: 'integer', required: true },
|
|
99
99
|
format: { type: 'string', required: true },
|
|
100
|
+
hasAlpha: { type: 'boolean', required: true, description: 'Whether the analyzed image carries an alpha (transparency) channel.' },
|
|
100
101
|
originalPath: { type: 'string', required: true, description: 'Original image path before automatic compression.' },
|
|
101
102
|
},
|
|
102
103
|
} as const satisfies ValueSchemaSpec
|
package/src/upstream.ts
CHANGED
|
@@ -176,6 +176,8 @@ export interface CompressedImageInfo {
|
|
|
176
176
|
height: number
|
|
177
177
|
format: 'png' | 'jpeg' | 'gif' | 'webp'
|
|
178
178
|
mode: string
|
|
179
|
+
/** True when the saved image carries an alpha (transparency) channel. */
|
|
180
|
+
hasAlpha: boolean
|
|
179
181
|
lossy: boolean
|
|
180
182
|
resized: boolean
|
|
181
183
|
candidate: string
|
|
@@ -614,7 +616,7 @@ const COMPRESS_IMAGE_SCRIPT = [
|
|
|
614
616
|
' if icc is not None: kwargs["icc_profile"]=icc',
|
|
615
617
|
' im.save(path,format=fmt,**kwargs)',
|
|
616
618
|
' with Image.open(path) as saved:',
|
|
617
|
-
' return os.path.getsize(path),(saved.format or "unknown").lower(),saved.mode',
|
|
619
|
+
' return os.path.getsize(path),(saved.format or "unknown").lower(),saved.mode,saved.mode in ("RGBA","LA") or (saved.mode=="P" and "transparency" in saved.info)',
|
|
618
620
|
'def has_alpha(im):',
|
|
619
621
|
' return im.mode in ("RGBA","LA") or (im.mode=="P" and "transparency" in im.info)',
|
|
620
622
|
'def flatten(im):',
|
|
@@ -672,11 +674,11 @@ const COMPRESS_IMAGE_SCRIPT = [
|
|
|
672
674
|
' candidates=[]',
|
|
673
675
|
' for label,fmt,lossy,saver in candidates:',
|
|
674
676
|
' try:',
|
|
675
|
-
' size,saved_fmt,mode=saver()',
|
|
677
|
+
' size,saved_fmt,mode,has_alpha=saver()',
|
|
676
678
|
' except Exception:',
|
|
677
679
|
' continue',
|
|
678
680
|
' if ok(w,h,size):',
|
|
679
|
-
' print(json.dumps({"ok":True,"bytes":size,"width":w,"height":h,"format":fmt,"mode":mode,"lossy":lossy,"resized":step>0,"candidate":label,"source_animated":meta["animated"]}))',
|
|
681
|
+
' print(json.dumps({"ok":True,"bytes":size,"width":w,"height":h,"format":fmt,"mode":mode,"has_alpha":has_alpha,"lossy":lossy,"resized":step>0,"candidate":label,"source_animated":meta["animated"]}))',
|
|
680
682
|
' sys.exit(0)',
|
|
681
683
|
' if best is None or size<best[0]:',
|
|
682
684
|
' best=(size,label,fmt,lossy,step>0)',
|
|
@@ -799,13 +801,15 @@ export class UpstreamAdapter {
|
|
|
799
801
|
async probeImageSize(
|
|
800
802
|
imagePath: string,
|
|
801
803
|
options: { signal: AbortSignal },
|
|
802
|
-
): Promise<{ width: number; height: number; format: string; mode: string }> {
|
|
804
|
+
): Promise<{ width: number; height: number; format: string; mode: string; hasAlpha: boolean }> {
|
|
803
805
|
if (this.prepared === undefined) await this.prepare()
|
|
804
806
|
const prepared = this.requirePrepared()
|
|
805
807
|
const script = [
|
|
806
808
|
'import json,sys',
|
|
807
809
|
'from PIL import Image',
|
|
808
|
-
'with Image.open(sys.argv[1]) as im:
|
|
810
|
+
'with Image.open(sys.argv[1]) as im:',
|
|
811
|
+
' has_alpha = im.mode in ("RGBA","LA") or (im.mode=="P" and "transparency" in im.info)',
|
|
812
|
+
' print(json.dumps({"width":im.width,"height":im.height,"format":str(im.format or "unknown").lower(),"mode":str(im.mode),"has_alpha":has_alpha}))',
|
|
809
813
|
].join('\n')
|
|
810
814
|
let handle: SubprocessHandle
|
|
811
815
|
try {
|
|
@@ -829,18 +833,19 @@ export class UpstreamAdapter {
|
|
|
829
833
|
throw new VisionToolkitError('input', `cannot decode image: ${outcome.stderr.trim() || 'unsupported or corrupt file'}`)
|
|
830
834
|
}
|
|
831
835
|
try {
|
|
832
|
-
const parsed = JSON.parse(outcome.stdout) as { width?: unknown; height?: unknown; format?: unknown; mode?: unknown }
|
|
836
|
+
const parsed = JSON.parse(outcome.stdout) as { width?: unknown; height?: unknown; format?: unknown; mode?: unknown; has_alpha?: unknown }
|
|
833
837
|
if (
|
|
834
838
|
typeof parsed.width !== 'number'
|
|
835
839
|
|| typeof parsed.height !== 'number'
|
|
836
840
|
|| typeof parsed.format !== 'string'
|
|
837
841
|
|| typeof parsed.mode !== 'string'
|
|
842
|
+
|| typeof parsed.has_alpha !== 'boolean'
|
|
838
843
|
|| !Number.isInteger(parsed.width)
|
|
839
844
|
|| !Number.isInteger(parsed.height)
|
|
840
845
|
|| parsed.width <= 0
|
|
841
846
|
|| parsed.height <= 0
|
|
842
847
|
) throw new Error('invalid dimensions')
|
|
843
|
-
return { width: parsed.width, height: parsed.height, format: parsed.format, mode: parsed.mode }
|
|
848
|
+
return { width: parsed.width, height: parsed.height, format: parsed.format, mode: parsed.mode, hasAlpha: parsed.has_alpha }
|
|
844
849
|
} catch (error) {
|
|
845
850
|
throw new VisionToolkitError('output', 'cannot read image dimensions: unexpected Python output', { cause: error })
|
|
846
851
|
}
|
|
@@ -884,7 +889,7 @@ export class UpstreamAdapter {
|
|
|
884
889
|
const detail = typeof record.error === 'string' ? record.error : 'compression failed'
|
|
885
890
|
throw new VisionToolkitError('capacity', `cannot compress image under ${maxBytes} bytes: ${detail}`)
|
|
886
891
|
}
|
|
887
|
-
const { bytes, width, height, format, mode, lossy, resized, candidate, source_animated } = record
|
|
892
|
+
const { bytes, width, height, format, mode, has_alpha, lossy, resized, candidate, source_animated } = record
|
|
888
893
|
if (
|
|
889
894
|
typeof bytes !== 'number' || !Number.isInteger(bytes) || bytes < 1 || bytes > maxBytes
|
|
890
895
|
|| typeof width !== 'number' || !Number.isInteger(width) || width < 1
|
|
@@ -892,6 +897,7 @@ export class UpstreamAdapter {
|
|
|
892
897
|
|| width * height > maxPixels
|
|
893
898
|
|| typeof format !== 'string' || !COMPRESSED_FORMATS.has(format)
|
|
894
899
|
|| typeof mode !== 'string' || mode.length === 0
|
|
900
|
+
|| typeof has_alpha !== 'boolean'
|
|
895
901
|
|| typeof lossy !== 'boolean'
|
|
896
902
|
|| typeof resized !== 'boolean'
|
|
897
903
|
|| typeof candidate !== 'string' || candidate.length === 0
|
|
@@ -905,6 +911,7 @@ export class UpstreamAdapter {
|
|
|
905
911
|
height,
|
|
906
912
|
format: format as CompressedImageInfo['format'],
|
|
907
913
|
mode,
|
|
914
|
+
hasAlpha: has_alpha,
|
|
908
915
|
lossy,
|
|
909
916
|
resized,
|
|
910
917
|
candidate,
|
package/src/web.ts
CHANGED
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
resolveConfig,
|
|
25
25
|
isBuiltInFreeVisionProvider,
|
|
26
26
|
VISION_TOOLKIT_SETTINGS_NAMESPACE,
|
|
27
|
+
type ResolvedProvider,
|
|
27
28
|
type ResolvedVisionToolkitConfig,
|
|
28
29
|
type VisionToolkitConfig,
|
|
29
30
|
} from './config.ts'
|
|
@@ -66,6 +67,13 @@ export interface VisionToolkitSettingsSnapshot {
|
|
|
66
67
|
source?: string
|
|
67
68
|
writable: boolean
|
|
68
69
|
}
|
|
70
|
+
/** Per-provider credential states, aligned with `settings.value.providers` order. */
|
|
71
|
+
credentials: Array<{
|
|
72
|
+
ref: string
|
|
73
|
+
configured: boolean
|
|
74
|
+
source?: string
|
|
75
|
+
writable: boolean
|
|
76
|
+
}>
|
|
69
77
|
runtime: RuntimeManagerStatus
|
|
70
78
|
release: {
|
|
71
79
|
pluginVersion: string
|
|
@@ -87,6 +95,8 @@ interface HealthRequest {
|
|
|
87
95
|
action: 'health'
|
|
88
96
|
testConnection: boolean
|
|
89
97
|
testModel: boolean
|
|
98
|
+
/** 0-based provider index; when absent, the primary provider is tested. */
|
|
99
|
+
providerIndex?: number
|
|
90
100
|
}
|
|
91
101
|
|
|
92
102
|
interface CredentialRequest {
|
|
@@ -96,6 +106,11 @@ interface CredentialRequest {
|
|
|
96
106
|
value: string
|
|
97
107
|
}
|
|
98
108
|
|
|
109
|
+
interface DeleteCredentialRequest {
|
|
110
|
+
action: 'delete-credential'
|
|
111
|
+
ref: CredentialRef
|
|
112
|
+
}
|
|
113
|
+
|
|
99
114
|
interface CheckUpdateRequest {
|
|
100
115
|
action: 'check-update'
|
|
101
116
|
}
|
|
@@ -105,7 +120,7 @@ interface ApplyUpdateRequest {
|
|
|
105
120
|
expectedVersion: string
|
|
106
121
|
}
|
|
107
122
|
|
|
108
|
-
type SettingsRequest = SaveRequest | HealthRequest | CredentialRequest | CheckUpdateRequest | ApplyUpdateRequest
|
|
123
|
+
type SettingsRequest = SaveRequest | HealthRequest | CredentialRequest | DeleteCredentialRequest | CheckUpdateRequest | ApplyUpdateRequest
|
|
109
124
|
|
|
110
125
|
interface JsonError {
|
|
111
126
|
ok: false
|
|
@@ -189,7 +204,16 @@ function parseRequest(value: unknown): SettingsRequest {
|
|
|
189
204
|
const testModel = value.testModel === undefined ? false : value.testModel
|
|
190
205
|
if (typeof testModel !== 'boolean') throw new TypeError('health.testModel must be boolean')
|
|
191
206
|
if (testModel && !value.testConnection) throw new TypeError('health.testModel requires health.testConnection')
|
|
192
|
-
|
|
207
|
+
const providerIndex = value.providerIndex
|
|
208
|
+
if (providerIndex !== undefined && (!Number.isSafeInteger(providerIndex) || (providerIndex as number) < 0)) {
|
|
209
|
+
throw new TypeError('health.providerIndex must be a non-negative integer')
|
|
210
|
+
}
|
|
211
|
+
return {
|
|
212
|
+
action: 'health',
|
|
213
|
+
testConnection: value.testConnection,
|
|
214
|
+
testModel,
|
|
215
|
+
...(providerIndex === undefined ? {} : { providerIndex: providerIndex as number }),
|
|
216
|
+
}
|
|
193
217
|
}
|
|
194
218
|
if (value.action === 'save') {
|
|
195
219
|
if (!Number.isSafeInteger(value.expectedRevision) || (value.expectedRevision as number) < 0) {
|
|
@@ -223,6 +247,12 @@ function parseRequest(value: unknown): SettingsRequest {
|
|
|
223
247
|
value: secret,
|
|
224
248
|
}
|
|
225
249
|
}
|
|
250
|
+
if (value.action === 'delete-credential') {
|
|
251
|
+
if (typeof value.ref !== 'string' || value.ref.trim().length === 0) {
|
|
252
|
+
throw new TypeError('delete-credential.ref must be a non-empty string')
|
|
253
|
+
}
|
|
254
|
+
return { action: 'delete-credential', ref: credentialRef(value.ref.trim()) }
|
|
255
|
+
}
|
|
226
256
|
if (value.action === 'check-update') return { action: 'check-update' }
|
|
227
257
|
if (value.action === 'apply-update') {
|
|
228
258
|
if (typeof value.expectedVersion !== 'string' || value.expectedVersion.trim().length === 0) {
|
|
@@ -272,6 +302,18 @@ export class VisionToolkitWebBackend {
|
|
|
272
302
|
const value = descriptor.value as VisionToolkitConfig
|
|
273
303
|
const resolved = resolveConfig(value)
|
|
274
304
|
const credential = await this.credential(resolved)
|
|
305
|
+
const credentials = await Promise.all(resolved.providers.map(async provider => {
|
|
306
|
+
if (isBuiltInFreeVisionProvider(provider)) {
|
|
307
|
+
return { ref: String(provider.credential), configured: true, source: 'built-in-free', writable: false }
|
|
308
|
+
}
|
|
309
|
+
const info = await this.ctx.credentials.describe(credentialRef(String(provider.credential)))
|
|
310
|
+
return {
|
|
311
|
+
ref: String(provider.credential),
|
|
312
|
+
configured: info.configured,
|
|
313
|
+
...(info.source === undefined ? {} : { source: info.source }),
|
|
314
|
+
writable: info.writable,
|
|
315
|
+
}
|
|
316
|
+
}))
|
|
275
317
|
const update = await this.updater.capability()
|
|
276
318
|
return {
|
|
277
319
|
schemaVersion: 1,
|
|
@@ -289,6 +331,7 @@ export class VisionToolkitWebBackend {
|
|
|
289
331
|
...(credential.source === undefined ? {} : { source: credential.source }),
|
|
290
332
|
writable: credential.writable,
|
|
291
333
|
},
|
|
334
|
+
credentials,
|
|
292
335
|
runtime: this.manager.status(),
|
|
293
336
|
release: {
|
|
294
337
|
pluginVersion: PLUGIN_VERSION,
|
|
@@ -330,16 +373,21 @@ export class VisionToolkitWebBackend {
|
|
|
330
373
|
)
|
|
331
374
|
}
|
|
332
375
|
const resolved = resolveConfig(descriptor.value as VisionToolkitConfig)
|
|
333
|
-
const
|
|
334
|
-
if (
|
|
376
|
+
const provider = resolved.providers.find(entry => String(entry.credential) === String(request.ref))
|
|
377
|
+
if (provider === undefined) {
|
|
335
378
|
throw new CredentialReferenceConflictError(
|
|
336
|
-
`credential reference
|
|
379
|
+
`credential reference "${String(request.ref)}" does not match any configured vision provider; reload Settings and try again`,
|
|
337
380
|
)
|
|
338
381
|
}
|
|
339
|
-
if (isBuiltInFreeVisionProvider(
|
|
382
|
+
if (isBuiltInFreeVisionProvider(provider)) {
|
|
340
383
|
throw new Error('The built-in free vision provider does not accept a user API key')
|
|
341
384
|
}
|
|
342
|
-
await this.ctx.credentials.set(
|
|
385
|
+
await this.ctx.credentials.set(request.ref, request.value)
|
|
386
|
+
return this.snapshot()
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
private async deleteCredential(request: DeleteCredentialRequest): Promise<VisionToolkitSettingsSnapshot> {
|
|
390
|
+
await this.ctx.credentials.unset(request.ref)
|
|
343
391
|
return this.snapshot()
|
|
344
392
|
}
|
|
345
393
|
|
|
@@ -351,12 +399,20 @@ export class VisionToolkitWebBackend {
|
|
|
351
399
|
req.socket.once('close', abort)
|
|
352
400
|
try {
|
|
353
401
|
const runtime = this.manager.current()
|
|
402
|
+
let provider: ResolvedProvider | undefined
|
|
403
|
+
if (request.providerIndex !== undefined) {
|
|
404
|
+
const resolved = resolveConfig(descriptorOf(this.ctx).value as VisionToolkitConfig)
|
|
405
|
+
provider = resolved.providers[request.providerIndex]
|
|
406
|
+
if (provider === undefined) {
|
|
407
|
+
throw new Error(`provider index ${request.providerIndex} is out of range`)
|
|
408
|
+
}
|
|
409
|
+
}
|
|
354
410
|
// Use the prepared runtime home instead of the host process cwd.
|
|
355
411
|
return await runtime.health(request.testConnection, {
|
|
356
412
|
signal: controller.signal,
|
|
357
413
|
workspace: runtime.upstreamVersion.runtimeHome,
|
|
358
414
|
sessionId: 'vision-toolkit-settings',
|
|
359
|
-
}, request.testModel)
|
|
415
|
+
}, request.testModel, provider)
|
|
360
416
|
} finally {
|
|
361
417
|
req.off('aborted', abort)
|
|
362
418
|
req.socket.off('close', abort)
|
|
@@ -401,6 +457,9 @@ export class VisionToolkitWebBackend {
|
|
|
401
457
|
case 'credential':
|
|
402
458
|
responseJson(res, 200, { ok: true, value: await this.saveCredential(parsed) })
|
|
403
459
|
break
|
|
460
|
+
case 'delete-credential':
|
|
461
|
+
responseJson(res, 200, { ok: true, value: await this.deleteCredential(parsed) })
|
|
462
|
+
break
|
|
404
463
|
case 'check-update':
|
|
405
464
|
responseJson(res, 200, { ok: true, value: await this.updater.check() })
|
|
406
465
|
break
|