Files
goodbuddy/src/main/deepseek-harness-host.test.ts
T
mesalogo b751c70d75 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 专属选择器移到独立一行,并固定上下文压缩入口。
2026-08-16 23:30:54 +08:00

388 lines
11 KiB
TypeScript

import { describe, expect, it } from 'vitest'
import {
createBoundedNdJsonStream,
installHarnessDiagnosticGuard,
startControlledDeepSeekHarnessHost
} from './deepseek-harness-host'
import { vi } from 'vitest'
import { mkdir, mkdtemp, realpath, writeFile } from 'node:fs/promises'
import type {
Agent,
CreateAgentOptions
} from '@deepseek-ai/dsh-agent'
import { GOODBUDDY_HARNESS_MAX_STEP_TOKENS } from './agent/goodbuddy-harness-control-plane'
import { GoodBuddyHarnessAttachmentStore } from './agent/goodbuddy-harness-attachment-store'
import { tmpdir } from 'node:os'
import { basename, join } from 'node:path'
async function readAllMessages(
readable: ReadableStream<unknown>
): Promise<unknown[]> {
const values: unknown[] = []
for await (const value of readable) {
values.push(value)
}
return values
}
describe('controlled DeepSeek Harness host', () => {
it('rejects unsupported endpoint protocols before Cordis starts', async () => {
await expect(
startControlledDeepSeekHarnessHost({
workspace: 'C:\\workspace',
baseUrl: 'file:///private/config',
api: 'openai-completions',
provider: 'goodbuddy',
model: 'deepseek-test',
harnessVersion: '0.1.0-rc.6',
credentialRefs: ['GOODBUDDY_API_KEY'],
dshHome: 'C:\\controlled-dsh-home',
skillPackages: []
})
).rejects.toThrow('secure OpenAI-compatible')
})
it('suppresses console payloads instead of contaminating stdout', () => {
const restore = installHarnessDiagnosticGuard()
const originalWrite = process.stderr.write
const writes: string[] = []
process.stderr.write = ((value: string | Uint8Array) => {
writes.push(String(value))
return true
}) as typeof process.stderr.write
try {
console.log('prompt and secret must not reach protocol stdout')
expect(writes.join('')).toBe(
'DeepSeek Harness diagnostic suppressed\n'
)
expect(writes.join('')).not.toContain('secret')
} finally {
process.stderr.write = originalWrite
restore()
}
})
it('starts the controlled host with local execution providers', async () => {
const root = await realpath(
await mkdtemp(join(tmpdir(), 'goodbuddy-harness-host-'))
)
const inbound = new TransformStream<
Record<string, unknown>,
Record<string, unknown>
>()
const outbound = new TransformStream<
Record<string, unknown>,
Record<string, unknown>
>()
const host = await startControlledDeepSeekHarnessHost({
workspace: root,
dshHome: root,
baseUrl: 'https://gateway.example/openai/v1',
api: 'openai-completions',
provider: 'goodbuddy',
model: 'qwen-plus',
supportsImageInput: true,
harnessVersion: '0.1.0-rc.6',
credentialRefs: ['GOODBUDDY_API_KEY'],
skillPackages: [],
stream: {
readable: inbound.readable,
writable: outbound.writable
} as never
})
expect(host.context.fs.sandboxMode).toBeUndefined()
expect(host.context.shell.sandboxMode).toBeUndefined()
expect(host.context.get('attachments')).toBeInstanceOf(
GoodBuddyHarnessAttachmentStore
)
expect(
host.context.shell.resolve({
command: 'echo goodbuddy-host-execution'
}).workdir
).toBe(root)
const execution = await host.context.shell.run(
host.context.shell.resolve({
command:
process.platform === 'win32'
? 'Write-Output goodbuddy-host-execution'
: 'printf goodbuddy-host-execution'
})
)
expect(execution).toMatchObject({
exitCode: 0,
timedOut: false,
aborted: false
})
expect(execution.stdout.text).toContain(
'goodbuddy-host-execution'
)
await host.dispose()
})
it('canonicalizes workspace aliases before binding the host', async () => {
const root = await realpath(
await mkdtemp(join(tmpdir(), 'goodbuddy-harness-alias-'))
)
const alias = join(root, '..', basename(root))
const inbound = new TransformStream<
Record<string, unknown>,
Record<string, unknown>
>()
const outbound = new TransformStream<
Record<string, unknown>,
Record<string, unknown>
>()
const host = await startControlledDeepSeekHarnessHost({
workspace: alias,
dshHome: alias,
baseUrl: 'https://api.deepseek.com',
api: 'openai-completions',
provider: 'goodbuddy',
model: 'deepseek-test',
harnessVersion: '0.1.0-rc.6',
credentialRefs: ['GOODBUDDY_API_KEY'],
skillPackages: [],
stream: {
readable: inbound.readable,
writable: outbound.writable
} as never
})
await host.dispose()
})
it('reports extension startup failures without failing the Host', async () => {
const root = await realpath(
await mkdtemp(join(tmpdir(), 'goodbuddy-harness-extensions-'))
)
const brokenEntrypoint = join(root, 'broken.mjs')
await writeFile(
brokenEntrypoint,
'export const value = 1\n',
'utf8'
)
const inbound = new TransformStream<
Record<string, unknown>,
Record<string, unknown>
>()
const outbound = new TransformStream<
Record<string, unknown>,
Record<string, unknown>
>()
const host = await startControlledDeepSeekHarnessHost({
workspace: root,
dshHome: root,
baseUrl: 'https://api.deepseek.com',
api: 'openai-completions',
provider: 'goodbuddy',
model: 'deepseek-test',
harnessVersion: '0.1.0-rc.6',
credentialRefs: ['GOODBUDDY_API_KEY'],
skillPackages: [],
extensionPackages: [
{
id: 'broken',
entrypoint: brokenEntrypoint,
configuration: {}
}
],
stream: {
readable: inbound.readable,
writable: outbound.writable
} as never
})
expect(host.failedExtensionIds).toEqual(['broken'])
await host.dispose()
})
it('loads only explicitly supplied Skill packages into a session scope', async () => {
const root = await realpath(
await mkdtemp(join(tmpdir(), 'goodbuddy-harness-skill-'))
)
const skillDirectory = join(root, 'web-3d-game')
await mkdir(skillDirectory)
await writeFile(
join(skillDirectory, 'SKILL.md'),
[
'---',
'name: web-3d-game',
'description: Build a playable browser 3D game.',
'---',
'',
'# Web 3D game',
'',
'Create and validate a playable project.'
].join('\n'),
'utf8'
)
const inbound = new TransformStream<
Record<string, unknown>,
Record<string, unknown>
>()
const outbound = new TransformStream<
Record<string, unknown>,
Record<string, unknown>
>()
const host = await startControlledDeepSeekHarnessHost({
workspace: root,
dshHome: root,
baseUrl: 'https://api.deepseek.com',
api: 'openai-completions',
provider: 'goodbuddy',
model: 'deepseek-test',
harnessVersion: '0.1.0-rc.6',
credentialRefs: ['GOODBUDDY_API_KEY'],
skillPackages: [
{ id: 'web-3d-game', directory: skillDirectory }
],
stream: {
readable: inbound.readable,
writable: outbound.writable
} as never
})
let createdContext: typeof host.context | undefined
let createdAgent: Agent | undefined
const create = vi
.spyOn(host.context.agents, 'create')
.mockImplementation(async (options: CreateAgentOptions) => {
const agentContext = host.context.extend({
isolate: ['skills', 'tools']
})
createdContext = agentContext
await options.setup?.(agentContext)
const agent = {
options: options.agentOptions ?? {},
session: {
id: options.sessionId,
header: { cwd: options.meta?.cwd ?? root },
events: [],
append: vi.fn()
},
ctx: agentContext,
cancel: vi.fn()
}
createdAgent = agent as never
return {
agent,
dispose: async () => {
await agentContext.fiber.dispose()
}
} as never
})
const api = (
host.controlPlane as unknown as {
createAgentApi(): {
newSession(params: {
cwd: string
mcpServers: never[]
}): Promise<{ sessionId: string }>
}
}
).createAgentApi()
const session = await api.newSession({
cwd: root,
mcpServers: []
})
expect(session.sessionId).toBeTruthy()
expect(
(
await createdContext!.skills.list({
cwd: root,
scope: createdAgent
})
).map((skill) => skill.name)
).toEqual(['web-3d-game'])
expect(
createdContext!.tools
.schemas(createdAgent)
.map((tool) => tool.name)
).toContain('skill')
const loadedSkill = await createdContext!.tools.execute({
callId: 'skill-call',
name: 'skill',
arguments: { name: 'web-3d-game' },
agent: createdAgent,
signal: new AbortController().signal
} as never)
expect(loadedSkill).toMatchObject({
isError: false,
value: {
name: 'web-3d-game',
content: expect.stringContaining(
'Create and validate a playable project.'
)
}
})
expect(create).toHaveBeenCalledTimes(1)
expect(create).toHaveBeenCalledWith(
expect.objectContaining({
agentOptions: {
provider: 'goodbuddy',
model: 'deepseek-test',
maxTokens: GOODBUDDY_HARNESS_MAX_STEP_TOKENS
}
})
)
const assembly = await createdContext!.systemPrompt.assemble({
agent: createdAgent,
scope: createdAgent
})
expect(
assembly.sections.find(
(section) =>
section.name === 'goodbuddy:controlled-execution'
)?.text
).toContain('create or update the requested workspace files promptly')
await host.dispose()
})
it('frames fragmented and coalesced ACP messages individually', async () => {
const inbound = new TransformStream<Uint8Array, Uint8Array>()
const outbound = new TransformStream<Uint8Array, Uint8Array>()
const stream = createBoundedNdJsonStream(
outbound.writable,
inbound.readable,
24
)
const reading = readAllMessages(stream.readable)
const writer = inbound.writable.getWriter()
const encoder = new TextEncoder()
await writer.write(encoder.encode('{"text":"你'))
await writer.write(
encoder.encode('好"}\n{"value":"1234567890"}\n')
)
await writer.close()
await expect(reading).resolves.toEqual([
{ text: '你好' },
{ value: '1234567890' }
])
})
it('rejects oversized ACP frames at EOF in both directions', async () => {
const inbound = new TransformStream<Uint8Array, Uint8Array>()
const outbound = new TransformStream<Uint8Array, Uint8Array>()
const stream = createBoundedNdJsonStream(
outbound.writable,
inbound.readable,
8
)
const reading = readAllMessages(stream.readable)
const inputWriter = inbound.writable.getWriter()
await inputWriter.write(
new TextEncoder().encode('{"value":"too large"}')
)
await inputWriter.close()
await expect(reading).rejects.toThrow('input frame exceeds')
const outputWriter = stream.writable.getWriter()
await expect(
outputWriter.write({ value: 'too large' } as never)
).rejects.toThrow('output frame exceeds')
})
})