Files
deepseek-harness/packages/sdk/sdk-client
Tianyi Cui 87a249106f Merge origin/master: port the SDK stack onto the subprocess seam
Master's #660 replaced dsh-subagent-subprocess with the dsh-subprocess
capability seam (ctx.subprocess + scrubbedParentEnv, tree-scoped teardown)
and moved subagent-acp onto it. Convergence for this branch's packages:

- The shared out-of-process provider vocabulary this branch had grown in
  the deleted library (NO_START_CAPABILITIES, assertPositiveFinite, cwd
  resolution, settleRunResult, subprocessRunHandle) moves into the subagent
  seam package as out-of-process.ts — it enforces subagent-seam contracts,
  not process mechanics, and both out-of-process backends now import it
  from there (subagent-acp keeps master's shape otherwise).
- subagent-sdk spawns THROUGH the SDK client (the subprocess README's
  documented exception for SDK-managed transports) and now applies the
  seam's scrubbedParentEnv() + explicit-env merge in place of the deleted
  buildChildEnv.
- sdk-client inlines the EOF→SIGTERM→SIGKILL ladder as private helpers (it
  runs outside any harness context, so it cannot ride ctx.subprocess).
- The child harness fixture gains the now-required dsh-subprocess-local
  entry for bash-local; the fixture cordis.yml keeps exercising the
  shipped provider default.
2026-07-27 21:52:40 +08:00
..

@deepseek-ai/dsh-sdk-client

English | 中文

The TypeScript client SDK for driving a DeepSeek Harness runtime as a subprocess over stdio JSON-RPC — the design twin of the Python SDK (deepseek-harness), sharing the same runtime peer, protocol, and layering: DeepSeekHarness is the high-level turns API, HarnessClient the lower-level protocol client. A pure library: it registers nothing on a Cordis context; the runtime process it spawns is a complete harness whose composition its own cordis.yml decides.

Unlike the Python SDK, the launch spec is fully explicit (command/args): this package is for repo-adjacent TypeScript consumers — the dsh-subagent-sdk backend, tests, automation — which know which runtime they are launching. Bundled-runtime resolution (finding a packaged executable) remains the Python distribution's concern.

DeepSeekHarness

import { DeepSeekHarness } from '@deepseek-ai/dsh-sdk-client'

await using harness = new DeepSeekHarness({
  launch: { command: 'node', args: ['lib/bin.js', 'cordis.yml'] },
  provider: 'deepseek',
  model: 'deepseek-v4-flash',
})
const result = await harness.run('say hi')
console.log(result.status, result.finalResponse)

The subprocess starts lazily on first use and stays owned by the instance across run() calls; close() (or await using) is required so the child is always reaped. start() memoizes the initialize handshake (the workspace cwd — resolved absolute before it crosses the wire — plus the provider/model route); a failed handshake reaps the runtime and swaps in a fresh client, so a later call retries with a new subprocess (until close(), which is terminal). session(id?) opens a named or fresh session handle; run(input, { sessionId?, onNotification? }) sends one prompt turn and settles when the paired session.finished arrives, returning a TurnResult: status (ok/error as the deployment maps it), the structured reason (TurnEndReason), finalResponse (last assistant message text), plus every session.event envelope and raw notification observed for that session tree, in wire order. Model-level failure is a status: 'error' result, never a rejection; rejections mean transport loss, timeout, or protocol violation.

HarnessClient

The protocol client under the turns API: explicit start()/initialize()/prompt()/request()/close(), plus notification subscriptions. subscribe(filter?) returns a NotificationSubscription (awaitable next(), non-blocking tryNext(), async iteration); subscribeSessionTree(id) scopes to one session and the descendants discovered from subagent.started lineage edges — the runtime notifies for every session in its context, and scoping is client-side, exactly like the Python SDK. Error surfaces are typed: JsonRpcResponseError (wire error response, code/data preserved), RequestTimeoutError (a configured bound elapsed; there is no wire-level cancel, so the request keeps running server-side until close), SdkProtocolError (a response outside the documented protocol), TransportClosedError (the runtime is gone — message carries the exit code and a bounded stderr tail).

close() requests protocol shutdown (bounded by shutdownTimeoutMs, default 1000 ms), then walks the shared stdin-EOF → SIGTERM → SIGKILL dispose ladder (disposeEofGraceMs default 6000, disposeGraceMs default 3000) until the process has actually exited. It is idempotent, and a closed client refuses reuse.

HarnessClientOptions.env replaces the child environment entirely when given (undefined inherits the parent's); callers own credential policy — buildChildEnv from dsh-subagent-subprocess is the scrub-then-inject helper for isolation-minded launches.

Testing

Keyless unit tests drive a scripted fake runtime subprocess (tests/fake-runtime.ts, protocol-only, env-scripted) over real stdio: turn loop, session-tree scoping, timeout/death/malformed-response surfaces, and the dispose ladder. The SDK snapshot suite drives the real dsh-jsonrpc-agent runtime through this client keylessly via llm-replay, pinning the notification stream, the turn result, and the persisted logs; DSH_SNAPSHOT=record re-records against the live API.

Model Experience

None, as this is a client-process library; the model runs in the spawned runtime, whose experience is owned by the plugins its cordis.yml composes.

KV Cache effect

None; this package neither assembles nor sends a provider request.

Known Limitations and Deferred Work

  • No bundled-runtime resolution — callers name the runtime executable explicitly; packaged-executable discovery stays Python-side until a TypeScript distribution consumer exists.
  • No mid-turn cancel — the wire has no prompt-cancel method; abandoning a turn means closing the runtime (see the protocol's Known Limitations).
  • One in-flight prompt per session — a server-side rule this client surfaces as a JsonRpcResponseError; independent sessions run concurrently on one runtime.
  • Client→server notifications and server→client requests are unimplemented on both wire ends; the transport carries them for future approval flows.