The native adapter's route was named deepseek, colliding with pi-ai's catalog provider of the same name, so the two DeepSeek paths could never be mounted side by side. The web settings page needs both configurable at once. Compositions, fixtures, goldens, scaffolding defaults, and docs all move together (pre-release, no shim); TUI/session-query-spill/ missing-credential goldens re-recorded through their keyless refresh modes because provider-name length shifts box padding and spill truncation points.
5.9 KiB
@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. The package root enumerates the consumer interface: the two client layers, caller-facing types, and JsonRpcResponseError; source modules, normalization helpers, and subscription-delivery machinery are not consumer imports. 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-dsh-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-official',
model: 'deepseek-v4-flash',
maxTokens: 49_152,
})
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 and optional positive maxTokens output cap); 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). The cap applies to each root-agent request and is inherited by in-process descendants; compaction plugins own their separate summary limits. 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), root-session events, and raw notifications for that session plus descendants discovered from subagent.started, all 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 and exported from this package: 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 a stdin-EOF → SIGTERM → SIGKILL ladder (disposeEofGraceMs default 6000, disposeGraceMs default 3000) until the process has actually exited. The ladder is private to this client: it runs outside any harness context, so it cannot ride the dsh-subprocess service — the seam's documented exception for SDK-managed transports. 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.
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.