ec601ca13d
Machine-produced by `pnpm run rescope-vendor --apply` plus the regeneration it prints: `pnpm install` for the lockfile, `pnpm run gen-third-party-notices`, `verify-translation-pairing --write` for the touched bilingual pairs, `gen-doc-graphs`, and one typert snapshot whose ids embed character offsets. `pnpm run rescope-vendor --check` verifies the result. Renames nine vendored packages (cordis, cosmokit, schemastery and the six @cordisjs plugins) and every reference that resolves them: manifest names and dependency keys, module specifiers including declare-module merges, cordis.yml plugin names, tsconfig paths, every Markdown fence, and `docs/` prose. Directory names, upstream versions, and dependency ranges are unchanged, so vendor/README.md still reads as an upstream snapshot; its manifest table gains an upstream-name column so THIRD_PARTY_NOTICES keeps MIT attribution pointed at each fork's origin. The tutorial tier follows the rename end to end: its yaml fences named plugins the Loader can no longer resolve, its `ts ignore-check` fences disagreed with the compiled fences beside them, and its prose quoted both. The contracts that told readers to keep upstream names — the root convention and the vendoring cookbook's tree comment and manifest invariant — now say to rescope instead. Two rules read `@deepseek-ai/` as "another workspace plugin": the client bundle purity gate now names the vendored libraries a browser bundle inlines, and the files where a bare `cordis` is an agent-preset id keep that product data.
139 lines
6.4 KiB
TypeScript
139 lines
6.4 KiB
TypeScript
/**
|
|
* Out-of-process SDK subagent backend. Each child is a complete DeepSeek
|
|
* Harness runtime in its own process — own `cordis.yml`-decided composition,
|
|
* session, model route, and tools — driven over stdio JSON-RPC through the
|
|
* TypeScript SDK client, so it shares no Cordis context and advertises no
|
|
* parent-enforced start capabilities; the ONE thing it reads off
|
|
* `request.parent` is the session's workspace cwd. This plugin uses named
|
|
* exports only; a default would hide its loader metadata (see
|
|
* `docs/postmortem/0001-acp-default-export-drops-inject.md`).
|
|
* @module @deepseek-ai/dsh-subagent-dsh-sdk
|
|
*/
|
|
|
|
import type { Context } from '@deepseek-ai/cordis'
|
|
import z from '@deepseek-ai/schemastery'
|
|
import type { SubagentCapabilities, SubagentProvider, SubagentStartRequest } from '@deepseek-ai/dsh-subagent'
|
|
import { assertPositiveFinite, NO_START_CAPABILITIES, resolveChildCwd, validateConfiguredCwd } from '@deepseek-ai/dsh-subagent'
|
|
import {
|
|
DEFAULT_DISPOSE_EOF_GRACE_MS,
|
|
DEFAULT_DISPOSE_GRACE_MS,
|
|
DEFAULT_SHUTDOWN_TIMEOUT_MS,
|
|
startSdkRun,
|
|
type SdkRunSpec,
|
|
} from './run.ts'
|
|
|
|
export const name = 'subagent-dsh-sdk'
|
|
export const inject = ['subagents']
|
|
|
|
/** Config: how to spawn and drive the child SDK runtime process. */
|
|
export interface Config {
|
|
/** Provider name on `ctx.subagents` (default `dsh-sdk`). */
|
|
providerName: string
|
|
/** The executable to spawn for each run (the child runtime bin or packaged exe). */
|
|
command: string
|
|
/** Arguments passed to {@link command} (typically the child's `cordis.yml` path). */
|
|
args: string[]
|
|
/**
|
|
* Working directory override for the child process and its SDK session
|
|
* workspace. Must be non-empty; a relative path resolves against the
|
|
* harness launch directory at load, and the result must be an existing
|
|
* directory. When omitted, each child inherits its delegating parent
|
|
* session's cwd — and starting one from a parent session that has no cwd
|
|
* fails.
|
|
*/
|
|
cwd?: string
|
|
/** Provider route the child runtime initializes with (default `deepseek-official`). */
|
|
provider: string
|
|
/** Model the child runtime initializes with (default `deepseek-v4-flash`). */
|
|
model: string
|
|
/** Optional per-request output-token cap for the child runtime. */
|
|
maxTokens?: number
|
|
/**
|
|
* Extra environment variables for the child process — e.g. the child
|
|
* runtime's own `DEEPSEEK_API_KEY`, or `DSH_CORDIS_CONFIG` naming its
|
|
* config. Forwarded on top of a credential-scrubbed copy of the parent
|
|
* env, so an explicit key here reaches the child while ambient secrets do
|
|
* not leak implicitly.
|
|
*/
|
|
env: Record<string, string>
|
|
/** Bound (ms) on the protocol `shutdown` exchange during dispose. */
|
|
shutdownTimeoutMs?: number
|
|
/**
|
|
* Grace period (ms) for the child's EOF-driven quiesce on dispose — its
|
|
* window to flush persistence and tear down its own nested subprocesses
|
|
* before the parent escalates to a signal.
|
|
*/
|
|
disposeEofGraceMs?: number
|
|
/** Termination confirmation window (ms), including forced exit on every platform. */
|
|
disposeGraceMs?: number
|
|
}
|
|
|
|
export const Config: z<Config> = z.object({
|
|
providerName: z.string().default('dsh-sdk'),
|
|
command: z.string().required(),
|
|
args: z.array(z.string()).default([]),
|
|
cwd: z.string(),
|
|
provider: z.string().default('deepseek-official'),
|
|
model: z.string().default('deepseek-v4-flash'),
|
|
maxTokens: z.number().step(1).min(1).max(Number.MAX_SAFE_INTEGER),
|
|
env: z.dict(z.string()).default({}),
|
|
shutdownTimeoutMs: z.number().default(DEFAULT_SHUTDOWN_TIMEOUT_MS),
|
|
disposeEofGraceMs: z.number().default(DEFAULT_DISPOSE_EOF_GRACE_MS),
|
|
disposeGraceMs: z.number().default(DEFAULT_DISPOSE_GRACE_MS),
|
|
})
|
|
|
|
/** The shape after schemastery applied the defaults (`cwd` and `maxTokens` have none). */
|
|
type ResolvedConfig = Required<Omit<Config, 'cwd' | 'maxTokens'>> & Pick<Config, 'cwd' | 'maxTokens'>
|
|
|
|
/**
|
|
* The SDK provider. Advertises NO start-time capabilities: an out-of-process
|
|
* child cannot honor `outputSchema`/`maxDepth`/`toolFilter`/`persona` (the
|
|
* service rejects a request needing any of them before `start` runs).
|
|
*/
|
|
class SdkProvider implements SubagentProvider {
|
|
readonly capabilities: SubagentCapabilities = NO_START_CAPABILITIES
|
|
// Context contract: an out-of-process SDK child starts fresh — no parent conversation crosses the process boundary.
|
|
readonly inheritsParentContext = false
|
|
|
|
constructor(readonly name: string, private readonly ctx: Context, private readonly config: ResolvedConfig) {}
|
|
|
|
start(request: SubagentStartRequest) {
|
|
const spec: SdkRunSpec = {
|
|
command: this.config.command,
|
|
args: this.config.args,
|
|
cwd: resolveChildCwd('subagent-dsh-sdk', this.config.cwd, request.parent.session.header.cwd),
|
|
provider: this.config.provider,
|
|
model: this.config.model,
|
|
...this.config.maxTokens === undefined ? {} : { maxTokens: this.config.maxTokens },
|
|
env: this.config.env,
|
|
shutdownTimeoutMs: this.config.shutdownTimeoutMs,
|
|
disposeEofGraceMs: this.config.disposeEofGraceMs,
|
|
disposeGraceMs: this.config.disposeGraceMs,
|
|
onError: (error, stopReason) => {
|
|
// The seam forbids `result` rejecting, so a child-level failure is
|
|
// flattened to a stop reason — preserve it here rather than losing it.
|
|
this.ctx.logger.warn(`subagent-dsh-sdk "${this.name}": child run failed (${stopReason}): ${error.message}`)
|
|
},
|
|
}
|
|
return startSdkRun(request, spec)
|
|
}
|
|
}
|
|
|
|
export function apply(ctx: Context, config: Config): void {
|
|
// schemastery (Config) has already filled every defaulted field.
|
|
const resolved = config as ResolvedConfig
|
|
assertPositiveFinite('subagent-dsh-sdk', 'shutdownTimeoutMs', resolved.shutdownTimeoutMs)
|
|
assertPositiveFinite('subagent-dsh-sdk', 'disposeEofGraceMs', resolved.disposeEofGraceMs)
|
|
assertPositiveFinite('subagent-dsh-sdk', 'disposeGraceMs', resolved.disposeGraceMs)
|
|
if (resolved.maxTokens !== undefined && (!Number.isSafeInteger(resolved.maxTokens) || resolved.maxTokens <= 0)) {
|
|
throw new TypeError('subagent-dsh-sdk maxTokens must be a positive safe integer')
|
|
}
|
|
// Interpret a relative configured cwd against the harness launch directory
|
|
// ONCE, at load, and fail a misconfigured directory here — not per start.
|
|
const configuredCwd = validateConfiguredCwd('subagent-dsh-sdk', resolved.cwd)
|
|
const validated: ResolvedConfig = configuredCwd === undefined
|
|
? resolved
|
|
: { ...resolved, cwd: configuredCwd }
|
|
ctx.subagents.registerProvider(new SdkProvider(validated.providerName, ctx, validated))
|
|
}
|