feat: expand DeepSeek Harness compatibility

This commit is contained in:
lofyer
2026-08-14 12:38:01 +08:00
parent 36e05d45fa
commit 45aeecb6dd
32 changed files with 554 additions and 193 deletions
+3 -3
View File
@@ -177,10 +177,10 @@ async function run() {
config: { config: {
workspace, workspace,
dshHome, dshHome,
baseUrl: 'https://api.deepseek.com', baseUrl: 'https://gateway.example/openai/v1',
api: 'openai-completions', api: 'openai-completions',
provider: 'goodbuddy', provider: 'goodbuddy',
model: 'deepseek-chat', model: 'qwen-plus',
harnessVersion: '0.1.0-rc.6', harnessVersion: '0.1.0-rc.6',
sandbox: { sandbox: {
provider: provider:
@@ -192,7 +192,7 @@ async function run() {
enforcement: enforcement:
process.platform === 'win32' ? 'partial' : 'full' process.platform === 'win32' ? 'partial' : 'full'
}, },
credentialRefs: ['GOODBUDDY_DEEPSEEK_API_KEY'], credentialRefs: ['GOODBUDDY_HARNESS_MODEL_API_KEY'],
skillPackages: [], skillPackages: [],
maxFrameBytes: 1024 * 1024 maxFrameBytes: 1024 * 1024
} }
+12 -8
View File
@@ -77,7 +77,7 @@ GoodBuddy 不急于把该 Runtime 包装成标准 DSH 插件,也不以进入
- 支持多会话、同会话串行、跨会话并行。 - 支持多会话、同会话串行、跨会话并行。
- 支持按请求取消、超时、会话释放和应用退出时完整回收。 - 支持按请求取消、超时、会话释放和应用退出时完整回收。
- 输出文本、推理、工具参数、工具结果、stderr 和协议队列全部有界。 - 输出文本、推理、工具参数、工具结果、stderr 和协议队列全部有界。
- 使用真实 DeepSeek 模型验证调用,而不在日志、测试产物或提交中暴露凭据。 - 使用真实 OpenAI 兼容 Chat Completions 模型验证调用,而不在日志、测试产物或提交中暴露凭据。
- 保留 Windows、macOS、Linux 的 x64 和 arm64 发布能力。 - 保留 Windows、macOS、Linux 的 x64 和 arm64 发布能力。
### 4.2 首版非目标 ### 4.2 首版非目标
@@ -153,7 +153,7 @@ Electron utilityProcess
└─ 最小工具集 └─ 最小工具集
│ HTTPS │ HTTPS
用户选择的 DeepSeek 兼容模型连接 用户选择的 OpenAI 兼容模型连接
``` ```
### 6.1 信任边界 ### 6.1 信任边界
@@ -369,7 +369,7 @@ Windows ACL 和旧 Linux Landlock 可能只报告 `partial`。界面和诊断必
- Agent、Session、LLM 和 Tool Registry 基础服务。 - Agent、Session、LLM 和 Tool Registry 基础服务。
- GoodBuddy Harness Control Plane。 - GoodBuddy Harness Control Plane。
- DeepSeek 兼容 LLM 适配器。 - OpenAI 兼容 Chat Completions LLM 适配器。
- Sandbox Policy 与平台 Sandbox Provider。 - Sandbox Policy 与平台 Sandbox Provider。
- 平台对应的受沙箱 Shell。 - 平台对应的受沙箱 Shell。
- 受沙箱 Filesystem。 - 受沙箱 Filesystem。
@@ -396,11 +396,13 @@ Windows ACL 和旧 Linux Landlock 可能只报告 `partial`。界面和诊断必
### 12.1 配置来源 ### 12.1 配置来源
DeepSeek Harness 首版只使用 GoodBuddy 模型连接: DeepSeek Harness 首版只使用符合下列边界的 GoodBuddy 模型连接:
- 协议必须是 `openai-chat-completions` - 协议必须是 `openai-chat-completions`
- 认证必须是 API Key。 - 认证必须是 API Key。
- 服务地址必须`https://api.deepseek.com`,且不得包含用户信息 - 公网服务地址必须使用 HTTPS`localhost``127.0.0.1``::1` 回环地址可以使用 HTTP
- 服务地址可以使用自定义主机、端口和部署路径,但不得包含用户名、密码、查询参数或片段。
- 模型名称不限制为 DeepSeek 品牌,由所选 OpenAI 兼容服务决定。
- 模型名称和服务地址由 Main 传入受控 Host。 - 模型名称和服务地址由 Main 传入受控 Host。
- API Key 继续保存在 GoodBuddy 加密设置中。 - API Key 继续保存在 GoodBuddy 加密设置中。
- 启动环境提供的部署连接只由 Main 自动解析,不在 Renderer 中显示为可选来源。 - 启动环境提供的部署连接只由 Main 自动解析,不在 Renderer 中显示为可选来源。
@@ -458,7 +460,7 @@ Runtime 的概览、模型配置和检测信息放在同一张详情卡中。当
```text ```text
Runtime GoodBuddy 内置 DeepSeek Harness Runtime GoodBuddy 内置 DeepSeek Harness
模型配置: 跟随 GoodBuddy · dsv4flashdeepseek-v4-flash 模型配置: 跟随 GoodBuddy · 企业网关(qwen-plus
状态: 已就绪 状态: 已就绪
路径: <受控 Host 路径> 路径: <受控 Host 路径>
版本: 0.1.0-rc.6 版本: 0.1.0-rc.6
@@ -528,6 +530,7 @@ Renderer 只接收脱敏状态。任何凭据、完整环境、启动参数或
- 确认这些模块不会被加载。 - 确认这些模块不会被加载。
- 评估它们带来的 audit 和体积风险。 - 评估它们带来的 audit 和体积风险。
- 在后续上游版本允许时改为最小包族。 - 在后续上游版本允许时改为最小包族。
- 确认 `tests/fixtures` 以及 Web3D 测试 Skill/MCP 不进入正式发布资源。
### 16.4 漏洞门禁 ### 16.4 漏洞门禁
@@ -549,6 +552,7 @@ Renderer 只接收脱敏状态。任何凭据、完整环境、启动参数或
- 平台原生 Sandbox/PTY 依赖架构正确。 - 平台原生 Sandbox/PTY 依赖架构正确。
- Harness、ACP SDK 和其他新增第三方许可证已打包。 - Harness、ACP SDK 和其他新增第三方许可证已打包。
- `app.asar` 外需要执行或动态加载的资源位于预期目录。 - `app.asar` 外需要执行或动态加载的资源位于预期目录。
- Web3D Skill/MCP 等测试 fixture 不在 `app.asar``extraResources` 中。
## 17. 测试策略 ## 17. 测试策略
@@ -582,7 +586,7 @@ Renderer 只接收脱敏状态。任何凭据、完整环境、启动参数或
### 17.3 真实模型测试 ### 17.3 真实模型测试
真实测试已经获得用户授权,但必须由显式环境门禁启用。至少验证: 真实测试已经获得用户授权,但必须由显式环境门禁启用。Web3D Skill 和 MCP 仅作为 `tests/fixtures` 下的测试资产使用,不属于内置发布能力。至少验证:
1. 文本问答成功,并记录正确 Runtime 和模型用量。 1. 文本问答成功,并记录正确 Runtime 和模型用量。
2. Ask 可以读取工作区,但写入被拒绝,且不会弹出权限对话框。 2. Ask 可以读取工作区,但写入被拒绝,且不会弹出权限对话框。
@@ -625,7 +629,7 @@ npm run build
- 用户 DSH 配置、`.env`、遥测和 Web UI 未被加载。 - 用户 DSH 配置、`.env`、遥测和 Web UI 未被加载。
- API Key 不进入 Renderer、配置文件、日志、错误文本或测试产物。 - API Key 不进入 Renderer、配置文件、日志、错误文本或测试产物。
- 全量测试、类型检查、Lint 和生产构建通过。 - 全量测试、类型检查、Lint 和生产构建通过。
- 真实 DeepSeek 请求成功。 - 真实 OpenAI 兼容 Chat Completions 请求成功。
- 真实请求调用已分配 Skill 和 MCP,并生成、启动和实际游玩一个可用的 3D 游戏项目。 - 真实请求调用已分配 Skill 和 MCP,并生成、启动和实际游玩一个可用的 3D 游戏项目。
- 新增第三方许可证和发布校验完整。 - 新增第三方许可证和发布校验完整。
+28 -1
View File
@@ -99,10 +99,37 @@ describe('createAgentRuntime model compatibility', () => {
{ deepseekHarnessLauncher: vi.fn() } { deepseekHarnessLauncher: vi.fn() }
) )
).toThrow( ).toThrow(
'DeepSeek Harness 需要 api.deepseek.com 的 OpenAI Chat Completions 模型连接' 'DeepSeek Harness 需要使用 API Key 的安全 OpenAI 兼容 Chat Completions 模型连接'
) )
}) })
it('creates DeepSeek Harness with a compatible HTTPS gateway profile', async () => {
const profile = {
id: '00000000-0000-4000-8000-000000000006',
name: 'OpenAI-compatible gateway',
baseUrl: 'https://gateway.example/openai/v1',
modelName: 'qwen-plus',
protocol: 'openai-chat-completions' as const,
authentication: 'api-key' as const,
imageGenerationQuality: 'auto' as const,
apiKey: 'gateway-key'
}
const runtime = createAgentRuntime(
process.cwd(),
settings({
provider: 'deepseek-harness',
modelProfiles: [profile],
defaultModelProfileId: profile.id,
deepseekHarnessModelProfile: profile,
runtimeSandboxMode: 'auto'
}),
{ deepseekHarnessLauncher: vi.fn() }
)
expect(runtime.runtimeId).toBe('deepseek-harness')
await runtime.dispose()
})
it('creates an available direct runtime for a no-auth model', async () => { it('creates an available direct runtime for a no-auth model', async () => {
const runtime = createAgentRuntime(process.cwd(), settings()) const runtime = createAgentRuntime(process.cwd(), settings())
+2 -2
View File
@@ -113,7 +113,7 @@ export function createAgentRuntime(
const profile = settings?.deepseekHarnessModelProfile const profile = settings?.deepseekHarnessModelProfile
if (!profile || !isDeepSeekHarnessModelProfile(profile)) { if (!profile || !isDeepSeekHarnessModelProfile(profile)) {
throw new Error( throw new Error(
'DeepSeek Harness 需要 api.deepseek.com 的 OpenAI Chat Completions 模型连接' 'DeepSeek Harness 需要使用 API Key 的安全 OpenAI 兼容 Chat Completions 模型连接'
) )
} }
if (!profile.apiKey) { if (!profile.apiKey) {
@@ -131,7 +131,7 @@ export function createAgentRuntime(
model: profile.modelName, model: profile.modelName,
launch: capabilities.deepseekHarnessLauncher, launch: capabilities.deepseekHarnessLauncher,
credentialRefs: { credentialRefs: {
GOODBUDDY_DEEPSEEK_API_KEY: profile.apiKey GOODBUDDY_HARNESS_MODEL_API_KEY: profile.apiKey
}, },
requiredSandboxEnforcement: requiredSandboxEnforcement:
sandboxMode === 'strict' ? 'full' : 'partial', sandboxMode === 'strict' ? 'full' : 'partial',
@@ -26,7 +26,7 @@ import {
import { GOODBUDDY_HARNESS_MAX_STEP_TOKENS } from './goodbuddy-harness-control-plane' import { GOODBUDDY_HARNESS_MAX_STEP_TOKENS } from './goodbuddy-harness-control-plane'
const MAX_FRAME_BYTES = 1024 * 1024 const MAX_FRAME_BYTES = 1024 * 1024
const CREDENTIAL_REF = 'GOODBUDDY_DEEPSEEK_API_KEY' const CREDENTIAL_REF = 'GOODBUDDY_HARNESS_MODEL_API_KEY'
const SKILL_CALL_ID = 'e2e-skill-call' const SKILL_CALL_ID = 'e2e-skill-call'
const MCP_CALL_ID = 'e2e-mcp-call' const MCP_CALL_ID = 'e2e-mcp-call'
const ASK_MCP_CALL_ID = 'e2e-ask-mcp-call' const ASK_MCP_CALL_ID = 'e2e-ask-mcp-call'
@@ -536,9 +536,9 @@ describe('DeepSeek Harness real ACP control-plane E2E', () => {
{ {
id: 'web-3d-game', id: 'web-3d-game',
directory: resolve( directory: resolve(
'resources', 'tests',
'skills', 'fixtures',
'web-3d-game' 'web-3d-game-skill'
) )
} }
], ],
@@ -0,0 +1,118 @@
import type childProcess from 'node:child_process'
import { describe, expect, it, vi } from 'vitest'
import { installHarnessChildProcessWindowGuard } from './deepseek-harness-child-process'
type HarnessChildProcessModule = Pick<
typeof childProcess,
'execFileSync' | 'spawn' | 'spawnSync'
>
function fakeChildProcessModule(): {
target: HarnessChildProcessModule
execFileSync: ReturnType<typeof vi.fn>
spawn: ReturnType<typeof vi.fn>
spawnSync: ReturnType<typeof vi.fn>
} {
const execFileSync = vi.fn(() => 'output')
const spawn = vi.fn(() => ({ pid: 1 }))
const spawnSync = vi.fn(() => ({ status: 0 }))
return {
target: {
execFileSync:
execFileSync as unknown as HarnessChildProcessModule['execFileSync'],
spawn: spawn as unknown as HarnessChildProcessModule['spawn'],
spawnSync:
spawnSync as unknown as HarnessChildProcessModule['spawnSync']
},
execFileSync,
spawn,
spawnSync
}
}
describe('DeepSeek Harness child process window guard', () => {
it('does not alter child process launches outside Windows', () => {
const { target, spawn } = fakeChildProcessModule()
const originalSpawn = target.spawn
const syncExports = vi.fn()
const restore = installHarnessChildProcessWindowGuard(
'linux',
target,
syncExports
)
expect(target.spawn).toBe(originalSpawn)
expect(syncExports).not.toHaveBeenCalled()
restore()
expect(spawn).not.toHaveBeenCalled()
expect(syncExports).not.toHaveBeenCalled()
})
it('forces hidden Windows launches and restores the original functions', () => {
const { target, execFileSync, spawn, spawnSync } =
fakeChildProcessModule()
const originals = { ...target }
const syncExports = vi.fn()
const restore = installHarnessChildProcessWindowGuard(
'win32',
target,
syncExports
)
target.spawn('runner.exe', ['--probe'], {
cwd: 'C:\\workspace',
windowsHide: false
})
target.spawnSync('taskkill.exe', {
stdio: 'ignore'
})
target.spawnSync('where.exe', undefined, {
encoding: 'utf8'
})
target.execFileSync('where.exe', ['pwsh.exe'], {
encoding: 'utf8',
windowsHide: false
})
expect(spawn).toHaveBeenCalledWith(
'runner.exe',
['--probe'],
expect.objectContaining({
cwd: 'C:\\workspace',
windowsHide: true
})
)
expect(spawnSync).toHaveBeenCalledWith(
'where.exe',
undefined,
expect.objectContaining({
encoding: 'utf8',
windowsHide: true
})
)
expect(spawnSync).toHaveBeenCalledWith(
'taskkill.exe',
expect.objectContaining({
stdio: 'ignore',
windowsHide: true
})
)
expect(execFileSync).toHaveBeenCalledWith(
'where.exe',
['pwsh.exe'],
expect.objectContaining({
encoding: 'utf8',
windowsHide: true
})
)
expect(syncExports).toHaveBeenCalledTimes(1)
restore()
restore()
expect(target).toMatchObject(originals)
expect(syncExports).toHaveBeenCalledTimes(2)
})
})
@@ -0,0 +1,82 @@
import childProcess from 'node:child_process'
import { syncBuiltinESMExports } from 'node:module'
type HarnessChildProcessModule = Pick<
typeof childProcess,
'execFileSync' | 'spawn' | 'spawnSync'
>
type SyncBuiltinExports = () => void
function withHiddenWindow(args: unknown[]): unknown[] {
const next = [...args]
const optionsIndex =
Array.isArray(next[1]) ||
(next[1] === undefined && next.length >= 3)
? 2
: 1
const options = next[optionsIndex]
next[optionsIndex] = {
...(options && typeof options === 'object' ? options : {}),
windowsHide: true
}
return next
}
/**
* DeepSeek Harness 0.1.0-rc.6 omits `windowsHide` when its local subprocess
* service starts the ACL runner and PowerShell. In an Electron GUI process
* that can briefly create a visible console window. Keep this override scoped
* to the isolated Harness UtilityProcess and synchronize the built-in ESM
* bindings already captured by the bundled Harness modules.
*/
export function installHarnessChildProcessWindowGuard(
platform: NodeJS.Platform = process.platform,
target: HarnessChildProcessModule = childProcess,
syncExports: SyncBuiltinExports = syncBuiltinESMExports
): () => void {
if (platform !== 'win32') {
return () => undefined
}
const originals = {
execFileSync: target.execFileSync,
spawn: target.spawn,
spawnSync: target.spawnSync
}
const guardedExecFileSync = ((...args: unknown[]) =>
Reflect.apply(
originals.execFileSync,
target,
withHiddenWindow(args)
)) as typeof target.execFileSync
const guardedSpawn = ((...args: unknown[]) =>
Reflect.apply(
originals.spawn,
target,
withHiddenWindow(args)
)) as typeof target.spawn
const guardedSpawnSync = ((...args: unknown[]) =>
Reflect.apply(
originals.spawnSync,
target,
withHiddenWindow(args)
)) as typeof target.spawnSync
target.execFileSync = guardedExecFileSync
target.spawn = guardedSpawn
target.spawnSync = guardedSpawnSync
syncExports()
let restored = false
return () => {
if (restored) {
return
}
restored = true
target.execFileSync = originals.execFileSync
target.spawn = originals.spawn
target.spawnSync = originals.spawnSync
syncExports()
}
}
@@ -49,8 +49,8 @@ async function fixture() {
launchOptions: { launchOptions: {
cwd: workspace, cwd: workspace,
signal: new AbortController().signal, signal: new AbortController().signal,
baseUrl: 'https://api.deepseek.com', baseUrl: 'https://gateway.example/openai/v1',
model: 'deepseek-chat', model: 'qwen-plus',
credentialRefs: [DEEPSEEK_HARNESS_CREDENTIAL_REF], credentialRefs: [DEEPSEEK_HARNESS_CREDENTIAL_REF],
skillPackages: [] skillPackages: []
} }
@@ -97,7 +97,8 @@ describe('DeepSeek Harness utility launcher', () => {
expect(utility.messages[0]).toMatchObject({ expect(utility.messages[0]).toMatchObject({
type: 'start', type: 'start',
config: { config: {
baseUrl: 'https://api.deepseek.com', baseUrl: 'https://gateway.example/openai/v1',
model: 'qwen-plus',
credentialRefs: [DEEPSEEK_HARNESS_CREDENTIAL_REF] credentialRefs: [DEEPSEEK_HARNESS_CREDENTIAL_REF]
} }
}) })
@@ -144,4 +145,24 @@ describe('DeepSeek Harness utility launcher', () => {
await expect(launching).rejects.toThrow('启动协议无效') await expect(launching).rejects.toThrow('启动协议无效')
expect(terminateProcess).toHaveBeenCalledOnce() expect(terminateProcess).toHaveBeenCalledOnce()
}) })
it.each([
'http://gateway.example/v1',
'https://user:secret@gateway.example/v1',
'https://gateway.example/v1?api-version=2025-01-01'
])('rejects unsafe endpoint %s before forking', async (baseUrl) => {
const { dshHome, hostPath, launchOptions } = await fixture()
const fork = vi.fn()
const launcher = createDeepSeekHarnessUtilityLauncher({
bundledHostPath: hostPath,
dshHome,
environment: {},
fork
})
await expect(
launcher({ ...launchOptions, baseUrl })
).rejects.toThrow('HTTPS')
expect(fork).not.toHaveBeenCalled()
})
}) })
@@ -3,6 +3,7 @@ import { realpath, stat } from 'node:fs/promises'
import { isAbsolute } from 'node:path' import { isAbsolute } from 'node:path'
import type { UtilityProcess } from 'electron' import type { UtilityProcess } from 'electron'
import { z } from 'zod' import { z } from 'zod'
import { isDeepSeekHarnessCompatibleBaseUrl } from '../../shared/deepseek-harness-compatibility'
import type { import type {
DeepSeekHarnessChild, DeepSeekHarnessChild,
DeepSeekHarnessLaunchOptions DeepSeekHarnessLaunchOptions
@@ -14,7 +15,7 @@ export const DEEPSEEK_HARNESS_CONTROL_PROTOCOL =
export const DEEPSEEK_HARNESS_CONTROL_VERSION = 1 export const DEEPSEEK_HARNESS_CONTROL_VERSION = 1
export const DEEPSEEK_HARNESS_HOST_VERSION = '0.1.0-rc.6' export const DEEPSEEK_HARNESS_HOST_VERSION = '0.1.0-rc.6'
export const DEEPSEEK_HARNESS_CREDENTIAL_REF = export const DEEPSEEK_HARNESS_CREDENTIAL_REF =
'GOODBUDDY_DEEPSEEK_API_KEY' 'GOODBUDDY_HARNESS_MODEL_API_KEY'
const sandboxSchema = z const sandboxSchema = z
.object({ .object({
@@ -41,15 +42,7 @@ export const controlledHarnessHostConfigSchema = z
baseUrl: z baseUrl: z
.url() .url()
.max(2_048) .max(2_048)
.refine((value) => { .refine(isDeepSeekHarnessCompatibleBaseUrl),
const url = new URL(value)
return (
url.protocol === 'https:' &&
url.hostname.toLowerCase() === 'api.deepseek.com' &&
!url.username &&
!url.password
)
}),
api: z.literal('openai-completions'), api: z.literal('openai-completions'),
provider: z.literal('goodbuddy'), provider: z.literal('goodbuddy'),
model: z.string().min(1).max(128), model: z.string().min(1).max(128),
@@ -240,12 +233,9 @@ export function createDeepSeekHarnessUtilityLauncher(
'DeepSeek Harness 当前平台只能提供部分沙箱强制' 'DeepSeek Harness 当前平台只能提供部分沙箱强制'
) )
} }
if ( if (!isDeepSeekHarnessCompatibleBaseUrl(options.baseUrl)) {
options.baseUrl !== 'https://api.deepseek.com' &&
options.baseUrl !== 'https://api.deepseek.com/'
) {
throw new Error( throw new Error(
'DeepSeek Harness 仅允许 api.deepseek.com' 'DeepSeek Harness 模型地址必须使用 HTTPS 或本机回环 HTTP,且不得包含凭据、查询参数或片段'
) )
} }
if ( if (
+19 -19
View File
@@ -10,7 +10,7 @@ const defaultProfileId = '00000000-0000-4000-8000-000000000001'
const secondProfileId = '00000000-0000-4000-8000-000000000002' const secondProfileId = '00000000-0000-4000-8000-000000000002'
const responsesProfileId = '00000000-0000-4000-8000-000000000003' const responsesProfileId = '00000000-0000-4000-8000-000000000003'
const imageProfileId = '00000000-0000-4000-8000-000000000004' const imageProfileId = '00000000-0000-4000-8000-000000000004'
const deepseekProfileId = '00000000-0000-4000-8000-000000000005' const harnessProfileId = '00000000-0000-4000-8000-000000000005'
function settings( function settings(
overrides: Partial<ResolvedRuntimeSettings> = {} overrides: Partial<ResolvedRuntimeSettings> = {}
@@ -65,10 +65,10 @@ function settings(
apiKey: 'image-key' apiKey: 'image-key'
}, },
{ {
id: deepseekProfileId, id: harnessProfileId,
name: 'DeepSeek', name: 'OpenAI-compatible gateway',
baseUrl: 'https://api.deepseek.com', baseUrl: 'https://gateway.example/openai/v1',
modelName: 'deepseek-chat', modelName: 'qwen-plus',
protocol: 'openai-chat-completions', protocol: 'openai-chat-completions',
authentication: 'api-key', authentication: 'api-key',
imageGenerationQuality: 'auto', imageGenerationQuality: 'auto',
@@ -165,31 +165,31 @@ describe('runtime selection', () => {
).toThrow('自动启动') ).toThrow('自动启动')
}) })
it('selects DeepSeek Harness only with an official compatible profile', () => { it('selects DeepSeek Harness with a compatible gateway profile', () => {
const selected = applyRuntimeSelection(settings(), { const selected = applyRuntimeSelection(settings(), {
provider: 'deepseek-harness', provider: 'deepseek-harness',
profileId: deepseekProfileId profileId: harnessProfileId
}) })
expect(selected.target).toBe('deepseek-harness') expect(selected.target).toBe('deepseek-harness')
expect(selected.settings).toMatchObject({ expect(selected.settings).toMatchObject({
provider: 'deepseek-harness', provider: 'deepseek-harness',
deepseekHarnessModelProfile: { id: deepseekProfileId } deepseekHarnessModelProfile: { id: harnessProfileId }
}) })
expect(() => expect(() =>
applyRuntimeSelection(settings(), { applyRuntimeSelection(settings(), {
provider: 'deepseek-harness', provider: 'deepseek-harness',
profileId: secondProfileId profileId: secondProfileId
}) })
).toThrow('api.deepseek.com') ).toThrow('API Key')
}) })
it('keeps the controlled platform DeepSeek profile when selected without a profile ID', () => { it('keeps the controlled platform Harness profile when selected without a profile ID', () => {
const base = settings() const base = settings()
const platformProfile = { const platformProfile = {
...base.modelProfiles[4]!, ...base.modelProfiles[4]!,
id: 'goodbuddy-platform-deepseek', id: 'goodbuddy-platform-harness',
name: '平台 DeepSeek', name: '管理员预置模型',
modelName: 'deepseek-v4-flash' modelName: 'qwen-plus'
} }
const selected = applyRuntimeSelection( const selected = applyRuntimeSelection(
settings({ deepseekHarnessModelProfile: platformProfile }), settings({ deepseekHarnessModelProfile: platformProfile }),
@@ -199,8 +199,8 @@ describe('runtime selection', () => {
expect(selected.settings).toMatchObject({ expect(selected.settings).toMatchObject({
provider: 'deepseek-harness', provider: 'deepseek-harness',
deepseekHarnessModelProfile: { deepseekHarnessModelProfile: {
id: 'goodbuddy-platform-deepseek', id: 'goodbuddy-platform-harness',
modelName: 'deepseek-v4-flash' modelName: 'qwen-plus'
} }
}) })
}) })
@@ -219,7 +219,7 @@ describe('runtime selection', () => {
}) })
).toEqual({ ).toEqual({
provider: 'deepseek-harness', provider: 'deepseek-harness',
profileId: deepseekProfileId profileId: harnessProfileId
}) })
expect( expect(
resolveConfiguredAgentRuntimeSelection(configured, { resolveConfiguredAgentRuntimeSelection(configured, {
@@ -249,13 +249,13 @@ describe('runtime selection', () => {
}) })
}) })
it('keeps the controlled platform DeepSeek source profile-free across configured selection repair', () => { it('keeps the controlled platform Harness source profile-free across configured selection repair', () => {
const base = settings() const base = settings()
const configured = settings({ const configured = settings({
deepseekHarnessModelProfile: { deepseekHarnessModelProfile: {
...base.modelProfiles[4]!, ...base.modelProfiles[4]!,
id: 'goodbuddy-platform-deepseek', id: 'goodbuddy-platform-harness',
name: '平台 DeepSeek' name: '管理员预置模型'
} }
}) })
+1 -1
View File
@@ -139,7 +139,7 @@ export function applyRuntimeSelection(
!isDeepSeekHarnessModelProfile(selectedProfile) !isDeepSeekHarnessModelProfile(selectedProfile)
) { ) {
throw new Error( throw new Error(
'DeepSeek Harness 独立模型连接仅支持 api.deepseek.com 的 OpenAI Chat Completions 协议' 'DeepSeek Harness 仅支持使用 API Key 的安全 OpenAI 兼容 Chat Completions 连接'
) )
} }
return { return {
+5 -9
View File
@@ -63,12 +63,8 @@ describe('bundled skills', () => {
expect(snapshot.skills.map((skill) => skill.id)).toContain( expect(snapshot.skills.map((skill) => skill.id)).toContain(
'product-marketing' 'product-marketing'
) )
expect(snapshot.skills).toContainEqual( expect(snapshot.skills.map((skill) => skill.id)).not.toContain(
expect.objectContaining({ 'web-3d-game'
id: 'web-3d-game',
name: 'Web 3D Game',
assignments: expect.arrayContaining(['deepseek-harness'])
})
) )
}) })
@@ -84,7 +80,7 @@ describe('bundled skills', () => {
} }
}) })
it('exposes the 3D game Skill as a native Harness package', async () => { it('exposes bundled Skills as native Harness packages', async () => {
const service = await createService() const service = await createService()
await expect( await expect(
@@ -92,8 +88,8 @@ describe('bundled skills', () => {
).resolves.toMatchObject({ ).resolves.toMatchObject({
packages: expect.arrayContaining([ packages: expect.arrayContaining([
{ {
id: 'web-3d-game', id: 'product-marketing',
directory: join(builtinSkillsRoot, 'web-3d-game') directory: join(builtinSkillsRoot, 'product-marketing')
} }
]) ])
}) })
@@ -12,9 +12,12 @@ import {
startControlledDeepSeekHarnessHost, startControlledDeepSeekHarnessHost,
type ControlledHarnessHost type ControlledHarnessHost
} from './deepseek-harness-host' } from './deepseek-harness-host'
import { installHarnessChildProcessWindowGuard } from './agent/deepseek-harness-child-process'
const parentPort = process.parentPort const parentPort = process.parentPort
const restoreDiagnostics = installHarnessDiagnosticGuard() const restoreDiagnostics = installHarnessDiagnosticGuard()
const restoreChildProcessWindowGuard =
installHarnessChildProcessWindowGuard()
// The Windows ACL sandbox launches its JavaScript runner through // The Windows ACL sandbox launches its JavaScript runner through
// `process.execPath`. Inside an Electron UtilityProcess that path is Electron, // `process.execPath`. Inside an Electron UtilityProcess that path is Electron,
// so descendants must opt into Electron's supported Node execution mode. // so descendants must opt into Electron's supported Node execution mode.
@@ -41,6 +44,7 @@ async function close(): Promise<void> {
closed = true closed = true
await host?.dispose().catch(() => undefined) await host?.dispose().catch(() => undefined)
transport?.dispose() transport?.dispose()
restoreChildProcessWindowGuard()
restoreDiagnostics() restoreDiagnostics()
} }
+3 -3
View File
@@ -46,7 +46,7 @@ describe('controlled DeepSeek Harness host', () => {
dshHome: 'C:\\controlled-dsh-home', dshHome: 'C:\\controlled-dsh-home',
skillPackages: [] skillPackages: []
}) })
).rejects.toThrow('trusted HTTPS DeepSeek endpoint') ).rejects.toThrow('secure OpenAI-compatible')
}) })
it('suppresses console payloads instead of contaminating stdout', () => { it('suppresses console payloads instead of contaminating stdout', () => {
@@ -84,10 +84,10 @@ describe('controlled DeepSeek Harness host', () => {
const host = await startControlledDeepSeekHarnessHost({ const host = await startControlledDeepSeekHarnessHost({
workspace: root, workspace: root,
dshHome: root, dshHome: root,
baseUrl: 'https://api.deepseek.com', baseUrl: 'https://gateway.example/openai/v1',
api: 'openai-completions', api: 'openai-completions',
provider: 'goodbuddy', provider: 'goodbuddy',
model: 'deepseek-test', model: 'qwen-plus',
harnessVersion: '0.1.0-rc.6', harnessVersion: '0.1.0-rc.6',
sandbox: expectedSandbox, sandbox: expectedSandbox,
credentialRefs: ['GOODBUDDY_API_KEY'], credentialRefs: ['GOODBUDDY_API_KEY'],
+3 -9
View File
@@ -30,6 +30,7 @@ import {
} from './agent/goodbuddy-harness-control-plane' } from './agent/goodbuddy-harness-control-plane'
import type { Stream } from '@agentclientprotocol/sdk' import type { Stream } from '@agentclientprotocol/sdk'
import type { SandboxEnforcement } from '@deepseek-ai/dsh-sandbox' import type { SandboxEnforcement } from '@deepseek-ai/dsh-sandbox'
import { isDeepSeekHarnessCompatibleBaseUrl } from '../shared/deepseek-harness-compatibility'
const DEFAULT_MAX_FRAME_BYTES = 1024 * 1024 const DEFAULT_MAX_FRAME_BYTES = 1024 * 1024
const MAX_DIAGNOSTIC_BYTES = 64 * 1024 const MAX_DIAGNOSTIC_BYTES = 64 * 1024
@@ -133,19 +134,12 @@ type PluginSpec = {
function validateHostConfig( function validateHostConfig(
config: ControlledHarnessHostConfig config: ControlledHarnessHostConfig
): void { ): void {
const endpoint = URL.canParse(config.baseUrl)
? new URL(config.baseUrl)
: undefined
if ( if (
config.api !== 'openai-completions' || config.api !== 'openai-completions' ||
!endpoint || !isDeepSeekHarnessCompatibleBaseUrl(config.baseUrl)
endpoint.protocol !== 'https:' ||
endpoint.hostname.toLowerCase() !== 'api.deepseek.com' ||
endpoint.username ||
endpoint.password
) { ) {
throw new Error( throw new Error(
'Controlled Harness requires the trusted HTTPS DeepSeek endpoint' 'Controlled Harness requires a secure OpenAI-compatible Chat Completions endpoint'
) )
} }
if (!config.credentialRefs.length) { if (!config.credentialRefs.length) {
+34 -31
View File
@@ -104,7 +104,7 @@ describe('RuntimeSettingsStore', () => {
}) })
}) })
it('migrates DeepSeek Harness to controlled platform mode and stores an official profile', async () => { it('migrates DeepSeek Harness to controlled platform mode and stores a compatible profile', async () => {
const { filePath, store } = await createStore() const { filePath, store } = await createStore()
await store.update(settings()) await store.update(settings())
const versionFourteen = JSON.parse( const versionFourteen = JSON.parse(
@@ -127,9 +127,9 @@ describe('RuntimeSettingsStore', () => {
modelProfiles: [ modelProfiles: [
{ {
id: profileId, id: profileId,
name: 'DeepSeek', name: 'OpenAI-compatible gateway',
baseUrl: 'https://api.deepseek.com', baseUrl: 'https://gateway.example/openai/v1',
modelName: 'deepseek-chat', modelName: 'qwen-plus',
protocol: 'openai-chat-completions', protocol: 'openai-chat-completions',
authentication: 'api-key', authentication: 'api-key',
imageGenerationQuality: 'auto', imageGenerationQuality: 'auto',
@@ -149,21 +149,22 @@ describe('RuntimeSettingsStore', () => {
}) })
}) })
it('resolves a controlled platform DeepSeek profile without exposing its credential', async () => { it('resolves a controlled platform Harness profile without exposing its credential', async () => {
const apiKey = 'platform-deepseek-secret' const apiKey = 'platform-harness-secret'
const { store } = await createStore({ const { store } = await createStore({
GOODBUDDY_MODEL_API_KEY: apiKey, GOODBUDDY_MODEL_API_KEY: apiKey,
GOODBUDDY_MODEL_BASE_URL: 'https://api.deepseek.com/', GOODBUDDY_MODEL_BASE_URL:
GOODBUDDY_MODEL_NAME: 'deepseek-v4-flash' 'https://gateway.example/openai/v1',
GOODBUDDY_MODEL_NAME: 'qwen-plus'
}) })
await expect(store.getResolvedSettings()).resolves.toMatchObject({ await expect(store.getResolvedSettings()).resolves.toMatchObject({
modelProtocol: 'anthropic-messages', modelProtocol: 'anthropic-messages',
deepseekHarnessModelProfile: { deepseekHarnessModelProfile: {
id: 'goodbuddy-platform-deepseek', id: 'goodbuddy-platform-harness',
name: '平台 DeepSeek', name: '管理员预置模型',
baseUrl: 'https://api.deepseek.com/', baseUrl: 'https://gateway.example/openai/v1',
modelName: 'deepseek-v4-flash', modelName: 'qwen-plus',
protocol: 'openai-chat-completions', protocol: 'openai-chat-completions',
authentication: 'api-key', authentication: 'api-key',
supportsImageInput: false, supportsImageInput: false,
@@ -179,37 +180,39 @@ describe('RuntimeSettingsStore', () => {
it.each([ it.each([
[ [
'a non-DeepSeek endpoint', 'an insecure public endpoint',
{ {
GOODBUDDY_MODEL_API_KEY: 'platform-key', GOODBUDDY_MODEL_API_KEY: 'platform-key',
GOODBUDDY_MODEL_BASE_URL: 'https://deepseek.example', GOODBUDDY_MODEL_BASE_URL: 'http://gateway.example/v1',
GOODBUDDY_MODEL_NAME: 'deepseek-chat' GOODBUDDY_MODEL_NAME: 'qwen-plus'
} }
], ],
[ [
'an insecure DeepSeek endpoint', 'an endpoint with embedded credentials',
{ {
GOODBUDDY_MODEL_API_KEY: 'platform-key', GOODBUDDY_MODEL_API_KEY: 'platform-key',
GOODBUDDY_MODEL_BASE_URL: 'http://api.deepseek.com', GOODBUDDY_MODEL_BASE_URL:
GOODBUDDY_MODEL_NAME: 'deepseek-chat' 'https://user:secret@gateway.example/v1',
GOODBUDDY_MODEL_NAME: 'qwen-plus'
} }
], ],
[ [
'a DeepSeek endpoint path', 'an endpoint with a query string',
{ {
GOODBUDDY_MODEL_API_KEY: 'platform-key', GOODBUDDY_MODEL_API_KEY: 'platform-key',
GOODBUDDY_MODEL_BASE_URL: 'https://api.deepseek.com/v1', GOODBUDDY_MODEL_BASE_URL:
GOODBUDDY_MODEL_NAME: 'deepseek-chat' 'https://gateway.example/v1?api-version=1',
GOODBUDDY_MODEL_NAME: 'qwen-plus'
} }
], ],
[ [
'a missing API key', 'a missing API key',
{ {
GOODBUDDY_MODEL_BASE_URL: 'https://api.deepseek.com', GOODBUDDY_MODEL_BASE_URL: 'https://gateway.example/v1',
GOODBUDDY_MODEL_NAME: 'deepseek-chat' GOODBUDDY_MODEL_NAME: 'qwen-plus'
} }
] ]
])('does not resolve platform DeepSeek from %s', async (_, environment) => { ])('does not resolve platform Harness from %s', async (_, environment) => {
const { store } = await createStore(environment) const { store } = await createStore(environment)
await expect(store.getResolvedSettings()).resolves.toMatchObject({ await expect(store.getResolvedSettings()).resolves.toMatchObject({
@@ -253,7 +256,7 @@ describe('RuntimeSettingsStore', () => {
) )
}) })
it('rejects incompatible DeepSeek Harness model profiles', () => { it('accepts compatible gateways and rejects incompatible Harness profiles', () => {
const profileId = '00000000-0000-4000-8000-000000000045' const profileId = '00000000-0000-4000-8000-000000000045'
expect( expect(
runtimeSettingsInputSchema.safeParse( runtimeSettingsInputSchema.safeParse(
@@ -261,9 +264,9 @@ describe('RuntimeSettingsStore', () => {
modelProfiles: [ modelProfiles: [
{ {
id: profileId, id: profileId,
name: 'Other compatible API', name: 'Compatible API',
baseUrl: 'https://other.example/v1', baseUrl: 'https://other.example/v1',
modelName: 'deepseek-chat', modelName: 'qwen-plus',
protocol: 'openai-chat-completions', protocol: 'openai-chat-completions',
authentication: 'api-key', authentication: 'api-key',
imageGenerationQuality: 'auto', imageGenerationQuality: 'auto',
@@ -274,16 +277,16 @@ describe('RuntimeSettingsStore', () => {
deepseekHarnessModelSource: { kind: 'profile', profileId } deepseekHarnessModelSource: { kind: 'profile', profileId }
}) })
).success ).success
).toBe(false) ).toBe(true)
expect( expect(
runtimeSettingsInputSchema.safeParse( runtimeSettingsInputSchema.safeParse(
settings({ settings({
modelProfiles: [ modelProfiles: [
{ {
id: profileId, id: profileId,
name: 'DeepSeek without API key', name: 'Gateway without API key',
baseUrl: 'https://api.deepseek.com', baseUrl: 'https://other.example/v1',
modelName: 'deepseek-chat', modelName: 'qwen-plus',
protocol: 'openai-chat-completions', protocol: 'openai-chat-completions',
authentication: 'none', authentication: 'none',
imageGenerationQuality: 'auto', imageGenerationQuality: 'auto',
+13 -21
View File
@@ -263,7 +263,7 @@ const embeddingCredentialPayloadSchema = z.object({
}) })
const rerankCredentialPayloadSchema = embeddingCredentialPayloadSchema const rerankCredentialPayloadSchema = embeddingCredentialPayloadSchema
const platformDeepSeekProfileId = 'goodbuddy-platform-deepseek' const platformHarnessProfileId = 'goodbuddy-platform-harness'
export type CredentialCipher = SettingsCredentialCipher export type CredentialCipher = SettingsCredentialCipher
@@ -1041,33 +1041,25 @@ export class RuntimeSettingsStore {
) )
} }
private resolvePlatformDeepSeekProfile(): ResolvedModelProfile | undefined { private resolvePlatformHarnessProfile(): ResolvedModelProfile | undefined {
const apiKey = this.environment.GOODBUDDY_MODEL_API_KEY?.trim() const apiKey = this.environment.GOODBUDDY_MODEL_API_KEY?.trim()
const baseUrl = this.environment.GOODBUDDY_MODEL_BASE_URL?.trim() const baseUrl = this.environment.GOODBUDDY_MODEL_BASE_URL?.trim()
const modelName = this.environment.GOODBUDDY_MODEL_NAME?.trim() const modelName = this.environment.GOODBUDDY_MODEL_NAME?.trim()
if (!apiKey || !baseUrl || !modelName) { if (!apiKey || !baseUrl || !modelName) {
return undefined return undefined
} }
try { if (
const endpoint = new URL(baseUrl) !isDeepSeekHarnessModelProfile({
if ( baseUrl,
endpoint.protocol !== 'https:' || protocol: 'openai-chat-completions',
endpoint.hostname.toLowerCase() !== 'api.deepseek.com' || authentication: 'api-key'
endpoint.port || })
endpoint.pathname !== '/' || ) {
endpoint.search ||
endpoint.hash ||
endpoint.username ||
endpoint.password
) {
return undefined
}
} catch {
return undefined return undefined
} }
return { return {
id: platformDeepSeekProfileId, id: platformHarnessProfileId,
name: '平台 DeepSeek', name: '管理员预置模型',
baseUrl, baseUrl,
modelName, modelName,
protocol: 'openai-chat-completions', protocol: 'openai-chat-completions',
@@ -1406,7 +1398,7 @@ export class RuntimeSettingsStore {
? profilesById.get( ? profilesById.get(
settings.deepseekHarnessModelSource.profileId settings.deepseekHarnessModelSource.profileId
) )
: this.resolvePlatformDeepSeekProfile() : this.resolvePlatformHarnessProfile()
return { return {
provider: settings.provider, provider: settings.provider,
modelBaseUrl: effective.baseUrl, modelBaseUrl: effective.baseUrl,
@@ -1726,7 +1718,7 @@ export class RuntimeSettingsStore {
} }
if (!isDeepSeekHarnessModelProfile(profile)) { if (!isDeepSeekHarnessModelProfile(profile)) {
throw new Error( throw new Error(
'DeepSeek Harness 模型连接仅支持 api.deepseek.com 的 OpenAI Chat Completions 协议' 'DeepSeek Harness 仅支持使用 API Key 的安全 OpenAI 兼容 Chat Completions 连接'
) )
} }
} }
@@ -503,7 +503,7 @@ describe('ChannelSettingsSection', () => {
).toBeInTheDocument() ).toBeInTheDocument()
expect( expect(
within(backend).getByRole('option', { within(backend).getByRole('option', {
name: 'DeepSeek Harness(预览 · 仅 DeepSeek' name: 'DeepSeek Harness(预览 · OpenAI 兼容'
}) })
).toBeInTheDocument() ).toBeInTheDocument()
+17 -15
View File
@@ -1372,7 +1372,7 @@ describe('SettingsPanel runtime files', () => {
) )
}) })
it('configures DeepSeek Harness with a compatible GoodBuddy connection', async () => { it('configures DeepSeek Harness with an OpenAI-compatible gateway', async () => {
const harnessProfileId = const harnessProfileId =
'00000000-0000-4000-8000-000000000051' '00000000-0000-4000-8000-000000000051'
getRuntime.mockResolvedValueOnce({ getRuntime.mockResolvedValueOnce({
@@ -1382,9 +1382,9 @@ describe('SettingsPanel runtime files', () => {
{ {
...runtimeSettings.modelProfiles[0]!, ...runtimeSettings.modelProfiles[0]!,
id: harnessProfileId, id: harnessProfileId,
name: 'DeepSeek Chat', name: 'Compatible Gateway',
baseUrl: 'https://api.deepseek.com/v1', baseUrl: 'https://gateway.example/openai/v1',
modelName: 'deepseek-chat', modelName: 'qwen-plus',
protocol: 'openai-chat-completions' protocol: 'openai-chat-completions'
} }
], ],
@@ -1409,10 +1409,10 @@ describe('SettingsPanel runtime files', () => {
}) })
) )
expect( expect(
screen.getByText('开发者预览 · 仅支持 DeepSeek') screen.getByText('开发者预览 · OpenAI 兼容')
).toBeInTheDocument() ).toBeInTheDocument()
expect( expect(
screen.getByText(/当前仅支持 DeepSeek 模型/) screen.getByText(/公网地址必须使用 HTTPS/)
).toBeInTheDocument() ).toBeInTheDocument()
const harnessOverview = screen const harnessOverview = screen
.getByText('GoodBuddy 内置 DeepSeek Harness') .getByText('GoodBuddy 内置 DeepSeek Harness')
@@ -1451,17 +1451,19 @@ describe('SettingsPanel runtime files', () => {
}) })
).not.toBeInTheDocument() ).not.toBeInTheDocument()
const source = screen.getByLabelText( const source = screen.getByLabelText(
'DeepSeek Harness DeepSeek 模型连接' 'DeepSeek Harness OpenAI 兼容模型连接'
) )
expect(screen.queryByRole('radio')).not.toBeInTheDocument() expect(screen.queryByRole('radio')).not.toBeInTheDocument()
expect( expect(
screen.queryByText('使用平台 DeepSeek 环境配置') screen.queryByText('使用管理员预置模型连接')
).not.toBeInTheDocument() ).not.toBeInTheDocument()
expect( expect(
within(source).getByRole('option', { name: '默认模型(不兼容)' }) within(source).getByRole('option', { name: '默认模型(不兼容)' })
).toBeDisabled() ).toBeDisabled()
expect( expect(
within(source).getByRole('option', { name: 'DeepSeek Chat' }) within(source).getByRole('option', {
name: 'Compatible Gateway'
})
).not.toBeDisabled() ).not.toBeDisabled()
fireEvent.click(screen.getByRole('button', { name: '保存设置' })) fireEvent.click(screen.getByRole('button', { name: '保存设置' }))
@@ -1480,14 +1482,14 @@ describe('SettingsPanel runtime files', () => {
it('keeps an environment-managed source compatible without exposing it as an option', async () => { it('keeps an environment-managed source compatible without exposing it as an option', async () => {
getRuntime.mockResolvedValueOnce({ getRuntime.mockResolvedValueOnce({
...runtimeSettings, ...runtimeSettings,
modelBaseUrl: 'https://api.deepseek.com', modelBaseUrl: 'https://gateway.example/openai/v1',
modelName: 'deepseek-chat', modelName: 'qwen-plus',
modelProtocol: 'openai-chat-completions', modelProtocol: 'openai-chat-completions',
modelProfiles: [ modelProfiles: [
{ {
...runtimeSettings.modelProfiles[0]!, ...runtimeSettings.modelProfiles[0]!,
baseUrl: 'https://api.deepseek.com', baseUrl: 'https://gateway.example/openai/v1',
modelName: 'deepseek-chat', modelName: 'qwen-plus',
protocol: 'openai-chat-completions', protocol: 'openai-chat-completions',
credentialSource: 'environment' credentialSource: 'environment'
} }
@@ -1531,10 +1533,10 @@ describe('SettingsPanel runtime files', () => {
) )
expect(screen.queryByRole('radio')).not.toBeInTheDocument() expect(screen.queryByRole('radio')).not.toBeInTheDocument()
expect( expect(
screen.getByText('管理员预置的 DeepSeek 连接') screen.getByText('管理员预置的 OpenAI 兼容连接')
).toBeInTheDocument() ).toBeInTheDocument()
const source = screen.getByLabelText( const source = screen.getByLabelText(
'DeepSeek Harness DeepSeek 模型连接' 'DeepSeek Harness OpenAI 兼容模型连接'
) )
expect(source).toHaveValue('') expect(source).toHaveValue('')
fireEvent.change(source, { fireEvent.change(source, {
+1 -1
View File
@@ -1973,7 +1973,7 @@ export function SettingsPanel({
</div> </div>
</div> </div>
<p className="settings-notice"> <p className="settings-notice">
{t('runtime.deepseekHarness.deepseekOnlyNotice')} {t('runtime.deepseekHarness.compatibilityNotice')}
</p> </p>
<RuntimeOverviewCard <RuntimeOverviewCard
detection={detection?.deepseekHarness} detection={detection?.deepseekHarness}
+1 -1
View File
@@ -118,7 +118,7 @@ export const app = {
picker: 'Runtime and model', picker: 'Runtime and model',
directModels: 'Direct models', directModels: 'Direct models',
deepseekHarnessGroup: deepseekHarnessGroup:
'DeepSeek Harness (Developer preview · DeepSeek only)', 'DeepSeek Harness (Developer preview · OpenAI-compatible)',
manage: 'Manage Runtime and model connections', manage: 'Manage Runtime and model connections',
errors: { errors: {
readStatus: 'Failed to read Agent Runtime status', readStatus: 'Failed to read Agent Runtime status',
@@ -32,7 +32,7 @@ export const integrations = {
missingProfile: 'The previous direct model no longer exists', missingProfile: 'The previous direct model no longer exists',
noTextModels: 'No text models are available', noTextModels: 'No text models are available',
deepseekHarnessOption: deepseekHarnessOption:
'DeepSeek Harness (Preview · DeepSeek only)', 'DeepSeek Harness (Preview · OpenAI-compatible)',
missingSelection: missingSelection:
'The selected direct model no longer exists. Choose another model.', 'The selected direct model no longer exists. Choose another model.',
imageOnlySelection: imageOnlySelection:
@@ -237,16 +237,18 @@ export const settings = {
deepseekHarness: { deepseekHarness: {
selectorLabel: 'DeepSeek Harness (Preview)', selectorLabel: 'DeepSeek Harness (Preview)',
title: 'DeepSeek Harness', title: 'DeepSeek Harness',
previewDescription: 'Developer preview · DeepSeek only', previewDescription: 'Developer preview · OpenAI-compatible',
deepseekOnlyNotice: compatibilityNotice:
'DeepSeek Harness currently supports DeepSeek models only and is not intended for other model providers.', 'Supports OpenAI-compatible Chat Completions connections with an API key. Public endpoints must use HTTPS; loopback endpoints may use HTTP.',
description: description:
'GoodBuddy maintains the fixed Host and control protocol internally and uses pinned Harness libraries underneath. Execute tool calls receive automatic one-time authorization, Ask remains read-only, and cancellation and workspace safety boundaries remain in place. It does not integrate with the DSH plugin or marketplace mechanisms.', 'GoodBuddy maintains the fixed Host and control protocol internally and uses pinned Harness libraries underneath. Execute tool calls receive automatic one-time authorization, Ask remains read-only, and cancellation and workspace safety boundaries remain in place. It does not integrate with the DSH plugin or marketplace mechanisms.',
managedSource: 'Administrator-provided DeepSeek connection', managedSource:
connection: 'DeepSeek model connection', 'Administrator-provided OpenAI-compatible connection',
connectionPlaceholder: 'Select a DeepSeek model connection', connection: 'OpenAI-compatible model connection',
connectionPlaceholder:
'Select an OpenAI-compatible model connection',
connectionDescription: connectionDescription:
'Choose a GoodBuddy model connection. Only OpenAI-compatible Chat Completions connections that point to DeepSeek are supported.', 'Choose a GoodBuddy model connection. It must use OpenAI Chat Completions with API-key authentication.',
advancedDescription: advancedDescription:
'This Runtime always uses GoodBuddys bundled, version-pinned Host. It does not load external DSH plugins, marketplace packages, user profiles, or custom Hosts.' 'This Runtime always uses GoodBuddys bundled, version-pinned Host. It does not load external DSH plugins, marketplace packages, user profiles, or custom Hosts.'
} }
@@ -504,7 +506,7 @@ export const settings = {
incompatibleImageProtocol: incompatibleImageProtocol:
'Incompatible (image generation protocol is unsupported)', 'Incompatible (image generation protocol is unsupported)',
incompatibleHarnessProtocol: incompatibleHarnessProtocol:
'Incompatible (Chat Completions only)', 'Incompatible (requires Chat Completions, an API key, and a secure endpoint)',
secureStorageWarning: secureStorageWarning:
'Secure system key storage is unavailable. Use an environment variable to avoid storing an API Key in plaintext.' 'Secure system key storage is unavailable. Use an environment variable to avoid storing an API Key in plaintext.'
}, },
+2 -1
View File
@@ -113,7 +113,8 @@ export const app = {
switching: '切换中…', switching: '切换中…',
picker: 'Runtime 和模型', picker: 'Runtime 和模型',
directModels: '直连模型', directModels: '直连模型',
deepseekHarnessGroup: 'DeepSeek Harness(开发者预览 · 仅 DeepSeek', deepseekHarnessGroup:
'DeepSeek Harness(开发者预览 · OpenAI 兼容)',
manage: '管理 Runtime 和模型连接', manage: '管理 Runtime 和模型连接',
errors: { errors: {
readStatus: 'Agent Runtime 状态读取失败', readStatus: 'Agent Runtime 状态读取失败',
@@ -26,7 +26,8 @@ export const integrations = {
unavailableProfile: '{{name}} · {{modelName}}(不可用)', unavailableProfile: '{{name}} · {{modelName}}(不可用)',
missingProfile: '原直连模型已不存在', missingProfile: '原直连模型已不存在',
noTextModels: '暂无可用文本模型', noTextModels: '暂无可用文本模型',
deepseekHarnessOption: 'DeepSeek Harness(预览 · 仅 DeepSeek', deepseekHarnessOption:
'DeepSeek Harness(预览 · OpenAI 兼容)',
missingSelection: '所选直连模型已不存在,请重新选择。', missingSelection: '所选直连模型已不存在,请重新选择。',
imageOnlySelection: imageOnlySelection:
'所选连接仅支持图片生成,请选择文本模型或 Agent Runtime。', '所选连接仅支持图片生成,请选择文本模型或 Agent Runtime。',
@@ -215,16 +215,16 @@ export const settings = {
deepseekHarness: { deepseekHarness: {
selectorLabel: 'DeepSeek Harness(预览)', selectorLabel: 'DeepSeek Harness(预览)',
title: 'DeepSeek Harness', title: 'DeepSeek Harness',
previewDescription: '开发者预览 · 仅支持 DeepSeek', previewDescription: '开发者预览 · OpenAI 兼容',
deepseekOnlyNotice: compatibilityNotice:
'DeepSeek Harness 当前仅支持 DeepSeek 模型,不适用于其他模型提供商。', '支持使用 API Key 的 OpenAI 兼容 Chat Completions 连接;公网地址必须使用 HTTPS,本机回环地址可使用 HTTP。',
description: description:
'由 GoodBuddy 内部维护固定 Host 与控制协议,复用锁定的 Harness 底层库;Execute 工具调用自动单次授权,Ask 保持只读,并保留取消和工作区安全边界;不接入 DSH 插件或市场机制。', '由 GoodBuddy 内部维护固定 Host 与控制协议,复用锁定的 Harness 底层库;Execute 工具调用自动单次授权,Ask 保持只读,并保留取消和工作区安全边界;不接入 DSH 插件或市场机制。',
managedSource: '管理员预置的 DeepSeek 连接', managedSource: '管理员预置的 OpenAI 兼容连接',
connection: 'DeepSeek 模型连接', connection: 'OpenAI 兼容模型连接',
connectionPlaceholder: '选择 DeepSeek 模型连接', connectionPlaceholder: '选择 OpenAI 兼容模型连接',
connectionDescription: connectionDescription:
'从 GoodBuddy 模型连接中选择;仅支持指向 DeepSeek 的 OpenAI 兼容 Chat Completions 连接。', '从 GoodBuddy 模型连接中选择;协议必须为 OpenAI Chat Completions,并使用 API Key。',
advancedDescription: advancedDescription:
'该 Runtime 始终使用 GoodBuddy 内置并固定版本的 Host,不加载外部 DSH 插件、市场包、用户 profile 或自定义 Host。' '该 Runtime 始终使用 GoodBuddy 内置并固定版本的 Host,不加载外部 DSH 插件、市场包、用户 profile 或自定义 Host。'
} }
@@ -462,7 +462,7 @@ export const settings = {
incompatible: '不兼容', incompatible: '不兼容',
incompatibleImageProtocol: '不兼容(不支持图像生成协议)', incompatibleImageProtocol: '不兼容(不支持图像生成协议)',
incompatibleHarnessProtocol: incompatibleHarnessProtocol:
'不兼容(仅支持 Chat Completions', '不兼容( Chat Completions、API Key 和安全地址',
secureStorageWarning: secureStorageWarning:
'当前系统密钥服务不可用。为了避免明文落盘,请使用环境变量提供 API Key。' '当前系统密钥服务不可用。为了避免明文落盘,请使用环境变量提供 API Key。'
}, },
+6 -21
View File
@@ -121,6 +121,11 @@ import {
agentRuntimeSelectionSchema, agentRuntimeSelectionSchema,
type AgentRuntimeSelection type AgentRuntimeSelection
} from './runtime-selection-contracts' } from './runtime-selection-contracts'
import { isDeepSeekHarnessModelProfile } from './deepseek-harness-compatibility'
export {
isDeepSeekHarnessCompatibleBaseUrl,
isDeepSeekHarnessModelProfile
} from './deepseek-harness-compatibility'
export const workspaceRelativePathSchema = z export const workspaceRelativePathSchema = z
.string() .string()
@@ -392,26 +397,6 @@ export const runtimeModelSourceSchema = z.discriminatedUnion('kind', [
.strict() .strict()
]) ])
export function isDeepSeekHarnessModelProfile(
profile: Pick<
ModelConnectionSettings,
'baseUrl' | 'protocol' | 'authentication'
>
): boolean {
if (
profile.protocol !== 'openai-chat-completions' ||
profile.authentication !== 'api-key'
) {
return false
}
try {
return new URL(profile.baseUrl).hostname.toLowerCase() ===
'api.deepseek.com'
} catch {
return false
}
}
export const runtimeSettingsInputSchema = z export const runtimeSettingsInputSchema = z
.object({ .object({
provider: runtimeProviderSchema, provider: runtimeProviderSchema,
@@ -590,7 +575,7 @@ export const runtimeSettingsInputSchema = z
code: 'custom', code: 'custom',
path: ['deepseekHarnessModelSource'], path: ['deepseekHarnessModelSource'],
message: message:
'DeepSeek Harness 独立模型连接仅支持 api.deepseek.com 的 OpenAI Chat Completions 协议' 'DeepSeek Harness 仅支持使用 API Key 的安全 OpenAI 兼容 Chat Completions 连接'
}) })
} }
} }
@@ -0,0 +1,52 @@
import { describe, expect, it } from 'vitest'
import {
isDeepSeekHarnessCompatibleBaseUrl,
isDeepSeekHarnessModelProfile
} from './deepseek-harness-compatibility'
describe('DeepSeek Harness model compatibility', () => {
it.each([
'https://api.deepseek.com',
'https://gateway.example/openai/v1',
'https://gateway.example:8443/v1',
'http://127.0.0.1:11434/v1',
'http://localhost:1234/v1',
'http://[::1]:8080/v1'
])('accepts OpenAI-compatible base URL %s', (baseUrl) => {
expect(isDeepSeekHarnessCompatibleBaseUrl(baseUrl)).toBe(true)
expect(
isDeepSeekHarnessModelProfile({
baseUrl,
protocol: 'openai-chat-completions',
authentication: 'api-key'
})
).toBe(true)
})
it.each([
'http://gateway.example/v1',
'file:///private/config',
'https://user:secret@gateway.example/v1',
'https://gateway.example/v1?api-version=2025-01-01',
'https://gateway.example/v1#chat'
])('rejects unsafe or unsupported base URL %s', (baseUrl) => {
expect(isDeepSeekHarnessCompatibleBaseUrl(baseUrl)).toBe(false)
})
it('rejects other protocols and unauthenticated profiles', () => {
expect(
isDeepSeekHarnessModelProfile({
baseUrl: 'https://gateway.example/v1',
protocol: 'openai-responses',
authentication: 'api-key'
})
).toBe(false)
expect(
isDeepSeekHarnessModelProfile({
baseUrl: 'https://gateway.example/v1',
protocol: 'openai-chat-completions',
authentication: 'none'
})
).toBe(false)
})
})
@@ -0,0 +1,50 @@
type DeepSeekHarnessModelProfile = {
baseUrl: string
protocol: string
authentication: string
}
function isLoopbackHostname(hostname: string): boolean {
const normalized = hostname
.toLowerCase()
.replace(/^\[|\]$/gu, '')
return (
normalized === 'localhost' ||
normalized === '127.0.0.1' ||
normalized === '::1'
)
}
export function isDeepSeekHarnessCompatibleBaseUrl(
value: string
): boolean {
try {
const url = new URL(value)
if (
!url.hostname ||
url.username ||
url.password ||
url.search ||
url.hash
) {
return false
}
return (
url.protocol === 'https:' ||
(url.protocol === 'http:' &&
isLoopbackHostname(url.hostname))
)
} catch {
return false
}
}
export function isDeepSeekHarnessModelProfile(
profile: DeepSeekHarnessModelProfile
): boolean {
return (
profile.protocol === 'openai-chat-completions' &&
profile.authentication === 'api-key' &&
isDeepSeekHarnessCompatibleBaseUrl(profile.baseUrl)
)
}
+6 -6
View File
@@ -1,4 +1,5 @@
import { z } from 'zod' import { z } from 'zod'
import { isDeepSeekHarnessModelProfile } from './deepseek-harness-compatibility'
const runtimeSelectionProfileIdSchema = z.string().uuid() const runtimeSelectionProfileIdSchema = z.string().uuid()
@@ -82,12 +83,11 @@ function isDeepSeekHarnessRepairProfileUsable(
) { ) {
return false return false
} }
try { return isDeepSeekHarnessModelProfile({
return new URL(profile.baseUrl).hostname.toLowerCase() === baseUrl: profile.baseUrl,
'api.deepseek.com' protocol: profile.protocol,
} catch { authentication: profile.authentication
return false })
}
} }
export function repairChannelRuntimeSelection( export function repairChannelRuntimeSelection(
+37
View File
@@ -295,6 +295,43 @@ describe('release build arguments', () => {
'@koromix/koffi-win32-x64': '3.1.4' '@koromix/koffi-win32-x64': '3.1.4'
}) })
}) })
it('keeps Web3D test fixtures out of release resources', () => {
const packageJson = require('../package.json') as {
build: {
files: string[]
extraResources: Array<{ from: string }>
}
}
const resourceSources = packageJson.build.extraResources.map(
(entry) => entry.from
)
expect(packageJson.build.files).toEqual([
'out/**/*',
'package.json'
])
expect(resourceSources).toContain('resources/skills')
expect(
resourceSources.some((source) => source.startsWith('tests/'))
).toBe(false)
expect(
existsSync(join('resources', 'skills', 'web-3d-game'))
).toBe(false)
expect(
existsSync(join('resources', 'web-3d-game-mcp.mjs'))
).toBe(false)
expect(
existsSync(
join(
'tests',
'fixtures',
'web-3d-game-skill',
'SKILL.md'
)
)
).toBe(true)
})
}) })
describe('release binary architecture detection', () => { describe('release binary architecture detection', () => {