description: "The TypeScript SDK client for callers that spawn a DeepSeek Harness runtime subprocess and drive agent turns over stdio JSON-RPC: the DeepSeekHarness run API and the lower-level HarnessClient."
English | 中文
dsh-sdk-client lets TypeScript programs start and drive a complete DeepSeek Harness runtime over stdio JSON-RPC. Use DeepSeekHarness to open sessions, send text or image prompts, collect event and notification streams, and obtain the last committed assistant response when the runtime becomes idle; use HarnessClient for direct protocol requests and subscriptions. Callers may provide dshBin; otherwise the client resolves the same-version @deepseek-ai/dsh executable. The client owns the subprocess across runs, exposes typed transport and protocol failures, and reaps it on close() or await using. It is suitable when the caller can choose the runtime profile and launch settings.
Use this client when TypeScript code must drive a complete Harness runtime from another process and you can name the runtime executable explicitly. The common path is minimal: construct a DeepSeekHarness with a launch spec, run prompts, and close it so the child process is always reaped.
import { DeepSeekHarness } from '@deepseek-ai/dsh-sdk-client'
import { ReasoningEffortId } from '@deepseek-ai/dsh-llm'
await using harness = new DeepSeekHarness({
profile: 'sdk',
patches: ['./automation.cordis.yml'],
provider: 'deepseek-official',
model: 'deepseek-v4-flash',
reasoningEffort: ReasoningEffortId('max'),
maxTokens: 49_152,
})
const result = await harness.run('say hi')
console.log(result.finalResponse)
The subprocess starts lazily on first use and stays owned by the instance across run() calls; call close() (or use await using) so the child is always reaped. start() memoizes the bounded initialize handshake, which carries the workspace cwd, provider/model route, optional adapter-owned reasoningEffort, and optional positive maxTokens output cap. The server validates that exact route before it accepts prompts; an omitted effort preserves the model's default. initializeTimeoutMs defaults to 10 seconds, and its diagnostic names the selected profile with the retained stderr tail. run(input, { sessionId?, onNotification? }) accepts text or SdkPromptContentBlock[]; an inline raster block carries canonical base64 plus mimeType and becomes a durable attachment inside the runtime. The call owns one activity interval: it queues the prompt, waits until its message id appears in a durable inbox receipt, then collects through the next whole-agent idle. It returns RunResult { sessionId, finalResponse, events, notifications }, where finalResponse is the last committed root-session assistant text in that interval — not a response causally assigned to the prompt, because steering, injected context, and other queued work may contribute before idle. session(id?) opens a named or fresh session handle. When a failed handshake is cleaned up successfully, the instance installs a fresh client so a later call retries with a new process until terminal close(); if initialization and cleanup both fail, start() returns an ordered AggregateError and retains the failed client instead of spawning beside a process whose exit is unproved. maxTokens caps each root-agent request output and is inherited by in-process descendants; compaction plugins own their separate summary limits.
HarnessClient is the protocol client under the run API: explicit start(), initialize(), prompt(), request(), and close(), plus notification subscriptions. prompt() returns the queued message id as soon as the runtime accepts it and never waits for agent activity. 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.
The client exports typed errors for every failure mode: JsonRpcResponseError (a wire error response, code and data preserved), RequestTimeoutError (a configured bound elapsed), SdkProtocolError (a response outside the documented protocol), and TransportClosedError (the runtime is gone — the message carries the exit code and a bounded stderr tail). close() requests protocol shutdown (bounded by shutdownTimeoutMs, default 1000 ms), then walks a stdin-EOF → SIGTERM → SIGKILL ladder until the process has 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 — scrubbedParentEnv from dsh-subprocess is the shared scrub base for isolation-minded launches.
Read these pages when the client contract is not enough. They move from the wire protocol to the serving plugin and the applications that use this client.
dsh --profile sdk runtime application this client launches.None, as this is a client-process library; model-facing behavior lives in the spawned runtime's composed plugins.
None in the client process. Profile, patch, provider, model, and history choices determine cache reuse in the child.
These limits define when the client is a poor fit or needs special care. They are current package constraints, not a comparison with other SDK clients or a task backlog.
@deepseek-ai/dsh package (or a caller-provided dshBin); packaged-executable discovery stays Python-side until a TypeScript distribution consumer exists.prompt() returns only an enqueue receipt; high-level run() owns receipt-to-idle collection, and abandoning it means closing the runtime.