@morlay/dsh-subagent 0.0.10 → 0.0.11-alpha.0
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/dist/index.d.mts +77 -120
- package/dist/index.mjs +43 -49
- package/dist/rows.d.mts +1 -3
- package/dist/rows.mjs +1 -2
- package/package.json +28 -28
- package/src/continuation-messages.ts +8 -3
- package/src/index.ts +5 -4
- package/src/manager.ts +16 -2
- package/src/vendor/dsh-subagent/activation.ts +81 -89
- package/src/vendor/dsh-subagent/archive-admission.ts +38 -45
- package/src/vendor/dsh-subagent/assistant-output.ts +17 -19
- package/src/vendor/dsh-subagent/catalog.ts +79 -111
- package/src/vendor/dsh-subagent/child-agent.ts +83 -92
- package/src/vendor/dsh-subagent/continuation-messages.ts +64 -68
- package/src/vendor/dsh-subagent/control-types.ts +71 -68
- package/src/vendor/dsh-subagent/control.ts +39 -55
- package/src/vendor/dsh-subagent/depth.ts +14 -18
- package/src/vendor/dsh-subagent/descriptor.ts +107 -113
- package/src/vendor/dsh-subagent/error.ts +3 -3
- package/src/vendor/dsh-subagent/index.ts +193 -254
- package/src/vendor/dsh-subagent/internal.ts +17 -21
- package/src/vendor/dsh-subagent/lifecycle.ts +48 -51
- package/src/vendor/dsh-subagent/list-children.ts +57 -65
- package/src/vendor/dsh-subagent/manager.ts +726 -862
- package/src/vendor/dsh-subagent/out-of-process.ts +59 -61
- package/src/vendor/dsh-subagent/projection-types.ts +40 -38
- package/src/vendor/dsh-subagent/projection.ts +85 -107
- package/src/vendor/dsh-subagent/structured.ts +48 -57
- package/src/vendor/dsh-subagent/types.ts +68 -68
|
@@ -4,19 +4,19 @@
|
|
|
4
4
|
* @module @deepseek-ai/dsh-subagent/continuation-messages
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import type { Agent } from
|
|
8
|
-
import { boundContextSummary, createUserMessage } from
|
|
9
|
-
import type { ContentBlock } from
|
|
10
|
-
import type { SessionId } from
|
|
11
|
-
import type { SubagentResult } from
|
|
7
|
+
import type { Agent } from '@deepseek-ai/dsh-agent'
|
|
8
|
+
import { boundContextSummary, createUserMessage } from '@deepseek-ai/dsh-llm'
|
|
9
|
+
import type { ContentBlock } from '@deepseek-ai/dsh-llm'
|
|
10
|
+
import type { SessionId } from '@deepseek-ai/dsh-session'
|
|
11
|
+
import type { SubagentResult } from './types.ts'
|
|
12
12
|
|
|
13
13
|
/** Durable attribution for one model-authored message between Agents. */
|
|
14
14
|
export interface AgentMessageSource {
|
|
15
|
-
readonly kind:
|
|
15
|
+
readonly kind: 'agent-message'
|
|
16
16
|
/** A message another agent addressed to this one (`relay` context form). */
|
|
17
|
-
readonly form:
|
|
17
|
+
readonly form: 'relay'
|
|
18
18
|
/** Session id of the Agent whose tool call produced the message. */
|
|
19
|
-
readonly senderSessionId: SessionId
|
|
19
|
+
readonly senderSessionId: SessionId
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
/**
|
|
@@ -27,29 +27,29 @@ export interface AgentMessageSource {
|
|
|
27
27
|
* transcript that merged them would credit the child with words it never wrote.
|
|
28
28
|
*/
|
|
29
29
|
export interface SubagentSettledMessageSource {
|
|
30
|
-
readonly kind:
|
|
30
|
+
readonly kind: 'subagent-settled'
|
|
31
31
|
/** A runtime account shown without expanding the row (`notice` context form). */
|
|
32
|
-
readonly form:
|
|
32
|
+
readonly form: 'notice'
|
|
33
33
|
/** One-line account of how the child ended. */
|
|
34
|
-
readonly summary: string
|
|
34
|
+
readonly summary: string
|
|
35
35
|
/** Session id of the child that settled. */
|
|
36
|
-
readonly senderSessionId: SessionId
|
|
36
|
+
readonly senderSessionId: SessionId
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
declare module
|
|
39
|
+
declare module '@deepseek-ai/dsh-llm' {
|
|
40
40
|
interface MessageSourceMap {
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
'agent-message': AgentMessageSource
|
|
42
|
+
'subagent-settled': SubagentSettledMessageSource
|
|
43
43
|
}
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
/** Build durable attribution for one adjacent-Agent message. */
|
|
47
47
|
function agentMessageSource(sender: Agent): AgentMessageSource {
|
|
48
48
|
return {
|
|
49
|
-
kind:
|
|
50
|
-
form:
|
|
49
|
+
kind: 'agent-message',
|
|
50
|
+
form: 'relay',
|
|
51
51
|
senderSessionId: sender.id,
|
|
52
|
-
}
|
|
52
|
+
}
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
/**
|
|
@@ -63,9 +63,12 @@ export function createAgentMessage(
|
|
|
63
63
|
content: ContentBlock[],
|
|
64
64
|
): ReturnType<typeof createUserMessage> {
|
|
65
65
|
return createUserMessage({
|
|
66
|
-
content: [
|
|
66
|
+
content: [
|
|
67
|
+
{ type: 'text' as const, text: `Agent ${sender.id} sent a message: ` },
|
|
68
|
+
...content,
|
|
69
|
+
],
|
|
67
70
|
source: agentMessageSource(sender),
|
|
68
|
-
})
|
|
71
|
+
})
|
|
69
72
|
}
|
|
70
73
|
|
|
71
74
|
/**
|
|
@@ -78,19 +81,18 @@ export function withContinuableReturnGuidance(
|
|
|
78
81
|
parentId: SessionId,
|
|
79
82
|
prompt: ContentBlock[],
|
|
80
83
|
): ContentBlock[] {
|
|
81
|
-
const encodedParentId = JSON.stringify(parentId)
|
|
84
|
+
const encodedParentId = JSON.stringify(parentId)
|
|
82
85
|
return [
|
|
83
86
|
...prompt,
|
|
84
87
|
{
|
|
85
|
-
type:
|
|
86
|
-
text:
|
|
87
|
-
`
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
"does not end your turn.",
|
|
88
|
+
type: 'text',
|
|
89
|
+
text: `Your parent agent id is ${encodedParentId}. Before you finish, send your result to that agent with `
|
|
90
|
+
+ `send_message({ agent_id: ${encodedParentId}, message: "<self-contained result>" }). The parent shares `
|
|
91
|
+
+ 'your workspace but does not automatically receive your transcript, tool output, or reasoning. Send '
|
|
92
|
+
+ 'earlier messages as well when a finding changes what the parent should do next; sending a message '
|
|
93
|
+
+ 'does not end your turn.',
|
|
92
94
|
},
|
|
93
|
-
]
|
|
95
|
+
]
|
|
94
96
|
}
|
|
95
97
|
|
|
96
98
|
/**
|
|
@@ -101,32 +103,28 @@ export function withContinuableReturnGuidance(
|
|
|
101
103
|
* @param continuable - whether the child accepts a later message.
|
|
102
104
|
* @returns the model-facing opening line of the settlement notice.
|
|
103
105
|
*/
|
|
104
|
-
function settlementSummary(
|
|
105
|
-
childId
|
|
106
|
-
stopReason: SubagentResult["stopReason"],
|
|
107
|
-
continuable: boolean,
|
|
108
|
-
): string {
|
|
109
|
-
const subject = `Background subagent ${childId}`;
|
|
106
|
+
function settlementSummary(childId: SessionId, stopReason: SubagentResult['stopReason'], continuable: boolean): string {
|
|
107
|
+
const subject = `Background subagent ${childId}`
|
|
110
108
|
switch (stopReason) {
|
|
111
|
-
case
|
|
109
|
+
case 'completed':
|
|
112
110
|
return continuable
|
|
113
111
|
? `${subject} finished and will do no further work unless you send it more.`
|
|
114
|
-
: `${subject} finished. It cannot receive follow-up messages
|
|
115
|
-
case
|
|
116
|
-
return `${subject} was stopped before it finished
|
|
117
|
-
case
|
|
118
|
-
return `${subject} ran out of room before it finished
|
|
112
|
+
: `${subject} finished. It cannot receive follow-up messages.`
|
|
113
|
+
case 'aborted':
|
|
114
|
+
return `${subject} was stopped before it finished.`
|
|
115
|
+
case 'max-tokens':
|
|
116
|
+
return `${subject} ran out of room before it finished.`
|
|
119
117
|
// A pre-step rejection — a hook deny, a policy plugin — discarded input
|
|
120
118
|
// the child had claimed, so the parent must not treat the task as done.
|
|
121
|
-
case
|
|
122
|
-
return `${subject} declined the task
|
|
123
|
-
case
|
|
124
|
-
return `${subject} failed before it finished
|
|
119
|
+
case 'refusal':
|
|
120
|
+
return `${subject} declined the task.`
|
|
121
|
+
case 'error':
|
|
122
|
+
return `${subject} failed before it finished.`
|
|
125
123
|
/* v8 ignore next 4 -- `SubagentResult['stopReason']` is merge-extensible, so this arm
|
|
126
124
|
* needs a backend that adds a variant; an unnameable ending is reported as unfinished
|
|
127
125
|
* rather than silently as success. */
|
|
128
126
|
default:
|
|
129
|
-
return `${subject} ended abnormally (${String(stopReason)}) before it finished
|
|
127
|
+
return `${subject} ended abnormally (${String(stopReason)}) before it finished.`
|
|
130
128
|
}
|
|
131
129
|
}
|
|
132
130
|
|
|
@@ -135,43 +133,41 @@ function settlementSummary(
|
|
|
135
133
|
* @param childId - durable child session id named in the notice.
|
|
136
134
|
* @param terminal - recorded terminal state for the settled Activation.
|
|
137
135
|
* @param continuable - whether the child can accept another task.
|
|
136
|
+
* @param omitClosingText - replace the closing text with a note that the child's
|
|
137
|
+
* last message to the parent already carries it.
|
|
138
138
|
* @returns the durable user-message representation delivered to the parent.
|
|
139
139
|
*/
|
|
140
140
|
export function createSettlementMessage(
|
|
141
141
|
childId: SessionId,
|
|
142
142
|
terminal: SubagentResult,
|
|
143
143
|
continuable = true,
|
|
144
|
+
omitClosingText = false,
|
|
144
145
|
): ReturnType<typeof createUserMessage> {
|
|
145
|
-
const summary = settlementSummary(childId, terminal.stopReason, continuable)
|
|
146
|
+
const summary = settlementSummary(childId, terminal.stopReason, continuable)
|
|
146
147
|
// Parent providers receive this notice as a user message and may reject
|
|
147
148
|
// nontext assistant blocks. Keep this conversion local so SDK/UI consumers
|
|
148
149
|
// retain the complete child output.
|
|
149
|
-
const closingText = terminal.output.flatMap(
|
|
150
|
-
block.type ===
|
|
151
|
-
)
|
|
150
|
+
const closingText = terminal.output.flatMap(block =>
|
|
151
|
+
block.type === 'text' && block.text.length > 0 ? [block] : [],
|
|
152
|
+
)
|
|
152
153
|
return createUserMessage({
|
|
153
154
|
content: [
|
|
154
|
-
{ type:
|
|
155
|
-
...
|
|
156
|
-
? [{ type:
|
|
157
|
-
:
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
]
|
|
165
|
-
: []),
|
|
166
|
-
...(terminal.diagnostic !== undefined
|
|
167
|
-
? [{ type: "text" as const, text: terminal.diagnostic }]
|
|
168
|
-
: []),
|
|
155
|
+
{ type: 'text' as const, text: summary },
|
|
156
|
+
...omitClosingText
|
|
157
|
+
? [{ type: 'text' as const, text: 'It sent you a message just before finishing, so its closing message is omitted.' }]
|
|
158
|
+
: closingText.length === 0
|
|
159
|
+
? [{ type: 'text' as const, text: 'It left no closing message.' }]
|
|
160
|
+
: [{ type: 'text' as const, text: 'Its closing message:' }, ...closingText],
|
|
161
|
+
...terminal.structured !== undefined
|
|
162
|
+
? [{ type: 'text' as const, text: `Structured result: ${JSON.stringify(terminal.structured)}` }] : [],
|
|
163
|
+
...terminal.diagnostic !== undefined
|
|
164
|
+
? [{ type: 'text' as const, text: terminal.diagnostic }] : [],
|
|
169
165
|
],
|
|
170
166
|
source: {
|
|
171
|
-
kind:
|
|
172
|
-
form:
|
|
167
|
+
kind: 'subagent-settled' as const,
|
|
168
|
+
form: 'notice' as const,
|
|
173
169
|
summary: boundContextSummary(summary),
|
|
174
170
|
senderSessionId: childId,
|
|
175
171
|
},
|
|
176
|
-
})
|
|
172
|
+
})
|
|
177
173
|
}
|
|
@@ -5,12 +5,12 @@
|
|
|
5
5
|
* @module @deepseek-ai/dsh-subagent/control-types
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import type { PromptContentPart } from
|
|
9
|
-
import type { Branded } from
|
|
10
|
-
import type { MessageId } from
|
|
11
|
-
import type { SessionId } from
|
|
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
12
|
// Type-only: the Workspace registry's archive-admission family map this runtime merges `subagent` into.
|
|
13
|
-
import type {} from
|
|
13
|
+
import type {} from '@deepseek-ai/dsh-workspace/types'
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
16
|
* Client-minted identity of one browser prompt, persisted on the exact accepted
|
|
@@ -19,128 +19,131 @@ import type {} from "@deepseek-ai/dsh-workspace/types";
|
|
|
19
19
|
* vocabulary; that package depends on this one, so the brand is spelled here
|
|
20
20
|
* rather than imported.
|
|
21
21
|
*/
|
|
22
|
-
export type SubagentPromptRequestId = Branded<
|
|
22
|
+
export type SubagentPromptRequestId = Branded<'session-request-id'>
|
|
23
23
|
|
|
24
24
|
/** Shared child fields for recursive catalog listing. */
|
|
25
|
-
export type SubagentCatalogRow =
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
25
|
+
export type SubagentCatalogRow =
|
|
26
|
+
& {
|
|
27
|
+
/** The durable child session id, stable across Activations. */
|
|
28
|
+
readonly id: SessionId
|
|
29
|
+
/**
|
|
30
|
+
* Whether recursive catalog listing observed a resident Session. This
|
|
31
|
+
* does not encode a durable outcome or guarantee continuation delivery.
|
|
32
|
+
*/
|
|
33
|
+
readonly activity: 'running' | 'inactive'
|
|
34
|
+
} & (
|
|
35
|
+
| {
|
|
35
36
|
/** A terminal one-shot child. */
|
|
36
|
-
readonly mode:
|
|
37
|
+
readonly mode: 'one-shot'
|
|
37
38
|
/** Optional durable creation label from the parent catalog. */
|
|
38
|
-
readonly label?: string
|
|
39
|
+
readonly label?: string
|
|
39
40
|
}
|
|
40
|
-
|
|
41
|
+
| {
|
|
41
42
|
/** An external execution without a local child Session. */
|
|
42
|
-
readonly mode:
|
|
43
|
+
readonly mode: 'external'
|
|
43
44
|
/** Optional durable creation label from the parent catalog. */
|
|
44
|
-
readonly label?: string
|
|
45
|
+
readonly label?: string
|
|
45
46
|
}
|
|
46
|
-
|
|
47
|
+
| {
|
|
47
48
|
/** A resumable conversation. */
|
|
48
|
-
readonly mode:
|
|
49
|
+
readonly mode: 'continuable'
|
|
49
50
|
/** Durable creation label from the parent catalog. */
|
|
50
|
-
readonly label: string
|
|
51
|
+
readonly label: string
|
|
51
52
|
}
|
|
52
|
-
)
|
|
53
|
+
)
|
|
53
54
|
|
|
54
55
|
/**
|
|
55
56
|
* One recursive catalog row. Traversal may also return diagnostics for
|
|
56
57
|
* unknown catalog modes or unreadable child catalogs.
|
|
57
58
|
*/
|
|
58
59
|
export type SubagentListEntry =
|
|
59
|
-
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
60
|
+
| SubagentCatalogRow & {
|
|
61
|
+
readonly kind: 'child'
|
|
62
|
+
/** Whether the child catalog contains a direct child. */
|
|
63
|
+
readonly hasChildren: boolean
|
|
64
|
+
}
|
|
64
65
|
| {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
66
|
+
readonly kind: 'diagnostic'
|
|
67
|
+
/** The candidate's session id. */
|
|
68
|
+
readonly id: SessionId
|
|
69
|
+
/**
|
|
70
|
+
* `corrupt` means the child catalog read found invalid or conflicting
|
|
71
|
+
* Session data; `unavailable` means it could not be read. Either failure
|
|
72
|
+
* stops that branch. `unsupported` means the parent catalog records an
|
|
73
|
+
* unknown child mode; its readable catalog is still traversed.
|
|
74
|
+
*/
|
|
75
|
+
readonly reason: 'corrupt' | 'unsupported' | 'unavailable'
|
|
76
|
+
}
|
|
76
77
|
|
|
77
78
|
/** Durable parent/child browsing address; unknown mode is resolved when child history is read. */
|
|
78
|
-
export type SubagentAddress =
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
79
|
+
export type SubagentAddress =
|
|
80
|
+
& {
|
|
81
|
+
readonly parentSessionId: SessionId
|
|
82
|
+
readonly childSessionId: SessionId
|
|
83
|
+
}
|
|
84
|
+
& (
|
|
85
|
+
| { readonly mode: 'one-shot' }
|
|
86
|
+
| { readonly mode: 'continuable' }
|
|
87
|
+
| { readonly mode: 'unknown' }
|
|
88
|
+
)
|
|
86
89
|
|
|
87
90
|
/** One Agent inbox destination, as the prompt request selects it. */
|
|
88
|
-
export type SubagentDelivery = SubagentPromptRequest[
|
|
91
|
+
export type SubagentDelivery = SubagentPromptRequest['delivery']
|
|
89
92
|
|
|
90
93
|
/** One human message addressed to a continuable direct child. */
|
|
91
94
|
export interface SubagentPromptRequest {
|
|
92
95
|
/** Identity persisted on the accepted message, minted before the call. */
|
|
93
|
-
readonly requestId: SubagentPromptRequestId
|
|
94
|
-
readonly parentSessionId: SessionId
|
|
95
|
-
readonly childSessionId: SessionId
|
|
96
|
+
readonly requestId: SubagentPromptRequestId
|
|
97
|
+
readonly parentSessionId: SessionId
|
|
98
|
+
readonly childSessionId: SessionId
|
|
96
99
|
/** Required discriminator retained from the browser control address. */
|
|
97
|
-
readonly mode:
|
|
100
|
+
readonly mode: 'continuable'
|
|
98
101
|
/** Whether this message queues a later turn or targets the nearest step. */
|
|
99
|
-
readonly delivery:
|
|
102
|
+
readonly delivery: 'queue' | 'steer'
|
|
100
103
|
/**
|
|
101
104
|
* Browser prompt parts delivered as the child's user message. The Host
|
|
102
105
|
* admits and persists image parts before delivery, so the wire never
|
|
103
106
|
* carries a durable attachment reference the caller could fabricate.
|
|
104
107
|
*/
|
|
105
|
-
readonly content: readonly PromptContentPart[]
|
|
108
|
+
readonly content: readonly PromptContentPart[]
|
|
106
109
|
/** Optional browser zone sampled for this exact human prompt. */
|
|
107
|
-
readonly clientTimeZone?: string
|
|
110
|
+
readonly clientTimeZone?: string
|
|
108
111
|
}
|
|
109
112
|
|
|
110
113
|
/** Inbox identity returned once the continuation accepts one human message. */
|
|
111
114
|
export interface SubagentPromptReceipt {
|
|
112
|
-
readonly messageId: MessageId
|
|
115
|
+
readonly messageId: MessageId
|
|
113
116
|
}
|
|
114
117
|
|
|
115
118
|
/** Uniform acknowledgement that one interrupt request was admitted. */
|
|
116
119
|
export interface SubagentInterruptReceipt {
|
|
117
|
-
readonly accepted: true
|
|
120
|
+
readonly accepted: true
|
|
118
121
|
}
|
|
119
122
|
|
|
120
123
|
/**
|
|
121
124
|
* Failure details the control surface answers with. Prompts and interrupts
|
|
122
125
|
* share these failures with the Client Remote result.
|
|
123
126
|
*/
|
|
124
|
-
declare module
|
|
127
|
+
declare module '@deepseek-ai/dsh-workspace/types' {
|
|
125
128
|
interface SessionActivityKindMap {
|
|
126
129
|
/** A subagent session delegated from this session (at any depth) is inside a turn. */
|
|
127
|
-
subagent: true
|
|
130
|
+
subagent: true
|
|
128
131
|
}
|
|
129
132
|
}
|
|
130
133
|
|
|
131
|
-
declare module
|
|
134
|
+
declare module '@deepseek-ai/dsh-typert-protocol' {
|
|
132
135
|
interface RemoteErrorDetailsMap {
|
|
133
136
|
/** A browser-supplied zone is neither UTC nor a canonical IANA name. */
|
|
134
|
-
|
|
137
|
+
'subagent/invalid-time-zone': { readonly value: string }
|
|
135
138
|
/** No live Agent carries the addressed parent session. */
|
|
136
|
-
|
|
139
|
+
'subagent/parent-unavailable': { readonly parentSessionId: SessionId }
|
|
137
140
|
/** The addressed child cannot take a continuation. */
|
|
138
|
-
|
|
141
|
+
'subagent/not-resumable': { readonly childSessionId: SessionId }
|
|
139
142
|
/** The claimed parent does not own the addressed child. */
|
|
140
|
-
|
|
143
|
+
'subagent/unauthorized': { readonly childSessionId: SessionId }
|
|
141
144
|
/** Image admission or model image-capability refusal. */
|
|
142
|
-
|
|
145
|
+
'subagent/attachment-invalid': { readonly reason: string }
|
|
143
146
|
/** The child exists but its inbox cannot admit the message now. */
|
|
144
|
-
|
|
147
|
+
'subagent/delivery-unavailable': { readonly childSessionId: SessionId }
|
|
145
148
|
}
|
|
146
149
|
}
|
|
@@ -5,26 +5,26 @@
|
|
|
5
5
|
* @module @deepseek-ai/dsh-subagent
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { AttachmentError } from
|
|
9
|
-
import type { SessionId } from
|
|
10
|
-
import { RemoteError } from
|
|
11
|
-
import { z } from
|
|
12
|
-
import { SubagentError } from
|
|
8
|
+
import { AttachmentError } from '@deepseek-ai/dsh-attachment'
|
|
9
|
+
import type { SessionId } from '@deepseek-ai/dsh-session'
|
|
10
|
+
import { RemoteError } from '@deepseek-ai/dsh-typert-protocol'
|
|
11
|
+
import { z } from 'zod'
|
|
12
|
+
import { SubagentError } from './error.ts'
|
|
13
13
|
|
|
14
|
-
const SESSION_ID_SCHEMA = z.string().min(1)
|
|
14
|
+
const SESSION_ID_SCHEMA = z.string().min(1)
|
|
15
15
|
const CONTROL_ID_SCHEMAS = {
|
|
16
|
-
|
|
16
|
+
'subagent.prompt': z.object({
|
|
17
17
|
parentSessionId: SESSION_ID_SCHEMA,
|
|
18
18
|
childSessionId: SESSION_ID_SCHEMA,
|
|
19
|
-
mode: z.literal(
|
|
20
|
-
delivery: z.enum([
|
|
19
|
+
mode: z.literal('continuable'),
|
|
20
|
+
delivery: z.enum(['queue', 'steer']),
|
|
21
21
|
}),
|
|
22
|
-
|
|
22
|
+
'subagent.interrupt': z.object({
|
|
23
23
|
parentSessionId: SESSION_ID_SCHEMA,
|
|
24
24
|
childSessionId: SESSION_ID_SCHEMA,
|
|
25
|
-
mode: z.literal(
|
|
25
|
+
mode: z.literal('continuable'),
|
|
26
26
|
}),
|
|
27
|
-
} as const
|
|
27
|
+
} as const
|
|
28
28
|
|
|
29
29
|
/**
|
|
30
30
|
* Apply the subagent payload checks that are stricter than generated
|
|
@@ -37,11 +37,9 @@ export function validateControlRequest(
|
|
|
37
37
|
method: keyof typeof CONTROL_ID_SCHEMAS,
|
|
38
38
|
payload: unknown,
|
|
39
39
|
): void {
|
|
40
|
-
const parsed = CONTROL_ID_SCHEMAS[method].safeParse(payload)
|
|
40
|
+
const parsed = CONTROL_ID_SCHEMAS[method].safeParse(payload)
|
|
41
41
|
if (!parsed.success) {
|
|
42
|
-
throw new RemoteError(
|
|
43
|
-
issues: parsed.error.issues,
|
|
44
|
-
});
|
|
42
|
+
throw new RemoteError('gateway/bad-request', `invalid payload for ${method}`, { issues: parsed.error.issues })
|
|
45
43
|
}
|
|
46
44
|
}
|
|
47
45
|
|
|
@@ -55,69 +53,55 @@ export function validateControlRequest(
|
|
|
55
53
|
* @returns Never — the refusal is thrown.
|
|
56
54
|
* @throws {RemoteError} always.
|
|
57
55
|
*/
|
|
58
|
-
export function rejectPrompt(
|
|
59
|
-
error: unknown,
|
|
60
|
-
childSessionId: SessionId,
|
|
61
|
-
signal: AbortSignal,
|
|
62
|
-
): never {
|
|
56
|
+
export function rejectPrompt(error: unknown, childSessionId: SessionId, signal: AbortSignal): never {
|
|
63
57
|
if (isCancellation(error, signal)) {
|
|
64
|
-
throw new RemoteError(
|
|
65
|
-
"gateway/cancelled",
|
|
66
|
-
"subagent prompt was cancelled",
|
|
67
|
-
{},
|
|
68
|
-
{ cause: error },
|
|
69
|
-
);
|
|
58
|
+
throw new RemoteError('gateway/cancelled', 'subagent prompt was cancelled', {}, { cause: error })
|
|
70
59
|
}
|
|
71
60
|
if (error instanceof AttachmentError) {
|
|
72
|
-
throw new RemoteError(
|
|
73
|
-
"subagent/attachment-invalid",
|
|
74
|
-
error.message,
|
|
75
|
-
{ reason: error.code },
|
|
76
|
-
{ cause: error },
|
|
77
|
-
);
|
|
61
|
+
throw new RemoteError('subagent/attachment-invalid', error.message, { reason: error.code }, { cause: error })
|
|
78
62
|
}
|
|
79
63
|
if (error instanceof SubagentError) {
|
|
80
64
|
switch (error.code) {
|
|
81
|
-
case
|
|
65
|
+
case 'MODEL_DOES_NOT_SUPPORT_IMAGES':
|
|
82
66
|
throw new RemoteError(
|
|
83
|
-
|
|
67
|
+
'subagent/attachment-invalid',
|
|
84
68
|
error.message,
|
|
85
69
|
{ reason: error.code },
|
|
86
70
|
{ cause: error },
|
|
87
|
-
)
|
|
88
|
-
case
|
|
71
|
+
)
|
|
72
|
+
case 'NOT_RESUMABLE':
|
|
89
73
|
throw new RemoteError(
|
|
90
|
-
|
|
91
|
-
|
|
74
|
+
'subagent/not-resumable',
|
|
75
|
+
'subagent cannot be resumed',
|
|
92
76
|
{ childSessionId },
|
|
93
77
|
{ cause: error },
|
|
94
|
-
)
|
|
95
|
-
case
|
|
78
|
+
)
|
|
79
|
+
case 'UNAUTHORIZED':
|
|
96
80
|
throw new RemoteError(
|
|
97
|
-
|
|
98
|
-
|
|
81
|
+
'subagent/unauthorized',
|
|
82
|
+
'subagent does not belong to this parent',
|
|
99
83
|
{ childSessionId },
|
|
100
84
|
{ cause: error },
|
|
101
|
-
)
|
|
102
|
-
case
|
|
103
|
-
case
|
|
104
|
-
case
|
|
105
|
-
case
|
|
106
|
-
case
|
|
85
|
+
)
|
|
86
|
+
case 'DRAINING':
|
|
87
|
+
case 'ACTIVATION_CLOSING':
|
|
88
|
+
case 'ACTIVATION_LIMIT_REACHED':
|
|
89
|
+
case 'CONTINUATION_UNAVAILABLE':
|
|
90
|
+
case 'PERSISTENCE_UNAVAILABLE':
|
|
107
91
|
throw new RemoteError(
|
|
108
|
-
|
|
109
|
-
|
|
92
|
+
'subagent/delivery-unavailable',
|
|
93
|
+
'subagent follow-up is temporarily unavailable',
|
|
110
94
|
{ childSessionId },
|
|
111
95
|
{ cause: error },
|
|
112
|
-
)
|
|
96
|
+
)
|
|
113
97
|
// A code outside the admission vocabulary is not the caller's move to make.
|
|
114
98
|
default:
|
|
115
|
-
break
|
|
99
|
+
break
|
|
116
100
|
}
|
|
117
101
|
}
|
|
118
|
-
throw new RemoteError(
|
|
102
|
+
throw new RemoteError('gateway/internal', 'subagent prompt failed', {}, { cause: error })
|
|
119
103
|
}
|
|
120
104
|
|
|
121
105
|
function isCancellation(error: unknown, signal: AbortSignal): boolean {
|
|
122
|
-
return signal.aborted || (error instanceof SubagentError && error.code ===
|
|
106
|
+
return signal.aborted || (error instanceof SubagentError && error.code === 'CANCELLED')
|
|
123
107
|
}
|
|
@@ -6,12 +6,12 @@
|
|
|
6
6
|
* @module @deepseek-ai/dsh-subagent/depth
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import type { Agent } from
|
|
9
|
+
import type { Agent } from '@deepseek-ai/dsh-agent'
|
|
10
10
|
|
|
11
|
-
declare module
|
|
11
|
+
declare module '@deepseek-ai/dsh-agent' {
|
|
12
12
|
interface AgentOptions {
|
|
13
13
|
/** Delegation depth: zero for a top-level agent and parent depth + 1 for a child. */
|
|
14
|
-
subagentDepth?: number
|
|
14
|
+
subagentDepth?: number
|
|
15
15
|
}
|
|
16
16
|
}
|
|
17
17
|
|
|
@@ -26,16 +26,13 @@ declare module "@deepseek-ai/dsh-agent" {
|
|
|
26
26
|
* @throws if the runtime `AgentOptions.subagentDepth` is not a non-negative safe integer.
|
|
27
27
|
*/
|
|
28
28
|
export function delegationDepthOf(agent: Agent): number {
|
|
29
|
-
const runtime = agent.options.subagentDepth
|
|
30
|
-
if (
|
|
31
|
-
|
|
32
|
-
(!Number.isSafeInteger(runtime) || runtime < 0 || Object.is(runtime, -0))
|
|
33
|
-
) {
|
|
34
|
-
throw new TypeError("agent subagentDepth must be a non-negative safe integer");
|
|
29
|
+
const runtime = agent.options.subagentDepth
|
|
30
|
+
if (runtime !== undefined && (!Number.isSafeInteger(runtime) || runtime < 0 || Object.is(runtime, -0))) {
|
|
31
|
+
throw new TypeError('agent subagentDepth must be a non-negative safe integer')
|
|
35
32
|
}
|
|
36
33
|
// The header value was validated at the session boundary (creation and
|
|
37
34
|
// persistence load both construct through the store).
|
|
38
|
-
return Math.max(agent.session.header.delegationDepth ?? 0, runtime ?? 0)
|
|
35
|
+
return Math.max(agent.session.header.delegationDepth ?? 0, runtime ?? 0)
|
|
39
36
|
}
|
|
40
37
|
|
|
41
38
|
/**
|
|
@@ -43,13 +40,12 @@ export function delegationDepthOf(agent: Agent): number {
|
|
|
43
40
|
* @param maxDepth - the optional runtime value to validate.
|
|
44
41
|
*/
|
|
45
42
|
export function assertSubagentMaxDepth(maxDepth: unknown): void {
|
|
46
|
-
if (
|
|
47
|
-
maxDepth !==
|
|
48
|
-
(
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
throw new TypeError("subagent maxDepth must be a non-negative safe integer");
|
|
43
|
+
if (maxDepth !== undefined && (
|
|
44
|
+
typeof maxDepth !== 'number'
|
|
45
|
+
|| !Number.isSafeInteger(maxDepth)
|
|
46
|
+
|| maxDepth < 0
|
|
47
|
+
|| Object.is(maxDepth, -0)
|
|
48
|
+
)) {
|
|
49
|
+
throw new TypeError('subagent maxDepth must be a non-negative safe integer')
|
|
54
50
|
}
|
|
55
51
|
}
|