@mengruo/dsh-vision-toolkit 0.1.5 → 0.1.6-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/1.mp4 +0 -0
- package/assets/skill/SKILL.md +37 -5
- package/docs/plan-per-tool-visibility.md +82 -0
- package/lib/client.js +53 -3
- package/lib/client.js.map +1 -1
- package/lib/config.js +17 -0
- package/lib/config.js.map +1 -1
- package/lib/exposure.js +35 -8
- package/lib/exposure.js.map +1 -1
- package/lib/index.js +1 -1
- package/lib/index.js.map +1 -1
- package/lib/paths.js +9 -0
- package/lib/paths.js.map +1 -1
- package/lib/runtime.js +175 -1
- package/lib/runtime.js.map +1 -1
- package/lib/tools.js +116 -4
- package/lib/tools.js.map +1 -1
- package/lib/types/client/index.d.ts +24 -1
- package/lib/types/client/index.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/exposure.d.ts +14 -2
- package/lib/types/exposure.d.ts.map +1 -1
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/paths.d.ts +7 -0
- package/lib/types/paths.d.ts.map +1 -1
- package/lib/types/runtime.d.ts +38 -0
- package/lib/types/runtime.d.ts.map +1 -1
- package/lib/types/tools.d.ts +22 -1
- package/lib/types/tools.d.ts.map +1 -1
- package/lib/types/video.d.ts +78 -0
- package/lib/types/video.d.ts.map +1 -0
- package/lib/types/web.d.ts +1 -0
- package/lib/types/web.d.ts.map +1 -1
- package/lib/video.js +169 -0
- package/lib/video.js.map +1 -0
- package/lib/web.js +47 -4
- package/lib/web.js.map +1 -1
- package/package.json +2 -1
- package/src/client/index.tsx +72 -1
- package/src/config.ts +43 -0
- package/src/exposure.ts +34 -9
- package/src/index.ts +10 -5
- package/src/paths.ts +11 -0
- package/src/runtime.ts +196 -0
- package/src/tools.ts +144 -3
- package/src/video.ts +222 -0
- package/src/web.ts +53 -5
package/src/client/index.tsx
CHANGED
|
@@ -77,6 +77,8 @@ const en = {
|
|
|
77
77
|
streamHint: 'Request a streamed (SSE) completion instead of one JSON response. Use it for endpoints with weak non-streaming support or that time out on long outputs.',
|
|
78
78
|
uploadViaUrl: 'Transfer images via URL',
|
|
79
79
|
uploadViaUrlHint: 'Upload each image to the configured object storage and send the model a URL instead of base64. Requires object storage (below); region crops fall back to base64.',
|
|
80
|
+
videoSupport: 'Enable video support',
|
|
81
|
+
videoSupportHint: 'OpenAI-compatible video understanding. This setting is hidden for Anthropic providers and is currently a placeholder with no effect yet.',
|
|
80
82
|
language: 'Output language',
|
|
81
83
|
limits: 'Timeout and concurrency',
|
|
82
84
|
timeout: 'Request timeout (ms)',
|
|
@@ -131,8 +133,10 @@ const en = {
|
|
|
131
133
|
runHealth: 'Run health check',
|
|
132
134
|
testConnection: 'Test API connection',
|
|
133
135
|
testModel: 'Test vision model',
|
|
136
|
+
testVideo: 'Test video call',
|
|
134
137
|
testing: 'Checking…',
|
|
135
138
|
testingModel: 'Testing model…',
|
|
139
|
+
testingVideo: 'Testing video…',
|
|
136
140
|
connectionHint: 'The API connection test only queries GET /models. The vision model test sends the bundled diagnostic image and verifies one real multimodal request.',
|
|
137
141
|
saveBeforeTesting: 'Save service changes before testing the connection.',
|
|
138
142
|
advanced: 'Advanced settings',
|
|
@@ -141,6 +145,14 @@ const en = {
|
|
|
141
145
|
hiddenVariants: 'Transparent variant routing',
|
|
142
146
|
hiddenVariantsLabel: 'Keep the original model names and enable images automatically',
|
|
143
147
|
hiddenVariantsHint: 'Text-only models keep one model-selector entry with the original name while the session runs on the image-capable variant. Pasted images, image history, and the built-in read_image tool keep working; disable to restore the explicit (Vision Toolkit) entries.',
|
|
148
|
+
toolVisibility: 'Tool visibility',
|
|
149
|
+
toolVisibilityHint: 'Which groups of vision tools an Agent sees. Applies to the next Agent conversation: an already-active Agent keeps its activation-time set until it is reloaded.',
|
|
150
|
+
toolVisibilityLocal: 'Local tools',
|
|
151
|
+
toolVisibilityLocalHint: 'trace / crop / pixel diff / foreground / colors / HTML screenshot / video info. Local processing, no on-line parallel limits.',
|
|
152
|
+
toolVisibilityOnline: 'Online image tools',
|
|
153
|
+
toolVisibilityOnlineHint: 'glance / ground / detect / long-screenshot OCR, plus the concurrency/status probe. On-line service calls share the session concurrency budget.',
|
|
154
|
+
toolVisibilityVideo: 'Video tools',
|
|
155
|
+
toolVisibilityVideoHint: 'video understanding (experimental). Calling it without an enabled video-capable service returns "video understanding unavailable".',
|
|
144
156
|
pluginVersion: 'Plugin',
|
|
145
157
|
upstreamVersion: 'Upstream',
|
|
146
158
|
activeGeneration: 'Runtime generation',
|
|
@@ -312,6 +324,8 @@ const zh: Record<LocaleKey, string> = {
|
|
|
312
324
|
streamHint: '以流式(SSE)方式请求补全,而非一次性返回 JSON。适合对非流式支持不佳、长输出易连接超时的端点。',
|
|
313
325
|
uploadViaUrl: '通过 URL 传输图片',
|
|
314
326
|
uploadViaUrlHint: '把每张图片上传到下方配置的对象存储,将 URL 传给模型而不是 base64。需要先配置对象存储;使用 region 裁剪时回退为 base64。',
|
|
327
|
+
videoSupport: '启用视频支持',
|
|
328
|
+
videoSupportHint: 'OpenAI 兼容接口的视频理解能力。Anthropic 服务不显示此项;当前为占位设置,尚未生效。',
|
|
315
329
|
language: '结果语言',
|
|
316
330
|
limits: '超时与并发限制',
|
|
317
331
|
timeout: '单次请求超时(毫秒)',
|
|
@@ -366,8 +380,10 @@ const zh: Record<LocaleKey, string> = {
|
|
|
366
380
|
runHealth: '检查本地环境',
|
|
367
381
|
testConnection: '测试 API 连接',
|
|
368
382
|
testModel: '测试视觉模型',
|
|
383
|
+
testVideo: '测试视频调用',
|
|
369
384
|
testing: '检查中…',
|
|
370
385
|
testingModel: '正在测试模型…',
|
|
386
|
+
testingVideo: '正在测试视频…',
|
|
371
387
|
connectionHint: '“测试 API 连接”只请求 GET /models;“测试视觉模型”会发送插件自带的诊断图片,验证一次真实多模态调用。',
|
|
372
388
|
saveBeforeTesting: '修改服务配置后,请先保存,再执行 API 或视觉模型测试。',
|
|
373
389
|
advanced: '高级设置',
|
|
@@ -376,6 +392,14 @@ const zh: Record<LocaleKey, string> = {
|
|
|
376
392
|
hiddenVariants: '透明变体路由',
|
|
377
393
|
hiddenVariantsLabel: '保留原模型名并自动启用图片能力',
|
|
378
394
|
hiddenVariantsHint: '文本模型在模型列表中只显示原名称,会话实际运行在支持图片的变体路由上:粘贴图片、历史图片和内置 read_image 工具均可正常使用。关闭后恢复显示显式的(Vision Toolkit)条目。',
|
|
395
|
+
toolVisibility: '工具可见性',
|
|
396
|
+
toolVisibilityHint: 'Agent 可见的视觉工具分组。仅在下一个 agent 对话中生效:已激活的 agent 保持激活时的工具集,直到重载。',
|
|
397
|
+
toolVisibilityLocal: '本地工具',
|
|
398
|
+
toolVisibilityLocalHint: 'trace / crop / 像素对比 / 前景提取 / 取色 / HTML 截图 / 视频信息。本地处理,不受联机并发限制。',
|
|
399
|
+
toolVisibilityOnline: '联机图片工具',
|
|
400
|
+
toolVisibilityOnlineHint: 'glance / ground / detect / 长截图 OCR,以及并发/状态查询。联机调用共享会话并发额度。',
|
|
401
|
+
toolVisibilityVideo: '视频工具',
|
|
402
|
+
toolVisibilityVideoHint: '视频理解(测试)。调用时若没有已启用且支持视频的服务,将返回“视频理解不可用”。',
|
|
379
403
|
pluginVersion: '插件版本',
|
|
380
404
|
upstreamVersion: '工具包版本',
|
|
381
405
|
activeGeneration: '本次运行已应用',
|
|
@@ -569,6 +593,7 @@ interface ProviderValue {
|
|
|
569
593
|
userAgent?: string
|
|
570
594
|
stream?: boolean
|
|
571
595
|
uploadViaUrl?: boolean
|
|
596
|
+
videoSupport?: boolean
|
|
572
597
|
t1Seconds?: number
|
|
573
598
|
t2Seconds?: number
|
|
574
599
|
maxImageBytes?: number
|
|
@@ -587,6 +612,7 @@ interface SettingsValue {
|
|
|
587
612
|
userAgent?: string
|
|
588
613
|
stream?: boolean
|
|
589
614
|
uploadViaUrl?: boolean
|
|
615
|
+
videoSupport?: boolean
|
|
590
616
|
}
|
|
591
617
|
providers?: ProviderValue[]
|
|
592
618
|
language?: 'zh' | 'en'
|
|
@@ -612,6 +638,11 @@ interface SettingsValue {
|
|
|
612
638
|
autoSwitch?: boolean
|
|
613
639
|
hidden?: boolean
|
|
614
640
|
}
|
|
641
|
+
toolVisibility?: {
|
|
642
|
+
local?: boolean
|
|
643
|
+
online?: boolean
|
|
644
|
+
video?: boolean
|
|
645
|
+
}
|
|
615
646
|
}
|
|
616
647
|
|
|
617
648
|
type PluginUpdateUnavailableReason =
|
|
@@ -1049,7 +1080,8 @@ interface SettingsState {
|
|
|
1049
1080
|
update?: PluginUpdateCheck | undefined
|
|
1050
1081
|
restart?: PluginUpdateResult | undefined
|
|
1051
1082
|
storageTest?: { detail: string } | undefined
|
|
1052
|
-
|
|
1083
|
+
videoTest?: { detail: string } | undefined
|
|
1084
|
+
action?: 'save' | 'health' | 'connection' | 'model' | 'check-update' | 'apply-update' | 'test-storage' | 'test-video' | undefined
|
|
1053
1085
|
message?: string | undefined
|
|
1054
1086
|
error?: string | undefined
|
|
1055
1087
|
}
|
|
@@ -1198,6 +1230,20 @@ export class VisionSettingsController {
|
|
|
1198
1230
|
}
|
|
1199
1231
|
}
|
|
1200
1232
|
|
|
1233
|
+
async runVideoTest(providerIndex?: number): Promise<void> {
|
|
1234
|
+
this.set({ ...this.state, action: 'test-video', error: undefined, message: undefined })
|
|
1235
|
+
try {
|
|
1236
|
+
const result = await apiRequest<{ detail: string }>({
|
|
1237
|
+
method: 'POST',
|
|
1238
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1239
|
+
body: JSON.stringify({ action: 'test-video', ...(providerIndex === undefined ? {} : { providerIndex }) }),
|
|
1240
|
+
})
|
|
1241
|
+
this.set({ ...this.state, action: undefined, videoTest: result })
|
|
1242
|
+
} catch (error) {
|
|
1243
|
+
this.set({ ...this.state, action: undefined, error: error instanceof Error ? error.message : String(error) })
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1201
1247
|
async checkUpdate(): Promise<void> {
|
|
1202
1248
|
this.set({ ...this.state, action: 'check-update', error: undefined, message: undefined })
|
|
1203
1249
|
try {
|
|
@@ -1248,6 +1294,7 @@ interface ProviderDraft {
|
|
|
1248
1294
|
userAgent: string
|
|
1249
1295
|
stream: boolean
|
|
1250
1296
|
uploadViaUrl: boolean
|
|
1297
|
+
videoSupport: boolean
|
|
1251
1298
|
t1Seconds: string
|
|
1252
1299
|
t2Seconds: string
|
|
1253
1300
|
maxImageBytes: string
|
|
@@ -1277,6 +1324,9 @@ interface Draft {
|
|
|
1277
1324
|
variantEnabled: boolean
|
|
1278
1325
|
variantProviders: string
|
|
1279
1326
|
variantAutoSwitch: boolean
|
|
1327
|
+
toolVisibilityLocal: boolean
|
|
1328
|
+
toolVisibilityOnline: boolean
|
|
1329
|
+
toolVisibilityVideo: boolean
|
|
1280
1330
|
}
|
|
1281
1331
|
|
|
1282
1332
|
function randomProviderId(): string {
|
|
@@ -1313,6 +1363,7 @@ function emptyProviderDraft(defaults: ProviderDefaults): ProviderDraft {
|
|
|
1313
1363
|
userAgent: DEFAULT_USER_AGENT,
|
|
1314
1364
|
stream: false,
|
|
1315
1365
|
uploadViaUrl: false,
|
|
1366
|
+
videoSupport: false,
|
|
1316
1367
|
t1Seconds: '90',
|
|
1317
1368
|
t2Seconds: '90',
|
|
1318
1369
|
maxImageBytes: String(defaults.maxImageBytes),
|
|
@@ -1337,6 +1388,7 @@ function providerDraftOf(value: ProviderValue | undefined, fallback: ProviderVal
|
|
|
1337
1388
|
userAgent: source?.userAgent ?? DEFAULT_USER_AGENT,
|
|
1338
1389
|
stream: source?.stream === true,
|
|
1339
1390
|
uploadViaUrl: source?.uploadViaUrl === true,
|
|
1391
|
+
videoSupport: source?.videoSupport === true,
|
|
1340
1392
|
t1Seconds: String(source?.t1Seconds ?? 90),
|
|
1341
1393
|
t2Seconds: String(source?.t2Seconds ?? 90),
|
|
1342
1394
|
maxImageBytes: String(source?.maxImageBytes ?? defaults.maxImageBytes),
|
|
@@ -1377,6 +1429,9 @@ function draftOf(value: SettingsValue): Draft {
|
|
|
1377
1429
|
variantEnabled: value.imageInputVariants?.enabled ?? true,
|
|
1378
1430
|
variantProviders: (value.imageInputVariants?.providers ?? []).join('\n'),
|
|
1379
1431
|
variantAutoSwitch: value.imageInputVariants?.autoSwitch ?? true,
|
|
1432
|
+
toolVisibilityLocal: value.toolVisibility?.local ?? true,
|
|
1433
|
+
toolVisibilityOnline: value.toolVisibility?.online ?? true,
|
|
1434
|
+
toolVisibilityVideo: value.toolVisibility?.video ?? false,
|
|
1380
1435
|
}
|
|
1381
1436
|
}
|
|
1382
1437
|
|
|
@@ -1409,6 +1464,7 @@ function valueOf(draft: Draft, t: Translate): SettingsValue {
|
|
|
1409
1464
|
...(provider.userAgent.trim() === DEFAULT_USER_AGENT ? {} : { userAgent: provider.userAgent.trim() }),
|
|
1410
1465
|
...(provider.stream ? { stream: true } : {}),
|
|
1411
1466
|
...(provider.uploadViaUrl ? { uploadViaUrl: true } : {}),
|
|
1467
|
+
...(provider.videoSupport ? { videoSupport: true } : {}),
|
|
1412
1468
|
...(provider.t1Seconds.trim().length === 0 ? {} : { t1Seconds: positiveInteger(provider.t1Seconds, t('t1'), t) }),
|
|
1413
1469
|
...(provider.t2Seconds.trim().length === 0 ? {} : { t2Seconds: positiveInteger(provider.t2Seconds, t('t2'), t) }),
|
|
1414
1470
|
...(provider.maxImageBytes.trim().length === 0 ? {} : { maxImageBytes: positiveInteger(provider.maxImageBytes, t('maxBytes'), t) }),
|
|
@@ -1427,6 +1483,7 @@ function valueOf(draft: Draft, t: Translate): SettingsValue {
|
|
|
1427
1483
|
userAgent: primary?.userAgent.trim() || DEFAULT_USER_AGENT,
|
|
1428
1484
|
stream: primary?.stream === true,
|
|
1429
1485
|
uploadViaUrl: primary?.uploadViaUrl === true,
|
|
1486
|
+
videoSupport: primary?.videoSupport === true,
|
|
1430
1487
|
},
|
|
1431
1488
|
providers,
|
|
1432
1489
|
language: draft.language,
|
|
@@ -1461,6 +1518,11 @@ function valueOf(draft: Draft, t: Translate): SettingsValue {
|
|
|
1461
1518
|
...(draft.variantAutoSwitch ? {} : { autoSwitch: false }),
|
|
1462
1519
|
hidden: draft.hiddenVariants,
|
|
1463
1520
|
},
|
|
1521
|
+
toolVisibility: {
|
|
1522
|
+
local: draft.toolVisibilityLocal,
|
|
1523
|
+
online: draft.toolVisibilityOnline,
|
|
1524
|
+
video: draft.toolVisibilityVideo,
|
|
1525
|
+
},
|
|
1464
1526
|
}
|
|
1465
1527
|
}
|
|
1466
1528
|
|
|
@@ -1803,6 +1865,7 @@ function LoadedSettings({ controller, t }: SettingsInjected) {
|
|
|
1803
1865
|
<Field label={t('userAgent')}><Input aria-label={t('userAgent')} value={selectedProvider.userAgent} onChange={(event) => { updateProvider(selectedIndex, 'userAgent', event.target.value) }} /></Field>
|
|
1804
1866
|
<Field label={t('stream')} hint={t('streamHint')}><select aria-label={t('stream')} disabled={!snapshot.writable || busy} value={selectedProvider.stream ? 'enabled' : 'disabled'} onChange={(event) => { updateProvider(selectedIndex, 'stream', event.target.value === 'enabled') }}><option value="disabled">{t('disabled')}</option><option value="enabled">{t('enabled')}</option></select></Field>
|
|
1805
1867
|
<Field label={t('uploadViaUrl')} hint={t('uploadViaUrlHint')}><select aria-label={t('uploadViaUrl')} disabled={!snapshot.writable || busy} value={selectedProvider.uploadViaUrl ? 'enabled' : 'disabled'} onChange={(event) => { updateProvider(selectedIndex, 'uploadViaUrl', event.target.value === 'enabled') }}><option value="disabled">{t('disabled')}</option><option value="enabled">{t('enabled')}</option></select></Field>
|
|
1868
|
+
{selectedProvider.protocol === 'openai' ? <Field label={t('videoSupport')} hint={t('videoSupportHint')}><select aria-label={t('videoSupport')} disabled={!snapshot.writable || busy} value={selectedProvider.videoSupport ? 'enabled' : 'disabled'} onChange={(event) => { updateProvider(selectedIndex, 'videoSupport', event.target.value === 'enabled') }}><option value="disabled">{t('disabled')}</option><option value="enabled">{t('enabled')}</option></select></Field> : null}
|
|
1806
1869
|
<Field label={t('t1')} hint={t('t1Hint')}><Input aria-label={t('t1')} inputMode="numeric" value={selectedProvider.t1Seconds} onChange={(event) => { updateProvider(selectedIndex, 't1Seconds', event.target.value) }} /></Field>
|
|
1807
1870
|
<Field label={t('t2')} hint={t('t2Hint')}><Input aria-label={t('t2')} inputMode="numeric" value={selectedProvider.t2Seconds} onChange={(event) => { updateProvider(selectedIndex, 't2Seconds', event.target.value) }} /></Field>
|
|
1808
1871
|
<Field label={t('maxBytes')}><Input aria-label={t('maxBytes')} inputMode="numeric" value={selectedProvider.maxImageBytes} onChange={(event) => { updateProvider(selectedIndex, 'maxImageBytes', event.target.value) }} /></Field>
|
|
@@ -1814,12 +1877,14 @@ function LoadedSettings({ controller, t }: SettingsInjected) {
|
|
|
1814
1877
|
<div className="dvt-provider-tests">
|
|
1815
1878
|
<Button size="sm" variant="outline" disabled={busy || !snapshot.runtime.ready} onClick={() => { void controller.runHealth('connection', selectedIndex) }}>{state.action === 'connection' ? t('testing') : t('testConnection')}</Button>
|
|
1816
1879
|
<Button size="sm" variant="primary" disabled={busy || !snapshot.runtime.ready} onClick={() => { void controller.runHealth('model', selectedIndex) }}>{state.action === 'model' ? t('testingModel') : t('testModel')}</Button>
|
|
1880
|
+
{selectedProvider.protocol === 'openai' ? <Button size="sm" variant="primary" disabled={busy || !snapshot.runtime.ready} onClick={() => { void controller.runVideoTest(selectedIndex) }}>{state.action === 'test-video' ? t('testingVideo') : t('testVideo')}</Button> : null}
|
|
1817
1881
|
</div>
|
|
1818
1882
|
{providerHealth === undefined ? null : <div className="dvt-provider-test-result">{(['service', 'model'] as const).map(name => {
|
|
1819
1883
|
const check = providerHealth.checks[name]
|
|
1820
1884
|
if (check === undefined) return null
|
|
1821
1885
|
return <div key={name} data-status={check.status}><span>{t(HEALTH_NAME_KEYS[name] ?? 'health')}</span><strong>{t(HEALTH_STATUS_KEYS[check.status])}</strong><p>{healthDetail(name, check.detail, t)}</p></div>
|
|
1822
1886
|
})}</div>}
|
|
1887
|
+
{state.videoTest === undefined ? null : <div className="dvt-provider-test-result"><div data-status="ok"><span>{t('videoSupport')}</span><strong>{t('statusOk')}</strong><p>{state.videoTest.detail}</p></div></div>}
|
|
1823
1888
|
</div>
|
|
1824
1889
|
)}
|
|
1825
1890
|
</section>
|
|
@@ -1887,6 +1952,12 @@ function LoadedSettings({ controller, t }: SettingsInjected) {
|
|
|
1887
1952
|
|
|
1888
1953
|
<section className="dvt-panel"><div className="dvt-panel-title"><h3>{t('imageInput')}</h3></div><label className="dvt-check"><input type="checkbox" checked={draft.hiddenVariants} disabled={!snapshot.writable || busy} onChange={(event) => { update('hiddenVariants', event.target.checked) }} /><span>{t('hiddenVariantsLabel')}</span><small>{t('hiddenVariantsHint')}</small></label></section>
|
|
1889
1954
|
|
|
1955
|
+
<section className="dvt-panel"><div className="dvt-panel-title"><div><h3>{t('toolVisibility')}</h3><p>{t('toolVisibilityHint')}</p></div></div>
|
|
1956
|
+
<label className="dvt-check"><input type="checkbox" checked={draft.toolVisibilityLocal} disabled={!snapshot.writable || busy} onChange={(event) => { update('toolVisibilityLocal', event.target.checked) }} /><span>{t('toolVisibilityLocal')}</span><small>{t('toolVisibilityLocalHint')}</small></label>
|
|
1957
|
+
<label className="dvt-check"><input type="checkbox" checked={draft.toolVisibilityOnline} disabled={!snapshot.writable || busy} onChange={(event) => { update('toolVisibilityOnline', event.target.checked) }} /><span>{t('toolVisibilityOnline')}</span><small>{t('toolVisibilityOnlineHint')}</small></label>
|
|
1958
|
+
<label className="dvt-check"><input type="checkbox" checked={draft.toolVisibilityVideo} disabled={!snapshot.writable || busy} onChange={(event) => { update('toolVisibilityVideo', event.target.checked) }} /><span>{t('toolVisibilityVideo')} <em>(测试)</em></span><small>{t('toolVisibilityVideoHint')}</small></label>
|
|
1959
|
+
</section>
|
|
1960
|
+
|
|
1890
1961
|
<section className="dvt-panel"><div className="dvt-panel-title"><h3>{t('storage')}</h3></div><div className="dvt-form-grid">
|
|
1891
1962
|
<Field label={t('storageDir')} hint={t('storageDirHint')}><Input aria-label={t('storageDir')} placeholder="/tmp/dsh-vision-toolkit" value={draft.storageDir} onChange={(event) => { update('storageDir', event.target.value) }} /></Field>
|
|
1892
1963
|
<Field label={t('allowedDirs')} hint={t('allowedDirsHint')}><textarea rows={3} value={draft.allowedDirs} onChange={(event) => { update('allowedDirs', event.target.value) }} /></Field>
|
package/src/config.ts
CHANGED
|
@@ -81,6 +81,8 @@ export interface VisionProviderConfig {
|
|
|
81
81
|
stream?: boolean
|
|
82
82
|
/** Whether to upload images to object storage and send the model a URL instead of base64 (default false). */
|
|
83
83
|
uploadViaUrl?: boolean
|
|
84
|
+
/** OpenAI-compatible video understanding (default false); Anthropic providers ignore this placeholder flag. */
|
|
85
|
+
videoSupport?: boolean
|
|
84
86
|
/** t1: per-request hedge threshold in seconds. A single request exceeding t1 keeps running while the next provider starts in parallel. */
|
|
85
87
|
t1Seconds?: number
|
|
86
88
|
/** t2: per-provider cumulative cutoff in seconds. Total accumulated request time reaching t2 terminates the provider. */
|
|
@@ -114,6 +116,8 @@ export interface VisionToolkitConfig {
|
|
|
114
116
|
stream?: boolean
|
|
115
117
|
/** Whether to upload images to object storage and send the model a URL instead of base64 (default false). */
|
|
116
118
|
uploadViaUrl?: boolean
|
|
119
|
+
/** OpenAI-compatible video understanding (default false); Anthropic providers ignore this placeholder flag. */
|
|
120
|
+
videoSupport?: boolean
|
|
117
121
|
}
|
|
118
122
|
/** Ordered online vision providers; array order is the failover priority. */
|
|
119
123
|
providers?: VisionProviderConfig[]
|
|
@@ -194,6 +198,21 @@ export interface VisionToolkitConfig {
|
|
|
194
198
|
*/
|
|
195
199
|
hidden?: boolean
|
|
196
200
|
}
|
|
201
|
+
/**
|
|
202
|
+
* Per-group tool visibility, applied when each Agent's visual tool set is
|
|
203
|
+
* materialized (a session-head snapshot: an already-active Agent keeps its
|
|
204
|
+
* activation-time set; a change only affects the next Agent's tool set).
|
|
205
|
+
* Tools are grouped into three buckets; a bucket that is off contributes none
|
|
206
|
+
* of its tools to an Agent's visible surface.
|
|
207
|
+
*/
|
|
208
|
+
toolVisibility?: {
|
|
209
|
+
/** Local-processing tools (no on-line fan-out, no concurrency charge). Default true. */
|
|
210
|
+
local?: boolean
|
|
211
|
+
/** On-line image tools plus the concurrency/status probe. Default true. */
|
|
212
|
+
online?: boolean
|
|
213
|
+
/** Video-understanding tool (experimental). Default false. */
|
|
214
|
+
video?: boolean
|
|
215
|
+
}
|
|
197
216
|
}
|
|
198
217
|
|
|
199
218
|
/** Configuration schema with the documented P0 defaults. */
|
|
@@ -206,6 +225,8 @@ export const Config: Schema<VisionToolkitConfig> = z.object({
|
|
|
206
225
|
anthropicThinking: z.union(['omit', 'disabled', 'adaptive'] as const).default('omit'),
|
|
207
226
|
userAgent: z.string().default(DEFAULT_VISION_USER_AGENT),
|
|
208
227
|
stream: z.boolean().default(false),
|
|
228
|
+
uploadViaUrl: z.boolean().default(false),
|
|
229
|
+
videoSupport: z.boolean().default(false),
|
|
209
230
|
}),
|
|
210
231
|
providers: z.array(z.object({
|
|
211
232
|
name: z.string(),
|
|
@@ -219,6 +240,7 @@ export const Config: Schema<VisionToolkitConfig> = z.object({
|
|
|
219
240
|
userAgent: z.string(),
|
|
220
241
|
stream: z.boolean().default(false),
|
|
221
242
|
uploadViaUrl: z.boolean().default(false),
|
|
243
|
+
videoSupport: z.boolean().default(false),
|
|
222
244
|
t1Seconds: z.number(),
|
|
223
245
|
t2Seconds: z.number(),
|
|
224
246
|
maxImageBytes: z.number(),
|
|
@@ -253,6 +275,11 @@ export const Config: Schema<VisionToolkitConfig> = z.object({
|
|
|
253
275
|
autoSwitch: z.boolean().default(true),
|
|
254
276
|
hidden: z.boolean().default(true),
|
|
255
277
|
}),
|
|
278
|
+
toolVisibility: z.object({
|
|
279
|
+
local: z.boolean().default(true),
|
|
280
|
+
online: z.boolean().default(true),
|
|
281
|
+
video: z.boolean().default(false),
|
|
282
|
+
}),
|
|
256
283
|
})
|
|
257
284
|
|
|
258
285
|
/** One resolved online vision provider, with every default materialized. */
|
|
@@ -269,6 +296,7 @@ export interface ResolvedProvider {
|
|
|
269
296
|
userAgent: string
|
|
270
297
|
stream: boolean
|
|
271
298
|
uploadViaUrl: boolean
|
|
299
|
+
videoSupport: boolean
|
|
272
300
|
t1Seconds: number
|
|
273
301
|
t2Seconds: number
|
|
274
302
|
maxImageBytes: number
|
|
@@ -288,6 +316,7 @@ export interface ResolvedVisionToolkitConfig {
|
|
|
288
316
|
userAgent: string
|
|
289
317
|
stream: boolean
|
|
290
318
|
uploadViaUrl: boolean
|
|
319
|
+
videoSupport: boolean
|
|
291
320
|
}
|
|
292
321
|
/** Ordered failover pool; array order is the priority, highest first. */
|
|
293
322
|
providers: ResolvedProvider[]
|
|
@@ -318,6 +347,11 @@ export interface ResolvedVisionToolkitConfig {
|
|
|
318
347
|
autoSwitch: boolean
|
|
319
348
|
hidden: boolean
|
|
320
349
|
}
|
|
350
|
+
toolVisibility: {
|
|
351
|
+
local: boolean
|
|
352
|
+
online: boolean
|
|
353
|
+
video: boolean
|
|
354
|
+
}
|
|
321
355
|
}
|
|
322
356
|
|
|
323
357
|
const MAX_TIMEOUT_SECONDS = 600
|
|
@@ -410,6 +444,7 @@ function resolveProvider(
|
|
|
410
444
|
}
|
|
411
445
|
const stream = input.stream === true
|
|
412
446
|
const uploadViaUrl = input.uploadViaUrl === true
|
|
447
|
+
const videoSupport = input.videoSupport === true
|
|
413
448
|
const t1Seconds = input.t1Seconds ?? 90
|
|
414
449
|
if (!Number.isInteger(t1Seconds) || t1Seconds < 1 || t1Seconds > MAX_TIMEOUT_SECONDS) {
|
|
415
450
|
throw new VisionToolkitError('config', `${label}.t1Seconds must be an integer between 1 and ${MAX_TIMEOUT_SECONDS}`)
|
|
@@ -448,6 +483,7 @@ function resolveProvider(
|
|
|
448
483
|
userAgent,
|
|
449
484
|
stream,
|
|
450
485
|
uploadViaUrl,
|
|
486
|
+
videoSupport,
|
|
451
487
|
t1Seconds,
|
|
452
488
|
t2Seconds,
|
|
453
489
|
maxImageBytes,
|
|
@@ -535,6 +571,7 @@ export function resolveConfig(config: VisionToolkitConfig = {}): ResolvedVisionT
|
|
|
535
571
|
const variantProviders = (imageInputVariants.providers ?? [])
|
|
536
572
|
.map(provider => provider.trim())
|
|
537
573
|
.filter(provider => provider.length > 0)
|
|
574
|
+
const toolVisibility = config.toolVisibility ?? {}
|
|
538
575
|
const providerDefaults: ProviderDefaults = { maxImageBytes, maxImagePixels, concurrency }
|
|
539
576
|
const configuredProviders = config.providers ?? []
|
|
540
577
|
if (configuredProviders.length > MAX_PROVIDERS) {
|
|
@@ -555,6 +592,7 @@ export function resolveConfig(config: VisionToolkitConfig = {}): ResolvedVisionT
|
|
|
555
592
|
userAgent: primary.userAgent,
|
|
556
593
|
stream: primary.stream,
|
|
557
594
|
uploadViaUrl: primary.uploadViaUrl,
|
|
595
|
+
videoSupport: primary.videoSupport,
|
|
558
596
|
},
|
|
559
597
|
providers,
|
|
560
598
|
language,
|
|
@@ -584,6 +622,11 @@ export function resolveConfig(config: VisionToolkitConfig = {}): ResolvedVisionT
|
|
|
584
622
|
autoSwitch: imageInputVariants.autoSwitch ?? true,
|
|
585
623
|
hidden: imageInputVariants.hidden ?? true,
|
|
586
624
|
},
|
|
625
|
+
toolVisibility: {
|
|
626
|
+
local: toolVisibility.local ?? true,
|
|
627
|
+
online: toolVisibility.online ?? true,
|
|
628
|
+
video: toolVisibility.video ?? false,
|
|
629
|
+
},
|
|
587
630
|
}
|
|
588
631
|
}
|
|
589
632
|
|
package/src/exposure.ts
CHANGED
|
@@ -12,7 +12,7 @@ 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'
|
|
15
|
-
import { VISION_TOOL_NAMES } from './tools.ts'
|
|
15
|
+
import { VISION_TOOL_NAMES, type ToolVisibility } from './tools.ts'
|
|
16
16
|
|
|
17
17
|
/** Small bootstrap tool retained only until the current Agent gains visual tools. */
|
|
18
18
|
export const VISION_TOOLKIT_ACTIVATE = 'vision_toolkit_activate'
|
|
@@ -139,17 +139,21 @@ export class VisionToolExposure {
|
|
|
139
139
|
|
|
140
140
|
/**
|
|
141
141
|
* @param ctx - Plugin context with Tool and Agent registries.
|
|
142
|
-
* @param createTools - Fresh definitions bound to the current runtime
|
|
142
|
+
* @param createTools - Fresh definitions bound to the current runtime
|
|
143
|
+
* generation, filtered by a tool-visibility snapshot.
|
|
144
|
+
* @param resolveVisibility - Live resolver for the current tool-visibility
|
|
145
|
+
* snapshot; read once per activation (a session-head snapshot).
|
|
143
146
|
*/
|
|
144
147
|
constructor(
|
|
145
148
|
private readonly ctx: Context,
|
|
146
|
-
private readonly createTools: () => ToolDefinition[],
|
|
149
|
+
private readonly createTools: (snapshot: ToolVisibility) => ToolDefinition[],
|
|
150
|
+
private readonly resolveVisibility: () => ToolVisibility = () => ({ local: true, online: true, video: false }),
|
|
147
151
|
) {
|
|
148
152
|
this.activationTool = defineTool({
|
|
149
153
|
name: VISION_TOOLKIT_ACTIVATE,
|
|
150
|
-
description: `
|
|
151
|
-
+ `
|
|
152
|
-
+ 'It
|
|
154
|
+
description: `Report and (re)mount the Vision Toolkit execution tools for this Agent: the currently visible subset of ${Object.values(VISION_TOOL_NAMES).join(', ')} plus optional video understanding. `
|
|
155
|
+
+ `The visual tool set is normally mounted automatically when the ${VISION_SKILLS_NAME} Skill loads; call this ONLY when the user explicitly asks you to refresh or reload the current session's vision tools, or to list which vision tools are currently available. `
|
|
156
|
+
+ 'It restores the tool set to the latest Settings snapshot and returns the tool names actually mounted.',
|
|
153
157
|
parameters: {},
|
|
154
158
|
output: {
|
|
155
159
|
schema: {
|
|
@@ -166,9 +170,12 @@ export class VisionToolExposure {
|
|
|
166
170
|
if (exec.agent === undefined) {
|
|
167
171
|
throw new Error(`${VISION_TOOLKIT_ACTIVATE}: an Agent Session is required`)
|
|
168
172
|
}
|
|
169
|
-
|
|
173
|
+
// An already-active Agent is reloaded (re-reads the latest Settings
|
|
174
|
+
// snapshot) rather than returning the stale set, so the tool genuinely
|
|
175
|
+
// refreshes the visible tool list on an explicit user request.
|
|
176
|
+
return Promise.resolve(this.reload(exec.agent))
|
|
170
177
|
},
|
|
171
|
-
presentCall: () => ({ card: 'generic', title: '
|
|
178
|
+
presentCall: () => ({ card: 'generic', title: 'Refresh or list vision tools', kind: 'execute' }),
|
|
172
179
|
})
|
|
173
180
|
}
|
|
174
181
|
|
|
@@ -222,7 +229,7 @@ export class VisionToolExposure {
|
|
|
222
229
|
if (state === undefined) throw new Error(`dsh-vision-toolkit: Agent ${String(agent.id)} has no exposure state`)
|
|
223
230
|
if (state.active) return { activated: false, tools: [...state.toolNames] }
|
|
224
231
|
|
|
225
|
-
const definitions = this.createTools()
|
|
232
|
+
const definitions = this.createTools(this.resolveVisibility())
|
|
226
233
|
const toolDisposers: Array<() => void> = []
|
|
227
234
|
try {
|
|
228
235
|
for (const definition of definitions) toolDisposers.push(agent.ctx.tools.register(definition))
|
|
@@ -241,6 +248,24 @@ export class VisionToolExposure {
|
|
|
241
248
|
return { activated: true, tools: [...state.toolNames] }
|
|
242
249
|
}
|
|
243
250
|
|
|
251
|
+
/**
|
|
252
|
+
* Explicit refresh/reload from the bootstrap tool. An inactive Agent is
|
|
253
|
+
* mounted; an active Agent is torn down and re-mounted from the *current*
|
|
254
|
+
* Settings snapshot, so the returned tool list reflects the latest
|
|
255
|
+
* `toolVisibility` instead of the stale activation-time set.
|
|
256
|
+
*/
|
|
257
|
+
private reload(agent: Agent): VisionToolkitActivationResult {
|
|
258
|
+
this.attach(agent)
|
|
259
|
+
const state = this.states.get(agent)
|
|
260
|
+
if (state === undefined) return { activated: false, tools: [] }
|
|
261
|
+
if (!state.active) return this.activate(agent)
|
|
262
|
+
// Dispose the current tool generation (including the hide-restriction on
|
|
263
|
+
// the bootstrap gauntlet) and rebuild from the fresh snapshot.
|
|
264
|
+
this.disposeState(state)
|
|
265
|
+
this.states.set(agent, { active: false, toolDisposers: [], toolNames: [] })
|
|
266
|
+
return this.activate(agent)
|
|
267
|
+
}
|
|
268
|
+
|
|
244
269
|
/** Whether the session is attached to the live SessionStore (production). */
|
|
245
270
|
private isLiveSession(session: Session): boolean {
|
|
246
271
|
return this.ctx.sessions.get(session.id) === session
|
package/src/index.ts
CHANGED
|
@@ -83,11 +83,16 @@ export async function apply(ctx: Context, config: VisionToolkitConfig = {}): Pro
|
|
|
83
83
|
|
|
84
84
|
const ensureOperational = (): void => {
|
|
85
85
|
if (!manager.ready || operationalDisposers !== undefined) return
|
|
86
|
-
const exposure = new VisionToolExposure(
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
86
|
+
const exposure = new VisionToolExposure(
|
|
87
|
+
ctx,
|
|
88
|
+
(visibility) => createVisionTools(
|
|
89
|
+
() => manager.current(),
|
|
90
|
+
value => artifacts.presentationMeta(value),
|
|
91
|
+
lifecycle.signal,
|
|
92
|
+
visibility,
|
|
93
|
+
),
|
|
94
|
+
() => manager.currentConfig().toolVisibility,
|
|
95
|
+
)
|
|
91
96
|
let activationTool: (() => void) | undefined
|
|
92
97
|
let exposureDisposer: (() => void) | undefined
|
|
93
98
|
let skill: (() => void) | undefined
|
package/src/paths.ts
CHANGED
|
@@ -16,6 +16,12 @@ import { VisionToolkitError } from './errors.ts'
|
|
|
16
16
|
/** Supported input image extensions (the upstream client's allowlist). */
|
|
17
17
|
export const SUPPORTED_IMAGE_EXTENSIONS = ['.png', '.jpg', '.jpeg', '.gif', '.webp'] as const
|
|
18
18
|
|
|
19
|
+
/** Supported input video extensions probed with the bundled ffprobe binary. */
|
|
20
|
+
export const SUPPORTED_VIDEO_EXTENSIONS = [
|
|
21
|
+
'.mp4', '.mov', '.webm', '.mkv', '.avi', '.m4v',
|
|
22
|
+
'.mpg', '.mpeg', '.wmv', '.flv', '.ts', '.m2ts', '.3gp',
|
|
23
|
+
] as const
|
|
24
|
+
|
|
19
25
|
/** Resolved path policy for one tool invocation. */
|
|
20
26
|
export interface PathPolicy {
|
|
21
27
|
/** Real workspace root. */
|
|
@@ -399,6 +405,11 @@ export function resolveHtmlFile(raw: string, policy: PathPolicy): Promise<{ path
|
|
|
399
405
|
return resolveAuthorizedFile(raw, policy, ['.html', '.htm'], 'HTML source')
|
|
400
406
|
}
|
|
401
407
|
|
|
408
|
+
/** Validate one input video path against the video extension allowlist. */
|
|
409
|
+
export function resolveInputVideo(raw: string, policy: PathPolicy): Promise<{ path: string; bytes: number }> {
|
|
410
|
+
return resolveAuthorizedFile(raw, policy, SUPPORTED_VIDEO_EXTENSIONS, 'video')
|
|
411
|
+
}
|
|
412
|
+
|
|
402
413
|
/**
|
|
403
414
|
* Resolve an optional user-supplied output filename inside the plugin output
|
|
404
415
|
* directory. Absolute paths, `..` segments, and wrong extensions are rejected.
|