@tanstack/ai 0.58.0 → 0.61.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/esm/activities/chat/adapter.d.ts +9 -0
- package/dist/esm/activities/chat/adapter.js +1 -0
- package/dist/esm/activities/chat/adapter.js.map +1 -1
- package/dist/esm/activities/chat/agents/define-agent.d.ts +81 -0
- package/dist/esm/activities/chat/agents/define-agent.js +34 -0
- package/dist/esm/activities/chat/agents/define-agent.js.map +1 -0
- package/dist/esm/activities/chat/agents/route.d.ts +53 -0
- package/dist/esm/activities/chat/agents/route.js +59 -0
- package/dist/esm/activities/chat/agents/route.js.map +1 -0
- package/dist/esm/activities/chat/agents/spawn.d.ts +124 -0
- package/dist/esm/activities/chat/agents/spawn.js +490 -0
- package/dist/esm/activities/chat/agents/spawn.js.map +1 -0
- package/dist/esm/activities/chat/agents/turn.d.ts +36 -0
- package/dist/esm/activities/chat/agents/turn.js +78 -0
- package/dist/esm/activities/chat/agents/turn.js.map +1 -0
- package/dist/esm/activities/chat/index.d.ts +13 -3
- package/dist/esm/activities/chat/index.js +462 -26
- package/dist/esm/activities/chat/index.js.map +1 -1
- package/dist/esm/activities/chat/messages.d.ts +7 -1
- package/dist/esm/activities/chat/messages.js +99 -19
- package/dist/esm/activities/chat/messages.js.map +1 -1
- package/dist/esm/activities/chat/middleware/run-store.d.ts +43 -7
- package/dist/esm/activities/chat/middleware/run-store.js +8 -1
- package/dist/esm/activities/chat/middleware/run-store.js.map +1 -1
- package/dist/esm/activities/chat/middleware/types.d.ts +47 -1
- package/dist/esm/activities/chat/middleware/types.js.map +1 -1
- package/dist/esm/activities/chat/stream/message-updaters.js +9 -2
- package/dist/esm/activities/chat/stream/message-updaters.js.map +1 -1
- package/dist/esm/activities/chat/stream/processor.d.ts +46 -1
- package/dist/esm/activities/chat/stream/processor.js +294 -18
- package/dist/esm/activities/chat/stream/processor.js.map +1 -1
- package/dist/esm/activities/chat/tools/tool-calls.d.ts +17 -3
- package/dist/esm/activities/chat/tools/tool-calls.js +56 -5
- package/dist/esm/activities/chat/tools/tool-calls.js.map +1 -1
- package/dist/esm/activities/embed/adapter.d.ts +7 -0
- package/dist/esm/activities/embed/adapter.js +1 -0
- package/dist/esm/activities/embed/adapter.js.map +1 -1
- package/dist/esm/activities/embed/index.js +2 -0
- package/dist/esm/activities/embed/index.js.map +1 -1
- package/dist/esm/activities/files/adapter.d.ts +97 -0
- package/dist/esm/activities/files/adapter.js +45 -0
- package/dist/esm/activities/files/adapter.js.map +1 -0
- package/dist/esm/activities/files/index.d.ts +66 -0
- package/dist/esm/activities/files/index.js +78 -0
- package/dist/esm/activities/files/index.js.map +1 -0
- package/dist/esm/activities/generateAudio/index.js +1 -1
- package/dist/esm/activities/generateImage/adapter.d.ts +8 -0
- package/dist/esm/activities/generateImage/adapter.js +1 -0
- package/dist/esm/activities/generateImage/adapter.js.map +1 -1
- package/dist/esm/activities/generateImage/index.js +3 -1
- package/dist/esm/activities/generateImage/index.js.map +1 -1
- package/dist/esm/activities/generateLiveVideo/index.js +1 -1
- package/dist/esm/activities/generateSpeech/index.js +1 -1
- package/dist/esm/activities/generateTranscription/index.js +1 -1
- package/dist/esm/activities/generateVideo/adapter.d.ts +8 -0
- package/dist/esm/activities/generateVideo/adapter.js +1 -0
- package/dist/esm/activities/generateVideo/adapter.js.map +1 -1
- package/dist/esm/activities/generateVideo/index.js +3 -0
- package/dist/esm/activities/generateVideo/index.js.map +1 -1
- package/dist/esm/activities/generateVoice/index.js +1 -1
- package/dist/esm/activities/generateWorld/adapter.d.ts +4 -2
- package/dist/esm/activities/generateWorld/adapter.js.map +1 -1
- package/dist/esm/activities/generateWorld/index.d.ts +4 -3
- package/dist/esm/activities/generateWorld/index.js +6 -5
- package/dist/esm/activities/generateWorld/index.js.map +1 -1
- package/dist/esm/activities/index.d.ts +9 -3
- package/dist/esm/activities/index.js +17 -13
- package/dist/esm/activities/summarize/chat-stream-summarize.d.ts +2 -0
- package/dist/esm/activities/summarize/chat-stream-summarize.js +8 -8
- package/dist/esm/activities/summarize/chat-stream-summarize.js.map +1 -1
- package/dist/esm/activities/summarize/index.js +1 -1
- package/dist/esm/client.d.ts +7 -36
- package/dist/esm/client.js +5 -37
- package/dist/esm/client.js.map +1 -1
- package/dist/esm/index.d.ts +8 -2
- package/dist/esm/index.js +9 -4
- package/dist/esm/middlewares/content-guard.js.map +1 -1
- package/dist/esm/types.d.ts +179 -98
- package/dist/esm/utilities/adapter-yield-chunk.d.ts +5 -1
- package/dist/esm/utilities/ag-ui-usage.d.ts +9 -9
- package/dist/esm/utilities/ag-ui-usage.js +66 -3
- package/dist/esm/utilities/ag-ui-usage.js.map +1 -1
- package/dist/esm/utilities/ag-ui-wire.js +90 -13
- package/dist/esm/utilities/ag-ui-wire.js.map +1 -1
- package/dist/esm/utilities/content-source.d.ts +60 -0
- package/dist/esm/utilities/content-source.js +85 -0
- package/dist/esm/utilities/content-source.js.map +1 -0
- package/dist/esm/utilities/normalize-stream-chunk.js +7 -2
- package/dist/esm/utilities/normalize-stream-chunk.js.map +1 -1
- package/dist/esm/utilities/provider-executed.d.ts +7 -0
- package/dist/esm/utilities/provider-executed.js +10 -1
- package/dist/esm/utilities/provider-executed.js.map +1 -1
- package/dist/esm/utilities/spec-event-keys.js +13 -8
- package/dist/esm/utilities/spec-event-keys.js.map +1 -1
- package/dist/esm/utilities/subagent-wire.d.ts +36 -0
- package/dist/esm/utilities/subagent-wire.js +131 -0
- package/dist/esm/utilities/subagent-wire.js.map +1 -0
- package/dist/esm/utilities/tool-result.d.ts +12 -2
- package/dist/esm/utilities/tool-result.js +23 -3
- package/dist/esm/utilities/tool-result.js.map +1 -1
- package/package.json +3 -3
- package/skills/ai-core/adapter-configuration/SKILL.md +62 -0
- package/skills/ai-core/adapter-configuration/references/grok-adapter.md +1 -1
- package/skills/ai-core/chat-experience/SKILL.md +14 -0
- package/skills/ai-core/media-generation/SKILL.md +10 -2
- package/skills/ai-core/middleware/SKILL.md +7 -4
- package/src/activities/chat/adapter.ts +10 -0
- package/src/activities/chat/agents/define-agent.ts +121 -0
- package/src/activities/chat/agents/route.ts +115 -0
- package/src/activities/chat/agents/spawn.ts +806 -0
- package/src/activities/chat/agents/turn.ts +151 -0
- package/src/activities/chat/index.ts +734 -30
- package/src/activities/chat/messages.ts +137 -16
- package/src/activities/chat/middleware/run-store.ts +56 -7
- package/src/activities/chat/middleware/types.ts +47 -0
- package/src/activities/chat/stream/message-updaters.ts +24 -2
- package/src/activities/chat/stream/processor.ts +452 -30
- package/src/activities/chat/tools/tool-calls.ts +83 -11
- package/src/activities/embed/adapter.ts +7 -0
- package/src/activities/embed/index.ts +5 -0
- package/src/activities/files/adapter.ts +120 -0
- package/src/activities/files/index.ts +113 -0
- package/src/activities/generateImage/adapter.ts +8 -0
- package/src/activities/generateImage/index.ts +4 -0
- package/src/activities/generateVideo/adapter.ts +8 -0
- package/src/activities/generateVideo/index.ts +7 -0
- package/src/activities/generateWorld/adapter.ts +4 -2
- package/src/activities/generateWorld/index.ts +7 -6
- package/src/activities/index.ts +40 -1
- package/src/activities/summarize/chat-stream-summarize.ts +22 -12
- package/src/client.ts +29 -35
- package/src/index.ts +39 -0
- package/src/middlewares/content-guard.ts +7 -5
- package/src/types.ts +226 -103
- package/src/utilities/adapter-yield-chunk.ts +10 -2
- package/src/utilities/ag-ui-usage.test.ts +38 -0
- package/src/utilities/ag-ui-usage.ts +98 -11
- package/src/utilities/ag-ui-wire.ts +134 -16
- package/src/utilities/content-source.ts +138 -0
- package/src/utilities/normalize-stream-chunk.ts +10 -2
- package/src/utilities/provider-executed.ts +13 -0
- package/src/utilities/spec-event-keys.ts +34 -7
- package/src/utilities/subagent-wire.ts +184 -0
- package/src/utilities/tool-result.ts +38 -2
|
@@ -1,9 +1,16 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
isAssistantSegmentOf,
|
|
3
|
+
isProviderExecutedToolCall,
|
|
4
|
+
} from '../../utilities/provider-executed'
|
|
2
5
|
import {
|
|
3
6
|
isContentPartArray,
|
|
4
7
|
normalizeToolResult,
|
|
5
8
|
} from '../../utilities/tool-result'
|
|
6
9
|
import { tanstackMetadata } from '../../utilities/merge-metadata'
|
|
10
|
+
import {
|
|
11
|
+
splitSubagentWire,
|
|
12
|
+
subagentWireText,
|
|
13
|
+
} from '../../utilities/subagent-wire'
|
|
7
14
|
import type { Message as AGUIMessage } from '@ag-ui/core'
|
|
8
15
|
import type {
|
|
9
16
|
ContentPart,
|
|
@@ -14,6 +21,7 @@ import type {
|
|
|
14
21
|
TextPart,
|
|
15
22
|
ToolCall,
|
|
16
23
|
ToolCallPart,
|
|
24
|
+
SubagentPart,
|
|
17
25
|
UIMessage,
|
|
18
26
|
UIResourcePart,
|
|
19
27
|
} from '../../types'
|
|
@@ -124,6 +132,37 @@ function getTextContent(
|
|
|
124
132
|
.join('')
|
|
125
133
|
}
|
|
126
134
|
|
|
135
|
+
function historyTextFromParts(parts: ReadonlyArray<MessagePart>): string {
|
|
136
|
+
const blocks: Array<string> = []
|
|
137
|
+
for (const part of parts) {
|
|
138
|
+
if (part.type === 'text' && part.content !== '') {
|
|
139
|
+
blocks.push(part.content)
|
|
140
|
+
} else if (
|
|
141
|
+
part.type === 'structured-output' &&
|
|
142
|
+
part.status === 'complete' &&
|
|
143
|
+
part.raw !== ''
|
|
144
|
+
) {
|
|
145
|
+
blocks.push(part.raw)
|
|
146
|
+
} else if (part.type === 'subagent') {
|
|
147
|
+
const nested = subagentHistoryText(part)
|
|
148
|
+
if (nested !== '') blocks.push(nested)
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return blocks.join('\n\n')
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** Child text for a later turn. The name stays so the next agent can tell the notes apart. */
|
|
155
|
+
export function subagentHistoryText(part: SubagentPart): string {
|
|
156
|
+
const blocks: Array<string> = []
|
|
157
|
+
for (const message of part.subagent.messages) {
|
|
158
|
+
if (!('parts' in message)) continue
|
|
159
|
+
const text = historyTextFromParts(message.parts).trim()
|
|
160
|
+
if (text !== '') blocks.push(text)
|
|
161
|
+
}
|
|
162
|
+
if (blocks.length === 0) return ''
|
|
163
|
+
return `${part.subagent.name}:\n${blocks.join('\n\n')}`
|
|
164
|
+
}
|
|
165
|
+
|
|
127
166
|
function toolResultContent(
|
|
128
167
|
content: string | null | undefined | Array<ContentPart>,
|
|
129
168
|
): string | Array<ContentPart> {
|
|
@@ -135,6 +174,57 @@ function toolResultContent(
|
|
|
135
174
|
*/
|
|
136
175
|
export function convertMessagesToModelMessages(
|
|
137
176
|
messages: Array<UIMessage | ModelMessage>,
|
|
177
|
+
): Array<ModelMessage> {
|
|
178
|
+
const { top, groups } = splitSubagentWire(messages)
|
|
179
|
+
if (groups.length === 0) return convertOwnMessages(messages)
|
|
180
|
+
|
|
181
|
+
// Child wire messages leave the parent history. The parent model reads each
|
|
182
|
+
// child's text on the assistant message before it, the same as for a UI
|
|
183
|
+
// subagent part. A child that a tool call started reports through the tool
|
|
184
|
+
// result instead.
|
|
185
|
+
const blocks = new Map<string | undefined, Array<string>>()
|
|
186
|
+
for (const group of groups) {
|
|
187
|
+
if (group.info.parentToolCallId !== undefined) continue
|
|
188
|
+
const text = subagentWireText(group.messages)
|
|
189
|
+
if (text === '') continue
|
|
190
|
+
const host = top
|
|
191
|
+
.slice(0, group.hostIndex + 1)
|
|
192
|
+
.findLast((message) => message.role === 'assistant')
|
|
193
|
+
const hostId = host && 'id' in host ? host.id : undefined
|
|
194
|
+
blocks.set(hostId, [
|
|
195
|
+
...(blocks.get(hostId) ?? []),
|
|
196
|
+
`${group.info.name}:\n${text}`,
|
|
197
|
+
])
|
|
198
|
+
}
|
|
199
|
+
const converted = convertOwnMessages(top)
|
|
200
|
+
for (const [hostId, texts] of blocks) {
|
|
201
|
+
const block = texts.join('\n\n')
|
|
202
|
+
const index =
|
|
203
|
+
hostId === undefined
|
|
204
|
+
? -1
|
|
205
|
+
: converted.findIndex(
|
|
206
|
+
(message) => message.role === 'assistant' && message.id === hostId,
|
|
207
|
+
)
|
|
208
|
+
const host = converted[index]
|
|
209
|
+
if (!host) {
|
|
210
|
+
converted.push({ role: 'assistant', content: block })
|
|
211
|
+
continue
|
|
212
|
+
}
|
|
213
|
+
converted[index] = {
|
|
214
|
+
...host,
|
|
215
|
+
content:
|
|
216
|
+
typeof host.content === 'string' && host.content !== ''
|
|
217
|
+
? `${host.content}\n\n${block}`
|
|
218
|
+
: Array.isArray(host.content)
|
|
219
|
+
? [...host.content, { type: 'text', content: block }]
|
|
220
|
+
: block,
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
return converted
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function convertOwnMessages(
|
|
227
|
+
messages: Array<UIMessage | ModelMessage>,
|
|
138
228
|
): Array<ModelMessage> {
|
|
139
229
|
// Pre-pass: collect toolCallIds already represented in anchor UIMessage parts.
|
|
140
230
|
// Fan-out tool messages whose toolCallId matches an anchored ToolResultPart
|
|
@@ -455,6 +545,7 @@ function assistantMetadata(
|
|
|
455
545
|
const previous = tanstackMetadata(uiMessage)
|
|
456
546
|
const tanstack: TanStackMessageMetadata = {}
|
|
457
547
|
if (previous?.model !== undefined) tanstack.model = previous.model
|
|
548
|
+
if (previous?.runId !== undefined) tanstack.runId = previous.runId
|
|
458
549
|
if (previous?.signature !== undefined) tanstack.signature = previous.signature
|
|
459
550
|
if (fromParts.length > 0) tanstack.uiResources = fromParts
|
|
460
551
|
const result = { ...current }
|
|
@@ -690,6 +781,22 @@ function buildAssistantMessages(uiMessage: UIMessage): Array<ModelMessage> {
|
|
|
690
781
|
// model input, so it is intentionally dropped from the model message.
|
|
691
782
|
break
|
|
692
783
|
|
|
784
|
+
case 'subagent': {
|
|
785
|
+
// A child that a tool call started reports through the tool result.
|
|
786
|
+
const block =
|
|
787
|
+
part.subagent.parentToolCallId === undefined
|
|
788
|
+
? subagentHistoryText(part)
|
|
789
|
+
: ''
|
|
790
|
+
if (block !== '') {
|
|
791
|
+
const prefix = current.contentParts.length > 0 ? '\n\n' : ''
|
|
792
|
+
current.contentParts.push({
|
|
793
|
+
type: 'text',
|
|
794
|
+
content: `${prefix}${block}`,
|
|
795
|
+
})
|
|
796
|
+
}
|
|
797
|
+
break
|
|
798
|
+
}
|
|
799
|
+
|
|
693
800
|
default:
|
|
694
801
|
break
|
|
695
802
|
}
|
|
@@ -947,7 +1054,9 @@ export function aguiSnapshotMessageToUIMessage(
|
|
|
947
1054
|
modelMessageToUIMessage(
|
|
948
1055
|
{
|
|
949
1056
|
role: 'tool',
|
|
950
|
-
content: message.content
|
|
1057
|
+
content: isContentPartArray(message.content)
|
|
1058
|
+
? message.content
|
|
1059
|
+
: aguiContentToContentParts(message.content),
|
|
951
1060
|
toolCallId: message.toolCallId,
|
|
952
1061
|
...('name' in message && typeof message.name === 'string'
|
|
953
1062
|
? { name: message.name }
|
|
@@ -1071,25 +1180,29 @@ function snapshotStructuredOutput(
|
|
|
1071
1180
|
* AG-UI user content is either a plain string or a multimodal array whose text
|
|
1072
1181
|
* entries use `{ type: 'text', text }` (vs. TanStack's `{ type: 'text', content }`).
|
|
1073
1182
|
* Text entries are rewritten to the TanStack shape; image/audio/video/document
|
|
1074
|
-
* entries already match `ContentPart` and pass through.
|
|
1075
|
-
* TanStack equivalent and are dropped.
|
|
1183
|
+
* entries already match `ContentPart` and pass through.
|
|
1076
1184
|
*/
|
|
1077
1185
|
function aguiUserContentToParts(
|
|
1078
1186
|
content: Extract<AGUIMessage, { role: 'user' }>['content'],
|
|
1079
1187
|
): Array<MessagePart> {
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1188
|
+
const converted = aguiContentToContentParts(content)
|
|
1189
|
+
return typeof converted === 'string'
|
|
1190
|
+
? converted
|
|
1191
|
+
? [{ type: 'text', content: converted }]
|
|
1192
|
+
: []
|
|
1193
|
+
: converted
|
|
1194
|
+
}
|
|
1083
1195
|
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1196
|
+
/** Convert wire content parts. Data, url, and file sources pass through. */
|
|
1197
|
+
export function aguiContentToContentParts(
|
|
1198
|
+
content: Extract<AGUIMessage, { role: 'user' }>['content'],
|
|
1199
|
+
): string | Array<ContentPart> {
|
|
1200
|
+
if (typeof content === 'string') return content
|
|
1201
|
+
return content.map((part) => {
|
|
1202
|
+
if (part.type !== 'text') return part
|
|
1203
|
+
const { text, ...rest } = part
|
|
1204
|
+
return { ...rest, content: text }
|
|
1205
|
+
})
|
|
1093
1206
|
}
|
|
1094
1207
|
|
|
1095
1208
|
/**
|
|
@@ -1149,6 +1262,14 @@ export function modelMessagesToUIMessages(
|
|
|
1149
1262
|
// Regular message. Preserve a persisted stable id so a hydrated message
|
|
1150
1263
|
// keeps the same identity as its live stream (enables in-place resume).
|
|
1151
1264
|
const uiMessage = modelMessageToUIMessage(msg, msg.id)
|
|
1265
|
+
if (
|
|
1266
|
+
msg.role === 'assistant' &&
|
|
1267
|
+
currentAssistantMessage &&
|
|
1268
|
+
isAssistantSegmentOf(msg.id, currentAssistantMessage.id)
|
|
1269
|
+
) {
|
|
1270
|
+
currentAssistantMessage.parts.push(...uiMessage.parts)
|
|
1271
|
+
continue
|
|
1272
|
+
}
|
|
1152
1273
|
uiMessages.push(uiMessage)
|
|
1153
1274
|
|
|
1154
1275
|
// Track assistant messages for potential tool result merging
|
|
@@ -112,8 +112,25 @@ export interface RunRecord {
|
|
|
112
112
|
* reuse this record by faking `threadId = requestId`; they need a separate
|
|
113
113
|
* job store. `withGenerationPersistence` currently does exactly that and
|
|
114
114
|
* labels itself a stopgap — do not copy it.
|
|
115
|
+
*
|
|
116
|
+
* A subagent child record stores `subagent:<subagentRunId>` here, the key of
|
|
117
|
+
* its own transcript, so `findActiveRun` and `listByThread` on the
|
|
118
|
+
* conversation never return children. Use `listByParentRun`.
|
|
115
119
|
*/
|
|
116
120
|
threadId: string
|
|
121
|
+
/**
|
|
122
|
+
* Parent chat run that started this child, when this record is a subagent.
|
|
123
|
+
* Absent on the parent run itself.
|
|
124
|
+
*/
|
|
125
|
+
parentRunId?: string
|
|
126
|
+
/**
|
|
127
|
+
* The child's AG-UI subagentRunId, the id on its `SUBAGENT_*` chunks and on
|
|
128
|
+
* every chunk it streams. On a child record this equals `runId`. Absent on
|
|
129
|
+
* the parent run.
|
|
130
|
+
*/
|
|
131
|
+
subagentRunId?: string
|
|
132
|
+
/** Agent name (`researcher`, `writer`) when this record is a subagent. */
|
|
133
|
+
name?: string
|
|
117
134
|
status: RunStatus
|
|
118
135
|
startedAt: number
|
|
119
136
|
finishedAt?: number
|
|
@@ -172,9 +189,10 @@ export interface RunRecord {
|
|
|
172
189
|
* instead of failing at build time. It was optional for exactly one release
|
|
173
190
|
* cycle and cost precisely that.
|
|
174
191
|
*
|
|
175
|
-
* OPTIONAL: `listByThread`, `listReclaimable`. Each serves
|
|
176
|
-
* feature (thread history,
|
|
177
|
-
* degrading
|
|
192
|
+
* OPTIONAL: `listByThread`, `listByParentRun`, `listReclaimable`. Each serves
|
|
193
|
+
* one higher-level feature (thread history, subagent card reload, reclaim
|
|
194
|
+
* reaping) and callers feature-detect them, degrading when a backend omits
|
|
195
|
+
* them.
|
|
178
196
|
*/
|
|
179
197
|
export interface RunStore {
|
|
180
198
|
/**
|
|
@@ -182,12 +200,17 @@ export interface RunStore {
|
|
|
182
200
|
* already present.
|
|
183
201
|
*
|
|
184
202
|
* INVARIANT (idempotency): an existing record is returned **unchanged** and
|
|
185
|
-
* the passed `threadId
|
|
186
|
-
*
|
|
203
|
+
* the passed `threadId`, `startedAt`, `status`, `parentRunId`,
|
|
204
|
+
* `subagentRunId`, and `name` are ignored. This is what makes resuming a
|
|
205
|
+
* run safe. `status` defaults to `'running'` on first creation. The three
|
|
206
|
+
* link fields are copied only on the first insert.
|
|
187
207
|
*/
|
|
188
208
|
createOrResume: (
|
|
189
209
|
input: Pick<RunRecord, 'runId' | 'threadId' | 'startedAt'> & {
|
|
190
210
|
status?: RunStatus
|
|
211
|
+
parentRunId?: string
|
|
212
|
+
subagentRunId?: string
|
|
213
|
+
name?: string
|
|
191
214
|
},
|
|
192
215
|
) => Promise<RunRecord>
|
|
193
216
|
/**
|
|
@@ -215,10 +238,19 @@ export interface RunStore {
|
|
|
215
238
|
/** Current record, or null when unknown. */
|
|
216
239
|
get: (runId: string) => Promise<RunRecord | null>
|
|
217
240
|
/**
|
|
218
|
-
* Every run in a conversation, ascending by `startedAt`. OPTIONAL
|
|
219
|
-
*
|
|
241
|
+
* Every run in a conversation, ascending by `startedAt`. OPTIONAL.
|
|
242
|
+
* `reconstructChat` calls it to find the parent runs of children that a
|
|
243
|
+
* tool call started. Without it those cards stay absent on reload.
|
|
244
|
+
* Consumers feature-detect.
|
|
220
245
|
*/
|
|
221
246
|
listByThread?: (threadId: string) => Promise<Array<RunRecord>>
|
|
247
|
+
/**
|
|
248
|
+
* Child runs started by `parentRunId`, ascending by `startedAt`.
|
|
249
|
+
* OPTIONAL. `reconstructChat` uses this to put subagent cards back
|
|
250
|
+
* on the parent assistant message. A store that omits it reloads the
|
|
251
|
+
* text and not the cards.
|
|
252
|
+
*/
|
|
253
|
+
listByParentRun?: (parentRunId: string) => Promise<Array<RunRecord>>
|
|
222
254
|
/**
|
|
223
255
|
* Runs that may be reclaimed: ALL THREE of `status === 'running'`,
|
|
224
256
|
* `detachedSince` is set, and `detachedSince <= now - ttlMs`. The cutoff is
|
|
@@ -341,6 +373,9 @@ export class InMemoryRunStore implements RunStore {
|
|
|
341
373
|
createOrResume(
|
|
342
374
|
input: Pick<RunRecord, 'runId' | 'threadId' | 'startedAt'> & {
|
|
343
375
|
status?: RunStatus
|
|
376
|
+
parentRunId?: string
|
|
377
|
+
subagentRunId?: string
|
|
378
|
+
name?: string
|
|
344
379
|
},
|
|
345
380
|
): Promise<RunRecord> {
|
|
346
381
|
const existing = this.runs.get(input.runId)
|
|
@@ -350,6 +385,13 @@ export class InMemoryRunStore implements RunStore {
|
|
|
350
385
|
threadId: input.threadId,
|
|
351
386
|
status: input.status ?? 'running',
|
|
352
387
|
startedAt: input.startedAt,
|
|
388
|
+
...(input.parentRunId !== undefined
|
|
389
|
+
? { parentRunId: input.parentRunId }
|
|
390
|
+
: {}),
|
|
391
|
+
...(input.subagentRunId !== undefined
|
|
392
|
+
? { subagentRunId: input.subagentRunId }
|
|
393
|
+
: {}),
|
|
394
|
+
...(input.name !== undefined ? { name: input.name } : {}),
|
|
353
395
|
}
|
|
354
396
|
this.runs.set(record.runId, record)
|
|
355
397
|
return Promise.resolve(record)
|
|
@@ -387,6 +429,13 @@ export class InMemoryRunStore implements RunStore {
|
|
|
387
429
|
return Promise.resolve(matching)
|
|
388
430
|
}
|
|
389
431
|
|
|
432
|
+
listByParentRun(parentRunId: string): Promise<Array<RunRecord>> {
|
|
433
|
+
const matching = [...this.runs.values()]
|
|
434
|
+
.filter((run) => run.parentRunId === parentRunId)
|
|
435
|
+
.sort((a, b) => a.startedAt - b.startedAt)
|
|
436
|
+
return Promise.resolve(matching)
|
|
437
|
+
}
|
|
438
|
+
|
|
390
439
|
listReclaimable(opts: {
|
|
391
440
|
now: number
|
|
392
441
|
ttlMs: number
|
|
@@ -5,8 +5,10 @@ import type {
|
|
|
5
5
|
import type {
|
|
6
6
|
AgentLoopState,
|
|
7
7
|
EmitCustomEventOptions,
|
|
8
|
+
Interrupt,
|
|
8
9
|
JSONSchema,
|
|
9
10
|
ModelMessage,
|
|
11
|
+
UIMessage,
|
|
10
12
|
RunAgentResumeItem,
|
|
11
13
|
StreamChunk,
|
|
12
14
|
TokenUsage,
|
|
@@ -191,6 +193,11 @@ export interface ChatMiddlewareContext<TContext = unknown> {
|
|
|
191
193
|
runId: string
|
|
192
194
|
/** Interrupted or parent run correlated with this continuation. */
|
|
193
195
|
parentRunId?: string
|
|
196
|
+
/**
|
|
197
|
+
* Set when this run is a subagent. The id on the child's `SUBAGENT_STARTED`
|
|
198
|
+
* and on every chunk it streams. Absent on a top-level run.
|
|
199
|
+
*/
|
|
200
|
+
subagentRunId?: string
|
|
194
201
|
/**
|
|
195
202
|
* AG-UI thread identifier — a stable per-conversation ID used to
|
|
196
203
|
* correlate client and server devtools events. Resolves to the
|
|
@@ -544,6 +551,40 @@ export interface ErrorInfo {
|
|
|
544
551
|
duration: number
|
|
545
552
|
}
|
|
546
553
|
|
|
554
|
+
/**
|
|
555
|
+
* Saves subagent runs while a router owns the turn.
|
|
556
|
+
* `withPersistence` sets this. `chat()` calls it. Apps do not.
|
|
557
|
+
*/
|
|
558
|
+
export interface RoutedSubagentPersistence {
|
|
559
|
+
start: (input: {
|
|
560
|
+
threadId: string
|
|
561
|
+
runId: string
|
|
562
|
+
messages: ReadonlyArray<UIMessage | ModelMessage>
|
|
563
|
+
/**
|
|
564
|
+
* The run's resume entries: answers to earlier child interrupts, plus any
|
|
565
|
+
* the parent answers itself.
|
|
566
|
+
*/
|
|
567
|
+
resume?: ReadonlyArray<RunAgentResumeItem>
|
|
568
|
+
}) => Promise<void>
|
|
569
|
+
chunk: (input: {
|
|
570
|
+
threadId: string
|
|
571
|
+
runId: string
|
|
572
|
+
chunk: StreamChunk
|
|
573
|
+
}) => Promise<void>
|
|
574
|
+
finish: (input: { threadId: string; runId: string }) => Promise<void>
|
|
575
|
+
/** The run stopped because a child waits for outside input. */
|
|
576
|
+
suspend?: (input: {
|
|
577
|
+
threadId: string
|
|
578
|
+
runId: string
|
|
579
|
+
interrupts: ReadonlyArray<Interrupt>
|
|
580
|
+
}) => Promise<void>
|
|
581
|
+
abort: (input: {
|
|
582
|
+
threadId: string
|
|
583
|
+
runId: string
|
|
584
|
+
error?: unknown
|
|
585
|
+
}) => Promise<void>
|
|
586
|
+
}
|
|
587
|
+
|
|
547
588
|
// ===========================
|
|
548
589
|
// Middleware Interface
|
|
549
590
|
// ===========================
|
|
@@ -584,6 +625,12 @@ export interface ChatMiddleware<
|
|
|
584
625
|
/** Optional name for debugging and identification */
|
|
585
626
|
name?: string
|
|
586
627
|
|
|
628
|
+
/**
|
|
629
|
+
* Present when this middleware stores subagent runs.
|
|
630
|
+
* The router calls it. An app does not set it.
|
|
631
|
+
*/
|
|
632
|
+
routedSubagentPersistence?: RoutedSubagentPersistence
|
|
633
|
+
|
|
587
634
|
/**
|
|
588
635
|
* Called at a lifecycle boundary. Return interrupt requests to pause the run.
|
|
589
636
|
* Requests from every middleware in the same boundary form one batch.
|
|
@@ -455,15 +455,37 @@ export function updateThinkingPart(
|
|
|
455
455
|
}
|
|
456
456
|
|
|
457
457
|
const parts = [...msg.parts]
|
|
458
|
-
|
|
458
|
+
let thinkingPartIndex = parts.findIndex(
|
|
459
459
|
(p) => p.type === 'thinking' && p.stepId === stepId,
|
|
460
460
|
)
|
|
461
461
|
|
|
462
|
+
// A hydrated message carries its thinking without a stepId: the stored form
|
|
463
|
+
// has no field for one, so `modelMessageToUIMessage` cannot put it back.
|
|
464
|
+
// When a run is rejoined mid-stream the replayed reasoning is keyed by
|
|
465
|
+
// stepId, matches nothing, and gets appended -- leaving a second thinking
|
|
466
|
+
// part sitting after the answer text. Adopt the first stepId-less thinking
|
|
467
|
+
// part instead, so the replay lands on the part it belongs to. Live
|
|
468
|
+
// streaming always writes a stepId, so the only parts this can match are
|
|
469
|
+
// hydrated ones.
|
|
470
|
+
let adopted: ThinkingPart | undefined
|
|
471
|
+
if (thinkingPartIndex < 0) {
|
|
472
|
+
thinkingPartIndex = parts.findIndex(
|
|
473
|
+
(p) => p.type === 'thinking' && p.stepId === undefined,
|
|
474
|
+
)
|
|
475
|
+
const candidate = parts[thinkingPartIndex]
|
|
476
|
+
if (candidate?.type === 'thinking') adopted = candidate
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// Keep the signature the hydrated part already had when this update does
|
|
480
|
+
// not carry one; losing it would strip the provider's encrypted reasoning
|
|
481
|
+
// from a message that is about to be sent back.
|
|
482
|
+
const nextSignature = signature ?? adopted?.signature
|
|
483
|
+
|
|
462
484
|
const thinkingPart: ThinkingPart = {
|
|
463
485
|
type: 'thinking',
|
|
464
486
|
content,
|
|
465
487
|
stepId,
|
|
466
|
-
...(
|
|
488
|
+
...(nextSignature && { signature: nextSignature }),
|
|
467
489
|
}
|
|
468
490
|
|
|
469
491
|
if (thinkingPartIndex >= 0) {
|