feat: expand DeepSeek Harness compatibility
This commit is contained in:
@@ -99,10 +99,37 @@ describe('createAgentRuntime model compatibility', () => {
|
||||
{ deepseekHarnessLauncher: vi.fn() }
|
||||
)
|
||||
).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 () => {
|
||||
const runtime = createAgentRuntime(process.cwd(), settings())
|
||||
|
||||
|
||||
@@ -113,7 +113,7 @@ export function createAgentRuntime(
|
||||
const profile = settings?.deepseekHarnessModelProfile
|
||||
if (!profile || !isDeepSeekHarnessModelProfile(profile)) {
|
||||
throw new Error(
|
||||
'DeepSeek Harness 需要 api.deepseek.com 的 OpenAI Chat Completions 模型连接'
|
||||
'DeepSeek Harness 需要使用 API Key 的安全 OpenAI 兼容 Chat Completions 模型连接'
|
||||
)
|
||||
}
|
||||
if (!profile.apiKey) {
|
||||
@@ -131,7 +131,7 @@ export function createAgentRuntime(
|
||||
model: profile.modelName,
|
||||
launch: capabilities.deepseekHarnessLauncher,
|
||||
credentialRefs: {
|
||||
GOODBUDDY_DEEPSEEK_API_KEY: profile.apiKey
|
||||
GOODBUDDY_HARNESS_MODEL_API_KEY: profile.apiKey
|
||||
},
|
||||
requiredSandboxEnforcement:
|
||||
sandboxMode === 'strict' ? 'full' : 'partial',
|
||||
|
||||
@@ -26,7 +26,7 @@ import {
|
||||
import { GOODBUDDY_HARNESS_MAX_STEP_TOKENS } from './goodbuddy-harness-control-plane'
|
||||
|
||||
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 MCP_CALL_ID = 'e2e-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',
|
||||
directory: resolve(
|
||||
'resources',
|
||||
'skills',
|
||||
'web-3d-game'
|
||||
'tests',
|
||||
'fixtures',
|
||||
'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: {
|
||||
cwd: workspace,
|
||||
signal: new AbortController().signal,
|
||||
baseUrl: 'https://api.deepseek.com',
|
||||
model: 'deepseek-chat',
|
||||
baseUrl: 'https://gateway.example/openai/v1',
|
||||
model: 'qwen-plus',
|
||||
credentialRefs: [DEEPSEEK_HARNESS_CREDENTIAL_REF],
|
||||
skillPackages: []
|
||||
}
|
||||
@@ -97,7 +97,8 @@ describe('DeepSeek Harness utility launcher', () => {
|
||||
expect(utility.messages[0]).toMatchObject({
|
||||
type: 'start',
|
||||
config: {
|
||||
baseUrl: 'https://api.deepseek.com',
|
||||
baseUrl: 'https://gateway.example/openai/v1',
|
||||
model: 'qwen-plus',
|
||||
credentialRefs: [DEEPSEEK_HARNESS_CREDENTIAL_REF]
|
||||
}
|
||||
})
|
||||
@@ -144,4 +145,24 @@ describe('DeepSeek Harness utility launcher', () => {
|
||||
await expect(launching).rejects.toThrow('启动协议无效')
|
||||
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 type { UtilityProcess } from 'electron'
|
||||
import { z } from 'zod'
|
||||
import { isDeepSeekHarnessCompatibleBaseUrl } from '../../shared/deepseek-harness-compatibility'
|
||||
import type {
|
||||
DeepSeekHarnessChild,
|
||||
DeepSeekHarnessLaunchOptions
|
||||
@@ -14,7 +15,7 @@ export const DEEPSEEK_HARNESS_CONTROL_PROTOCOL =
|
||||
export const DEEPSEEK_HARNESS_CONTROL_VERSION = 1
|
||||
export const DEEPSEEK_HARNESS_HOST_VERSION = '0.1.0-rc.6'
|
||||
export const DEEPSEEK_HARNESS_CREDENTIAL_REF =
|
||||
'GOODBUDDY_DEEPSEEK_API_KEY'
|
||||
'GOODBUDDY_HARNESS_MODEL_API_KEY'
|
||||
|
||||
const sandboxSchema = z
|
||||
.object({
|
||||
@@ -41,15 +42,7 @@ export const controlledHarnessHostConfigSchema = z
|
||||
baseUrl: z
|
||||
.url()
|
||||
.max(2_048)
|
||||
.refine((value) => {
|
||||
const url = new URL(value)
|
||||
return (
|
||||
url.protocol === 'https:' &&
|
||||
url.hostname.toLowerCase() === 'api.deepseek.com' &&
|
||||
!url.username &&
|
||||
!url.password
|
||||
)
|
||||
}),
|
||||
.refine(isDeepSeekHarnessCompatibleBaseUrl),
|
||||
api: z.literal('openai-completions'),
|
||||
provider: z.literal('goodbuddy'),
|
||||
model: z.string().min(1).max(128),
|
||||
@@ -240,12 +233,9 @@ export function createDeepSeekHarnessUtilityLauncher(
|
||||
'DeepSeek Harness 当前平台只能提供部分沙箱强制'
|
||||
)
|
||||
}
|
||||
if (
|
||||
options.baseUrl !== 'https://api.deepseek.com' &&
|
||||
options.baseUrl !== 'https://api.deepseek.com/'
|
||||
) {
|
||||
if (!isDeepSeekHarnessCompatibleBaseUrl(options.baseUrl)) {
|
||||
throw new Error(
|
||||
'DeepSeek Harness 仅允许 api.deepseek.com'
|
||||
'DeepSeek Harness 模型地址必须使用 HTTPS 或本机回环 HTTP,且不得包含凭据、查询参数或片段'
|
||||
)
|
||||
}
|
||||
if (
|
||||
|
||||
@@ -10,7 +10,7 @@ const defaultProfileId = '00000000-0000-4000-8000-000000000001'
|
||||
const secondProfileId = '00000000-0000-4000-8000-000000000002'
|
||||
const responsesProfileId = '00000000-0000-4000-8000-000000000003'
|
||||
const imageProfileId = '00000000-0000-4000-8000-000000000004'
|
||||
const deepseekProfileId = '00000000-0000-4000-8000-000000000005'
|
||||
const harnessProfileId = '00000000-0000-4000-8000-000000000005'
|
||||
|
||||
function settings(
|
||||
overrides: Partial<ResolvedRuntimeSettings> = {}
|
||||
@@ -65,10 +65,10 @@ function settings(
|
||||
apiKey: 'image-key'
|
||||
},
|
||||
{
|
||||
id: deepseekProfileId,
|
||||
name: 'DeepSeek',
|
||||
baseUrl: 'https://api.deepseek.com',
|
||||
modelName: 'deepseek-chat',
|
||||
id: harnessProfileId,
|
||||
name: 'OpenAI-compatible gateway',
|
||||
baseUrl: 'https://gateway.example/openai/v1',
|
||||
modelName: 'qwen-plus',
|
||||
protocol: 'openai-chat-completions',
|
||||
authentication: 'api-key',
|
||||
imageGenerationQuality: 'auto',
|
||||
@@ -165,31 +165,31 @@ describe('runtime selection', () => {
|
||||
).toThrow('自动启动')
|
||||
})
|
||||
|
||||
it('selects DeepSeek Harness only with an official compatible profile', () => {
|
||||
it('selects DeepSeek Harness with a compatible gateway profile', () => {
|
||||
const selected = applyRuntimeSelection(settings(), {
|
||||
provider: 'deepseek-harness',
|
||||
profileId: deepseekProfileId
|
||||
profileId: harnessProfileId
|
||||
})
|
||||
expect(selected.target).toBe('deepseek-harness')
|
||||
expect(selected.settings).toMatchObject({
|
||||
provider: 'deepseek-harness',
|
||||
deepseekHarnessModelProfile: { id: deepseekProfileId }
|
||||
deepseekHarnessModelProfile: { id: harnessProfileId }
|
||||
})
|
||||
expect(() =>
|
||||
applyRuntimeSelection(settings(), {
|
||||
provider: 'deepseek-harness',
|
||||
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 platformProfile = {
|
||||
...base.modelProfiles[4]!,
|
||||
id: 'goodbuddy-platform-deepseek',
|
||||
name: '平台 DeepSeek',
|
||||
modelName: 'deepseek-v4-flash'
|
||||
id: 'goodbuddy-platform-harness',
|
||||
name: '管理员预置模型',
|
||||
modelName: 'qwen-plus'
|
||||
}
|
||||
const selected = applyRuntimeSelection(
|
||||
settings({ deepseekHarnessModelProfile: platformProfile }),
|
||||
@@ -199,8 +199,8 @@ describe('runtime selection', () => {
|
||||
expect(selected.settings).toMatchObject({
|
||||
provider: 'deepseek-harness',
|
||||
deepseekHarnessModelProfile: {
|
||||
id: 'goodbuddy-platform-deepseek',
|
||||
modelName: 'deepseek-v4-flash'
|
||||
id: 'goodbuddy-platform-harness',
|
||||
modelName: 'qwen-plus'
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -219,7 +219,7 @@ describe('runtime selection', () => {
|
||||
})
|
||||
).toEqual({
|
||||
provider: 'deepseek-harness',
|
||||
profileId: deepseekProfileId
|
||||
profileId: harnessProfileId
|
||||
})
|
||||
expect(
|
||||
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 configured = settings({
|
||||
deepseekHarnessModelProfile: {
|
||||
...base.modelProfiles[4]!,
|
||||
id: 'goodbuddy-platform-deepseek',
|
||||
name: '平台 DeepSeek'
|
||||
id: 'goodbuddy-platform-harness',
|
||||
name: '管理员预置模型'
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -139,7 +139,7 @@ export function applyRuntimeSelection(
|
||||
!isDeepSeekHarnessModelProfile(selectedProfile)
|
||||
) {
|
||||
throw new Error(
|
||||
'DeepSeek Harness 独立模型连接仅支持 api.deepseek.com 的 OpenAI Chat Completions 协议'
|
||||
'DeepSeek Harness 仅支持使用 API Key 的安全 OpenAI 兼容 Chat Completions 连接'
|
||||
)
|
||||
}
|
||||
return {
|
||||
|
||||
@@ -63,12 +63,8 @@ describe('bundled skills', () => {
|
||||
expect(snapshot.skills.map((skill) => skill.id)).toContain(
|
||||
'product-marketing'
|
||||
)
|
||||
expect(snapshot.skills).toContainEqual(
|
||||
expect.objectContaining({
|
||||
id: 'web-3d-game',
|
||||
name: 'Web 3D Game',
|
||||
assignments: expect.arrayContaining(['deepseek-harness'])
|
||||
})
|
||||
expect(snapshot.skills.map((skill) => skill.id)).not.toContain(
|
||||
'web-3d-game'
|
||||
)
|
||||
})
|
||||
|
||||
@@ -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()
|
||||
|
||||
await expect(
|
||||
@@ -92,8 +88,8 @@ describe('bundled skills', () => {
|
||||
).resolves.toMatchObject({
|
||||
packages: expect.arrayContaining([
|
||||
{
|
||||
id: 'web-3d-game',
|
||||
directory: join(builtinSkillsRoot, 'web-3d-game')
|
||||
id: 'product-marketing',
|
||||
directory: join(builtinSkillsRoot, 'product-marketing')
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
@@ -12,9 +12,12 @@ import {
|
||||
startControlledDeepSeekHarnessHost,
|
||||
type ControlledHarnessHost
|
||||
} from './deepseek-harness-host'
|
||||
import { installHarnessChildProcessWindowGuard } from './agent/deepseek-harness-child-process'
|
||||
|
||||
const parentPort = process.parentPort
|
||||
const restoreDiagnostics = installHarnessDiagnosticGuard()
|
||||
const restoreChildProcessWindowGuard =
|
||||
installHarnessChildProcessWindowGuard()
|
||||
// The Windows ACL sandbox launches its JavaScript runner through
|
||||
// `process.execPath`. Inside an Electron UtilityProcess that path is Electron,
|
||||
// so descendants must opt into Electron's supported Node execution mode.
|
||||
@@ -41,6 +44,7 @@ async function close(): Promise<void> {
|
||||
closed = true
|
||||
await host?.dispose().catch(() => undefined)
|
||||
transport?.dispose()
|
||||
restoreChildProcessWindowGuard()
|
||||
restoreDiagnostics()
|
||||
}
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ describe('controlled DeepSeek Harness host', () => {
|
||||
dshHome: 'C:\\controlled-dsh-home',
|
||||
skillPackages: []
|
||||
})
|
||||
).rejects.toThrow('trusted HTTPS DeepSeek endpoint')
|
||||
).rejects.toThrow('secure OpenAI-compatible')
|
||||
})
|
||||
|
||||
it('suppresses console payloads instead of contaminating stdout', () => {
|
||||
@@ -84,10 +84,10 @@ describe('controlled DeepSeek Harness host', () => {
|
||||
const host = await startControlledDeepSeekHarnessHost({
|
||||
workspace: root,
|
||||
dshHome: root,
|
||||
baseUrl: 'https://api.deepseek.com',
|
||||
baseUrl: 'https://gateway.example/openai/v1',
|
||||
api: 'openai-completions',
|
||||
provider: 'goodbuddy',
|
||||
model: 'deepseek-test',
|
||||
model: 'qwen-plus',
|
||||
harnessVersion: '0.1.0-rc.6',
|
||||
sandbox: expectedSandbox,
|
||||
credentialRefs: ['GOODBUDDY_API_KEY'],
|
||||
|
||||
@@ -30,6 +30,7 @@ import {
|
||||
} from './agent/goodbuddy-harness-control-plane'
|
||||
import type { Stream } from '@agentclientprotocol/sdk'
|
||||
import type { SandboxEnforcement } from '@deepseek-ai/dsh-sandbox'
|
||||
import { isDeepSeekHarnessCompatibleBaseUrl } from '../shared/deepseek-harness-compatibility'
|
||||
|
||||
const DEFAULT_MAX_FRAME_BYTES = 1024 * 1024
|
||||
const MAX_DIAGNOSTIC_BYTES = 64 * 1024
|
||||
@@ -133,19 +134,12 @@ type PluginSpec = {
|
||||
function validateHostConfig(
|
||||
config: ControlledHarnessHostConfig
|
||||
): void {
|
||||
const endpoint = URL.canParse(config.baseUrl)
|
||||
? new URL(config.baseUrl)
|
||||
: undefined
|
||||
if (
|
||||
config.api !== 'openai-completions' ||
|
||||
!endpoint ||
|
||||
endpoint.protocol !== 'https:' ||
|
||||
endpoint.hostname.toLowerCase() !== 'api.deepseek.com' ||
|
||||
endpoint.username ||
|
||||
endpoint.password
|
||||
!isDeepSeekHarnessCompatibleBaseUrl(config.baseUrl)
|
||||
) {
|
||||
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) {
|
||||
|
||||
@@ -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()
|
||||
await store.update(settings())
|
||||
const versionFourteen = JSON.parse(
|
||||
@@ -127,9 +127,9 @@ describe('RuntimeSettingsStore', () => {
|
||||
modelProfiles: [
|
||||
{
|
||||
id: profileId,
|
||||
name: 'DeepSeek',
|
||||
baseUrl: 'https://api.deepseek.com',
|
||||
modelName: 'deepseek-chat',
|
||||
name: 'OpenAI-compatible gateway',
|
||||
baseUrl: 'https://gateway.example/openai/v1',
|
||||
modelName: 'qwen-plus',
|
||||
protocol: 'openai-chat-completions',
|
||||
authentication: 'api-key',
|
||||
imageGenerationQuality: 'auto',
|
||||
@@ -149,21 +149,22 @@ describe('RuntimeSettingsStore', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('resolves a controlled platform DeepSeek profile without exposing its credential', async () => {
|
||||
const apiKey = 'platform-deepseek-secret'
|
||||
it('resolves a controlled platform Harness profile without exposing its credential', async () => {
|
||||
const apiKey = 'platform-harness-secret'
|
||||
const { store } = await createStore({
|
||||
GOODBUDDY_MODEL_API_KEY: apiKey,
|
||||
GOODBUDDY_MODEL_BASE_URL: 'https://api.deepseek.com/',
|
||||
GOODBUDDY_MODEL_NAME: 'deepseek-v4-flash'
|
||||
GOODBUDDY_MODEL_BASE_URL:
|
||||
'https://gateway.example/openai/v1',
|
||||
GOODBUDDY_MODEL_NAME: 'qwen-plus'
|
||||
})
|
||||
|
||||
await expect(store.getResolvedSettings()).resolves.toMatchObject({
|
||||
modelProtocol: 'anthropic-messages',
|
||||
deepseekHarnessModelProfile: {
|
||||
id: 'goodbuddy-platform-deepseek',
|
||||
name: '平台 DeepSeek',
|
||||
baseUrl: 'https://api.deepseek.com/',
|
||||
modelName: 'deepseek-v4-flash',
|
||||
id: 'goodbuddy-platform-harness',
|
||||
name: '管理员预置模型',
|
||||
baseUrl: 'https://gateway.example/openai/v1',
|
||||
modelName: 'qwen-plus',
|
||||
protocol: 'openai-chat-completions',
|
||||
authentication: 'api-key',
|
||||
supportsImageInput: false,
|
||||
@@ -179,37 +180,39 @@ describe('RuntimeSettingsStore', () => {
|
||||
|
||||
it.each([
|
||||
[
|
||||
'a non-DeepSeek endpoint',
|
||||
'an insecure public endpoint',
|
||||
{
|
||||
GOODBUDDY_MODEL_API_KEY: 'platform-key',
|
||||
GOODBUDDY_MODEL_BASE_URL: 'https://deepseek.example',
|
||||
GOODBUDDY_MODEL_NAME: 'deepseek-chat'
|
||||
GOODBUDDY_MODEL_BASE_URL: 'http://gateway.example/v1',
|
||||
GOODBUDDY_MODEL_NAME: 'qwen-plus'
|
||||
}
|
||||
],
|
||||
[
|
||||
'an insecure DeepSeek endpoint',
|
||||
'an endpoint with embedded credentials',
|
||||
{
|
||||
GOODBUDDY_MODEL_API_KEY: 'platform-key',
|
||||
GOODBUDDY_MODEL_BASE_URL: 'http://api.deepseek.com',
|
||||
GOODBUDDY_MODEL_NAME: 'deepseek-chat'
|
||||
GOODBUDDY_MODEL_BASE_URL:
|
||||
'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_BASE_URL: 'https://api.deepseek.com/v1',
|
||||
GOODBUDDY_MODEL_NAME: 'deepseek-chat'
|
||||
GOODBUDDY_MODEL_BASE_URL:
|
||||
'https://gateway.example/v1?api-version=1',
|
||||
GOODBUDDY_MODEL_NAME: 'qwen-plus'
|
||||
}
|
||||
],
|
||||
[
|
||||
'a missing API key',
|
||||
{
|
||||
GOODBUDDY_MODEL_BASE_URL: 'https://api.deepseek.com',
|
||||
GOODBUDDY_MODEL_NAME: 'deepseek-chat'
|
||||
GOODBUDDY_MODEL_BASE_URL: 'https://gateway.example/v1',
|
||||
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)
|
||||
|
||||
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'
|
||||
expect(
|
||||
runtimeSettingsInputSchema.safeParse(
|
||||
@@ -261,9 +264,9 @@ describe('RuntimeSettingsStore', () => {
|
||||
modelProfiles: [
|
||||
{
|
||||
id: profileId,
|
||||
name: 'Other compatible API',
|
||||
name: 'Compatible API',
|
||||
baseUrl: 'https://other.example/v1',
|
||||
modelName: 'deepseek-chat',
|
||||
modelName: 'qwen-plus',
|
||||
protocol: 'openai-chat-completions',
|
||||
authentication: 'api-key',
|
||||
imageGenerationQuality: 'auto',
|
||||
@@ -274,16 +277,16 @@ describe('RuntimeSettingsStore', () => {
|
||||
deepseekHarnessModelSource: { kind: 'profile', profileId }
|
||||
})
|
||||
).success
|
||||
).toBe(false)
|
||||
).toBe(true)
|
||||
expect(
|
||||
runtimeSettingsInputSchema.safeParse(
|
||||
settings({
|
||||
modelProfiles: [
|
||||
{
|
||||
id: profileId,
|
||||
name: 'DeepSeek without API key',
|
||||
baseUrl: 'https://api.deepseek.com',
|
||||
modelName: 'deepseek-chat',
|
||||
name: 'Gateway without API key',
|
||||
baseUrl: 'https://other.example/v1',
|
||||
modelName: 'qwen-plus',
|
||||
protocol: 'openai-chat-completions',
|
||||
authentication: 'none',
|
||||
imageGenerationQuality: 'auto',
|
||||
|
||||
@@ -263,7 +263,7 @@ const embeddingCredentialPayloadSchema = z.object({
|
||||
})
|
||||
|
||||
const rerankCredentialPayloadSchema = embeddingCredentialPayloadSchema
|
||||
const platformDeepSeekProfileId = 'goodbuddy-platform-deepseek'
|
||||
const platformHarnessProfileId = 'goodbuddy-platform-harness'
|
||||
|
||||
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 baseUrl = this.environment.GOODBUDDY_MODEL_BASE_URL?.trim()
|
||||
const modelName = this.environment.GOODBUDDY_MODEL_NAME?.trim()
|
||||
if (!apiKey || !baseUrl || !modelName) {
|
||||
return undefined
|
||||
}
|
||||
try {
|
||||
const endpoint = new URL(baseUrl)
|
||||
if (
|
||||
endpoint.protocol !== 'https:' ||
|
||||
endpoint.hostname.toLowerCase() !== 'api.deepseek.com' ||
|
||||
endpoint.port ||
|
||||
endpoint.pathname !== '/' ||
|
||||
endpoint.search ||
|
||||
endpoint.hash ||
|
||||
endpoint.username ||
|
||||
endpoint.password
|
||||
) {
|
||||
return undefined
|
||||
}
|
||||
} catch {
|
||||
if (
|
||||
!isDeepSeekHarnessModelProfile({
|
||||
baseUrl,
|
||||
protocol: 'openai-chat-completions',
|
||||
authentication: 'api-key'
|
||||
})
|
||||
) {
|
||||
return undefined
|
||||
}
|
||||
return {
|
||||
id: platformDeepSeekProfileId,
|
||||
name: '平台 DeepSeek',
|
||||
id: platformHarnessProfileId,
|
||||
name: '管理员预置模型',
|
||||
baseUrl,
|
||||
modelName,
|
||||
protocol: 'openai-chat-completions',
|
||||
@@ -1406,7 +1398,7 @@ export class RuntimeSettingsStore {
|
||||
? profilesById.get(
|
||||
settings.deepseekHarnessModelSource.profileId
|
||||
)
|
||||
: this.resolvePlatformDeepSeekProfile()
|
||||
: this.resolvePlatformHarnessProfile()
|
||||
return {
|
||||
provider: settings.provider,
|
||||
modelBaseUrl: effective.baseUrl,
|
||||
@@ -1726,7 +1718,7 @@ export class RuntimeSettingsStore {
|
||||
}
|
||||
if (!isDeepSeekHarnessModelProfile(profile)) {
|
||||
throw new Error(
|
||||
'DeepSeek Harness 模型连接仅支持 api.deepseek.com 的 OpenAI Chat Completions 协议'
|
||||
'DeepSeek Harness 仅支持使用 API Key 的安全 OpenAI 兼容 Chat Completions 连接'
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user