DeepSeek Harness and Weixin binding configuration rejected non-loopback HTTP endpoints even when users explicitly selected those internal services. Configurable endpoints now accept valid HTTP and HTTPS URLs without host-location restrictions, while retaining the required protocol and API-key checks. Release note: 模型连接、DeepSeek Harness 与微信绑定现在可直接使用局域网或公网 HTTP/HTTPS 服务地址,不再强制公网 HTTPS 或本机回环地址。
256 lines
7.3 KiB
TypeScript
256 lines
7.3 KiB
TypeScript
import { EventEmitter } from 'node:events'
|
||
import { mkdir, mkdtemp, realpath, writeFile } from 'node:fs/promises'
|
||
import { tmpdir } from 'node:os'
|
||
import { join } from 'node:path'
|
||
import { PassThrough } from 'node:stream'
|
||
import { describe, expect, it, vi } from 'vitest'
|
||
import {
|
||
DEEPSEEK_HARNESS_CONTROL_PROTOCOL,
|
||
DEEPSEEK_HARNESS_CONTROL_VERSION,
|
||
DEEPSEEK_HARNESS_CREDENTIAL_REF,
|
||
createDeepSeekHarnessUtilityLauncher,
|
||
parseHarnessControlMessage
|
||
} from './deepseek-harness-utility-launcher'
|
||
|
||
class FakeUtility extends EventEmitter {
|
||
readonly messages: unknown[] = []
|
||
readonly stderr = new PassThrough()
|
||
readonly pid = 123
|
||
killed = false
|
||
|
||
postMessage(message: unknown): void {
|
||
this.messages.push(message)
|
||
}
|
||
|
||
kill(): boolean {
|
||
this.killed = true
|
||
return true
|
||
}
|
||
}
|
||
|
||
async function fixture() {
|
||
const root = await realpath(
|
||
await mkdtemp(join(tmpdir(), 'goodbuddy-harness-launcher-'))
|
||
)
|
||
const workspace = join(root, 'workspace')
|
||
const dshHome = join(root, 'home')
|
||
const hostPath = join(
|
||
root,
|
||
'deepseek-harness-host-bootstrap.js'
|
||
)
|
||
await Promise.all([
|
||
mkdir(workspace),
|
||
mkdir(dshHome),
|
||
writeFile(hostPath, '', 'utf8')
|
||
])
|
||
return {
|
||
root,
|
||
dshHome,
|
||
hostPath,
|
||
launchOptions: {
|
||
cwd: workspace,
|
||
signal: new AbortController().signal,
|
||
baseUrl: 'http://gateway.example/openai/v1?api-version=1',
|
||
model: 'qwen-plus',
|
||
supportsImageInput: false,
|
||
credentialRefs: [DEEPSEEK_HARNESS_CREDENTIAL_REF],
|
||
skillPackages: [],
|
||
extensionPackages: []
|
||
}
|
||
}
|
||
}
|
||
|
||
describe('DeepSeek Harness utility launcher', () => {
|
||
it('accepts only strict control messages and secret-free config', () => {
|
||
expect(
|
||
parseHarnessControlMessage({
|
||
protocol: DEEPSEEK_HARNESS_CONTROL_PROTOCOL,
|
||
version: DEEPSEEK_HARNESS_CONTROL_VERSION,
|
||
type: 'ready',
|
||
failedExtensionIds: []
|
||
})
|
||
).toMatchObject({ type: 'ready' })
|
||
expect(
|
||
parseHarnessControlMessage({
|
||
protocol: DEEPSEEK_HARNESS_CONTROL_PROTOCOL,
|
||
version: DEEPSEEK_HARNESS_CONTROL_VERSION,
|
||
type: 'ready',
|
||
failedExtensionIds: [],
|
||
apiKey: 'must-not-pass'
|
||
})
|
||
).toBeUndefined()
|
||
})
|
||
|
||
it('waits for Host readiness and sends no credential value', async () => {
|
||
const { dshHome, hostPath, launchOptions } = await fixture()
|
||
const utility = new FakeUtility()
|
||
const fork = vi.fn(() => utility as never)
|
||
const launcher = createDeepSeekHarnessUtilityLauncher({
|
||
bundledHostPath: hostPath,
|
||
dshHome,
|
||
environment: { PATH: 'C:\\Tools' },
|
||
fork
|
||
})
|
||
|
||
const launching = launcher(launchOptions)
|
||
await vi.waitFor(() =>
|
||
expect(utility.messages).toHaveLength(1)
|
||
)
|
||
expect(JSON.stringify(utility.messages[0])).not.toContain(
|
||
'secret'
|
||
)
|
||
expect(utility.messages[0]).toMatchObject({
|
||
type: 'start',
|
||
config: {
|
||
baseUrl: 'http://gateway.example/openai/v1?api-version=1',
|
||
model: 'qwen-plus',
|
||
supportsImageInput: false,
|
||
credentialRefs: [DEEPSEEK_HARNESS_CREDENTIAL_REF]
|
||
}
|
||
})
|
||
utility.emit('message', {
|
||
protocol: DEEPSEEK_HARNESS_CONTROL_PROTOCOL,
|
||
version: DEEPSEEK_HARNESS_CONTROL_VERSION,
|
||
type: 'ready',
|
||
failedExtensionIds: []
|
||
})
|
||
|
||
await expect(launching).resolves.toMatchObject({
|
||
stdin: expect.any(WritableStream),
|
||
stdout: expect.any(ReadableStream)
|
||
})
|
||
expect(fork).toHaveBeenCalledWith(
|
||
hostPath,
|
||
[],
|
||
expect.objectContaining({
|
||
cwd: launchOptions.cwd,
|
||
stdio: ['ignore', 'ignore', 'pipe']
|
||
})
|
||
)
|
||
})
|
||
|
||
it('persists extension startup failures before exposing the child', async () => {
|
||
const { root, dshHome, hostPath, launchOptions } =
|
||
await fixture()
|
||
const entrypoint = join(root, 'greet.mjs')
|
||
await writeFile(entrypoint, 'export function apply() {}\n', 'utf8')
|
||
const utility = new FakeUtility()
|
||
const onExtensionStartupFailures = vi.fn(async () => undefined)
|
||
const launcher = createDeepSeekHarnessUtilityLauncher({
|
||
bundledHostPath: hostPath,
|
||
dshHome,
|
||
environment: {},
|
||
fork: () => utility as never,
|
||
onExtensionStartupFailures
|
||
})
|
||
|
||
const launching = launcher({
|
||
...launchOptions,
|
||
extensionPackages: [
|
||
{
|
||
id: 'greet',
|
||
entrypoint,
|
||
configuration: {}
|
||
}
|
||
]
|
||
})
|
||
await vi.waitFor(() =>
|
||
expect(utility.messages).toHaveLength(1)
|
||
)
|
||
utility.emit('message', {
|
||
protocol: DEEPSEEK_HARNESS_CONTROL_PROTOCOL,
|
||
version: DEEPSEEK_HARNESS_CONTROL_VERSION,
|
||
type: 'ready',
|
||
failedExtensionIds: ['greet']
|
||
})
|
||
|
||
await expect(launching).resolves.toBeDefined()
|
||
expect(onExtensionStartupFailures).toHaveBeenCalledWith([
|
||
'greet'
|
||
])
|
||
})
|
||
|
||
it('fails when the Host exits while startup failures are being persisted', async () => {
|
||
const { dshHome, hostPath, launchOptions } = await fixture()
|
||
const utility = new FakeUtility()
|
||
let finishPersistence!: () => void
|
||
const persistence = new Promise<void>((resolve) => {
|
||
finishPersistence = resolve
|
||
})
|
||
const onExtensionStartupFailures = vi.fn(() => persistence)
|
||
const terminateProcess = vi.fn()
|
||
const launcher = createDeepSeekHarnessUtilityLauncher({
|
||
bundledHostPath: hostPath,
|
||
dshHome,
|
||
environment: {},
|
||
fork: () => utility as never,
|
||
terminateProcess,
|
||
onExtensionStartupFailures
|
||
})
|
||
|
||
const launching = launcher(launchOptions)
|
||
await vi.waitFor(() =>
|
||
expect(utility.messages).toHaveLength(1)
|
||
)
|
||
utility.emit('message', {
|
||
protocol: DEEPSEEK_HARNESS_CONTROL_PROTOCOL,
|
||
version: DEEPSEEK_HARNESS_CONTROL_VERSION,
|
||
type: 'ready',
|
||
failedExtensionIds: ['greet']
|
||
})
|
||
await vi.waitFor(() =>
|
||
expect(onExtensionStartupFailures).toHaveBeenCalledOnce()
|
||
)
|
||
utility.emit('exit', 9)
|
||
|
||
await expect(launching).rejects.toThrow(
|
||
'Host 启动前退出(code 9)'
|
||
)
|
||
expect(terminateProcess).toHaveBeenCalledOnce()
|
||
finishPersistence()
|
||
})
|
||
|
||
it('fails closed on an invalid Host startup message', async () => {
|
||
const { dshHome, hostPath, launchOptions } = await fixture()
|
||
const utility = new FakeUtility()
|
||
const terminateProcess = vi.fn(() => {
|
||
utility.killed = true
|
||
})
|
||
const launcher = createDeepSeekHarnessUtilityLauncher({
|
||
bundledHostPath: hostPath,
|
||
dshHome,
|
||
environment: {},
|
||
fork: () => utility as never,
|
||
terminateProcess
|
||
})
|
||
|
||
const launching = launcher(launchOptions)
|
||
await vi.waitFor(() =>
|
||
expect(utility.messages).toHaveLength(1)
|
||
)
|
||
utility.emit('message', { type: 'ready' })
|
||
|
||
await expect(launching).rejects.toThrow('启动协议无效')
|
||
expect(terminateProcess).toHaveBeenCalledOnce()
|
||
})
|
||
|
||
it.each([
|
||
'file:///private/config',
|
||
'ftp://gateway.example/v1'
|
||
])('rejects unsupported 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('HTTP 或 HTTPS')
|
||
expect(fork).not.toHaveBeenCalled()
|
||
})
|
||
})
|