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:
mesalogo
2026-08-16 23:30:54 +08:00
parent 18263a6b23
commit b751c70d75
30 changed files with 1589 additions and 187 deletions
+33 -7
View File
@@ -19,6 +19,9 @@ import * as ToolBash from '@deepseek-ai/dsh-tool-bash'
import * as ToolFs from '@deepseek-ai/dsh-tool-fs'
import * as ToolPwsh from '@deepseek-ai/dsh-tool-pwsh'
import * as ShellEnv from '@deepseek-ai/dsh-shell-env'
import {
GoodBuddyHarnessAttachmentStore
} from './agent/goodbuddy-harness-attachment-store'
import {
GoodBuddyCredentialProvider,
GoodBuddyHarnessControlPlane,
@@ -31,8 +34,8 @@ import {
} from './agent/deepseek-harness-extension-loader'
import type { Stream } from '@agentclientprotocol/sdk'
import { isDeepSeekHarnessCompatibleBaseUrl } from '../shared/deepseek-harness-compatibility'
import { DEEPSEEK_HARNESS_MAX_FRAME_BYTES } from './agent/deepseek-harness-control-protocol'
const DEFAULT_MAX_FRAME_BYTES = 1024 * 1024
const MAX_DIAGNOSTIC_BYTES = 64 * 1024
export type ControlledHarnessHostConfig = Omit<
@@ -204,9 +207,10 @@ async function loadControlledSkills(
* Boots a fixed, programmatic Cordis graph. It never imports app-boot, a
* profile loader, settings-file, local credentials, persistence, telemetry,
* web, HMR, marketplace discovery, direct MCP clients, jobs, subagents,
* hooks, or workflow packages. The control plane registers Main-selected
* Skill snapshots, Main-mediated MCP tool proxies, and explicitly enabled
* extension entrypoints.
* hooks, or workflow packages. When the selected model declares image input,
* the graph adds only a bounded process-local attachment store. The control
* plane registers Main-selected Skill snapshots, Main-mediated MCP tool
* proxies, and explicitly enabled extension entrypoints.
*/
export async function startControlledDeepSeekHarnessHost(
input: ControlledHarnessHostConfig
@@ -219,6 +223,9 @@ export async function startControlledDeepSeekHarnessHost(
const specs: PluginSpec[] = [
{ plugin: LlmRuntime },
{ plugin: SessionStore },
...(config.supportsImageInput
? [{ plugin: GoodBuddyHarnessAttachmentStore }]
: []),
{ plugin: SkillRegistry },
{
plugin: SystemPrompt,
@@ -242,7 +249,14 @@ export async function startControlledDeepSeekHarnessHost(
apiKeyEnv: config.credentialRefs[0],
api: config.api,
baseURL: config.baseUrl,
models: [{ id: config.model, input: ['text'] }]
models: [
{
id: config.model,
input: config.supportsImageInput
? ['text', 'image']
: ['text']
}
]
}
}
}
@@ -310,13 +324,22 @@ export async function startControlledDeepSeekHarnessHost(
'Controlled Harness credential provider failed to start'
)
}
const attachmentStore = ctx.get('attachments')
if (
config.supportsImageInput &&
!(attachmentStore instanceof GoodBuddyHarnessAttachmentStore)
) {
throw new Error(
'Controlled Harness attachment store failed to start'
)
}
startupCode = 'HOST_CONTROL_PLANE_FAILED'
const rawStream =
config.stream ??
createBoundedNdJsonStream(
stdoutStream(),
stdinStream(),
config.maxFrameBytes ?? DEFAULT_MAX_FRAME_BYTES
config.maxFrameBytes ?? DEEPSEEK_HARNESS_MAX_FRAME_BYTES
)
const controlPlane = new GoodBuddyHarnessControlPlane(ctx, {
...config,
@@ -325,7 +348,7 @@ export async function startControlledDeepSeekHarnessHost(
execution: { mode: 'host' },
stream: createBoundedAcpStream(
rawStream,
config.maxFrameBytes ?? DEFAULT_MAX_FRAME_BYTES
config.maxFrameBytes ?? DEEPSEEK_HARNESS_MAX_FRAME_BYTES
)
})
controlPlane.bindCredentialProvider(credentialProvider)
@@ -337,6 +360,9 @@ export async function startControlledDeepSeekHarnessHost(
extensionFailures: extensions.failures,
async dispose() {
await controlPlane.dispose()
if (attachmentStore instanceof GoodBuddyHarnessAttachmentStore) {
attachmentStore.clear()
}
await ctx.fiber.dispose()
}
}