1
/**2
* Resolve the public SDK launch configuration to one dsh subprocess.3
* @module @deepseek-ai/dsh-sdk-client/launch4
*/6
import { existsSync, readFileSync } from 'node:fs'7
import { dirname, resolve } from 'node:path'8
import { fileURLToPath } from 'node:url'9
import type { HarnessClientOptions } from './types.ts'11
/** Default bound for a profile to answer the SDK initialize handshake. */12
export const DEFAULT_INITIALIZE_TIMEOUT_MS = 10_00014
/** Internal generic process launch used by the transport and fake-runtime tests. */15
export interface RuntimeProcessOptions {16
command: string17
args: string[]18
cwd?: string19
/** Materialize the complete child environment when the client starts its subprocess. */20
environment: () => NodeJS.ProcessEnv21
description: string22
initializeTimeoutMs: number23
requestTimeoutMs?: number24
shutdownTimeoutMs?: number25
disposeEofGraceMs?: number26
disposeGraceMs?: number27
}29
/** Node argv plus internal profile patches required by one resolved dsh entry. */30
export interface DshNodeLaunch {31
/** Arguments before the profile selector. */32
nodeArgs: string[]33
/** Internal patches applied below caller-supplied patches. */34
patches: string[]35
/** Environment values required by the resolved entry mode. */36
environment: NodeJS.ProcessEnv37
}39
interface PackageManifest {40
version?: unknown41
bin?: unknown42
}44
/** Read a package manifest from one resolved package.json URL. */45
function manifest(url: string): PackageManifest {46
return JSON.parse(readFileSync(fileURLToPath(url), 'utf8')) as PackageManifest47
}49
/**50
* Resolve and version-check a dsh executable from package manifests.51
* @param dshManifestUrl - resolved URL of the dsh package manifest.52
* @param clientManifestUrl - resolved URL of the SDK client manifest.53
* @returns the absolute dsh executable path.54
*/55
export function resolveDshBinFromManifests(dshManifestUrl: string, clientManifestUrl: string): string {56
const dshManifest = manifest(dshManifestUrl)57
const clientManifest = manifest(clientManifestUrl)58
if (typeof dshManifest.version !== 'string' || dshManifest.version !== clientManifest.version) {59
throw new Error(`dsh SDK client ${String(clientManifest.version)} requires the same dsh version, got ${String(dshManifest.version)}`)60
}61
const bin = typeof dshManifest.bin === 'object' && dshManifest.bin !== null62
? (dshManifest.bin as Record<string, unknown>).dsh63
: dshManifest.bin64
if (typeof bin !== 'string' || bin === '') throw new Error('@deepseek-ai/dsh declares no dsh executable')65
return resolve(dirname(fileURLToPath(dshManifestUrl)), bin)66
}68
/**69
* Resolve and version-check the built dsh executable installed with this SDK.70
* @returns the absolute built executable path, whether or not it exists in a source checkout.71
*/72
export function installedDshBin(): string {73
return resolveDshBinFromManifests(74
import.meta.resolve('@deepseek-ai/dsh/package.json'),75
new URL('../package.json', import.meta.url).href,76
)77
}79
/**80
* Resolve the Node launch for one same-version dsh package.81
* @param dshManifestUrl - resolved URL of the dsh package manifest.82
* @param clientManifestUrl - resolved URL of the SDK client manifest.83
* @param sourceLoaderUrl - optional absolute tsx loader URL for deterministic tests.84
* @returns built output, or the source entry plus its compatibility patch and tsx environment.85
*/86
export function resolveDshNodeLaunchFromManifests(87
dshManifestUrl: string,88
clientManifestUrl: string,89
sourceLoaderUrl?: string,90
): DshNodeLaunch {91
const bin = resolveDshBinFromManifests(dshManifestUrl, clientManifestUrl)92
if (existsSync(bin)) return { nodeArgs: [bin], patches: [], environment: {} }94
const packageDir = dirname(fileURLToPath(dshManifestUrl))95
const sourceBin = resolve(packageDir, 'src/bin.ts')96
const sourcePatch = resolve(packageDir, 'src/sdk-source.cordis.patch.yml')97
const sourceTsconfig = resolve(packageDir, 'tsconfig.json')98
if (!existsSync(sourceBin) || !existsSync(sourcePatch) || !existsSync(sourceTsconfig)) {99
throw new Error(100
`@deepseek-ai/dsh is missing its built executable ${bin} and complete source launch files ${sourceBin}, ${sourcePatch}, ${sourceTsconfig}`,101
)102
}103
const loader = sourceLoaderUrl ?? import.meta.resolve('tsx/esm')104
return {105
nodeArgs: ['--import', loader, sourceBin],106
patches: [sourcePatch],107
environment: { TSX_TSCONFIG_PATH: sourceTsconfig },108
}109
}111
/**112
* Resolve the installed dsh package to a built or source Node launch.113
* @returns the launch descriptor for the current checkout or installed package.114
*/115
function installedDshNodeLaunch(): DshNodeLaunch {116
return resolveDshNodeLaunchFromManifests(117
import.meta.resolve('@deepseek-ai/dsh/package.json'),118
new URL('../package.json', import.meta.url).href,119
)120
}122
/**123
* Resolve caller-relative filesystem inputs and construct canonical dsh argv.124
* @param options - public SDK launch options.125
* @param callerCwd - parent-process directory used for lexical resolution.126
* @returns one generic subprocess spec for the JSON-RPC transport.127
*/128
export function resolveDshLaunch(129
options: HarnessClientOptions = {},130
callerCwd: string = process.cwd(),131
): RuntimeProcessOptions {132
const profile = options.profile ?? 'sdk'133
const dshLaunch = options.dshBin === undefined134
? installedDshNodeLaunch()135
: { nodeArgs: [resolve(callerCwd, options.dshBin)], patches: [], environment: {} }136
const patches = [137
...dshLaunch.patches,138
...(options.patches ?? []).map(path => resolve(callerCwd, path)),139
]140
const dshHome = options.dshHome === undefined ? undefined : resolve(callerCwd, options.dshHome)141
return {142
command: process.execPath,143
args: [...dshLaunch.nodeArgs, '--profile', profile, ...patches.flatMap(path => ['--patch', path])],144
...options.processCwd === undefined ? {} : { cwd: resolve(callerCwd, options.processCwd) },145
environment: () => ({146
...(options.env ?? process.env),147
...dshLaunch.environment,148
...dshHome === undefined ? {} : { DSH_HOME: dshHome },149
}),150
description: `dsh profile ${JSON.stringify(profile)}`,151
initializeTimeoutMs: options.initializeTimeoutMs ?? DEFAULT_INITIALIZE_TIMEOUT_MS,152
...options.requestTimeoutMs === undefined ? {} : { requestTimeoutMs: options.requestTimeoutMs },153
...options.shutdownTimeoutMs === undefined ? {} : { shutdownTimeoutMs: options.shutdownTimeoutMs },154
...options.disposeEofGraceMs === undefined ? {} : { disposeEofGraceMs: options.disposeEofGraceMs },155
...options.disposeGraceMs === undefined ? {} : { disposeGraceMs: options.disposeGraceMs },156
}157
}