@deepseek-ai/dsh-subagent 0.1.1-rc.2 → 0.1.2-alpha.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.i18n.yaml +2 -2
- package/README.md +105 -75
- package/README.zh.md +110 -84
- package/lib/index.js +934 -434
- package/lib/typert.host.d.ts +3 -0
- package/lib/typert.host.js +925 -0
- package/lib/typert.remote-client.d.ts +27 -0
- package/lib/typert.remote-client.js +159 -0
- package/lib/types/child-agent.d.ts +14 -3
- package/lib/types/child-agent.js +48 -10
- package/lib/types/client.d.ts +2 -1
- package/lib/types/client.js +1 -1
- package/lib/types/continuation.d.ts +17 -2
- package/lib/types/continuation.js +174 -48
- package/lib/types/control-types.d.ts +144 -0
- package/lib/types/control-types.js +9 -0
- package/lib/types/control.d.ts +67 -0
- package/lib/types/control.js +115 -0
- package/lib/types/descriptor.d.ts +6 -1
- package/lib/types/descriptor.js +6 -2
- package/lib/types/index.d.ts +62 -26
- package/lib/types/index.js +444 -299
- package/lib/types/list-children.d.ts +10 -58
- package/lib/types/list-children.js +161 -97
- package/lib/types/out-of-process.d.ts +3 -2
- package/lib/types/out-of-process.js +11 -3
- package/lib/types/run-settlement.js +7 -3
- package/lib/types/types.d.ts +18 -0
- package/package.json +63 -37
|
@@ -20,9 +20,61 @@
|
|
|
20
20
|
*
|
|
21
21
|
* @module @deepseek-ai/dsh-subagent
|
|
22
22
|
*/
|
|
23
|
+
var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) {
|
|
24
|
+
if (value !== null && value !== void 0) {
|
|
25
|
+
if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
|
|
26
|
+
var dispose, inner;
|
|
27
|
+
if (async) {
|
|
28
|
+
if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
|
|
29
|
+
dispose = value[Symbol.asyncDispose];
|
|
30
|
+
}
|
|
31
|
+
if (dispose === void 0) {
|
|
32
|
+
if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
|
|
33
|
+
dispose = value[Symbol.dispose];
|
|
34
|
+
if (async) inner = dispose;
|
|
35
|
+
}
|
|
36
|
+
if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
|
|
37
|
+
if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } };
|
|
38
|
+
env.stack.push({ value: value, dispose: dispose, async: async });
|
|
39
|
+
}
|
|
40
|
+
else if (async) {
|
|
41
|
+
env.stack.push({ async: true });
|
|
42
|
+
}
|
|
43
|
+
return value;
|
|
44
|
+
};
|
|
45
|
+
var __disposeResources = (this && this.__disposeResources) || (function (SuppressedError) {
|
|
46
|
+
return function (env) {
|
|
47
|
+
function fail(e) {
|
|
48
|
+
env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
|
|
49
|
+
env.hasError = true;
|
|
50
|
+
}
|
|
51
|
+
var r, s = 0;
|
|
52
|
+
function next() {
|
|
53
|
+
while (r = env.stack.pop()) {
|
|
54
|
+
try {
|
|
55
|
+
if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
|
|
56
|
+
if (r.dispose) {
|
|
57
|
+
var result = r.dispose.call(r.value);
|
|
58
|
+
if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); });
|
|
59
|
+
}
|
|
60
|
+
else s |= 1;
|
|
61
|
+
}
|
|
62
|
+
catch (e) {
|
|
63
|
+
fail(e);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
|
|
67
|
+
if (env.hasError) throw env.error;
|
|
68
|
+
}
|
|
69
|
+
return next();
|
|
70
|
+
};
|
|
71
|
+
})(typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
|
|
72
|
+
var e = new Error(message);
|
|
73
|
+
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
74
|
+
});
|
|
23
75
|
import { randomUUID } from 'node:crypto';
|
|
24
|
-
import {
|
|
25
|
-
import {
|
|
76
|
+
import { brandString } from '@deepseek-ai/dsh-brand';
|
|
77
|
+
import { ReasoningEffortId, boundContextSummary, contentHasImage, createUserMessage, errorChain } from '@deepseek-ai/dsh-llm';
|
|
26
78
|
import { foldSubagentDescriptor, snapshotSubagentDescriptor } from "./descriptor.js";
|
|
27
79
|
import { appendDelegatedPolicyOverrides, applyChildComposition, captureDelegatedPolicyOverrides, childSessionMeta, resolveChildAgentOptions, resolveChildDepth, } from "./child-agent.js";
|
|
28
80
|
import { assertSubagentMaxDepth } from "./depth.js";
|
|
@@ -156,19 +208,22 @@ export class SubagentContinuationManager {
|
|
|
156
208
|
this.assertAdmitting(parent);
|
|
157
209
|
const persistence = this.requirePersistence();
|
|
158
210
|
assertSubagentMaxDepth(request.maxDepth);
|
|
159
|
-
const childId = spec.childId ??
|
|
211
|
+
const childId = spec.childId ?? brandString(randomUUID());
|
|
160
212
|
this.assertChildIdAvailable(childId);
|
|
161
213
|
const childDepth = resolveChildDepth(parent, request.maxDepth);
|
|
162
214
|
// Snapshot before any await: invalid descriptor JSON rejects the call
|
|
163
215
|
// before a child exists, and the detached value is what reaches the log.
|
|
164
|
-
const
|
|
165
|
-
const
|
|
216
|
+
const agentOptions = resolveChildAgentOptions(parent, request.agentOptions, childDepth);
|
|
217
|
+
const agentProvider = agentOptions.provider;
|
|
218
|
+
const agentModel = agentOptions.model;
|
|
219
|
+
const agentReasoningEffort = agentOptions.reasoningEffort;
|
|
166
220
|
const descriptor = snapshotSubagentDescriptor({
|
|
167
221
|
mode: 'continuable',
|
|
168
222
|
provider: spec.provider,
|
|
169
223
|
label: spec.label,
|
|
170
224
|
...agentProvider !== undefined ? { agentProvider } : {},
|
|
171
225
|
...agentModel !== undefined ? { agentModel } : {},
|
|
226
|
+
...agentReasoningEffort !== undefined ? { agentReasoningEffort } : {},
|
|
172
227
|
...request.persona !== undefined ? { persona: request.persona } : {},
|
|
173
228
|
...request.toolFilter !== undefined ? { toolFilter: request.toolFilter } : {},
|
|
174
229
|
});
|
|
@@ -202,7 +257,7 @@ export class SubagentContinuationManager {
|
|
|
202
257
|
provider: spec.provider,
|
|
203
258
|
parent,
|
|
204
259
|
create: { seed, meta: childSessionMeta(parent, childDepth, lineageSeedLength), delegatedPolicies },
|
|
205
|
-
agentOptions
|
|
260
|
+
agentOptions,
|
|
206
261
|
composition: { persona: request.persona, toolFilter: request.toolFilter },
|
|
207
262
|
signal: spec.signal,
|
|
208
263
|
});
|
|
@@ -242,12 +297,25 @@ export class SubagentContinuationManager {
|
|
|
242
297
|
return this.coldResume(parent, childId, content, options);
|
|
243
298
|
// A delivery that arrives after the disposal transaction began must not
|
|
244
299
|
// reach a handle being torn down; wait for release, then cold-resume.
|
|
300
|
+
const disposal = activation.disposal;
|
|
245
301
|
/* v8 ignore next 3 -- the send-versus-dispose cutoff: reaching this arm needs a
|
|
246
302
|
* delivery to observe the transaction inside the same critical section that opened it,
|
|
247
303
|
* which no test can schedule deterministically. The behavior is covered end-to-end by
|
|
248
304
|
* "cold-resumes a delivery that lost the race with final disposal". */
|
|
249
|
-
if (
|
|
250
|
-
return
|
|
305
|
+
if (disposal !== undefined) {
|
|
306
|
+
return disposal.then(() => undefined, () => undefined);
|
|
307
|
+
}
|
|
308
|
+
// Text-only delivery stays await-free, so the disposal-cutoff check
|
|
309
|
+
// above and the submit share one critical window. The image path
|
|
310
|
+
// awaits a capability read, so it re-checks the cutoff afterwards; a
|
|
311
|
+
// disposal that began during the read is waited out and retried like
|
|
312
|
+
// one observed on entry.
|
|
313
|
+
if (contentHasImage(content)) {
|
|
314
|
+
await this.assertImageCapable(activation.handle.agent, options.signal);
|
|
315
|
+
if (activation.disposal !== undefined) {
|
|
316
|
+
await Promise.allSettled([activation.disposal]);
|
|
317
|
+
return undefined;
|
|
318
|
+
}
|
|
251
319
|
}
|
|
252
320
|
return this.submitAdmitted(activation, content, options.source, parent, options.signal);
|
|
253
321
|
});
|
|
@@ -610,56 +678,71 @@ export class SubagentContinuationManager {
|
|
|
610
678
|
return 'settled';
|
|
611
679
|
}
|
|
612
680
|
/**
|
|
613
|
-
* Cold-resume a persisted child:
|
|
681
|
+
* Cold-resume a persisted child: retain and authorize its prepared Session, fold the
|
|
614
682
|
* generic descriptor, create the Activation through `ctx.agents.resume()`,
|
|
615
683
|
* and submit the waiting turn. This never dispatches through a subagent
|
|
616
684
|
* provider — the persisted Session already holds the initial prefix and the
|
|
617
685
|
* descriptor is the whole reconstruction input.
|
|
618
686
|
*/
|
|
619
687
|
async coldResume(parent, childId, content, options) {
|
|
620
|
-
const
|
|
621
|
-
let loaded;
|
|
688
|
+
const env_1 = { stack: [], error: void 0, hasError: false };
|
|
622
689
|
try {
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
690
|
+
const query = this.requireSessionQuery();
|
|
691
|
+
let observation;
|
|
692
|
+
try {
|
|
693
|
+
observation = await query.observeSession(childId, {
|
|
694
|
+
signal: options.signal,
|
|
695
|
+
});
|
|
696
|
+
}
|
|
697
|
+
catch (error) {
|
|
698
|
+
options.signal.throwIfAborted();
|
|
699
|
+
throw new SubagentError(`subagent "${childId}" is unavailable`, 'NOT_RESUMABLE', { cause: error });
|
|
700
|
+
}
|
|
701
|
+
const source = __addDisposableResource(env_1, observation, false);
|
|
702
|
+
this.assertAdmitting(parent);
|
|
703
|
+
// Authorize the persisted header before folding: only the durable child's
|
|
704
|
+
// exact live direct parent may continue it.
|
|
705
|
+
this.authorizeLineage(parent, childId, source.header.parentSession);
|
|
706
|
+
// Fold only the child's own suffix: a fork seed replays the parent's log,
|
|
707
|
+
// which may carry an ANCESTOR's descriptor when the parent is itself a
|
|
708
|
+
// continuable child.
|
|
709
|
+
const descriptor = foldSubagentDescriptor(source.events.slice(source.header.seedLength ?? 0));
|
|
710
|
+
if (descriptor === undefined || descriptor.mode !== 'continuable') {
|
|
711
|
+
throw new SubagentError(`subagent "${childId}" has no supported continuation state and cannot be resumed; `
|
|
712
|
+
+ 'do not retry send_message with this id', 'NOT_RESUMABLE');
|
|
713
|
+
}
|
|
714
|
+
let activation;
|
|
715
|
+
try {
|
|
716
|
+
activation = await this.materialize({
|
|
717
|
+
childId,
|
|
718
|
+
provider: descriptor.provider,
|
|
719
|
+
parent,
|
|
720
|
+
agentOptions: {
|
|
721
|
+
...descriptor.agentProvider !== undefined ? { provider: descriptor.agentProvider } : {},
|
|
722
|
+
...descriptor.agentModel !== undefined ? { model: descriptor.agentModel } : {},
|
|
723
|
+
...descriptor.agentReasoningEffort !== undefined
|
|
724
|
+
? { reasoningEffort: ReasoningEffortId(descriptor.agentReasoningEffort) }
|
|
725
|
+
: {},
|
|
726
|
+
},
|
|
727
|
+
composition: { persona: descriptor.persona, toolFilter: descriptor.toolFilter },
|
|
728
|
+
signal: options.signal,
|
|
729
|
+
});
|
|
730
|
+
}
|
|
731
|
+
catch (error) {
|
|
732
|
+
options.signal.throwIfAborted();
|
|
733
|
+
if (error instanceof SubagentError)
|
|
734
|
+
throw error;
|
|
735
|
+
throw new SubagentError(`subagent "${childId}" is unavailable`, 'NOT_RESUMABLE', { cause: error });
|
|
736
|
+
}
|
|
737
|
+
return await this.submitMaterialized(activation, content, options.source, parent, options.signal);
|
|
641
738
|
}
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
childId,
|
|
646
|
-
provider: descriptor.provider,
|
|
647
|
-
parent,
|
|
648
|
-
agentOptions: {
|
|
649
|
-
...descriptor.agentProvider !== undefined ? { provider: descriptor.agentProvider } : {},
|
|
650
|
-
...descriptor.agentModel !== undefined ? { model: descriptor.agentModel } : {},
|
|
651
|
-
},
|
|
652
|
-
composition: { persona: descriptor.persona, toolFilter: descriptor.toolFilter },
|
|
653
|
-
signal: options.signal,
|
|
654
|
-
});
|
|
739
|
+
catch (e_1) {
|
|
740
|
+
env_1.error = e_1;
|
|
741
|
+
env_1.hasError = true;
|
|
655
742
|
}
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
if (error instanceof SubagentError)
|
|
659
|
-
throw error;
|
|
660
|
-
throw new SubagentError(`subagent "${childId}" is unavailable`, 'NOT_RESUMABLE', { cause: error });
|
|
743
|
+
finally {
|
|
744
|
+
__disposeResources(env_1);
|
|
661
745
|
}
|
|
662
|
-
return this.submitMaterialized(activation, content, options.source, parent, options.signal);
|
|
663
746
|
}
|
|
664
747
|
/**
|
|
665
748
|
* Submit to a freshly materialized Activation or roll it back completely.
|
|
@@ -672,6 +755,15 @@ export class SubagentContinuationManager {
|
|
|
672
755
|
*/
|
|
673
756
|
async submitMaterialized(activation, content, source, parent, signal) {
|
|
674
757
|
try {
|
|
758
|
+
if (contentHasImage(content)) {
|
|
759
|
+
// The capability read awaits with the activation already published, so
|
|
760
|
+
// the disposal cutoff is re-checked before the submit; a drain that
|
|
761
|
+
// began during the read turns into a clean closing rejection.
|
|
762
|
+
await this.assertImageCapable(activation.handle.agent, signal);
|
|
763
|
+
if (activation.disposal !== undefined) {
|
|
764
|
+
throw new SubagentError(`subagent "${activation.childId}" is closing`, 'ACTIVATION_CLOSING');
|
|
765
|
+
}
|
|
766
|
+
}
|
|
675
767
|
return this.submitAdmitted(activation, content, source, parent, signal);
|
|
676
768
|
}
|
|
677
769
|
catch (error) {
|
|
@@ -681,6 +773,32 @@ export class SubagentContinuationManager {
|
|
|
681
773
|
throw error;
|
|
682
774
|
}
|
|
683
775
|
}
|
|
776
|
+
/**
|
|
777
|
+
* Refuse image content addressed to a child whose model accepts text only.
|
|
778
|
+
* Callers guard with `contentHasImage`, so text-only delivery never awaits.
|
|
779
|
+
* The check runs inside the per-child delivery lock, before the message
|
|
780
|
+
* exists, so a rejection leaves no partial user message. When the child's
|
|
781
|
+
* route is not fixed by its options (a request-waterfall listener owns it)
|
|
782
|
+
* or no LLM registry is composed, delivery proceeds and the LLM layer's
|
|
783
|
+
* text-only projection replaces each image with its stable placeholder.
|
|
784
|
+
* @param agent - the live or freshly materialized child agent.
|
|
785
|
+
* @param signal - caller cancellation bounding the model-info read.
|
|
786
|
+
* @throws {SubagentError} `MODEL_DOES_NOT_SUPPORT_IMAGES` when the child's resolved model declines image input.
|
|
787
|
+
*/
|
|
788
|
+
async assertImageCapable(agent, signal) {
|
|
789
|
+
const { provider, model } = agent.options;
|
|
790
|
+
if (provider === undefined || model === undefined)
|
|
791
|
+
return;
|
|
792
|
+
const llm = this.ctx.get('llm');
|
|
793
|
+
/* v8 ignore next -- a deployment without the LLM registry serves no model
|
|
794
|
+
* to refuse against; delivery then defers to the text-only projection. */
|
|
795
|
+
if (llm === undefined)
|
|
796
|
+
return;
|
|
797
|
+
const info = await llm.resolveModelInfo(provider, model, signal);
|
|
798
|
+
if (info.inputModalities !== undefined && !info.inputModalities.includes('image')) {
|
|
799
|
+
throw new SubagentError(`Model "${model}" does not support image input.`, 'MODEL_DOES_NOT_SUPPORT_IMAGES');
|
|
800
|
+
}
|
|
801
|
+
}
|
|
684
802
|
/**
|
|
685
803
|
* Create or resume the child Agent through the private activation-owner
|
|
686
804
|
* scope, install the handle in a fresh Activation, and register ownership on
|
|
@@ -1146,6 +1264,14 @@ export class SubagentContinuationManager {
|
|
|
1146
1264
|
}
|
|
1147
1265
|
return persistence;
|
|
1148
1266
|
}
|
|
1267
|
+
/** Resolve the Session query service used for cold child observations. */
|
|
1268
|
+
requireSessionQuery() {
|
|
1269
|
+
const query = this.ctx.get('sessionQuery');
|
|
1270
|
+
if (query === undefined) {
|
|
1271
|
+
throw new SubagentError('continuable subagents require session query (load @deepseek-ai/dsh-session-query)', 'CONTINUATION_UNAVAILABLE');
|
|
1272
|
+
}
|
|
1273
|
+
return query;
|
|
1274
|
+
}
|
|
1149
1275
|
}
|
|
1150
1276
|
export default SubagentContinuationManager;
|
|
1151
1277
|
//# sourceMappingURL=continuation.js.map
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client-safe subagent catalog and control vocabulary: the durable direct-child
|
|
3
|
+
* row both the listing and the browser catalog answer with, plus the
|
|
4
|
+
* browser-facing control surface's prompt, receipts, and failures.
|
|
5
|
+
*
|
|
6
|
+
* @module @deepseek-ai/dsh-subagent/control-types
|
|
7
|
+
*/
|
|
8
|
+
import type { PromptContentPart } from '@deepseek-ai/dsh-attachment/types';
|
|
9
|
+
import type { Branded } from '@deepseek-ai/dsh-brand';
|
|
10
|
+
import type { MessageId } from '@deepseek-ai/dsh-llm/brand';
|
|
11
|
+
import type { SessionId } from '@deepseek-ai/dsh-session/types';
|
|
12
|
+
/**
|
|
13
|
+
* Client-minted identity of one browser prompt, persisted on the exact accepted
|
|
14
|
+
* message. It carries the Session Controller's `session-request-id` brand so a
|
|
15
|
+
* subagent prompt and an ordinary Session prompt share one identity
|
|
16
|
+
* vocabulary; that package depends on this one, so the brand is spelled here
|
|
17
|
+
* rather than imported.
|
|
18
|
+
*/
|
|
19
|
+
export type SubagentPromptRequestId = Branded<'session-request-id'>;
|
|
20
|
+
/**
|
|
21
|
+
* One durable direct-child row, ordered by header `createdAt` with ties broken
|
|
22
|
+
* on id. Only a candidate whose durable header has `origin: 'subagent'` is
|
|
23
|
+
* interpreted. A served `subagent` projection value produces a `child`; a
|
|
24
|
+
* settled candidate whose fold served no identity produces a `diagnostic`; a
|
|
25
|
+
* running candidate without one is omitted — its descriptor may not be
|
|
26
|
+
* appended yet (the creation window). Diagnostics relay the projection fold's
|
|
27
|
+
* outcome or a failed read, never a per-child event scan, and never expose
|
|
28
|
+
* model-hidden descriptor content.
|
|
29
|
+
*/
|
|
30
|
+
export type SubagentListEntry = {
|
|
31
|
+
readonly kind: 'child';
|
|
32
|
+
/** The durable child session id, stable across Activations. */
|
|
33
|
+
readonly id: SessionId;
|
|
34
|
+
/**
|
|
35
|
+
* Whether the child is live at the moment its reader sampled it: the
|
|
36
|
+
* durable listing reads the Session store (`running` means the logical
|
|
37
|
+
* record is resident, `inactive` that it exists only in persistence),
|
|
38
|
+
* while the browser catalog re-samples the child's Agent driver. Neither
|
|
39
|
+
* encodes a durable outcome, and a continuable child may still reject
|
|
40
|
+
* delivery as an ownership conflict.
|
|
41
|
+
*/
|
|
42
|
+
readonly activity: 'running' | 'inactive';
|
|
43
|
+
/** Whether a direct descendant has durable `origin: 'subagent'`. */
|
|
44
|
+
readonly hasChildren: boolean;
|
|
45
|
+
} & ({
|
|
46
|
+
/** A terminal one-shot child. */
|
|
47
|
+
readonly mode: 'one-shot';
|
|
48
|
+
/** Optional durable creation label from the child's descriptor. */
|
|
49
|
+
readonly label?: string;
|
|
50
|
+
} | {
|
|
51
|
+
/** A resumable conversation. */
|
|
52
|
+
readonly mode: 'continuable';
|
|
53
|
+
/** Durable creation label from the child's descriptor. */
|
|
54
|
+
readonly label: string;
|
|
55
|
+
}) | {
|
|
56
|
+
readonly kind: 'diagnostic';
|
|
57
|
+
/** The candidate's session id. */
|
|
58
|
+
readonly id: SessionId;
|
|
59
|
+
/**
|
|
60
|
+
* Why the candidate has no `child` row: `corrupt` for a settled candidate
|
|
61
|
+
* whose projection fold served no identity (a missing, malformed, or
|
|
62
|
+
* unrecognized-version descriptor — deliberately undistinguished), and
|
|
63
|
+
* for any candidate whose log makes a registered unit's fold or schema
|
|
64
|
+
* throw (deterministic data damage, contained per child); `unavailable`
|
|
65
|
+
* when the candidate's Session observation was absent or transiently
|
|
66
|
+
* unreadable (retried on the next listing). `unsupported` is never produced; it remains in the
|
|
67
|
+
* union for consumers that route on it.
|
|
68
|
+
*/
|
|
69
|
+
readonly reason: 'corrupt' | 'unsupported' | 'unavailable';
|
|
70
|
+
};
|
|
71
|
+
/** Complete direct-child catalog plus the delivery-time parent availability hint. */
|
|
72
|
+
export interface SubagentCatalog {
|
|
73
|
+
readonly entries: readonly SubagentListEntry[];
|
|
74
|
+
readonly parentAvailable: boolean;
|
|
75
|
+
}
|
|
76
|
+
/** Durable parent/child address that selects subagent transport in the client. */
|
|
77
|
+
export type SubagentAddress = {
|
|
78
|
+
readonly parentSessionId: SessionId;
|
|
79
|
+
readonly childSessionId: SessionId;
|
|
80
|
+
} & ({
|
|
81
|
+
readonly mode: 'one-shot';
|
|
82
|
+
} | {
|
|
83
|
+
readonly mode: 'continuable';
|
|
84
|
+
});
|
|
85
|
+
/** One human message addressed to a continuable direct child. */
|
|
86
|
+
export interface SubagentPromptRequest {
|
|
87
|
+
/** Identity persisted on the accepted message, minted before the call. */
|
|
88
|
+
readonly requestId: SubagentPromptRequestId;
|
|
89
|
+
readonly parentSessionId: SessionId;
|
|
90
|
+
readonly childSessionId: SessionId;
|
|
91
|
+
/** Required discriminator retained from the browser control address. */
|
|
92
|
+
readonly mode: 'continuable';
|
|
93
|
+
/**
|
|
94
|
+
* Browser prompt parts delivered as the child's user message. The Host
|
|
95
|
+
* admits and persists image parts before delivery, so the wire never
|
|
96
|
+
* carries a durable attachment reference the caller could fabricate.
|
|
97
|
+
*/
|
|
98
|
+
readonly content: readonly PromptContentPart[];
|
|
99
|
+
/** Optional browser zone sampled for this exact human prompt. */
|
|
100
|
+
readonly clientTimeZone?: string;
|
|
101
|
+
}
|
|
102
|
+
/** Inbox identity returned once the continuation accepts one human message. */
|
|
103
|
+
export interface SubagentPromptReceipt {
|
|
104
|
+
readonly messageId: MessageId;
|
|
105
|
+
}
|
|
106
|
+
/** Uniform acknowledgement that one interrupt request was admitted. */
|
|
107
|
+
export interface SubagentInterruptReceipt {
|
|
108
|
+
readonly accepted: true;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Failure details the control surface answers with. Catalog reads, prompts,
|
|
112
|
+
* and interrupts share this vocabulary with the Client Remote result.
|
|
113
|
+
*/
|
|
114
|
+
declare module '@deepseek-ai/dsh-typert-protocol' {
|
|
115
|
+
interface RemoteErrorDetailsMap {
|
|
116
|
+
/** A browser-supplied zone is neither UTC nor a canonical IANA name. */
|
|
117
|
+
'subagent/invalid-time-zone': {
|
|
118
|
+
readonly value: string;
|
|
119
|
+
};
|
|
120
|
+
/** No live Agent carries the addressed parent session. */
|
|
121
|
+
'subagent/parent-unavailable': {
|
|
122
|
+
readonly parentSessionId: SessionId;
|
|
123
|
+
};
|
|
124
|
+
/** The addressed child cannot take a continuation. */
|
|
125
|
+
'subagent/not-resumable': {
|
|
126
|
+
readonly childSessionId: SessionId;
|
|
127
|
+
};
|
|
128
|
+
/** The claimed parent does not own the addressed child. */
|
|
129
|
+
'subagent/unauthorized': {
|
|
130
|
+
readonly childSessionId: SessionId;
|
|
131
|
+
};
|
|
132
|
+
/** Image admission or model image-capability refusal. */
|
|
133
|
+
'subagent/attachment-invalid': {
|
|
134
|
+
readonly reason: string;
|
|
135
|
+
};
|
|
136
|
+
/** The child exists but its inbox cannot admit the message now. */
|
|
137
|
+
'subagent/delivery-unavailable': {
|
|
138
|
+
readonly childSessionId: SessionId;
|
|
139
|
+
};
|
|
140
|
+
/** The deployment mounts no session-projection registry. */
|
|
141
|
+
'subagent/projections-unavailable': {};
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
//# sourceMappingURL=control-types.d.ts.map
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client-safe subagent catalog and control vocabulary: the durable direct-child
|
|
3
|
+
* row both the listing and the browser catalog answer with, plus the
|
|
4
|
+
* browser-facing control surface's prompt, receipts, and failures.
|
|
5
|
+
*
|
|
6
|
+
* @module @deepseek-ai/dsh-subagent/control-types
|
|
7
|
+
*/
|
|
8
|
+
export {};
|
|
9
|
+
//# sourceMappingURL=control-types.js.map
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser-facing subagent control assembly: the catalog view sampled against
|
|
3
|
+
* the live Agent registry, one browser zone's validation, and the stable
|
|
4
|
+
* failure codes the Remote surface answers with.
|
|
5
|
+
*
|
|
6
|
+
* @module @deepseek-ai/dsh-subagent
|
|
7
|
+
*/
|
|
8
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
9
|
+
import type { SessionId } from '@deepseek-ai/dsh-session';
|
|
10
|
+
import { z } from 'zod';
|
|
11
|
+
import type { SubagentCatalog, SubagentListEntry } from './control-types.ts';
|
|
12
|
+
declare const CONTROL_ID_SCHEMAS: {
|
|
13
|
+
readonly 'subagent.list': z.ZodObject<{
|
|
14
|
+
parentSessionId: z.ZodString;
|
|
15
|
+
}, z.core.$strip>;
|
|
16
|
+
readonly 'subagent.prompt': z.ZodObject<{
|
|
17
|
+
parentSessionId: z.ZodString;
|
|
18
|
+
childSessionId: z.ZodString;
|
|
19
|
+
mode: z.ZodLiteral<"continuable">;
|
|
20
|
+
}, z.core.$strip>;
|
|
21
|
+
readonly 'subagent.interrupt': z.ZodObject<{
|
|
22
|
+
parentSessionId: z.ZodString;
|
|
23
|
+
childSessionId: z.ZodString;
|
|
24
|
+
mode: z.ZodLiteral<"continuable">;
|
|
25
|
+
}, z.core.$strip>;
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Apply the subagent payload checks that are stricter than generated
|
|
29
|
+
* branded-string codecs.
|
|
30
|
+
* @param method - method name carried in the failure message.
|
|
31
|
+
* @param payload - decoded control fields to validate.
|
|
32
|
+
* @throws {RemoteError} `gateway/bad-request` with the original Zod issues.
|
|
33
|
+
*/
|
|
34
|
+
export declare function validateControlRequest(method: keyof typeof CONTROL_ID_SCHEMAS, payload: unknown): void;
|
|
35
|
+
/**
|
|
36
|
+
* Project one durable listing onto the catalog view, replacing each row's
|
|
37
|
+
* store-derived activity with the live Agent driver's status and reporting
|
|
38
|
+
* whether the exact parent Agent is live. Without an Agent registry no driver
|
|
39
|
+
* runs at all, so every row is inactive and the parent is unavailable.
|
|
40
|
+
* @param ctx - Host context that may carry the Agent registry.
|
|
41
|
+
* @param parentSessionId - the listed parent.
|
|
42
|
+
* @param entries - the durable direct-child listing.
|
|
43
|
+
* @returns the catalog view answered to one browser.
|
|
44
|
+
*/
|
|
45
|
+
export declare function catalogView(ctx: Context, parentSessionId: SessionId, entries: readonly SubagentListEntry[]): SubagentCatalog;
|
|
46
|
+
/**
|
|
47
|
+
* Refuse one catalog read while preserving cancellation and a missing
|
|
48
|
+
* projections registry as distinct failures.
|
|
49
|
+
* @param error - the thrown value.
|
|
50
|
+
* @param signal - the caller's cancellation.
|
|
51
|
+
* @returns Never — the refusal is thrown.
|
|
52
|
+
* @throws {RemoteError} always.
|
|
53
|
+
*/
|
|
54
|
+
export declare function rejectCatalogRead(error: unknown, signal: AbortSignal): never;
|
|
55
|
+
/**
|
|
56
|
+
* Refuse one continuation prompt without exposing provider detail: admission
|
|
57
|
+
* failures the caller can act on keep their own code, everything else is
|
|
58
|
+
* internal.
|
|
59
|
+
* @param error - the thrown value.
|
|
60
|
+
* @param childSessionId - the addressed child.
|
|
61
|
+
* @param signal - the caller's cancellation.
|
|
62
|
+
* @returns Never — the refusal is thrown.
|
|
63
|
+
* @throws {RemoteError} always.
|
|
64
|
+
*/
|
|
65
|
+
export declare function rejectPrompt(error: unknown, childSessionId: SessionId, signal: AbortSignal): never;
|
|
66
|
+
export {};
|
|
67
|
+
//# sourceMappingURL=control.d.ts.map
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser-facing subagent control assembly: the catalog view sampled against
|
|
3
|
+
* the live Agent registry, one browser zone's validation, and the stable
|
|
4
|
+
* failure codes the Remote surface answers with.
|
|
5
|
+
*
|
|
6
|
+
* @module @deepseek-ai/dsh-subagent
|
|
7
|
+
*/
|
|
8
|
+
import { AttachmentError } from '@deepseek-ai/dsh-attachment';
|
|
9
|
+
import { RemoteError } from '@deepseek-ai/dsh-typert-protocol';
|
|
10
|
+
import { z } from 'zod';
|
|
11
|
+
import { SubagentError } from "./error.js";
|
|
12
|
+
const SESSION_ID_SCHEMA = z.string().min(1);
|
|
13
|
+
const CONTROL_ID_SCHEMAS = {
|
|
14
|
+
'subagent.list': z.object({ parentSessionId: SESSION_ID_SCHEMA }),
|
|
15
|
+
'subagent.prompt': z.object({
|
|
16
|
+
parentSessionId: SESSION_ID_SCHEMA,
|
|
17
|
+
childSessionId: SESSION_ID_SCHEMA,
|
|
18
|
+
mode: z.literal('continuable'),
|
|
19
|
+
}),
|
|
20
|
+
'subagent.interrupt': z.object({
|
|
21
|
+
parentSessionId: SESSION_ID_SCHEMA,
|
|
22
|
+
childSessionId: SESSION_ID_SCHEMA,
|
|
23
|
+
mode: z.literal('continuable'),
|
|
24
|
+
}),
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Apply the subagent payload checks that are stricter than generated
|
|
28
|
+
* branded-string codecs.
|
|
29
|
+
* @param method - method name carried in the failure message.
|
|
30
|
+
* @param payload - decoded control fields to validate.
|
|
31
|
+
* @throws {RemoteError} `gateway/bad-request` with the original Zod issues.
|
|
32
|
+
*/
|
|
33
|
+
export function validateControlRequest(method, payload) {
|
|
34
|
+
const parsed = CONTROL_ID_SCHEMAS[method].safeParse(payload);
|
|
35
|
+
if (!parsed.success) {
|
|
36
|
+
throw new RemoteError('gateway/bad-request', `invalid payload for ${method}`, { issues: parsed.error.issues });
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Project one durable listing onto the catalog view, replacing each row's
|
|
41
|
+
* store-derived activity with the live Agent driver's status and reporting
|
|
42
|
+
* whether the exact parent Agent is live. Without an Agent registry no driver
|
|
43
|
+
* runs at all, so every row is inactive and the parent is unavailable.
|
|
44
|
+
* @param ctx - Host context that may carry the Agent registry.
|
|
45
|
+
* @param parentSessionId - the listed parent.
|
|
46
|
+
* @param entries - the durable direct-child listing.
|
|
47
|
+
* @returns the catalog view answered to one browser.
|
|
48
|
+
*/
|
|
49
|
+
export function catalogView(ctx, parentSessionId, entries) {
|
|
50
|
+
const agents = ctx.get('agents');
|
|
51
|
+
return {
|
|
52
|
+
entries: entries.map((entry) => entry.kind === 'child'
|
|
53
|
+
? { ...entry, activity: agents?.get(entry.id)?.status === 'running' ? 'running' : 'inactive' }
|
|
54
|
+
: entry),
|
|
55
|
+
parentAvailable: agents?.get(parentSessionId) !== undefined,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Refuse one catalog read while preserving cancellation and a missing
|
|
60
|
+
* projections registry as distinct failures.
|
|
61
|
+
* @param error - the thrown value.
|
|
62
|
+
* @param signal - the caller's cancellation.
|
|
63
|
+
* @returns Never — the refusal is thrown.
|
|
64
|
+
* @throws {RemoteError} always.
|
|
65
|
+
*/
|
|
66
|
+
export function rejectCatalogRead(error, signal) {
|
|
67
|
+
if (isCancellation(error, signal)) {
|
|
68
|
+
throw new RemoteError('gateway/cancelled', 'subagent catalog read was cancelled', {}, { cause: error });
|
|
69
|
+
}
|
|
70
|
+
if (error instanceof SubagentError && error.code === 'SUBAGENT_CONTROL_PROJECTIONS_UNAVAILABLE') {
|
|
71
|
+
throw new RemoteError('subagent/projections-unavailable', 'subagent catalog is unavailable: this deployment does not mount the sessionProjections registry (load @deepseek-ai/dsh-session-projection)', {}, { cause: error });
|
|
72
|
+
}
|
|
73
|
+
throw new RemoteError('gateway/internal', 'subagent catalog read failed', {}, { cause: error });
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Refuse one continuation prompt without exposing provider detail: admission
|
|
77
|
+
* failures the caller can act on keep their own code, everything else is
|
|
78
|
+
* internal.
|
|
79
|
+
* @param error - the thrown value.
|
|
80
|
+
* @param childSessionId - the addressed child.
|
|
81
|
+
* @param signal - the caller's cancellation.
|
|
82
|
+
* @returns Never — the refusal is thrown.
|
|
83
|
+
* @throws {RemoteError} always.
|
|
84
|
+
*/
|
|
85
|
+
export function rejectPrompt(error, childSessionId, signal) {
|
|
86
|
+
if (isCancellation(error, signal)) {
|
|
87
|
+
throw new RemoteError('gateway/cancelled', 'subagent prompt was cancelled', {}, { cause: error });
|
|
88
|
+
}
|
|
89
|
+
if (error instanceof AttachmentError) {
|
|
90
|
+
throw new RemoteError('subagent/attachment-invalid', error.message, { reason: error.code }, { cause: error });
|
|
91
|
+
}
|
|
92
|
+
if (error instanceof SubagentError) {
|
|
93
|
+
switch (error.code) {
|
|
94
|
+
case 'MODEL_DOES_NOT_SUPPORT_IMAGES':
|
|
95
|
+
throw new RemoteError('subagent/attachment-invalid', error.message, { reason: error.code }, { cause: error });
|
|
96
|
+
case 'NOT_RESUMABLE':
|
|
97
|
+
throw new RemoteError('subagent/not-resumable', 'subagent cannot be resumed', { childSessionId }, { cause: error });
|
|
98
|
+
case 'UNAUTHORIZED':
|
|
99
|
+
throw new RemoteError('subagent/unauthorized', 'subagent does not belong to this parent', { childSessionId }, { cause: error });
|
|
100
|
+
case 'DRAINING':
|
|
101
|
+
case 'ACTIVATION_CLOSING':
|
|
102
|
+
case 'CONTINUATION_UNAVAILABLE':
|
|
103
|
+
case 'PERSISTENCE_UNAVAILABLE':
|
|
104
|
+
throw new RemoteError('subagent/delivery-unavailable', 'subagent follow-up is temporarily unavailable', { childSessionId }, { cause: error });
|
|
105
|
+
// A code outside the admission vocabulary is not the caller's move to make.
|
|
106
|
+
default:
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
throw new RemoteError('gateway/internal', 'subagent prompt failed', {}, { cause: error });
|
|
111
|
+
}
|
|
112
|
+
function isCancellation(error, signal) {
|
|
113
|
+
return signal.aborted || (error instanceof SubagentError && error.code === 'CANCELLED');
|
|
114
|
+
}
|
|
115
|
+
//# sourceMappingURL=control.js.map
|