1
/**2
* The seam's consumer-facing contracts: request, result, and capability types3
* for {@link SubagentProvider}, plus the `subagent/start` and `subagent/end`4
* payloads that plugins and hosts observe. Internal control interfaces belong5
* with their implementation — the lifecycle observer in `./lifecycle.ts`, the6
* continuation host in `./continuation.ts` — so this module stays the published7
* surface rather than a bag of everything type-shaped.8
*9
* @module @deepseek-ai/dsh-subagent/types10
*/12
import type { Agent, AgentOptions } from '@deepseek-ai/dsh-agent'13
import type { Branded } from '@deepseek-ai/dsh-brand'14
import type { ContentBlock, MessageId } from '@deepseek-ai/dsh-llm'15
import type { SessionEvent, SessionId } from '@deepseek-ai/dsh-session'16
import type { ObjectJsonSchema, ToolRestriction } from '@deepseek-ai/dsh-tools'17
import type { SubagentDescriptorData } from './descriptor.ts'19
/** Identifies one accepted subagent run across its lifecycle event pair. */20
export type SubagentRunId = Branded<'SubagentRunId'>22
/**23
* Brand a string as a {@link SubagentRunId}.24
* @param id - the raw run id.25
* @returns the same string, branded.26
*/27
export function SubagentRunId(id: string): SubagentRunId {28
return id as SubagentRunId29
}31
/** What a caller asks for when starting a continuable background child. */32
export interface ContinuableStartSpec {33
/** The `ctx.subagents` provider whose continuable-creation capability establishes the child. */34
readonly provider: string35
/** The initial delegation's short `description`, persisted as the child's creation label. */36
readonly label: string37
/**38
* Optional caller-reserved child identity. Omission preserves the manager's39
* UUID allocation; supplying one lets a durable parent record provisioning40
* before child materialization without a second identity handshake.41
*/42
readonly childId?: SessionId43
/**44
* The delegation request. The manager reserves the stable child id, resolves45
* the durable descriptor, and composes the child itself.46
*/47
readonly request: Omit<SubagentStartRequest, 'label' | 'signal' | 'outputSchema'>48
/** Caller cancellation, owning the operation only until inbox acceptance. */49
readonly signal: AbortSignal50
}52
/** Identities returned once a continuable child accepted its initial prompt. */53
export interface ContinuableStart {54
/** The durable child session id, stable across activations. */55
readonly childId: SessionId56
/** The accepted initial prompt's inbox message id. */57
readonly messageId: MessageId58
}60
/**61
* Authority under which one interrupt request is admitted. `user` carries the62
* durable direct-parent address a human client presented; `ancestor` carries63
* the exact live Agent object whose recorded lineage must contain the caller.64
*/65
export type SubagentInterruptAuthority =66
| { readonly kind: 'user'; readonly parentSessionId: SessionId }67
| { readonly kind: 'ancestor'; readonly agent: Agent }69
/** Options for one model-authored message between adjacent Agents. */70
export interface SubagentSendMessageOptions {71
/** Caller cancellation, owning the operation only until inbox acceptance. */72
readonly signal: AbortSignal73
}75
/**76
* Observe-only identifying detail for a published subagent run, carried by77
* `subagent/start`. One-shot runs and continuable Activation epochs share this78
* payload, so an observer sees the same vocabulary for both.79
*/80
export interface SubagentRunInfo {81
/** Unique identity shared with the paired terminal event. */82
readonly runId: SubagentRunId83
/**84
* Provider name recorded when the child was first created. The provider may85
* be absent when an accepted one-shot run becomes ready or a persisted86
* Activation cold-resumes, because neither lifecycle depends on continued87
* registration.88
*/89
readonly provider: string90
/** The child agent's id. */91
readonly id: SessionId92
/** Snapshot of whether `SubagentRun.localAgent` was present when start fulfilled. */93
readonly local: boolean94
}96
/**97
* Observe-only outcome detail for a settled subagent run, carried by98
* `subagent/end` and paired with one {@link SubagentRunInfo} by `runId`.99
*/100
export interface SubagentRunEndInfo {101
/** Unique identity shared with the paired start event. */102
readonly runId: SubagentRunId103
/** The same provider name carried by the paired start event. */104
readonly provider: string105
/** The child agent's id. */106
readonly id: SessionId107
/** Snapshot of whether `SubagentRun.localAgent` was present when start fulfilled. */108
readonly local: boolean109
/** The terminal stop reason. */110
readonly stopReason: SubagentResult['stopReason']111
/**112
* The child's final assistant output, selected by the same rule as113
* {@link SubagentResult.output}; absent on infrastructure rejection or when114
* the child produced none.115
*/116
readonly lastAssistantMessage?: readonly ContentBlock[]117
}119
/**120
* Which START-TIME features a provider supports. Checked by the service before delegating to121
* {@link SubagentProvider.start}: a request that needs a capability the chosen provider lacks122
* is rejected with a typed error rather than accepted-then-ignored (the "fail loud, no silent123
* degradation" rule). These flags describe the ONE-SHOT124
* {@link SubagentProvider.start} path, where the provider composes the child;125
* continuable children are composed by the continuation manager itself and are126
* gated by {@link SubagentProvider.prepareContinuable} instead. Each flag127
* corresponds one-to-one to a {@link SubagentStartRequest} option: `depthLimit`128
* to `maxDepth`; the other names match.129
*/130
export interface SubagentCapabilities {131
readonly agentOptions: boolean132
readonly outputSchema: boolean133
readonly depthLimit: boolean134
readonly toolFilter: boolean135
readonly persona: boolean136
}138
/**139
* What a caller asks for when starting a ONE-SHOT subagent. The tool layer140
* builds this from the model's `{ description, prompt }` plus its own config;141
* the service validates {@link SubagentCapabilities} against the named provider142
* and resolves the durable descriptor before dispatching to143
* {@link SubagentProvider.start}.144
*/145
export interface SubagentStartRequest {146
/** Optional short display label persisted with a session-backed child. */147
readonly label?: string148
/** Content delivered as the child's user message. */149
readonly prompt: ContentBlock[]150
/**151
* The spawning agent. In-process providers derive workspace, lineage, and152
* delegation depth from its durable session state. ACP reads only its cwd,153
* and only when no deployment `cwd` override is configured.154
*/155
readonly parent: Agent156
/**157
* Cancellation signal from the spawning context (the tool's `exec.signal`).158
* This is the canonical cancellation channel both before and after startup:159
* a provider rejects `start()` after cleaning partial resources when it160
* fires before the run is published, and cancels the published run's161
* remaining turn work when it fires afterward.162
*/163
readonly signal: AbortSignal164
/**165
* Optional host-Agent provider, model, reasoning-effort, and output-token166
* overrides. Requires {@link SubagentCapabilities.agentOptions}; in-process167
* providers merge them over the parent Agent's options when they create the168
* child, while the DSH SDK provider merges them over its instance defaults169
* before initializing the separate child runtime.170
*/171
readonly agentOptions?: AgentOptions172
/**173
* Object-rooted JSON Schema within `assertObjectJsonSchema`'s enforced subset. Start rejects174
* unsupported schemas or providers without the capability. Data must be plain host-realm JSON;175
* a successful child returns the matching value as {@link SubagentResult.structured}.176
*/177
readonly outputSchema?: ObjectJsonSchema178
/**179
* Optional absolute delegation-depth cap for the child being started: its180
* computed depth must be less than or equal to this non-negative safe181
* integer. Requires {@link SubagentCapabilities.depthLimit}; rejected at182
* start otherwise.183
*/184
readonly maxDepth?: number185
/**186
* Optional child tool scoping. Requires {@link SubagentCapabilities.toolFilter};187
* rejected at start otherwise. In-process backends apply it as a scoped188
* `tools.restrict()` in the child's creation window: the named tools vanish189
* from the child's prompt AND refuse to execute (one visibility), with loud190
* unknown-name validation.191
*/192
readonly toolFilter?: ToolRestriction193
/**194
* Optional per-child persona. Requires {@link SubagentCapabilities.persona};195
* rejected at start otherwise. In-process backends register it as a scoped196
* `deployment:persona-prefix` section on the child, SHADOWING the deployment's197
* persona for this child alone — same template semantics as the deployment198
* persona (strict `{{…}}` interpolation against the registered variables).199
*/200
readonly persona?: string201
}203
/**204
* Provider-facing one-shot request after {@link SubagentRuntime.start} resolves205
* the durable child descriptor.206
*/207
export interface ResolvedSubagentStartRequest extends SubagentStartRequest {208
/** Detached descriptor a session-backed provider persists in the child log. */209
readonly descriptor: SubagentDescriptorData210
}212
/**213
* What the continuation manager asks a provider for while materializing one214
* continuable child's FIRST activation. The manager has already reserved the215
* durable child identity and owns every later operation, so this request216
* carries only what distinguishes a fresh child from one seeded with parent217
* history.218
*/219
export interface ContinuableCreateRequest {220
/** The reserved durable child session id, for provider diagnostics. */221
readonly sessionId: SessionId222
/** The delegating parent agent whose history a seeding provider reads. */223
readonly parent: Agent224
/**225
* Caller cancellation, which owns preparation only until the manager accepts226
* the initial prompt into the child's inbox.227
*/228
readonly signal: AbortSignal229
}231
/**232
* A provider's detached contribution to one continuable child's creation. This233
* is DATA, never a capability: it carries no Agent, `AgentHandle`, prompt234
* delivery, result, disposal, or resume operation, because the continuation235
* manager owns the child's whole lifecycle after preparation.236
*/237
export interface ContinuableCreateSpec {238
/**239
* Completed-turn prefix of the parent's log to seed the child session with,240
* or absent for a fresh child. Same durable contract as241
* `CreateAgentOptions.seed`: contiguous from seq 0, lossless JSON, balanced.242
*/243
readonly seed?: readonly SessionEvent[]244
}246
/**247
* Why a subagent run ended. Merge-extensible (a backend may add variants);248
* consumers branch on the known cases and fall through `default`. The known249
* cases mirror the harness turn-end vocabulary so the tool layer can map a250
* non-`completed` result to an `isError` tool result.251
*/252
export interface SubagentStopReasonMap {253
/** The child finished its turn normally. */254
completed: 'completed'255
/** Cancelled through the request signal or disposal. */256
aborted: 'aborted'257
/** Model or transport failure. */258
error: 'error'259
/** The child hit its token ceiling before finishing. */260
'max-tokens': 'max-tokens'261
/** The child declined the task. */262
refusal: 'refusal'263
}265
/** The union over {@link SubagentStopReasonMap} — widens automatically as backends merge in variants. */266
export type SubagentStopReason = SubagentStopReasonMap[keyof SubagentStopReasonMap]268
/**269
* The terminal outcome of a subagent run, resolved by {@link SubagentRun.result}.270
*/271
export interface SubagentResult {272
/**273
* The child's final assistant output is the content of its last non-empty274
* assistant message. Empty-content messages, including usage-only messages,275
* are skipped. Without a non-empty message, the output is its accumulated276
* assistant text stream, or `[]` when the child produced neither.277
*/278
readonly output: readonly ContentBlock[]279
/**280
* The structured result after a requested `outputSchema` was successfully281
* satisfied. Requesting a schema does not guarantee presence: a provider can282
* end with `stopReason: 'error'` when the child fails or finishes without a283
* valid capture. The structured value is validated against the requested284
* output schema by the provider; `unknown` here because the seam is285
* schema-agnostic.286
*/287
readonly structured?: unknown288
/**289
* Provider-authored, non-assistant failure detail for a non-`completed`290
* result. Providers keep this text free of tool inputs, file contents,291
* environment values, credentials, and raw protocol payloads, and limit it292
* to 4096 UTF-8 bytes. Consumers present it separately from {@link output}.293
*/294
readonly diagnostic?: string295
/** Why the run ended. A non-`completed` reason means `output` may be partial. */296
readonly stopReason: SubagentStopReason297
}299
/**300
* ONE-SHOT child handle returned after publication. Prompt submission, turn301
* work, and infrastructure faults after that boundary belong to {@link result}.302
* Consumers await that result and must always {@link dispose} to cancel303
* remaining work and reach quiescence. A run is one disposable foreground304
* delegation with one result; continuable conversations have no run — the305
* continuation manager holds their `AgentHandle` directly and orders every306
* turn through the child's own inbox.307
*/308
export interface SubagentRun {309
/**310
* Parent-scoped run id. For a local run, this MUST equal the published child311
* session id, whose `parentSession` records `request.parent.session.id`; a312
* remote provider mints an id unique in the parent namespace.313
*/314
readonly id: SessionId315
/**316
* The exact published in-process child, or `undefined` for a remote run.317
* When present, its id is {@link id}; the provider retains no ownership318
* implication beyond the run's ordinary {@link dispose} contract.319
*/320
readonly localAgent: Agent | undefined321
/**322
* Resolves with the child's terminal {@link SubagentResult} when the run323
* settles. Does NOT reject on a child-level failure — a model/transport324
* failure resolves with `stopReason: 'error'` so the consumer maps it to an325
* `isError` tool result. Rejects on an infrastructure fault the seam cannot326
* represent as a stop reason.327
*/328
readonly result: Promise<SubagentResult>329
/**330
* Cancel remaining work, reach child quiescence, and release resources.331
* Idempotent.332
*/333
dispose(): Promise<void>334
}336
/**337
* One registered transport for running child agents. Providers are trusted338
* same-process implementations; callers treat descriptors and returned values339
* as borrowed immutable data. The service may call one provider concurrently340
* for distinct children. Providers isolate operation-local mutable state; a341
* shared capacity controller may delay an operation but must not couple its342
* settlement or cleanup to a sibling.343
*/344
export interface SubagentProvider {345
/** Unique registry name (e.g. `spawn`, `fork`, `acp`). */346
readonly name: string347
/** The start-time features this provider supports (see {@link SubagentCapabilities}). */348
readonly capabilities: SubagentCapabilities349
/**350
* Whether the child sees the parent's completed-turn prefix. This is descriptive, not a351
* service-validated start capability: the model-facing tool derives truthful wording from it.352
* It says nothing about tool registration, injected services, or authority inheritance.353
*/354
readonly inheritsParentContext: boolean355
/**356
* Optional static provider-owned provider/model route for one-shot Agent357
* options. Consumers merge tool/model overrides over these values before358
* preflight; providers whose route derives from the parent omit it. The value359
* is detached immutable data and requires `agentOptions` support.360
*/361
readonly agentRouteDefaults?: Readonly<{ provider: string; model: string }>362
/**363
* Establish a ONE-SHOT child and return its handle after publication.364
* The service has already validated that every requested start-time365
* capability is supported and resolved `request.descriptor`, so a366
* session-backed implementation appends that descriptor inside the child's367
* initial turn. Before fulfillment, the provider owns setup and cleans any368
* unpublished partial resources before rejecting. Ownership transfers on369
* fulfillment; subsequent turn or infrastructure failure settles through370
* the returned run. Distinct starts may overlap; cancellation, failure,371
* result settlement, and disposal remain independent for each run.372
*/373
start(request: ResolvedSubagentStartRequest): Promise<SubagentRun>374
/**375
* OPTIONAL (continuable-creation capability): contribute the detached376
* creation inputs that distinguish this provider's continuable children —377
* only whether the child session is seeded with parent history. Method378
* presence IS the capability: the service rejects continuable starts on379
* providers without it, while a provider that has it may still serve380
* ordinary one-shot delegations.381
*382
* This is the provider's ONLY participation in a continuable child. The383
* continuation manager owns identity reservation, composition, Agent384
* creation, prompt delivery, cold resume, ownership, and disposal, so a385
* provider never sees the child's Agent, handle, turns, or teardown.386
* Distinct preparations may overlap; each follows its own signal and returns387
* data belonging only to `request.sessionId`.388
*/389
prepareContinuable?(request: ContinuableCreateRequest): Promise<ContinuableCreateSpec>390
}