@morlay/dsh-subagent 0.0.10 → 0.0.11-alpha.1
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
|
@@ -2,19 +2,19 @@
|
|
|
2
2
|
* @module @deepseek-ai/dsh-subagent/activation
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import type { Agent, AgentHandle } from
|
|
6
|
-
import type { SessionId, SessionLogOffset } from
|
|
7
|
-
import { assertNever } from
|
|
8
|
-
import { finalAssistantOutput } from
|
|
9
|
-
import { epochStopReason } from
|
|
10
|
-
import type { ActivationObserver } from
|
|
11
|
-
import { SubagentError } from
|
|
12
|
-
import type { StructuredAttachment } from
|
|
13
|
-
import type { SubagentResult, SubagentRun } from
|
|
5
|
+
import type { Agent, AgentHandle } from '@deepseek-ai/dsh-agent'
|
|
6
|
+
import type { SessionId, SessionLogOffset } from '@deepseek-ai/dsh-session'
|
|
7
|
+
import { assertNever } from '@deepseek-ai/dsh-util-values'
|
|
8
|
+
import { finalAssistantOutput } from './assistant-output.ts'
|
|
9
|
+
import { epochStopReason } from './lifecycle.ts'
|
|
10
|
+
import type { ActivationObserver } from './lifecycle.ts'
|
|
11
|
+
import { SubagentError } from './error.ts'
|
|
12
|
+
import type { StructuredAttachment } from './structured.ts'
|
|
13
|
+
import type { SubagentResult, SubagentRun } from './types.ts'
|
|
14
14
|
|
|
15
15
|
/** Process-local slots shared through uninterrupted continuable parent links. */
|
|
16
16
|
export class ActivationPool {
|
|
17
|
-
private readonly slots = new Set<symbol>()
|
|
17
|
+
private readonly slots = new Set<symbol>()
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
20
|
* Reserve before reconstruction; release also tolerates unpublished rollback.
|
|
@@ -24,16 +24,14 @@ export class ActivationPool {
|
|
|
24
24
|
reserve(capacity: number): () => void {
|
|
25
25
|
if (this.slots.size >= capacity) {
|
|
26
26
|
throw new SubagentError(
|
|
27
|
-
`subagent limit reached (active child limit: ${capacity}); wait for an existing child to finish `
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
)
|
|
27
|
+
`subagent limit reached (active child limit: ${capacity}); wait for an existing child to finish `
|
|
28
|
+
+ 'or complete this work with the current agents',
|
|
29
|
+
'ACTIVATION_LIMIT_REACHED',
|
|
30
|
+
)
|
|
31
31
|
}
|
|
32
|
-
const slot = Symbol()
|
|
33
|
-
this.slots.add(slot)
|
|
34
|
-
return () => {
|
|
35
|
-
this.slots.delete(slot);
|
|
36
|
-
};
|
|
32
|
+
const slot = Symbol()
|
|
33
|
+
this.slots.add(slot)
|
|
34
|
+
return () => { this.slots.delete(slot) }
|
|
37
35
|
}
|
|
38
36
|
}
|
|
39
37
|
|
|
@@ -43,61 +41,67 @@ export class ActivationPool {
|
|
|
43
41
|
*/
|
|
44
42
|
interface ActivationBase {
|
|
45
43
|
/** Shared capacity for this Activation and all its managed descendants. */
|
|
46
|
-
readonly pool: ActivationPool
|
|
44
|
+
readonly pool: ActivationPool
|
|
47
45
|
/** Return this epoch's slot after its handle has finished disposal. */
|
|
48
|
-
readonly releaseSlot: () => void
|
|
46
|
+
readonly releaseSlot: () => void
|
|
49
47
|
/** The durable child this Activation is an epoch of. */
|
|
50
|
-
readonly childId: SessionId
|
|
51
|
-
readonly parent: Agent
|
|
52
|
-
readonly delivery:
|
|
53
|
-
readonly result: PromiseWithResolvers<SubagentResult
|
|
48
|
+
readonly childId: SessionId
|
|
49
|
+
readonly parent: Agent
|
|
50
|
+
readonly delivery: 'parent' | 'caller'
|
|
51
|
+
readonly result: PromiseWithResolvers<SubagentResult>
|
|
54
52
|
/** Published synchronously before teardown; subsequent callers share this transaction. */
|
|
55
|
-
closing: Promise<void> | undefined
|
|
53
|
+
closing: Promise<void> | undefined
|
|
56
54
|
/**
|
|
57
55
|
* Exact live Agent ancestry observed when this Activation materialized.
|
|
58
56
|
* Weak membership preserves host-scope identity across an intermediate
|
|
59
57
|
* ancestor leaving the registry without retaining that ancestor's runtime.
|
|
60
58
|
*/
|
|
61
|
-
readonly ancestry: WeakSet<Agent
|
|
59
|
+
readonly ancestry: WeakSet<Agent>
|
|
62
60
|
/**
|
|
63
61
|
* Session ids of the child Activations this one owns. Because one Session has
|
|
64
62
|
* at most one live Activation, the id identifies the live child without
|
|
65
63
|
* another runtime-incarnation reference. Non-empty blocks settlement.
|
|
66
64
|
*/
|
|
67
|
-
readonly ownedChildren: Set<SessionId
|
|
65
|
+
readonly ownedChildren: Set<SessionId>
|
|
68
66
|
/** The lifecycle observer that emits this epoch's start and terminal edges. */
|
|
69
|
-
readonly observer: ActivationObserver
|
|
67
|
+
readonly observer: ActivationObserver
|
|
70
68
|
/**
|
|
71
69
|
* Whether any delivery to this child was ever accepted. A materialization
|
|
72
70
|
* rolled back before its first acceptance is a child the caller was told does
|
|
73
71
|
* not exist, so its teardown owes the parent no settlement account.
|
|
74
72
|
*/
|
|
75
|
-
announced: boolean
|
|
73
|
+
announced: boolean
|
|
76
74
|
}
|
|
77
75
|
|
|
78
76
|
/** Resources retained only for the selected execution kind. */
|
|
79
77
|
export type ActivationResource =
|
|
80
78
|
| {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
79
|
+
readonly kind: 'local'
|
|
80
|
+
readonly handle: AgentHandle
|
|
81
|
+
readonly outputStart: SessionLogOffset
|
|
82
|
+
readonly structured: StructuredAttachment | undefined
|
|
83
|
+
/** Latest live failure not superseded by a later committed turn ending. */
|
|
84
|
+
failureAt: SessionLogOffset | undefined
|
|
85
|
+
/** Renewed when local settlement must recheck input or owned children. */
|
|
86
|
+
poke: PromiseWithResolvers<void>
|
|
87
|
+
/**
|
|
88
|
+
* Whether this child's settlement notice omits its closing text: true while
|
|
89
|
+
* the child's last logged tool call was a message to its direct parent and
|
|
90
|
+
* no input was claimed since.
|
|
91
|
+
*/
|
|
92
|
+
omitClosingText: boolean
|
|
93
|
+
}
|
|
90
94
|
| {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
+
readonly kind: 'external'
|
|
96
|
+
readonly run: SubagentRun
|
|
97
|
+
readonly controller: AbortController
|
|
98
|
+
}
|
|
95
99
|
|
|
96
100
|
/** One managed execution period with its provider-specific resources. */
|
|
97
|
-
export type Activation = ActivationBase & ActivationResource
|
|
101
|
+
export type Activation = ActivationBase & ActivationResource
|
|
98
102
|
|
|
99
103
|
/** Local residency with input admission and Session-backed result capture. */
|
|
100
|
-
export type LocalActivation = Extract<Activation, { kind:
|
|
104
|
+
export type LocalActivation = Extract<Activation, { kind: 'local' }>
|
|
101
105
|
|
|
102
106
|
/**
|
|
103
107
|
* Read the live Agent retained by local execution.
|
|
@@ -105,7 +109,7 @@ export type LocalActivation = Extract<Activation, { kind: "local" }>;
|
|
|
105
109
|
* @returns the local Agent, absent for an external run.
|
|
106
110
|
*/
|
|
107
111
|
export function activationAgent(activation: Activation): Agent | undefined {
|
|
108
|
-
return activation.kind ===
|
|
112
|
+
return activation.kind === 'local' ? activation.handle.agent : undefined
|
|
109
113
|
}
|
|
110
114
|
|
|
111
115
|
/**
|
|
@@ -116,16 +120,11 @@ export function activationAgent(activation: Activation): Agent | undefined {
|
|
|
116
120
|
*/
|
|
117
121
|
export function requireLocalActivation(activation: Activation): LocalActivation {
|
|
118
122
|
switch (activation.kind) {
|
|
119
|
-
case
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
throw new SubagentError(
|
|
123
|
-
`subagent "${activation.childId}" does not accept follow-up input`,
|
|
124
|
-
"NOT_CONTINUABLE",
|
|
125
|
-
);
|
|
123
|
+
case 'local': return activation
|
|
124
|
+
case 'external':
|
|
125
|
+
throw new SubagentError(`subagent "${activation.childId}" does not accept follow-up input`, 'NOT_CONTINUABLE')
|
|
126
126
|
/* v8 ignore next 2 -- Both execution kinds are handled above. */
|
|
127
|
-
default:
|
|
128
|
-
return assertNever(activation);
|
|
127
|
+
default: return assertNever(activation)
|
|
129
128
|
}
|
|
130
129
|
}
|
|
131
130
|
|
|
@@ -135,22 +134,18 @@ export function requireLocalActivation(activation: Activation): LocalActivation
|
|
|
135
134
|
* @param wake - notify its settlement watcher when failure changes readiness.
|
|
136
135
|
*/
|
|
137
136
|
export function observeLocalFailure(activation: LocalActivation, wake: () => void): void {
|
|
138
|
-
const child = activation.handle.agent
|
|
139
|
-
child.ctx.on(
|
|
140
|
-
if (agent !== child) return
|
|
141
|
-
activation.failureAt = child.session.seq
|
|
142
|
-
wake()
|
|
143
|
-
})
|
|
144
|
-
child.ctx.on(
|
|
145
|
-
if (
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
activation.failureAt !== undefined &&
|
|
149
|
-
event.seq >= activation.failureAt
|
|
150
|
-
) {
|
|
151
|
-
activation.failureAt = undefined;
|
|
137
|
+
const child = activation.handle.agent
|
|
138
|
+
child.ctx.on('agent/error', ({ agent }) => {
|
|
139
|
+
if (agent !== child) return
|
|
140
|
+
activation.failureAt = child.session.seq
|
|
141
|
+
wake()
|
|
142
|
+
})
|
|
143
|
+
child.ctx.on('session/event', (session, event) => {
|
|
144
|
+
if (session === child.session && event.type === 'turn/end'
|
|
145
|
+
&& activation.failureAt !== undefined && event.seq >= activation.failureAt) {
|
|
146
|
+
activation.failureAt = undefined
|
|
152
147
|
}
|
|
153
|
-
})
|
|
148
|
+
})
|
|
154
149
|
}
|
|
155
150
|
|
|
156
151
|
/**
|
|
@@ -160,21 +155,21 @@ export function observeLocalFailure(activation: LocalActivation, wake: () => voi
|
|
|
160
155
|
*/
|
|
161
156
|
export function captureLocalResult(activation: LocalActivation): SubagentResult {
|
|
162
157
|
// oxlint-disable-next-line typescript/no-deprecated -- Existing Session history read; migration deferred.
|
|
163
|
-
const events = activation.handle.agent.session.snapshotEvents(activation.outputStart)
|
|
164
|
-
const output = finalAssistantOutput(events) ?? []
|
|
165
|
-
const stopReason = activation.failureAt === undefined ? epochStopReason(events) :
|
|
166
|
-
const structured = activation.structured
|
|
158
|
+
const events = activation.handle.agent.session.snapshotEvents(activation.outputStart)
|
|
159
|
+
const output = finalAssistantOutput(events) ?? []
|
|
160
|
+
const stopReason = activation.failureAt === undefined ? epochStopReason(events) : 'error'
|
|
161
|
+
const structured = activation.structured
|
|
167
162
|
if (structured !== undefined) {
|
|
168
|
-
const captured = structured.captured()
|
|
169
|
-
if (captured !== undefined) return { output, stopReason, structured: captured.value }
|
|
170
|
-
if (stopReason ===
|
|
163
|
+
const captured = structured.captured()
|
|
164
|
+
if (captured !== undefined) return { output, stopReason, structured: captured.value }
|
|
165
|
+
if (stopReason === 'completed') return { output, stopReason: 'error' }
|
|
171
166
|
}
|
|
172
|
-
return { output, stopReason }
|
|
167
|
+
return { output, stopReason }
|
|
173
168
|
}
|
|
174
169
|
|
|
175
170
|
/** Serialize each durable child's delivery, release, and disposal. */
|
|
176
171
|
export class ChildLock {
|
|
177
|
-
private tails = new Map<SessionId, Promise<unknown>>()
|
|
172
|
+
private tails = new Map<SessionId, Promise<unknown>>()
|
|
178
173
|
|
|
179
174
|
/**
|
|
180
175
|
* Run `operation` after every previously queued operation for `childId`.
|
|
@@ -183,18 +178,15 @@ export class ChildLock {
|
|
|
183
178
|
* @returns the operation's own settlement.
|
|
184
179
|
*/
|
|
185
180
|
run<T>(childId: SessionId, operation: () => Promise<T>): Promise<T> {
|
|
186
|
-
const previous = this.tails.get(childId) ?? Promise.resolve()
|
|
187
|
-
const result = previous.then(operation, operation)
|
|
181
|
+
const previous = this.tails.get(childId) ?? Promise.resolve()
|
|
182
|
+
const result = previous.then(operation, operation)
|
|
188
183
|
// Absorb rejections in the chaining tail so one failed critical section
|
|
189
184
|
// cannot reject an unrelated later caller.
|
|
190
|
-
const tail = result.then(
|
|
191
|
-
|
|
192
|
-
() => undefined,
|
|
193
|
-
);
|
|
194
|
-
this.tails.set(childId, tail);
|
|
185
|
+
const tail = result.then(() => undefined, () => undefined)
|
|
186
|
+
this.tails.set(childId, tail)
|
|
195
187
|
void tail.then(() => {
|
|
196
|
-
if (this.tails.get(childId) === tail) this.tails.delete(childId)
|
|
197
|
-
})
|
|
198
|
-
return result
|
|
188
|
+
if (this.tails.get(childId) === tail) this.tails.delete(childId)
|
|
189
|
+
})
|
|
190
|
+
return result
|
|
199
191
|
}
|
|
200
192
|
}
|
|
@@ -6,12 +6,12 @@
|
|
|
6
6
|
* @module @deepseek-ai/dsh-subagent
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import type { Context } from
|
|
10
|
-
import type { Agent } from
|
|
11
|
-
import type { SessionId } from
|
|
12
|
-
import type {} from
|
|
13
|
-
import type { SessionActivity, SessionActivityItem } from
|
|
14
|
-
import { foldSubagentDescriptor } from
|
|
9
|
+
import type { Context } from '@deepseek-ai/cordis'
|
|
10
|
+
import type { Agent } from '@deepseek-ai/dsh-agent'
|
|
11
|
+
import type { SessionId } from '@deepseek-ai/dsh-session'
|
|
12
|
+
import type {} from '@deepseek-ai/dsh-session-query'
|
|
13
|
+
import type { SessionActivity, SessionActivityItem } from '@deepseek-ai/dsh-workspace'
|
|
14
|
+
import { foldSubagentDescriptor } from './descriptor.ts'
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
17
|
* Answer `workspace/session-activity` with the running subagent descendants of
|
|
@@ -20,28 +20,23 @@ import { foldSubagentDescriptor } from "./descriptor.ts";
|
|
|
20
20
|
* @param ctx - context carrying the Agent registry; the Session query service is optional and only supplies labels.
|
|
21
21
|
*/
|
|
22
22
|
export function installSubagentArchiveAdmission(ctx: Context): void {
|
|
23
|
-
ctx.on(
|
|
24
|
-
const running = runningDescendants(ctx, sessionId)
|
|
25
|
-
const rest = await next()
|
|
26
|
-
if (running.length === 0) return rest
|
|
27
|
-
const own: SessionActivity = {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
return [own, ...rest];
|
|
32
|
-
});
|
|
33
|
-
ctx.on("workspace/session-stop", ({ sessionId }) => {
|
|
23
|
+
ctx.on('workspace/session-activity', async ({ sessionId }, next) => {
|
|
24
|
+
const running = runningDescendants(ctx, sessionId)
|
|
25
|
+
const rest = await next()
|
|
26
|
+
if (running.length === 0) return rest
|
|
27
|
+
const own: SessionActivity = { kind: 'subagent', items: await Promise.all(running.map(child => describe(ctx, child))) }
|
|
28
|
+
return [own, ...rest]
|
|
29
|
+
})
|
|
30
|
+
ctx.on('workspace/session-stop', ({ sessionId }) => {
|
|
34
31
|
for (const child of runningDescendants(ctx, sessionId)) {
|
|
35
32
|
try {
|
|
36
|
-
child.cancel({ kind:
|
|
33
|
+
child.cancel({ kind: 'parent' })
|
|
37
34
|
} catch (error: unknown) {
|
|
38
35
|
// One child refusing its cancel must not keep its siblings running for an archived parent.
|
|
39
|
-
ctx.logger.warn(
|
|
40
|
-
`subagent: cancelling "${child.id}" for an archived Session failed: ${String(error)}`,
|
|
41
|
-
);
|
|
36
|
+
ctx.logger.warn(`subagent: cancelling "${child.id}" for an archived Session failed: ${String(error)}`)
|
|
42
37
|
}
|
|
43
38
|
}
|
|
44
|
-
})
|
|
39
|
+
})
|
|
45
40
|
}
|
|
46
41
|
|
|
47
42
|
/**
|
|
@@ -52,27 +47,27 @@ export function installSubagentArchiveAdmission(ctx: Context): void {
|
|
|
52
47
|
* is read as data, so a damaged header chain that loops is visited once.
|
|
53
48
|
*/
|
|
54
49
|
function runningDescendants(ctx: Context, rootId: SessionId): Agent[] {
|
|
55
|
-
const childrenOf = new Map<SessionId, Agent[]>()
|
|
50
|
+
const childrenOf = new Map<SessionId, Agent[]>()
|
|
56
51
|
for (const agent of ctx.agents.list()) {
|
|
57
|
-
const { parentSession, origin } = agent.session.header
|
|
58
|
-
if (parentSession === undefined || origin !==
|
|
59
|
-
const siblings = childrenOf.get(parentSession) ?? []
|
|
60
|
-
siblings.push(agent)
|
|
61
|
-
childrenOf.set(parentSession, siblings)
|
|
52
|
+
const { parentSession, origin } = agent.session.header
|
|
53
|
+
if (parentSession === undefined || origin !== 'subagent') continue
|
|
54
|
+
const siblings = childrenOf.get(parentSession) ?? []
|
|
55
|
+
siblings.push(agent)
|
|
56
|
+
childrenOf.set(parentSession, siblings)
|
|
62
57
|
}
|
|
63
|
-
const running: Agent[] = []
|
|
64
|
-
const pending = [rootId]
|
|
65
|
-
const visited = new Set<SessionId>()
|
|
58
|
+
const running: Agent[] = []
|
|
59
|
+
const pending = [rootId]
|
|
60
|
+
const visited = new Set<SessionId>()
|
|
66
61
|
while (pending.length > 0) {
|
|
67
|
-
const parentId = pending.shift() as SessionId
|
|
68
|
-
if (visited.has(parentId)) continue
|
|
69
|
-
visited.add(parentId)
|
|
62
|
+
const parentId = pending.shift() as SessionId
|
|
63
|
+
if (visited.has(parentId)) continue
|
|
64
|
+
visited.add(parentId)
|
|
70
65
|
for (const child of childrenOf.get(parentId) ?? []) {
|
|
71
|
-
if (child.status ===
|
|
72
|
-
pending.push(child.id)
|
|
66
|
+
if (child.status === 'running') running.push(child)
|
|
67
|
+
pending.push(child.id)
|
|
73
68
|
}
|
|
74
69
|
}
|
|
75
|
-
return running
|
|
70
|
+
return running
|
|
76
71
|
}
|
|
77
72
|
|
|
78
73
|
/**
|
|
@@ -82,16 +77,14 @@ function runningDescendants(ctx: Context, rootId: SessionId): Agent[] {
|
|
|
82
77
|
* the item names the child by id alone.
|
|
83
78
|
*/
|
|
84
79
|
async function describe(ctx: Context, child: Agent): Promise<SessionActivityItem> {
|
|
85
|
-
const query = ctx.get(
|
|
86
|
-
if (query === undefined) return { id: child.id }
|
|
80
|
+
const query = ctx.get('sessionQuery')
|
|
81
|
+
if (query === undefined) return { id: child.id }
|
|
87
82
|
try {
|
|
88
|
-
using observation = await query.observeSession(child.id, { projectionMode:
|
|
89
|
-
const label = foldSubagentDescriptor(
|
|
90
|
-
|
|
91
|
-
)?.label;
|
|
92
|
-
return label === undefined ? { id: child.id } : { id: child.id, label };
|
|
83
|
+
using observation = await query.observeSession(child.id, { projectionMode: 'none' })
|
|
84
|
+
const label = foldSubagentDescriptor(observation.events.slice(observation.inheritedEventCount))?.label
|
|
85
|
+
return label === undefined ? { id: child.id } : { id: child.id, label }
|
|
93
86
|
} catch {
|
|
94
87
|
// A damaged descriptor is data damage in the child; the report still names the child.
|
|
95
|
-
return { id: child.id }
|
|
88
|
+
return { id: child.id }
|
|
96
89
|
}
|
|
97
90
|
}
|
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
* @module @deepseek-ai/dsh-subagent/assistant-output
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import { joinAssistantStreamText, type ContentBlock } from
|
|
14
|
-
import type { SessionEvent } from
|
|
13
|
+
import { joinAssistantStreamText, type ContentBlock } from '@deepseek-ai/dsh-llm'
|
|
14
|
+
import type { SessionEvent } from '@deepseek-ai/dsh-session'
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
17
|
* Incremental fold of the selection rule, for backends that observe a child's
|
|
@@ -20,8 +20,8 @@ import type { SessionEvent } from "@deepseek-ai/dsh-session";
|
|
|
20
20
|
* text into the same streamed fallback.
|
|
21
21
|
*/
|
|
22
22
|
export class AssistantOutputFold {
|
|
23
|
-
private message: readonly ContentBlock[] | undefined
|
|
24
|
-
private partial: string[] = []
|
|
23
|
+
private message: readonly ContentBlock[] | undefined
|
|
24
|
+
private partial: string[] = []
|
|
25
25
|
|
|
26
26
|
/**
|
|
27
27
|
* Fold one session event: a non-empty assistant message becomes the
|
|
@@ -30,12 +30,12 @@ export class AssistantOutputFold {
|
|
|
30
30
|
* @param event - the next observed session event.
|
|
31
31
|
*/
|
|
32
32
|
push(event: SessionEvent): void {
|
|
33
|
-
if (event.type ===
|
|
34
|
-
const content = event.data.message.content
|
|
35
|
-
if (content.length > 0) this.message = content
|
|
33
|
+
if (event.type === 'assistant/message') {
|
|
34
|
+
const content = event.data.message.content
|
|
35
|
+
if (content.length > 0) this.message = content
|
|
36
36
|
}
|
|
37
|
-
if (event.type ===
|
|
38
|
-
this.pushText(joinAssistantStreamText(event.data.stream))
|
|
37
|
+
if (event.type === 'assistant/message' || event.type === 'assistant/attempt') {
|
|
38
|
+
this.pushText(joinAssistantStreamText(event.data.stream))
|
|
39
39
|
}
|
|
40
40
|
}
|
|
41
41
|
|
|
@@ -44,7 +44,7 @@ export class AssistantOutputFold {
|
|
|
44
44
|
* @param text - the next streamed text piece (an empty piece is a no-op).
|
|
45
45
|
*/
|
|
46
46
|
pushText(text: string): void {
|
|
47
|
-
if (text.length > 0) this.partial.push(text)
|
|
47
|
+
if (text.length > 0) this.partial.push(text)
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
/**
|
|
@@ -53,9 +53,9 @@ export class AssistantOutputFold {
|
|
|
53
53
|
* streamed text, or `undefined` when the child produced neither.
|
|
54
54
|
*/
|
|
55
55
|
collect(): readonly ContentBlock[] | undefined {
|
|
56
|
-
if (this.message !== undefined) return this.message
|
|
57
|
-
const text = this.partial.join(
|
|
58
|
-
return text.length > 0 ? [{ type:
|
|
56
|
+
if (this.message !== undefined) return this.message
|
|
57
|
+
const text = this.partial.join('')
|
|
58
|
+
return text.length > 0 ? [{ type: 'text', text }] : undefined
|
|
59
59
|
}
|
|
60
60
|
}
|
|
61
61
|
|
|
@@ -64,14 +64,12 @@ export class AssistantOutputFold {
|
|
|
64
64
|
* @param events - the child-owned events (after any seed or epoch boundary).
|
|
65
65
|
* @returns the selected output, or `undefined` when the child produced none.
|
|
66
66
|
*/
|
|
67
|
-
export function finalAssistantOutput(
|
|
68
|
-
events: readonly SessionEvent[],
|
|
69
|
-
): readonly ContentBlock[] | undefined {
|
|
67
|
+
export function finalAssistantOutput(events: readonly SessionEvent[]): readonly ContentBlock[] | undefined {
|
|
70
68
|
// TODO: this folds the complete suffix once per run/epoch settlement. If a
|
|
71
69
|
// long continuable epoch ever profiles hot here, scan backward with early
|
|
72
70
|
// exit for the last non-empty message and fold text deltas only on the
|
|
73
71
|
// no-message fallback.
|
|
74
|
-
const fold = new AssistantOutputFold()
|
|
75
|
-
for (const event of events) fold.push(event)
|
|
76
|
-
return fold.collect()
|
|
72
|
+
const fold = new AssistantOutputFold()
|
|
73
|
+
for (const event of events) fold.push(event)
|
|
74
|
+
return fold.collect()
|
|
77
75
|
}
|