feat: add model-aware DSH image input
DeepSeek Harness previously treated every selected model as text-only. It now carries the model profile's image capability through Main, the Utility Host, ACP, and Pi-AI, rejects unsupported images before model invocation, and validates supported JPEG/PNG data in a bounded process-local attachment store. The Composer keeps universal controls on its first row, places OpenCode and Continue controls on a dedicated responsive row, and anchors context compaction without shifting the footer. Runtime supervision ownership for future Subagents and Jobs is documented for the right sidebar. Release note: DeepSeek Harness 现可按所选模型连接的能力安全接收 JPEG/PNG 图片;Composer 同时将 Runtime 专属选择器移到独立一行,并固定上下文压缩入口。
This commit is contained in:
@@ -0,0 +1,133 @@
|
||||
// @vitest-environment node
|
||||
import { Context } from '@deepseek-ai/cordis'
|
||||
import { createCanvas } from '@napi-rs/canvas'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { GoodBuddyHarnessAttachmentStore } from './goodbuddy-harness-attachment-store'
|
||||
|
||||
const canvas = createCanvas(1, 1)
|
||||
const transparentPng = canvas.toBuffer('image/png')
|
||||
const jpeg = canvas.toBuffer('image/jpeg')
|
||||
const secondPng = createCanvas(2, 1).toBuffer('image/png')
|
||||
|
||||
describe('GoodBuddy Harness attachment store', () => {
|
||||
it('decodes, stores, verifies, and releases inline images', async () => {
|
||||
const store = new GoodBuddyHarnessAttachmentStore(new Context())
|
||||
const input = {
|
||||
data: transparentPng,
|
||||
mediaType: 'image/png' as const,
|
||||
name: '..\\screenshots\\reference.png'
|
||||
}
|
||||
|
||||
const first = await store.saveImage(input)
|
||||
const second = await store.saveImage(input)
|
||||
|
||||
expect(first).toEqual(second)
|
||||
expect(first).toMatchObject({
|
||||
mediaType: 'image/png',
|
||||
bytes: transparentPng.byteLength,
|
||||
width: 1,
|
||||
height: 1,
|
||||
name: 'reference.png'
|
||||
})
|
||||
const stored = await store.readImage(first)
|
||||
expect(stored.ref).toBe(first)
|
||||
expect(Buffer.from(stored.data).equals(transparentPng)).toBe(true)
|
||||
|
||||
store.releaseImage(first)
|
||||
await expect(store.readImage(first)).resolves.toBeDefined()
|
||||
store.releaseImage(second)
|
||||
await expect(store.readImage(first)).rejects.toMatchObject({
|
||||
code: 'NOT_FOUND'
|
||||
})
|
||||
|
||||
const jpegRef = await store.saveImage({
|
||||
data: jpeg,
|
||||
mediaType: 'image/jpeg'
|
||||
})
|
||||
expect(jpegRef).toMatchObject({
|
||||
mediaType: 'image/jpeg',
|
||||
bytes: jpeg.byteLength,
|
||||
width: 1,
|
||||
height: 1
|
||||
})
|
||||
})
|
||||
|
||||
it('rejects mismatched, malformed, and over-capacity images', async () => {
|
||||
const store = new GoodBuddyHarnessAttachmentStore(new Context(), {
|
||||
maxStoredImages: 1
|
||||
})
|
||||
|
||||
await expect(
|
||||
store.saveImage({
|
||||
data: transparentPng,
|
||||
mediaType: 'image/jpeg'
|
||||
})
|
||||
).rejects.toMatchObject({ code: 'INVALID_IMAGE' })
|
||||
await expect(
|
||||
store.saveImage({
|
||||
data: Buffer.from([
|
||||
0x89, 0x50, 0x4e, 0x47,
|
||||
0x0d, 0x0a, 0x1a, 0x0a
|
||||
]),
|
||||
mediaType: 'image/png'
|
||||
})
|
||||
).rejects.toMatchObject({ code: 'INVALID_IMAGE' })
|
||||
const corruptPng = Buffer.from(transparentPng)
|
||||
corruptPng[corruptPng.length - 8] =
|
||||
(corruptPng[corruptPng.length - 8] ?? 0) ^ 1
|
||||
await expect(
|
||||
store.saveImage({
|
||||
data: corruptPng,
|
||||
mediaType: 'image/png'
|
||||
})
|
||||
).rejects.toMatchObject({ code: 'INVALID_IMAGE' })
|
||||
|
||||
await store.saveImage({
|
||||
data: transparentPng,
|
||||
mediaType: 'image/png'
|
||||
})
|
||||
await expect(
|
||||
store.saveImage({
|
||||
data: secondPng,
|
||||
mediaType: 'image/png'
|
||||
})
|
||||
).rejects.toMatchObject({ code: 'STORAGE_LIMIT' })
|
||||
})
|
||||
|
||||
it('does not retain a partial batch when capacity is exceeded', async () => {
|
||||
const store = new GoodBuddyHarnessAttachmentStore(new Context(), {
|
||||
maxStoredImages: 1
|
||||
})
|
||||
|
||||
await expect(
|
||||
store.saveImages([
|
||||
{ data: transparentPng, mediaType: 'image/png' },
|
||||
{ data: jpeg, mediaType: 'image/jpeg' }
|
||||
])
|
||||
).rejects.toMatchObject({ code: 'STORAGE_LIMIT' })
|
||||
await expect(
|
||||
store.saveImage({
|
||||
data: jpeg,
|
||||
mediaType: 'image/jpeg'
|
||||
})
|
||||
).resolves.toMatchObject({ mediaType: 'image/jpeg' })
|
||||
})
|
||||
|
||||
it('bounds aggregate decoded pixels before retaining a batch', async () => {
|
||||
const store = new GoodBuddyHarnessAttachmentStore(new Context(), {
|
||||
maxBatchImagePixels: 1
|
||||
})
|
||||
const input = {
|
||||
data: transparentPng,
|
||||
mediaType: 'image/png' as const
|
||||
}
|
||||
|
||||
await expect(
|
||||
store.saveImages([input, input])
|
||||
).rejects.toMatchObject({ code: 'INVALID_IMAGE' })
|
||||
await expect(store.saveImage(input)).resolves.toMatchObject({
|
||||
width: 1,
|
||||
height: 1
|
||||
})
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user