2026-07-27 04:37:23 +08:00
/ * *
* Keyless snapshot coverage for the TypeScript SDK path : each scenario spawns
feat(sdk): launch TypeScript clients through dsh profiles
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.
2026-08-23 01:47:42 +08:00
* the real ` dsh --profile sdk ` runtime through
* ` @deepseek-ai/dsh-sdk-client ` , drives one turn over stdio JSON - RPC ,
2026-08-11 15:51:03 +08:00
* and pins the SDK ` RunResult ` , the complete notification stream , and the
* persisted session logs . Replay serves recorded model
2026-07-27 04:37:23 +08:00
* responses via ` llm-replay ` ( ` cordis.snapshot.yml ` ) ; ` DSH_SNAPSHOT=record `
* re - records against the live API ; ` DSH_SNAPSHOT=refresh ` replays committed
* fixtures and rewrites expected outputs .
* /
2026-07-30 17:51:07 +08:00
import { existsSync } from 'node:fs'
2026-07-27 04:37:23 +08:00
import { mkdir , mkdtemp , readFile , readdir , rm , writeFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
2026-07-29 23:53:59 +08:00
import { basename , delimiter , join } from 'node:path'
feat(sdk): launch TypeScript clients through dsh profiles
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.
2026-08-23 01:47:42 +08:00
import { fileURLToPath , pathToFileURL } from 'node:url'
2026-07-27 04:37:23 +08:00
import { describe , expect , it } from 'vitest'
import {
normalizeSessionLog ,
2026-08-19 13:58:41 +08:00
normalizeSessionSnapshot ,
2026-07-27 04:37:23 +08:00
normalizeStdout ,
refreshFixtureReplacements ,
scrubRequestHeaders ,
2026-08-18 18:10:33 +08:00
scrubSessionSnapshot ,
2026-07-30 17:51:07 +08:00
stabilizeFixtureMessageIds ,
2026-07-27 04:37:23 +08:00
stabilizeRefreshLog ,
2026-07-28 23:46:01 +08:00
tokenizeSessionFixtureCwd ,
2026-07-27 04:37:23 +08:00
type HarvestedLog ,
type NormalizeContext ,
} from '@deepseek-ai/dsh-acp-snapshot'
2026-07-30 17:28:03 +08:00
import { DeepSeekHarness , type HarnessNotification , type RunResult } from '@deepseek-ai/dsh-sdk-client'
2026-07-27 04:37:23 +08:00
const testsDir = dirOf ( import . meta . url )
const snapshotsDir = join ( testsDir , 'snapshots' )
feat(sdk): launch TypeScript clients through dsh profiles
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.
2026-08-23 01:47:42 +08:00
const liveConfig = join ( testsDir , '..' , 'typescript-sdk.cordis.yml' )
const replayConfig = join ( testsDir , '..' , 'typescript-sdk.cordis.snapshot.yml' )
const minimalLiveConfig = join ( testsDir , '..' , 'typescript-sdk-minimal.cordis.yml' )
const minimalReplayConfig = join ( testsDir , '..' , 'typescript-sdk-minimal.cordis.snapshot.yml' )
const sessionUploadLivePatch = join ( testsDir , '..' , 'session-upload.cordis.yml' )
const sessionUploadReplayPatch = join ( testsDir , '..' , 'session-upload.snapshot.cordis.yml' )
const exampleMode = process . env . DSH_EXAMPLE_MODE ? ? 'src'
const replayPlugin = fileURLToPath ( new URL (
exampleMode === 'lib'
? '../../../packages/test-support/llm-replay/lib/index.js'
: '../../../packages/test-support/llm-replay/src/index.ts' ,
import . meta . url ,
) )
2026-07-27 04:37:23 +08:00
2026-08-11 15:19:24 +08:00
const MINIMAL_SYSTEM_PROMPT = 'You are the environment-selected minimal software engineer.'
2026-08-10 19:30:46 +08:00
const MINIMAL_BASH_DESCRIPTION = ` Run commands in a bash shell
* When invoking this tool , the contents of the "command" parameter does NOT need to be XML - escaped .
* You don ' t have access to the internet via this tool .
* You do have access to a mirror of common linux and python packages via apt and pip .
* State is persistent across command calls and discussions with the user .
* To inspect a particular line range of a file , e . g . lines 10 - 25 , try 'sed -n 10,25p /path/to/the/file' .
* Please avoid commands that may produce a very large amount of output .
* Please run long lived commands in the background , e . g . 'sleep 10 &' or start a server in the background . `
2026-07-27 04:37:23 +08:00
const mode = process . env . DSH_SNAPSHOT ? ? 'replay'
const recording = mode === 'record'
const refreshing = mode === 'refresh'
function dirOf ( url : string ) : string {
return fileURLToPath ( new URL ( '.' , url ) )
}
interface SdkScenario {
/** Scenario name; the snapshots/<name> fixture directory. */
name : string
/** The user prompt for the single SDK turn. */
prompt : string
/** Fixed SDK session id, so fixtures and replay binding stay stable. */
sessionId : string
/** How many child sessions the turn persists (subagent scenarios). */
children : number
2026-07-29 21:32:19 +08:00
/** Optional scenario-specific live and replay compositions. */
configs ? : { live : string ; replay : string }
feat(sdk): launch TypeScript clients through dsh profiles
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.
2026-08-23 01:47:42 +08:00
/** Additional ordered patches applied after the selected live or replay patch. */
additionalPatches ? : { live : readonly string [ ] ; replay : readonly string [ ] }
2026-08-11 15:19:24 +08:00
/** Environment overrides passed to the runtime subprocess. */
environment? : Readonly < Record < string , string > >
2026-07-29 23:53:59 +08:00
/** Cwd-relative files whose final contents are part of the scenario contract. */
2026-07-29 21:32:19 +08:00
expectedFiles? : Readonly < Record < string , string > >
2026-07-29 23:56:28 +08:00
/** Assembled model-facing tool names and required argument keys. */
expectedTools? : Readonly < Record < string , readonly string [ ] > >
2026-08-10 19:30:46 +08:00
/** Exact assembled system prompt for the root request. */
expectedSystem? : string
/** Exact model-facing descriptions for selected tools. */
expectedToolDescriptions? : Readonly < Record < string , string > >
2026-08-12 23:28:15 +08:00
/** Expected runtime-context state in the real assembled request. */
runtimeContext? : false | { includes : readonly string [ ] ; excludes : readonly string [ ] }
2026-07-27 04:37:23 +08:00
}
const SCENARIOS : SdkScenario [ ] = [
{
name : 'text-turn' ,
prompt : 'Reply with exactly: SDK snapshot OK' ,
sessionId : 'sdk-snapshot-text' ,
children : 0 ,
feat(sdk): launch TypeScript clients through dsh profiles
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.
2026-08-23 01:47:42 +08:00
additionalPatches : { live : [ sessionUploadLivePatch ] , replay : [ sessionUploadReplayPatch ] } ,
2026-07-27 04:37:23 +08:00
} ,
{
name : 'bash-tool' ,
prompt : 'Run this exact command with your bash tool, then reply with its stdout only: echo dsh-sdk-proof-7391' ,
sessionId : 'sdk-snapshot-bash' ,
children : 0 ,
} ,
{
2026-08-13 00:36:22 +08:00
name : 'subagent-spawn-in-process' ,
2026-07-27 04:37:23 +08:00
prompt : "Use the subagent tool exactly once with description 'echo probe' and prompt: Reply with exactly: child answer 42. Then reply with the subagent's final answer verbatim." ,
sessionId : 'sdk-snapshot-subagent' ,
children : 1 ,
} ,
2026-07-29 21:32:19 +08:00
{
name : 'persistent-tools' ,
2026-07-30 00:15:39 +08:00
prompt : 'Prove that bash state persists. Then create {{cwd}}/note.txt with a tab-indented line, view it, replace that literal tab-indented line, and make the persistent shell exit with code 9.' ,
2026-07-29 21:32:19 +08:00
sessionId : 'persistent-tools-snapshot' ,
children : 0 ,
2026-08-10 19:30:46 +08:00
configs : { live : minimalLiveConfig , replay : minimalReplayConfig } ,
2026-08-11 15:19:24 +08:00
environment : { DSH_SYSTEM_PROMPT : MINIMAL_SYSTEM_PROMPT } ,
2026-07-30 00:05:58 +08:00
expectedFiles : { 'note.txt' : 'target:\n\tnew\n' } ,
2026-07-29 23:56:28 +08:00
expectedTools : { bash : [ 'command' ] , str_replace_editor : [ 'command' , 'path' ] } ,
2026-08-10 19:30:46 +08:00
expectedSystem : MINIMAL_SYSTEM_PROMPT ,
expectedToolDescriptions : { bash : MINIMAL_BASH_DESCRIPTION } ,
feat(sdk): launch TypeScript clients through dsh profiles
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.
2026-08-23 01:47:42 +08:00
runtimeContext : {
includes : [ 'Current DSH file policy: danger-full-access' , 'Approval prompts are disabled in this session' ] ,
excludes : [ 'workspace-write' ] ,
} ,
2026-07-29 21:32:19 +08:00
} ,
2026-07-27 04:37:23 +08:00
]
interface PersistedLog {
readonly path : string
readonly content : string
readonly header : Record < string , unknown >
}
2026-07-29 23:53:59 +08:00
interface MissingFile {
readonly missing : true
}
2026-07-27 04:37:23 +08:00
async function jsonlFiles ( dir : string ) : Promise < string [ ] > {
const entries = await readdir ( dir , { recursive : true } )
return entries . filter ( entry = > entry . endsWith ( '.jsonl' ) ) . map ( entry = > join ( dir , entry ) ) . sort ( )
}
async function persistedLogs ( sessionsRoot : string ) : Promise < PersistedLog [ ] > {
const files = await jsonlFiles ( sessionsRoot )
return Promise . all ( files . map ( async ( path ) = > {
const content = await readFile ( path , 'utf8' )
const header = JSON . parse ( content . slice ( 0 , content . indexOf ( '\n' ) ) ) as Record < string , unknown >
return { path , content , header }
} ) )
}
2026-07-29 23:56:28 +08:00
interface LoggedRequestHeader {
type ? : string
2026-08-10 19:30:46 +08:00
data ? : { header ? : { system? : unknown ; tools? : LoggedTool [ ] } }
2026-07-29 23:56:28 +08:00
}
2026-08-10 19:30:46 +08:00
interface LoggedTool {
readonly name : string
readonly description? : unknown
readonly parameters : { readonly required? : string [ ] }
}
function assembledTools ( log : PersistedLog ) : LoggedTool [ ] {
2026-07-29 23:56:28 +08:00
const event = log . content . trimEnd ( ) . split ( '\n' )
. map ( line = > JSON . parse ( line ) as LoggedRequestHeader )
. find ( candidate = > candidate . type === 'request/header' )
const tools = event ? . data ? . header ? . tools
if ( tools === undefined ) throw new Error ( 'session log has no request/header tools' )
2026-08-10 19:30:46 +08:00
return tools
}
function assembledToolRequirements ( log : PersistedLog ) : Record < string , string [ ] > {
return Object . fromEntries ( assembledTools ( log ) . map ( tool = > [ tool . name , tool . parameters . required ? ? [ ] ] ) )
}
function assembledToolDescriptions ( log : PersistedLog ) : Record < string , string > {
return Object . fromEntries ( assembledTools ( log ) . map ( ( tool ) = > {
if ( typeof tool . description !== 'string' ) throw new Error ( ` tool ${ tool . name } has no description ` )
return [ tool . name , tool . description ]
} ) )
2026-07-29 23:56:28 +08:00
}
2026-07-30 18:51:29 +08:00
function assembledSystem ( log : PersistedLog ) : string {
const event = log . content . trimEnd ( ) . split ( '\n' )
. map ( line = > JSON . parse ( line ) as LoggedRequestHeader )
. find ( candidate = > candidate . type === 'request/header' )
const system = event ? . data ? . header ? . system
if ( typeof system !== 'string' ) throw new Error ( 'session log has no request/header system' )
return system
}
2026-08-12 23:28:15 +08:00
function assembledRuntimeContexts ( log : PersistedLog ) : string [ ] {
return log . content . trimEnd ( ) . split ( '\n' ) . flatMap ( ( line ) = > {
2026-07-30 22:15:34 +08:00
const event = JSON . parse ( line ) as {
type ? : string
data ? : { source ? : { kind? : string ; plugin? : string } ; content? : Array < { type ? : string ; text? : unknown } > }
}
if ( event . type !== 'user/message'
|| event . data ? . source ? . kind !== 'plugin'
|| event . data . source . plugin !== '@deepseek-ai/dsh-system-prompt' ) return [ ]
return event . data . content ? . flatMap ( block = > block . type === 'text' && typeof block . text === 'string' ? [ block . text ] : [ ] ) ? ? [ ]
} )
}
2026-07-27 04:37:23 +08:00
function contextOf ( logs : readonly { content : string ; header : Record < string , unknown > } [ ] , cwd : string ) : NormalizeContext {
return {
sessionIds : logs.flatMap ( log = > typeof log . header . id === 'string' ? [ log . header . id ] : [ ] ) ,
cwd ,
}
}
function contextOfContents ( contents : readonly string [ ] ) : NormalizeContext {
const headers = contents . map ( content = > JSON . parse ( content . slice ( 0 , content . indexOf ( '\n' ) ) ) as Record < string , unknown > )
return {
sessionIds : headers.flatMap ( header = > typeof header . id === 'string' ? [ header . id ] : [ ] ) ,
cwd : typeof headers [ 0 ] ? . cwd === 'string' ? headers [ 0 ] . cwd : '\0no-cwd\0' ,
}
}
2026-07-29 23:53:59 +08:00
async function hydrateReplayFixtures ( scenario : SdkScenario , cwd : string ) : Promise < string [ ] > {
const root = join ( cwd , '.replay-fixtures' )
await mkdir ( root , { recursive : true } )
return Promise . all ( fixtureFiles ( scenario ) . map ( async ( source ) = > {
const destination = join ( root , basename ( source ) )
await writeFile ( destination , ( await readFile ( source , 'utf8' ) ) . replaceAll ( '{{cwd}}' , cwd ) )
return destination
} ) )
}
async function readExpectedFile ( path : string ) : Promise < string | MissingFile > {
try {
return await readFile ( path , 'utf8' )
} catch ( error : unknown ) {
if ( error instanceof Error && ( error as NodeJS . ErrnoException ) . code === 'ENOENT' ) return { missing : true }
throw error
}
}
feat(sdk): launch TypeScript clients through dsh profiles
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.
2026-08-23 01:47:42 +08:00
/** Materialize a built-mode replay patch with an absolute test-plugin module URL. */
async function materializeReplayPatch ( source : string , cwd : string ) : Promise < string > {
const target = join ( cwd , ` .sdk- ${ basename ( source ) } ` )
const content = ( await readFile ( source , 'utf8' ) )
. replaceAll ( "'@deepseek-ai/dsh-llm-replay'" , JSON . stringify ( pathToFileURL ( replayPlugin ) . href ) )
await writeFile ( target , content )
return target
}
2026-07-27 04:37:23 +08:00
/ * *
* Normalize the SDK - visible notification stream : embedded ` session.event `
* envelopes get the session - log treatment ( times zeroed , headers tokenized ) ,
* then every record is scrubbed like a wire frame .
* /
function normalizeNotifications ( notifications : readonly HarnessNotification [ ] , ctx : NormalizeContext ) : string {
const events = notifications
. filter ( n = > n . method === 'session.event' )
. map ( n = > n . params . event as Record < string , unknown > )
const normalizedEvents = events . length === 0
? [ ]
: scrubRequestHeaders ( normalizeSessionLog (
` ${ events . map ( event = > JSON . stringify ( event ) ) . join ( '\n' ) } \ n ` ,
ctx ,
) ) . trimEnd ( ) . split ( '\n' ) . map ( line = > JSON . parse ( line ) as Record < string , unknown > )
let eventIndex = 0
const records = notifications . map ( ( notification ) = > {
if ( notification . method !== 'session.event' ) return { method : notification.method , params : notification.params }
const event = normalizedEvents [ eventIndex ++ ]
return { method : notification.method , params : { . . . notification . params , event } }
} )
return normalizeStdout ( ` ${ records . map ( record = > JSON . stringify ( record ) ) . join ( '\n' ) } \ n ` , ctx )
}
2026-07-30 17:28:03 +08:00
/** Normalize the owned-run projection. */
function normalizeResult ( result : RunResult , ctx : NormalizeContext ) : string {
2026-07-27 04:37:23 +08:00
return normalizeStdout ( ` ${ JSON . stringify ( {
2026-07-30 17:28:03 +08:00
sessionId : result.sessionId ,
2026-07-27 04:37:23 +08:00
finalResponse : result.finalResponse ,
} ) } \ n ` , ctx)
}
/** One SDK turn against a fresh runtime subprocess in an isolated cwd. */
async function runScenario ( scenario : SdkScenario ) : Promise < {
2026-07-30 17:28:03 +08:00
result : RunResult
2026-07-27 04:37:23 +08:00
notifications : HarnessNotification [ ]
logs : PersistedLog [ ]
2026-07-29 23:53:59 +08:00
observedFiles : Record < string , string | MissingFile >
2026-07-27 04:37:23 +08:00
cwd : string
} > {
const cwd = await mkdtemp ( join ( tmpdir ( ) , ` sdk-snapshot- ${ scenario . name } - ` ) )
feat(sdk): launch TypeScript clients through dsh profiles
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.
2026-08-23 01:47:42 +08:00
const dshHome = join ( cwd , '.dsh' )
const sessionsRoot = join ( dshHome , 'sessions' )
2026-07-29 23:53:59 +08:00
const replayFixtures = recording ? [ ] : await hydrateReplayFixtures ( scenario , cwd )
feat(sdk): launch TypeScript clients through dsh profiles
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.
2026-08-23 01:47:42 +08:00
const livePatch = scenario . configs ? . live ? ? liveConfig
const replayPatch = scenario . configs ? . replay ? ? replayConfig
const resolvedReplayPatch = recording ? undefined : await materializeReplayPatch ( replayPatch , cwd )
const additionalPatches = recording
? scenario . additionalPatches ? . live ? ? [ ]
: scenario . additionalPatches ? . replay ? ? [ ]
2026-07-29 23:53:59 +08:00
const [ parentFixture , . . . childFixtures ] = replayFixtures
2026-07-27 04:37:23 +08:00
const env : Record < string , string > = {
. . . Object . fromEntries ( Object . entries ( process . env ) . filter ( ( [ , value ] ) = > value !== undefined ) ) as Record < string , string > ,
DSH_SNAPSHOT : mode ,
feat(sdk): launch TypeScript clients through dsh profiles
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.
2026-08-23 01:47:42 +08:00
DSH_TELEMETRY_DISABLED : '1' ,
2026-07-27 04:37:23 +08:00
NODE_OPTIONS : [ process . env . NODE_OPTIONS , '--disable-warning=ExperimentalWarning' ] . filter ( Boolean ) . join ( ' ' ) ,
2026-07-29 23:53:59 +08:00
. . . parentFixture === undefined ? { } : {
DSH_SNAPSHOT_FILE : parentFixture ,
2026-07-27 04:37:23 +08:00
. . . childFixtures . length > 0 ? { DSH_SNAPSHOT_CHILD_FILES : childFixtures.join ( delimiter ) } : { } ,
} ,
2026-08-11 15:19:24 +08:00
. . . scenario . environment ,
2026-07-27 04:37:23 +08:00
}
const harness = new DeepSeekHarness ( {
feat(sdk): launch TypeScript clients through dsh profiles
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.
2026-08-23 01:47:42 +08:00
profile : 'sdk' ,
patches : [
livePatch ,
. . . resolvedReplayPatch === undefined ? [ ] : [ resolvedReplayPatch ] ,
. . . additionalPatches ,
] ,
dshHome ,
processCwd : cwd ,
env ,
requestTimeoutMs : 110_000 ,
2026-07-27 04:37:23 +08:00
cwd ,
2026-07-29 16:36:07 +08:00
provider : 'deepseek-official' ,
2026-07-27 04:37:23 +08:00
model : 'deepseek-v4-flash' ,
} )
try {
const notifications : HarnessNotification [ ] = [ ]
2026-07-29 23:53:59 +08:00
const result = await harness . run ( scenario . prompt . replaceAll ( '{{cwd}}' , cwd ) , {
2026-07-27 04:37:23 +08:00
sessionId : scenario.sessionId ,
onNotification : ( notification ) = > { notifications . push ( notification ) } ,
} )
await harness . close ( )
const logs = await persistedLogs ( sessionsRoot )
2026-07-29 21:32:19 +08:00
const observedFiles = Object . fromEntries ( await Promise . all (
2026-07-29 23:53:59 +08:00
Object . keys ( scenario . expectedFiles ? ? { } ) . map ( async ( path ) : Promise < [ string , string | MissingFile ] > = > [
2026-07-29 21:32:19 +08:00
path ,
2026-07-29 23:53:59 +08:00
await readExpectedFile ( join ( cwd , path ) ) ,
2026-07-29 21:32:19 +08:00
] ) ,
) )
return { result , notifications , logs , observedFiles , cwd }
2026-07-27 04:37:23 +08:00
} finally {
await harness . close ( )
await rm ( cwd , { recursive : true , force : true } )
}
}
/** Order logs parent-first, children by creation time (fixture layout order). */
function orderLogs ( logs : PersistedLog [ ] , scenario : SdkScenario ) : PersistedLog [ ] {
const parents = logs . filter ( log = > typeof log . header . parentSession !== 'string' )
const children = logs . filter ( log = > typeof log . header . parentSession === 'string' )
. sort ( ( left , right ) = > Number ( left . header . createdAt ) - Number ( right . header . createdAt ) )
expect ( parents ) . toHaveLength ( 1 )
expect ( children ) . toHaveLength ( scenario . children )
return [ . . . parents , . . . children ]
}
function fixtureFiles ( scenario : SdkScenario ) : string [ ] {
const dir = join ( snapshotsDir , scenario . name )
return [
join ( dir , 'session.jsonl' ) ,
. . . Array . from ( { length : scenario.children } , ( _ , index ) = > join ( dir , ` session. ${ index + 1 } .jsonl ` ) ) ,
]
}
describe ( 'TypeScript SDK snapshots over the jsonrpc runtime' , ( ) = > {
for ( const scenario of SCENARIOS ) {
it ( ` replays ${ scenario . name } through the SDK ` , async ( ) = > {
const scenarioDir = join ( snapshotsDir , scenario . name )
const notificationsExpectedPath = join ( scenarioDir , 'notifications.expected.jsonl' )
const resultExpectedPath = join ( scenarioDir , 'result.expected.json' )
2026-07-29 21:32:19 +08:00
const { result , notifications , logs , observedFiles , cwd } = await runScenario ( scenario )
2026-07-27 04:37:23 +08:00
const ordered = orderLogs ( logs , scenario )
const actualContext = contextOf ( ordered , cwd )
2026-07-30 17:51:07 +08:00
const files = fixtureFiles ( scenario )
2026-07-27 04:37:23 +08:00
if ( recording ) {
// Fixtures carry tokenized request headers; llm-replay reads only
// assistant output and tool traffic, so scrubbing keeps prompts and
// schemas out of the corpus without affecting replay.
await mkdir ( scenarioDir , { recursive : true } )
2026-07-30 17:51:07 +08:00
const existing = await Promise . all ( files . map ( async file = > existsSync ( file ) ? readFile ( file , 'utf8' ) : '' ) )
const fixtures = stabilizeFixtureMessageIds (
2026-08-18 18:10:33 +08:00
ordered . map ( log = > scrubSessionSnapshot ( tokenizeSessionFixtureCwd ( log . content ) ) ) ,
2026-07-30 17:51:07 +08:00
existing ,
)
await Promise . all ( fixtures . map ( async ( fixture , index ) = > {
const file = files [ index ]
2026-07-27 04:37:23 +08:00
if ( file === undefined ) throw new Error ( ` no fixture path for persisted log ${ index } ` )
2026-07-30 17:51:07 +08:00
await writeFile ( file , fixture )
2026-07-27 04:37:23 +08:00
} ) )
}
let expectedContents = await Promise . all ( files . map ( file = > readFile ( file , 'utf8' ) ) )
if ( refreshing ) {
const harvested = ordered . map ( ( log ) : HarvestedLog = > ( {
id : String ( log . header . id ) ,
createdAt : Number ( log . header . createdAt ) ,
. . . typeof log . header . parentSession === 'string' ? { parentSession : log.header.parentSession } : { } ,
content : log.content ,
} ) )
const replacements = refreshFixtureReplacements ( harvested , expectedContents )
2026-08-08 02:15:44 +08:00
const refreshed = ordered . map ( ( log , index ) = > {
2026-07-27 04:37:23 +08:00
const existing = expectedContents [ index ]
2026-08-08 02:15:44 +08:00
if ( existing === undefined ) throw new Error ( ` no fixture for persisted log ${ index } ` )
2026-08-18 18:10:33 +08:00
return scrubSessionSnapshot ( tokenizeSessionFixtureCwd (
2026-07-28 21:28:38 +08:00
stabilizeRefreshLog ( log . content , existing , replacements , actualContext ) ,
) )
2026-08-08 02:15:44 +08:00
} )
expectedContents = stabilizeFixtureMessageIds ( refreshed , expectedContents )
await Promise . all ( expectedContents . map ( async ( stable , index ) = > {
const file = files [ index ]
if ( file === undefined ) throw new Error ( ` no fixture for persisted log ${ index } ` )
2026-07-27 04:37:23 +08:00
await writeFile ( file , stable )
} ) )
}
2026-07-28 21:28:38 +08:00
for ( const [ index , expected ] of expectedContents . entries ( ) ) {
expect ( scrubRequestHeaders ( expected ) , ` ${ scenario . name } session fixture ${ index } carries request-header bulk ` )
. toBe ( expected )
}
2026-07-27 04:37:23 +08:00
// Persisted transcripts match the committed fixtures.
const expectedContext = contextOfContents ( expectedContents )
for ( const [ index , log ] of ordered . entries ( ) ) {
const expected = expectedContents [ index ]
if ( expected === undefined ) throw new Error ( ` no fixture for persisted log ${ index } ` )
2026-08-19 13:58:41 +08:00
expect ( normalizeSessionSnapshot ( log . content , actualContext ) )
. toBe ( normalizeSessionSnapshot ( expected , expectedContext ) )
2026-07-27 04:37:23 +08:00
}
// The SDK-visible wire stream and turn result match their expected outputs.
const normalizedNotifications = normalizeNotifications ( notifications , actualContext )
const normalizedResult = normalizeResult ( result , actualContext )
if ( recording || refreshing ) {
await writeFile ( notificationsExpectedPath , normalizedNotifications )
await writeFile ( resultExpectedPath , normalizedResult )
}
expect ( normalizedNotifications ) . toBe ( await readFile ( notificationsExpectedPath , 'utf8' ) )
expect ( normalizedResult ) . toBe ( await readFile ( resultExpectedPath , 'utf8' ) )
// Wire-shape invariants that must hold in every mode.
2026-07-30 17:28:03 +08:00
expect ( notifications . at ( - 1 ) ) . toMatchObject ( {
method : 'session.status' ,
params : { status : 'idle' } ,
} )
2026-07-29 21:32:19 +08:00
expect ( observedFiles ) . toEqual ( scenario . expectedFiles ? ? { } )
2026-07-29 23:56:28 +08:00
if ( scenario . expectedTools !== undefined ) {
const parent = ordered [ 0 ]
if ( parent === undefined ) throw new Error ( ` ${ scenario . name } has no parent session log ` )
expect ( assembledToolRequirements ( parent ) ) . toEqual ( scenario . expectedTools )
}
2026-08-10 19:30:46 +08:00
if ( scenario . expectedSystem !== undefined ) {
const parent = ordered [ 0 ]
if ( parent === undefined ) throw new Error ( ` ${ scenario . name } has no parent session log ` )
expect ( assembledSystem ( parent ) ) . toBe ( scenario . expectedSystem )
}
if ( scenario . expectedToolDescriptions !== undefined ) {
const parent = ordered [ 0 ]
if ( parent === undefined ) throw new Error ( ` ${ scenario . name } has no parent session log ` )
expect ( assembledToolDescriptions ( parent ) ) . toMatchObject ( scenario . expectedToolDescriptions )
}
2026-08-12 23:28:15 +08:00
if ( scenario . runtimeContext !== undefined ) {
2026-07-30 18:51:29 +08:00
const parent = ordered [ 0 ]
if ( parent === undefined ) throw new Error ( ` ${ scenario . name } has no parent session log ` )
2026-08-12 23:28:15 +08:00
const contexts = assembledRuntimeContexts ( parent )
if ( scenario . runtimeContext === false ) {
expect ( contexts ) . toEqual ( [ ] )
} else {
expect ( contexts ) . toHaveLength ( 1 )
const context = contexts [ 0 ] as string
for ( const clause of scenario . runtimeContext . includes ) expect ( context ) . toContain ( clause )
for ( const clause of scenario . runtimeContext . excludes ) expect ( context ) . not . toContain ( clause )
const system = assembledSystem ( parent )
for ( const clause of scenario . runtimeContext . includes ) expect ( system ) . not . toContain ( clause )
}
2026-07-30 18:51:29 +08:00
}
2026-07-27 04:37:23 +08:00
if ( scenario . children > 0 ) {
expect ( notifications . some ( n = > n . method === 'subagent.started' ) ) . toBe ( true )
expect ( notifications . some ( n = > n . method === 'subagent.finished' ) ) . toBe ( true )
}
} )
}
} )