Files
goodbuddy/src/main/agent/deepseek-harness-utility-launcher.ts
T
mesalogo 4e60a4ca61 feat: customize model provider requests
Model connections can now save bounded JSON request headers and top-level body fields. Direct requests and Continue support both; local OpenCode and DeepSeek Harness use only native header support, while the managed remote OpenCode gateway applies both.

Runtime, protocol, and authentication fields keep precedence. Custom values are ordinary connection settings, not credential storage. This migrates Runtime settings from v20 to v21 and updates the DeepSeek control and managed Runtime ACP capabilities.

Release note: 模型连接详情新增自定义请求头和请求体;直连与 Continue 支持两者,本地 OpenCode 和 DeepSeek Harness 仅应用原生支持的请求头,受管远程 OpenCode 支持两者,界面会明确提示生效范围。
2026-09-03 22:34:01 +08:00

316 lines
9.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { Readable } from 'node:stream'
import { realpath, stat } from 'node:fs/promises'
import { isAbsolute } from 'node:path'
import type { UtilityProcess } from 'electron'
import { isDeepSeekHarnessCompatibleBaseUrl } from '../../shared/deepseek-harness-compatibility'
import type {
DeepSeekHarnessChild,
DeepSeekHarnessLaunchOptions
} from './deepseek-harness-runtime'
import { createDeepSeekHarnessUtilityChild } from './deepseek-harness-utility-transport'
import {
controlledHarnessHostConfigSchema,
DEEPSEEK_HARNESS_CONTROL_PROTOCOL,
DEEPSEEK_HARNESS_CONTROL_VERSION,
DEEPSEEK_HARNESS_CREDENTIAL_REF,
DEEPSEEK_HARNESS_HOST_VERSION,
DEEPSEEK_HARNESS_MAX_FRAME_BYTES,
deepSeekHarnessStartupBudget,
parseHarnessControlMessage,
type DeepSeekHarnessControlMessage as HarnessControlMessage
} from './deepseek-harness-control-protocol'
import { applyLaunchEnvironmentPath } from './process-environment'
import type { LaunchEnvironmentProvider } from '../local-tool-environment/launch-environment-provider'
export {
controlledHarnessHostConfigSchema,
DEEPSEEK_HARNESS_CONTROL_PROTOCOL,
DEEPSEEK_HARNESS_CONTROL_VERSION,
DEEPSEEK_HARNESS_CREDENTIAL_REF,
DEEPSEEK_HARNESS_HOST_VERSION,
DEEPSEEK_HARNESS_MAX_FRAME_BYTES,
parseHarnessControlMessage
} from './deepseek-harness-control-protocol'
export type {
ControlledHarnessBootstrapConfig,
DeepSeekHarnessControlMessage
} from './deepseek-harness-control-protocol'
export type DeepSeekHarnessFork = (
modulePath: string,
args: string[],
options: {
cwd: string
env: NodeJS.ProcessEnv
serviceName: string
stdio: ['ignore', 'ignore', 'pipe']
}
) => UtilityProcess
export type DeepSeekHarnessUtilityLauncherOptions = {
bundledHostPath: string
dshHome: string
environment: NodeJS.ProcessEnv
launchEnvironmentProvider?: LaunchEnvironmentProvider
fork: DeepSeekHarnessFork
terminateProcess?: (utility: UtilityProcess) => void
onExtensionStartupFailures?: (
extensionIds: readonly string[]
) => Promise<void>
/**
* Explicit hard Host-handshake deadline. Callers that also set the Runtime
* initialization timeout must leave enough additional time for startup
* failure persistence.
*/
startupTimeoutMs?: number
}
function hasControlCharacter(value: string): boolean {
for (const character of value) {
const codePoint = character.codePointAt(0)
if (
codePoint !== undefined &&
(codePoint <= 0x1f || codePoint === 0x7f)
) {
return true
}
}
return false
}
export function createDeepSeekHarnessUtilityLauncher(
launcherOptions: DeepSeekHarnessUtilityLauncherOptions
): (options: DeepSeekHarnessLaunchOptions) => Promise<DeepSeekHarnessChild> {
return async (options) => {
options.signal.throwIfAborted()
const hostPath = launcherOptions.bundledHostPath
if (!isAbsolute(hostPath)) {
throw new Error('DeepSeek Harness Host 路径必须为绝对路径')
}
if (!isAbsolute(options.cwd) || !isAbsolute(launcherOptions.dshHome)) {
throw new Error(
'DeepSeek Harness 工作区和隔离目录必须为绝对路径'
)
}
if (
options.model.length === 0 ||
options.model.length > 128 ||
hasControlCharacter(options.model)
) {
throw new Error('DeepSeek Harness 模型名称无效')
}
const canonicalSkillPackages = await Promise.all(
options.skillPackages.map(async (skill) => {
const directory = await realpath(skill.directory)
const metadata = await stat(directory)
if (!metadata.isDirectory()) {
throw new Error(
'DeepSeek Harness Skill 路径必须为目录'
)
}
return {
id: skill.id,
directory
}
})
)
const canonicalExtensionPackages = await Promise.all(
options.extensionPackages.map(async (extension) => {
const entrypoint = await realpath(extension.entrypoint)
const metadata = await stat(entrypoint)
if (!metadata.isFile()) {
throw new Error(
'DeepSeek Harness 插件入口必须为文件'
)
}
return {
...extension,
entrypoint
}
})
)
const [canonicalHostPath, canonicalWorkspace, canonicalDshHome] =
await Promise.all([
realpath(hostPath),
realpath(options.cwd),
realpath(launcherOptions.dshHome)
])
const [hostMetadata, workspaceMetadata, homeMetadata] =
await Promise.all([
stat(canonicalHostPath),
stat(canonicalWorkspace),
stat(canonicalDshHome)
])
if (
!hostMetadata.isFile() ||
!workspaceMetadata.isDirectory() ||
!homeMetadata.isDirectory()
) {
throw new Error(
'DeepSeek Harness Host、工作区或隔离目录类型无效'
)
}
if (!isDeepSeekHarnessCompatibleBaseUrl(options.baseUrl)) {
throw new Error(
'DeepSeek Harness 模型地址必须使用 HTTP 或 HTTPS'
)
}
if (
options.credentialRefs.length !== 1 ||
options.credentialRefs[0] !==
DEEPSEEK_HARNESS_CREDENTIAL_REF
) {
throw new Error('DeepSeek Harness 凭据引用不受信任')
}
options.signal.throwIfAborted()
const utility = launcherOptions.fork(canonicalHostPath, [], {
cwd: canonicalWorkspace,
env: applyLaunchEnvironmentPath(
{ ...launcherOptions.environment },
launcherOptions.launchEnvironmentProvider
),
serviceName: 'GoodBuddy DeepSeek Harness Host',
stdio: ['ignore', 'ignore', 'pipe']
})
let terminated = false
const terminate = (): void => {
if (terminated) {
return
}
terminated = true
if (launcherOptions.terminateProcess) {
launcherOptions.terminateProcess(utility)
} else {
utility.kill()
}
}
const startupTimeoutMs =
launcherOptions.startupTimeoutMs ??
deepSeekHarnessStartupBudget(
canonicalExtensionPackages.length
).hostTimeoutMs
let timer: ReturnType<typeof setTimeout> | undefined
let onAbort: (() => void) | undefined
try {
await new Promise<void>((resolve, reject) => {
let settled = false
const cleanup = (): void => {
if (timer) {
clearTimeout(timer)
}
if (onAbort) {
options.signal.removeEventListener('abort', onAbort)
}
utility.removeListener('message', onMessage)
utility.removeListener('exit', onExit)
}
const fail = (error: Error): void => {
if (settled) {
return
}
settled = true
cleanup()
terminate()
reject(error)
}
const succeed = (): void => {
if (settled) {
return
}
settled = true
cleanup()
resolve()
}
const onMessage = (message: unknown): void => {
const control = parseHarnessControlMessage(message)
if (!control) {
fail(new Error('DeepSeek Harness Host 启动协议无效'))
return
}
if (control.type === 'ready') {
utility.removeListener('message', onMessage)
if (timer) {
clearTimeout(timer)
timer = undefined
}
void (
control.failedExtensionIds.length > 0
? launcherOptions.onExtensionStartupFailures?.(
control.failedExtensionIds
) ?? Promise.resolve()
: Promise.resolve()
).then(succeed, (error: unknown) => {
fail(
error instanceof Error
? error
: new Error(
'DeepSeek Harness 插件失败状态保存失败'
)
)
})
} else if (control.type === 'fatal') {
fail(
new Error(
`DeepSeek Harness Host 启动失败(${control.code})`
)
)
}
}
const onExit = (exitCode: number): void => {
fail(
new Error(
`DeepSeek Harness Host 启动前退出(code ${exitCode})`
)
)
}
onAbort = () => {
fail(
options.signal.reason instanceof Error
? options.signal.reason
: new Error('DeepSeek Harness Host 启动已取消')
)
}
utility.on('message', onMessage)
utility.on('exit', onExit)
options.signal.addEventListener('abort', onAbort, {
once: true
})
timer = setTimeout(
() =>
fail(new Error('DeepSeek Harness Host 启动握手超时')),
startupTimeoutMs
)
const config = controlledHarnessHostConfigSchema.parse({
workspace: canonicalWorkspace,
dshHome: canonicalDshHome,
baseUrl: options.baseUrl,
api: 'openai-completions',
provider: 'goodbuddy',
model: options.model,
supportsImageInput: options.supportsImageInput,
requestHeaders: options.requestHeaders ?? {},
harnessVersion: DEEPSEEK_HARNESS_HOST_VERSION,
credentialRefs: [DEEPSEEK_HARNESS_CREDENTIAL_REF],
skillPackages: canonicalSkillPackages,
extensionPackages: canonicalExtensionPackages,
maxFrameBytes: DEEPSEEK_HARNESS_MAX_FRAME_BYTES
})
utility.postMessage({
protocol: DEEPSEEK_HARNESS_CONTROL_PROTOCOL,
version: DEEPSEEK_HARNESS_CONTROL_VERSION,
type: 'start',
config
} satisfies HarnessControlMessage)
})
return createDeepSeekHarnessUtilityChild(utility, {
stderrToWeb: (stderr) =>
Readable.toWeb(stderr) as ReadableStream<Uint8Array>,
terminateProcess: terminate
})
} catch (error) {
terminate()
throw error
}
}
}