mirror of
https://github.com/deepseek-ai/deepseek-harness.git
synced 2026-09-11 04:00:38 +00:00
Replace the TypeScript SDK's public arbitrary command/argv launch surface with the same-version dsh CLI, a named profile, ordered per-launch patches, optional process cwd, and explicit Harness home selection. Installed consumers use the built CLI; clean source checkouts use the package's src/bin.ts through an absolute tsx/esm loader and a source-only patch that omits build-generated Typert loading. Materialize explicit or inherited environments at spawn time, keep profile-internal patches below caller patches, and resolve every caller-relative path before the child starts. The SDK subagent provider validates its optional CLI and patch files at plugin load and requires an isolated absolute child home. Treat JSON-RPC initialize as the Loader-owned readiness point: Loader settlement joins entry imports, fiber lifecycle work, and synchronous effect registration, so the server needs no scheduler tick. A delayed profile entry registers a private adapter before initialize resolves, proving caller-supplied plugin routes are visible without fallback. Update sdk-app ownership, server diagnostics, TypeScript SDK examples, nested-loader coverage, and session-upload replay layering together. The following commit contains only the regenerated SDK transcripts, keeping this API and lifecycle change directly reviewable.
158 lines
6.4 KiB
TypeScript
158 lines
6.4 KiB
TypeScript
/**
|
|
* Resolve the public SDK launch configuration to one dsh subprocess.
|
|
* @module @deepseek-ai/dsh-sdk-client/launch
|
|
*/
|
|
|
|
import { existsSync, readFileSync } from 'node:fs'
|
|
import { dirname, resolve } from 'node:path'
|
|
import { fileURLToPath } from 'node:url'
|
|
import type { HarnessClientOptions } from './types.ts'
|
|
|
|
/** Default bound for a profile to answer the SDK initialize handshake. */
|
|
export const DEFAULT_INITIALIZE_TIMEOUT_MS = 10_000
|
|
|
|
/** Internal generic process launch used by the transport and fake-runtime tests. */
|
|
export interface RuntimeProcessOptions {
|
|
command: string
|
|
args: string[]
|
|
cwd?: string
|
|
/** Materialize the complete child environment when the client starts its subprocess. */
|
|
environment: () => NodeJS.ProcessEnv
|
|
description: string
|
|
initializeTimeoutMs: number
|
|
requestTimeoutMs?: number
|
|
shutdownTimeoutMs?: number
|
|
disposeEofGraceMs?: number
|
|
disposeGraceMs?: number
|
|
}
|
|
|
|
/** Node argv plus internal profile patches required by one resolved dsh entry. */
|
|
export interface DshNodeLaunch {
|
|
/** Arguments before the profile selector. */
|
|
nodeArgs: string[]
|
|
/** Internal patches applied below caller-supplied patches. */
|
|
patches: string[]
|
|
/** Environment values required by the resolved entry mode. */
|
|
environment: NodeJS.ProcessEnv
|
|
}
|
|
|
|
interface PackageManifest {
|
|
version?: unknown
|
|
bin?: unknown
|
|
}
|
|
|
|
/** Read a package manifest from one resolved package.json URL. */
|
|
function manifest(url: string): PackageManifest {
|
|
return JSON.parse(readFileSync(fileURLToPath(url), 'utf8')) as PackageManifest
|
|
}
|
|
|
|
/**
|
|
* Resolve and version-check a dsh executable from package manifests.
|
|
* @param dshManifestUrl - resolved URL of the dsh package manifest.
|
|
* @param clientManifestUrl - resolved URL of the SDK client manifest.
|
|
* @returns the absolute dsh executable path.
|
|
*/
|
|
export function resolveDshBinFromManifests(dshManifestUrl: string, clientManifestUrl: string): string {
|
|
const dshManifest = manifest(dshManifestUrl)
|
|
const clientManifest = manifest(clientManifestUrl)
|
|
if (typeof dshManifest.version !== 'string' || dshManifest.version !== clientManifest.version) {
|
|
throw new Error(`dsh SDK client ${String(clientManifest.version)} requires the same dsh version, got ${String(dshManifest.version)}`)
|
|
}
|
|
const bin = typeof dshManifest.bin === 'object' && dshManifest.bin !== null
|
|
? (dshManifest.bin as Record<string, unknown>).dsh
|
|
: dshManifest.bin
|
|
if (typeof bin !== 'string' || bin === '') throw new Error('@deepseek-ai/dsh declares no dsh executable')
|
|
return resolve(dirname(fileURLToPath(dshManifestUrl)), bin)
|
|
}
|
|
|
|
/**
|
|
* Resolve and version-check the built dsh executable installed with this SDK.
|
|
* @returns the absolute built executable path, whether or not it exists in a source checkout.
|
|
*/
|
|
export function installedDshBin(): string {
|
|
return resolveDshBinFromManifests(
|
|
import.meta.resolve('@deepseek-ai/dsh/package.json'),
|
|
new URL('../package.json', import.meta.url).href,
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Resolve the Node launch for one same-version dsh package.
|
|
* @param dshManifestUrl - resolved URL of the dsh package manifest.
|
|
* @param clientManifestUrl - resolved URL of the SDK client manifest.
|
|
* @param sourceLoaderUrl - optional absolute tsx loader URL for deterministic tests.
|
|
* @returns built output, or the source entry plus its compatibility patch and tsx environment.
|
|
*/
|
|
export function resolveDshNodeLaunchFromManifests(
|
|
dshManifestUrl: string,
|
|
clientManifestUrl: string,
|
|
sourceLoaderUrl?: string,
|
|
): DshNodeLaunch {
|
|
const bin = resolveDshBinFromManifests(dshManifestUrl, clientManifestUrl)
|
|
if (existsSync(bin)) return { nodeArgs: [bin], patches: [], environment: {} }
|
|
|
|
const packageDir = dirname(fileURLToPath(dshManifestUrl))
|
|
const sourceBin = resolve(packageDir, 'src/bin.ts')
|
|
const sourcePatch = resolve(packageDir, 'src/sdk-source.cordis.patch.yml')
|
|
const sourceTsconfig = resolve(packageDir, 'tsconfig.json')
|
|
if (!existsSync(sourceBin) || !existsSync(sourcePatch) || !existsSync(sourceTsconfig)) {
|
|
throw new Error(
|
|
`@deepseek-ai/dsh is missing its built executable ${bin} and complete source launch files ${sourceBin}, ${sourcePatch}, ${sourceTsconfig}`,
|
|
)
|
|
}
|
|
const loader = sourceLoaderUrl ?? import.meta.resolve('tsx/esm')
|
|
return {
|
|
nodeArgs: ['--import', loader, sourceBin],
|
|
patches: [sourcePatch],
|
|
environment: { TSX_TSCONFIG_PATH: sourceTsconfig },
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Resolve the installed dsh package to a built or source Node launch.
|
|
* @returns the launch descriptor for the current checkout or installed package.
|
|
*/
|
|
function installedDshNodeLaunch(): DshNodeLaunch {
|
|
return resolveDshNodeLaunchFromManifests(
|
|
import.meta.resolve('@deepseek-ai/dsh/package.json'),
|
|
new URL('../package.json', import.meta.url).href,
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Resolve caller-relative filesystem inputs and construct canonical dsh argv.
|
|
* @param options - public SDK launch options.
|
|
* @param callerCwd - parent-process directory used for lexical resolution.
|
|
* @returns one generic subprocess spec for the JSON-RPC transport.
|
|
*/
|
|
export function resolveDshLaunch(
|
|
options: HarnessClientOptions = {},
|
|
callerCwd: string = process.cwd(),
|
|
): RuntimeProcessOptions {
|
|
const profile = options.profile ?? 'sdk'
|
|
const dshLaunch = options.dshBin === undefined
|
|
? installedDshNodeLaunch()
|
|
: { nodeArgs: [resolve(callerCwd, options.dshBin)], patches: [], environment: {} }
|
|
const patches = [
|
|
...dshLaunch.patches,
|
|
...(options.patches ?? []).map(path => resolve(callerCwd, path)),
|
|
]
|
|
const dshHome = options.dshHome === undefined ? undefined : resolve(callerCwd, options.dshHome)
|
|
return {
|
|
command: process.execPath,
|
|
args: [...dshLaunch.nodeArgs, '--profile', profile, ...patches.flatMap(path => ['--patch', path])],
|
|
...options.processCwd === undefined ? {} : { cwd: resolve(callerCwd, options.processCwd) },
|
|
environment: () => ({
|
|
...(options.env ?? process.env),
|
|
...dshLaunch.environment,
|
|
...dshHome === undefined ? {} : { DSH_HOME: dshHome },
|
|
}),
|
|
description: `dsh profile ${JSON.stringify(profile)}`,
|
|
initializeTimeoutMs: options.initializeTimeoutMs ?? DEFAULT_INITIALIZE_TIMEOUT_MS,
|
|
...options.requestTimeoutMs === undefined ? {} : { requestTimeoutMs: options.requestTimeoutMs },
|
|
...options.shutdownTimeoutMs === undefined ? {} : { shutdownTimeoutMs: options.shutdownTimeoutMs },
|
|
...options.disposeEofGraceMs === undefined ? {} : { disposeEofGraceMs: options.disposeEofGraceMs },
|
|
...options.disposeGraceMs === undefined ? {} : { disposeGraceMs: options.disposeGraceMs },
|
|
}
|
|
}
|