@tanstack/ai 0.58.0 → 0.59.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/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 +337 -13
- 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 +94 -18
- 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/processor.d.ts +46 -0
- package/dist/esm/activities/chat/stream/processor.js +280 -7
- 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 +54 -5
- package/dist/esm/activities/chat/tools/tool-calls.js.map +1 -1
- package/dist/esm/activities/generateAudio/index.js +1 -1
- package/dist/esm/activities/generateImage/index.js +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/generateVoice/index.js +1 -1
- package/dist/esm/activities/generateWorld/index.js +1 -1
- package/dist/esm/activities/index.d.ts +3 -0
- package/dist/esm/activities/index.js +5 -3
- package/dist/esm/activities/summarize/index.js +1 -1
- package/dist/esm/client.d.ts +5 -36
- package/dist/esm/client.js +4 -37
- package/dist/esm/client.js.map +1 -1
- package/dist/esm/index.d.ts +5 -0
- package/dist/esm/index.js +6 -3
- package/dist/esm/middlewares/content-guard.js.map +1 -1
- package/dist/esm/types.d.ts +110 -88
- 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 +56 -0
- package/dist/esm/utilities/ag-ui-wire.js.map +1 -1
- 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/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/package.json +2 -2
- package/skills/ai-core/adapter-configuration/references/grok-adapter.md +1 -1
- package/skills/ai-core/media-generation/SKILL.md +2 -2
- package/skills/ai-core/middleware/SKILL.md +7 -4
- 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 +529 -11
- package/src/activities/chat/messages.ts +125 -15
- package/src/activities/chat/middleware/run-store.ts +56 -7
- package/src/activities/chat/middleware/types.ts +47 -0
- package/src/activities/chat/stream/processor.ts +428 -8
- package/src/activities/chat/tools/tool-calls.ts +77 -11
- package/src/activities/index.ts +15 -0
- package/src/client.ts +22 -35
- package/src/index.ts +23 -0
- package/src/middlewares/content-guard.ts +7 -5
- package/src/types.ts +154 -94
- 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 +74 -0
- package/src/utilities/normalize-stream-chunk.ts +10 -2
- package/src/utilities/spec-event-keys.ts +34 -7
- package/src/utilities/subagent-wire.ts +184 -0
|
@@ -38,6 +38,7 @@ import {
|
|
|
38
38
|
tanstackMetadata,
|
|
39
39
|
withTanstackMetadata,
|
|
40
40
|
} from '../../utilities/merge-metadata'
|
|
41
|
+
import { subagentHostMessageId } from '../../utilities/subagent-wire'
|
|
41
42
|
import { withDurabilityBatchHint } from '../../utilities/durability-batch'
|
|
42
43
|
import { normalizeStreamChunk } from '../../utilities/normalize-stream-chunk'
|
|
43
44
|
import { restorePublicUsage } from '../../utilities/restore-inbound-chunk'
|
|
@@ -46,6 +47,25 @@ import { normalizeToolResult } from '../../utilities/tool-result'
|
|
|
46
47
|
import { isProviderExecutedToolCall } from '../../utilities/provider-executed'
|
|
47
48
|
import { LazyToolManager } from './tools/lazy-tool-manager'
|
|
48
49
|
import { assertUniqueToolNames } from './tools/unique-tool-names'
|
|
50
|
+
import type { DefinedAgent } from './agents/define-agent'
|
|
51
|
+
import {
|
|
52
|
+
collectNamedText,
|
|
53
|
+
createSubagentSink,
|
|
54
|
+
createSyntheticSubagentTools,
|
|
55
|
+
normalizeRouterPick,
|
|
56
|
+
rebindInterrupts,
|
|
57
|
+
spawnNamedAgents,
|
|
58
|
+
subagentCallMessages,
|
|
59
|
+
withChildUsage,
|
|
60
|
+
} from './agents/spawn'
|
|
61
|
+
import type {
|
|
62
|
+
SpawnEntry,
|
|
63
|
+
SubagentSink,
|
|
64
|
+
SubagentStep,
|
|
65
|
+
SubagentStepsPlan,
|
|
66
|
+
SubagentsBag,
|
|
67
|
+
} from './agents/spawn'
|
|
68
|
+
import { SUBAGENT_PLAN_KEY, readSubagentTurn } from './agents/turn'
|
|
49
69
|
import {
|
|
50
70
|
MiddlewareAbortError,
|
|
51
71
|
ToolCallManager,
|
|
@@ -163,6 +183,28 @@ type RuntimeToolWithApproval = AnyRuntimeTool & {
|
|
|
163
183
|
}
|
|
164
184
|
const interruptBindingMetadataKey = INTERRUPT_BINDING_METADATA_KEY
|
|
165
185
|
|
|
186
|
+
/** Resume entries a subagent tool call owns. The parent run skips them. */
|
|
187
|
+
const CHILD_RESUME_IDS = Symbol('tanstack.ai.childResumeIds')
|
|
188
|
+
|
|
189
|
+
// ponytail: no adapter maps `{ type: 'file' }` yet, so every one fails closed
|
|
190
|
+
// instead of reading the handle as a URL or base64. The Files API work
|
|
191
|
+
// replaces this with a per-adapter capability check.
|
|
192
|
+
function assertNoFileSources(
|
|
193
|
+
adapterName: string,
|
|
194
|
+
messages: ReadonlyArray<ModelMessage>,
|
|
195
|
+
): void {
|
|
196
|
+
for (const message of messages) {
|
|
197
|
+
if (!Array.isArray(message.content)) continue
|
|
198
|
+
for (const part of message.content) {
|
|
199
|
+
if ('source' in part && part.source.type === 'file') {
|
|
200
|
+
throw new Error(
|
|
201
|
+
`${adapterName} does not support provider file-handle sources ({ type: 'file' }). Pass a data or url source.`,
|
|
202
|
+
)
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
166
208
|
interface StructuralInterruptFailure {
|
|
167
209
|
error: Error
|
|
168
210
|
errors: ReadonlyArray<InterruptSubmissionError>
|
|
@@ -395,8 +437,9 @@ type TextActivityOptionsWithContext<
|
|
|
395
437
|
[],
|
|
396
438
|
TContext = unknown,
|
|
397
439
|
TMiddleware extends Array<unknown> | undefined = undefined,
|
|
440
|
+
TAgents extends ReadonlyArray<DefinedAgent> = ReadonlyArray<DefinedAgent>,
|
|
398
441
|
> = Omit<
|
|
399
|
-
TextActivityOptions<TAdapter, TSchema, TStream, any>,
|
|
442
|
+
TextActivityOptions<TAdapter, TSchema, TStream, any, TAgents>,
|
|
400
443
|
'tools' | 'middleware' | 'context' | 'interrupts'
|
|
401
444
|
> & {
|
|
402
445
|
tools?: TTools
|
|
@@ -422,6 +465,7 @@ export interface TextActivityOptions<
|
|
|
422
465
|
TSchema extends SchemaInput | undefined,
|
|
423
466
|
TStream extends boolean,
|
|
424
467
|
TContext = unknown,
|
|
468
|
+
TAgents extends ReadonlyArray<DefinedAgent> = ReadonlyArray<DefinedAgent>,
|
|
425
469
|
> {
|
|
426
470
|
/** The text adapter to use (created by a provider function like openaiText('gpt-5.5')) */
|
|
427
471
|
adapter: TAdapter
|
|
@@ -499,6 +543,8 @@ export interface TextActivityOptions<
|
|
|
499
543
|
runId?: TextOptions['runId']
|
|
500
544
|
/** Parent run ID for AG-UI protocol nested run correlation. */
|
|
501
545
|
parentRunId?: TextOptions['parentRunId']
|
|
546
|
+
/** Subagent run id when this chat runs as a child. See `defineAgent`. */
|
|
547
|
+
subagentRunId?: TextOptions['subagentRunId']
|
|
502
548
|
/** Application state mirrored in a STATE_SNAPSHOT before an interrupt terminal. */
|
|
503
549
|
state?: TextOptions['state']
|
|
504
550
|
/**
|
|
@@ -506,6 +552,12 @@ export interface TextActivityOptions<
|
|
|
506
552
|
* before accepting new input on a thread with pending interrupts.
|
|
507
553
|
*/
|
|
508
554
|
resume?: TextOptions['resume']
|
|
555
|
+
/**
|
|
556
|
+
* Named child agents. When `router` is set, the library spawns that agent
|
|
557
|
+
* directly. When `router` is omitted, the main model gets one synthetic
|
|
558
|
+
* server tool per agent and picks the child.
|
|
559
|
+
*/
|
|
560
|
+
subagents?: SubagentsBag<TAgents>
|
|
509
561
|
/**
|
|
510
562
|
* Optional Standard Schema for structured output.
|
|
511
563
|
* When provided, the activity will:
|
|
@@ -842,6 +894,7 @@ class TextEngine<
|
|
|
842
894
|
private readonly threadId: string
|
|
843
895
|
private readonly runIdOverride?: string
|
|
844
896
|
private readonly parentRunIdOverride?: string
|
|
897
|
+
private readonly subagentRunIdOverride?: string
|
|
845
898
|
|
|
846
899
|
// Middleware support
|
|
847
900
|
private readonly middlewareRunner: MiddlewareRunner<
|
|
@@ -972,6 +1025,7 @@ class TextEngine<
|
|
|
972
1025
|
this.createId('thread')
|
|
973
1026
|
this.runIdOverride = config.params.runId
|
|
974
1027
|
this.parentRunIdOverride = config.params.parentRunId
|
|
1028
|
+
this.subagentRunIdOverride = config.params.subagentRunId
|
|
975
1029
|
|
|
976
1030
|
// Initialize middleware — devtools first. Spec stripping for the AG-UI
|
|
977
1031
|
// wire happens in toServerSentEventsStream, so in-process chat()
|
|
@@ -990,6 +1044,7 @@ class TextEngine<
|
|
|
990
1044
|
streamId: this.streamId,
|
|
991
1045
|
runId: this.runIdOverride ?? this.requestId,
|
|
992
1046
|
parentRunId: this.parentRunIdOverride,
|
|
1047
|
+
subagentRunId: this.subagentRunIdOverride,
|
|
993
1048
|
threadId: this.threadId,
|
|
994
1049
|
// Legacy alias kept on the ctx so middleware that reads
|
|
995
1050
|
// `ctx.conversationId` keeps working. Always equals `threadId`.
|
|
@@ -1516,6 +1571,8 @@ class TextEngine<
|
|
|
1516
1571
|
)
|
|
1517
1572
|
}
|
|
1518
1573
|
|
|
1574
|
+
assertNoFileSources(this.adapter.name, this.messages)
|
|
1575
|
+
|
|
1519
1576
|
for await (const raw of this.adapter.chatStream({
|
|
1520
1577
|
model: this.params.model,
|
|
1521
1578
|
messages: this.providerMessages,
|
|
@@ -2083,7 +2140,8 @@ class TextEngine<
|
|
|
2083
2140
|
|
|
2084
2141
|
if (
|
|
2085
2142
|
executionResult.needsApproval.length > 0 ||
|
|
2086
|
-
executionResult.needsClientExecution.length > 0
|
|
2143
|
+
executionResult.needsClientExecution.length > 0 ||
|
|
2144
|
+
executionResult.subagentInterrupts.length > 0
|
|
2087
2145
|
) {
|
|
2088
2146
|
this.discardDeferredToolCallRunFinishedChunks()
|
|
2089
2147
|
|
|
@@ -2100,6 +2158,8 @@ class TextEngine<
|
|
|
2100
2158
|
finishEvent,
|
|
2101
2159
|
executionResult.needsApproval,
|
|
2102
2160
|
executionResult.needsClientExecution,
|
|
2161
|
+
[],
|
|
2162
|
+
executionResult.subagentInterrupts,
|
|
2103
2163
|
)
|
|
2104
2164
|
this.setToolPhase(emitted ? 'wait' : 'stop')
|
|
2105
2165
|
return emitted ? 'wait' : 'stop'
|
|
@@ -2292,7 +2352,8 @@ class TextEngine<
|
|
|
2292
2352
|
|
|
2293
2353
|
if (
|
|
2294
2354
|
executionResult.needsApproval.length > 0 ||
|
|
2295
|
-
executionResult.needsClientExecution.length > 0
|
|
2355
|
+
executionResult.needsClientExecution.length > 0 ||
|
|
2356
|
+
executionResult.subagentInterrupts.length > 0
|
|
2296
2357
|
) {
|
|
2297
2358
|
if (allResults.length > 0) {
|
|
2298
2359
|
for (const chunk of afterToolBoundaryChunks) {
|
|
@@ -2304,6 +2365,8 @@ class TextEngine<
|
|
|
2304
2365
|
finishEvent,
|
|
2305
2366
|
executionResult.needsApproval,
|
|
2306
2367
|
executionResult.needsClientExecution,
|
|
2368
|
+
[],
|
|
2369
|
+
executionResult.subagentInterrupts,
|
|
2307
2370
|
)
|
|
2308
2371
|
this.setToolPhase(emitted ? 'wait' : 'stop')
|
|
2309
2372
|
return
|
|
@@ -2622,6 +2685,7 @@ class TextEngine<
|
|
|
2622
2685
|
GenericInterruptRequest<InterruptDefinition<any, any, any, any>>
|
|
2623
2686
|
> = [],
|
|
2624
2687
|
genericInterruptIds: ReadonlyArray<string> = [],
|
|
2688
|
+
childInterrupts: ReadonlyArray<Interrupt> = [],
|
|
2625
2689
|
): Array<Interrupt> {
|
|
2626
2690
|
const interrupts: Array<Interrupt> = []
|
|
2627
2691
|
|
|
@@ -2739,6 +2803,9 @@ class TextEngine<
|
|
|
2739
2803
|
})
|
|
2740
2804
|
}
|
|
2741
2805
|
|
|
2806
|
+
// A subagent tool call raised these. They keep their `subagentRunId`.
|
|
2807
|
+
interrupts.push(...childInterrupts)
|
|
2808
|
+
|
|
2742
2809
|
const ids = new Set<string>()
|
|
2743
2810
|
for (const interrupt of interrupts) {
|
|
2744
2811
|
if (ids.has(interrupt.id)) {
|
|
@@ -2760,6 +2827,7 @@ class TextEngine<
|
|
|
2760
2827
|
GenericInterruptRequest<InterruptDefinition<any, any, any, any>>
|
|
2761
2828
|
> = [],
|
|
2762
2829
|
genericInterruptIds?: ReadonlyArray<string>,
|
|
2830
|
+
childInterrupts?: ReadonlyArray<Interrupt>,
|
|
2763
2831
|
): StreamChunk {
|
|
2764
2832
|
return {
|
|
2765
2833
|
...finishEvent,
|
|
@@ -2771,6 +2839,7 @@ class TextEngine<
|
|
|
2771
2839
|
clientRequests,
|
|
2772
2840
|
genericRequests,
|
|
2773
2841
|
genericInterruptIds,
|
|
2842
|
+
childInterrupts,
|
|
2774
2843
|
),
|
|
2775
2844
|
},
|
|
2776
2845
|
}
|
|
@@ -2920,11 +2989,13 @@ class TextEngine<
|
|
|
2920
2989
|
genericRequests: ReadonlyArray<
|
|
2921
2990
|
GenericInterruptRequest<InterruptDefinition<any, any, any, any>>
|
|
2922
2991
|
> = [],
|
|
2992
|
+
childInterrupts: ReadonlyArray<Interrupt> = [],
|
|
2923
2993
|
): AsyncGenerator<StreamChunk, boolean, void> {
|
|
2924
2994
|
yield* this.emitSyntheticRunStarted(finishEvent)
|
|
2925
2995
|
const genericInterruptIds = genericRequests.map(() =>
|
|
2926
2996
|
this.genericInterruptId(),
|
|
2927
2997
|
)
|
|
2998
|
+
// Binding completion also binds child interrupts to this run.
|
|
2928
2999
|
const terminal = this.completeEphemeralInterruptBindings(
|
|
2929
3000
|
this.buildInterruptFinishedChunk(
|
|
2930
3001
|
finishEvent,
|
|
@@ -2932,6 +3003,7 @@ class TextEngine<
|
|
|
2932
3003
|
clientRequests,
|
|
2933
3004
|
genericRequests,
|
|
2934
3005
|
genericInterruptIds,
|
|
3006
|
+
childInterrupts,
|
|
2935
3007
|
),
|
|
2936
3008
|
)
|
|
2937
3009
|
let terminalOutputs: Array<StreamChunk>
|
|
@@ -3517,6 +3589,8 @@ class TextEngine<
|
|
|
3517
3589
|
// Apply merged config back to engine state
|
|
3518
3590
|
this.applyMiddlewareConfig(postOnConfig)
|
|
3519
3591
|
|
|
3592
|
+
assertNoFileSources(this.adapter.name, this.messages)
|
|
3593
|
+
|
|
3520
3594
|
// Build the StructuredOutputOptions the adapter expects.
|
|
3521
3595
|
// `this.adapter` is already `TAdapter extends AnyTextAdapter` per the
|
|
3522
3596
|
// class generics — no cast needed.
|
|
@@ -4013,9 +4087,25 @@ class TextEngine<
|
|
|
4013
4087
|
}
|
|
4014
4088
|
}
|
|
4015
4089
|
|
|
4090
|
+
/** Resume entries this run answers itself. A subagent tool owns the rest. */
|
|
4091
|
+
private ownResume(
|
|
4092
|
+
resume: ChatMiddlewareConfig['resume'],
|
|
4093
|
+
): ChatMiddlewareConfig['resume'] {
|
|
4094
|
+
const childIds = (
|
|
4095
|
+
this.params as { [CHILD_RESUME_IDS]?: ReadonlySet<string> }
|
|
4096
|
+
)[CHILD_RESUME_IDS]
|
|
4097
|
+
return childIds
|
|
4098
|
+
? resume?.filter((entry) => !childIds.has(entry.interruptId))
|
|
4099
|
+
: resume
|
|
4100
|
+
}
|
|
4101
|
+
|
|
4016
4102
|
private async applyEphemeralInterruptResume(
|
|
4017
|
-
|
|
4103
|
+
middlewareConfig: ChatMiddlewareConfig,
|
|
4018
4104
|
): Promise<void> {
|
|
4105
|
+
const config = {
|
|
4106
|
+
...middlewareConfig,
|
|
4107
|
+
resume: this.ownResume(middlewareConfig.resume),
|
|
4108
|
+
}
|
|
4019
4109
|
if ((config.resume?.length ?? 0) === 0) {
|
|
4020
4110
|
return
|
|
4021
4111
|
}
|
|
@@ -4248,7 +4338,7 @@ class TextEngine<
|
|
|
4248
4338
|
}> = []
|
|
4249
4339
|
const ids = new Set<string>()
|
|
4250
4340
|
const batchIndexes = new Set<number>()
|
|
4251
|
-
for (const resumeItem of this.params.resume ?? []) {
|
|
4341
|
+
for (const resumeItem of this.ownResume(this.params.resume) ?? []) {
|
|
4252
4342
|
const parsed = readGenericInterruptContinuation(resumeItem.metadata)
|
|
4253
4343
|
if (parsed.status === 'absent') continue
|
|
4254
4344
|
if (parsed.status === 'invalid') {
|
|
@@ -4558,11 +4648,12 @@ class TextEngine<
|
|
|
4558
4648
|
*/
|
|
4559
4649
|
private async *drainToolCallGenerator(
|
|
4560
4650
|
generator: AsyncGenerator<
|
|
4561
|
-
CustomEvent,
|
|
4651
|
+
CustomEvent | StreamChunk,
|
|
4562
4652
|
{
|
|
4563
4653
|
results: Array<ToolResult>
|
|
4564
4654
|
needsApproval: Array<ApprovalRequest>
|
|
4565
4655
|
needsClientExecution: Array<ClientToolRequest>
|
|
4656
|
+
subagentInterrupts: Array<Interrupt>
|
|
4566
4657
|
},
|
|
4567
4658
|
void
|
|
4568
4659
|
>,
|
|
@@ -4572,6 +4663,7 @@ class TextEngine<
|
|
|
4572
4663
|
results: Array<ToolResult>
|
|
4573
4664
|
needsApproval: Array<ApprovalRequest>
|
|
4574
4665
|
needsClientExecution: Array<ClientToolRequest>
|
|
4666
|
+
subagentInterrupts: Array<Interrupt>
|
|
4575
4667
|
},
|
|
4576
4668
|
void
|
|
4577
4669
|
> {
|
|
@@ -4683,6 +4775,8 @@ export function chat<
|
|
|
4683
4775
|
> = [],
|
|
4684
4776
|
TContext = unknown,
|
|
4685
4777
|
const TMiddleware extends Array<unknown> | undefined = undefined,
|
|
4778
|
+
const TAgents extends ReadonlyArray<DefinedAgent> =
|
|
4779
|
+
ReadonlyArray<DefinedAgent>,
|
|
4686
4780
|
>(
|
|
4687
4781
|
options: TextActivityOptionsWithContext<
|
|
4688
4782
|
TAdapter,
|
|
@@ -4691,7 +4785,8 @@ export function chat<
|
|
|
4691
4785
|
TTools,
|
|
4692
4786
|
TInterrupts,
|
|
4693
4787
|
TContext,
|
|
4694
|
-
TMiddleware
|
|
4788
|
+
TMiddleware,
|
|
4789
|
+
TAgents
|
|
4695
4790
|
>,
|
|
4696
4791
|
): TextActivityResult<TSchema, TStream, TTools> {
|
|
4697
4792
|
validateInterruptDefinitions(options.interrupts)
|
|
@@ -4705,6 +4800,12 @@ export function chat<
|
|
|
4705
4800
|
|
|
4706
4801
|
const { outputSchema, stream } = options
|
|
4707
4802
|
|
|
4803
|
+
if (outputSchema && (options.subagents?.agents.length ?? 0) > 0) {
|
|
4804
|
+
throw new Error(
|
|
4805
|
+
'chat() does not support subagents together with outputSchema. Put outputSchema on a child chat() instead.',
|
|
4806
|
+
)
|
|
4807
|
+
}
|
|
4808
|
+
|
|
4708
4809
|
if (outputSchema && stream === true) {
|
|
4709
4810
|
return runStreamingStructuredOutput(
|
|
4710
4811
|
toRuntimeTextActivityOptions(options, {
|
|
@@ -4744,7 +4845,10 @@ type RuntimeTextActivityOptions<
|
|
|
4744
4845
|
TAdapter extends AnyTextAdapter,
|
|
4745
4846
|
TSchema extends SchemaInput | undefined,
|
|
4746
4847
|
TStream extends boolean,
|
|
4747
|
-
> = Omit<
|
|
4848
|
+
> = Omit<
|
|
4849
|
+
TextActivityOptions<TAdapter, TSchema, TStream, any, any>,
|
|
4850
|
+
'middleware'
|
|
4851
|
+
> & {
|
|
4748
4852
|
middleware?: Array<AnyChatMiddleware>
|
|
4749
4853
|
}
|
|
4750
4854
|
|
|
@@ -4773,6 +4877,7 @@ function toRuntimeTextActivityOptions<
|
|
|
4773
4877
|
TInterrupts extends ReadonlyArray<InterruptDefinition<any, any, any, any>>,
|
|
4774
4878
|
TContext,
|
|
4775
4879
|
TMiddleware extends Array<unknown> | undefined,
|
|
4880
|
+
TAgents extends ReadonlyArray<DefinedAgent> = ReadonlyArray<DefinedAgent>,
|
|
4776
4881
|
>(
|
|
4777
4882
|
options: TextActivityOptionsWithContext<
|
|
4778
4883
|
TAdapter,
|
|
@@ -4781,7 +4886,8 @@ function toRuntimeTextActivityOptions<
|
|
|
4781
4886
|
TTools,
|
|
4782
4887
|
TInterrupts,
|
|
4783
4888
|
TContext,
|
|
4784
|
-
TMiddleware
|
|
4889
|
+
TMiddleware,
|
|
4890
|
+
TAgents
|
|
4785
4891
|
>,
|
|
4786
4892
|
overrides: { outputSchema: TOutputSchema; stream: TOutputStream },
|
|
4787
4893
|
): RuntimeTextActivityOptions<TAdapter, TOutputSchema, TOutputStream> {
|
|
@@ -4868,11 +4974,19 @@ function runStreamingText(
|
|
|
4868
4974
|
return stream
|
|
4869
4975
|
}
|
|
4870
4976
|
|
|
4871
|
-
async function*
|
|
4977
|
+
async function* runChatEngine(
|
|
4872
4978
|
options: RuntimeTextActivityOptions<AnyTextAdapter, undefined, boolean>,
|
|
4873
4979
|
engineRef: DeliveryEngineRef,
|
|
4874
4980
|
): AsyncIterable<StreamChunk> {
|
|
4875
|
-
const {
|
|
4981
|
+
const {
|
|
4982
|
+
adapter,
|
|
4983
|
+
middleware,
|
|
4984
|
+
context,
|
|
4985
|
+
debug,
|
|
4986
|
+
mcp,
|
|
4987
|
+
subagents: _subagents,
|
|
4988
|
+
...textOptions
|
|
4989
|
+
} = options
|
|
4876
4990
|
const model = adapter.model
|
|
4877
4991
|
const logger = resolveDebugOption(debug)
|
|
4878
4992
|
|
|
@@ -4906,6 +5020,410 @@ async function* streamTextChunks(
|
|
|
4906
5020
|
}
|
|
4907
5021
|
}
|
|
4908
5022
|
|
|
5023
|
+
async function* streamTextChunks(
|
|
5024
|
+
options: RuntimeTextActivityOptions<AnyTextAdapter, undefined, boolean>,
|
|
5025
|
+
engineRef: DeliveryEngineRef,
|
|
5026
|
+
): AsyncIterable<StreamChunk> {
|
|
5027
|
+
const bag = options.subagents
|
|
5028
|
+
const agents = bag?.agents ?? []
|
|
5029
|
+
if (bag && agents.length > 0 && bag.router) {
|
|
5030
|
+
yield* runRoutedSubagents(options, engineRef)
|
|
5031
|
+
return
|
|
5032
|
+
}
|
|
5033
|
+
if (bag && agents.length > 0) {
|
|
5034
|
+
const threadId = options.threadId ?? `thread-${Date.now()}`
|
|
5035
|
+
const runId = options.runId ?? `run-${Date.now()}`
|
|
5036
|
+
const messages = options.messages ?? []
|
|
5037
|
+
// A resume can answer a child's interrupts. Those entries go to the child
|
|
5038
|
+
// tool call. The parent run validates only its own entries.
|
|
5039
|
+
const turn = readSubagentTurn(messages, options.resume)
|
|
5040
|
+
const sink = createSubagentSink()
|
|
5041
|
+
const calls = subagentCallMessages(
|
|
5042
|
+
new Set(bag.agents.map((agent: DefinedAgent) => agent.name)),
|
|
5043
|
+
)
|
|
5044
|
+
const synthetic = createSyntheticSubagentTools(bag, {
|
|
5045
|
+
messages: turn?.before ?? messages,
|
|
5046
|
+
messagesFor: calls.messagesFor,
|
|
5047
|
+
threadId,
|
|
5048
|
+
runId,
|
|
5049
|
+
...(options.parentRunId !== undefined && {
|
|
5050
|
+
interruptedRunId: options.parentRunId,
|
|
5051
|
+
}),
|
|
5052
|
+
...(options.abortController && {
|
|
5053
|
+
abortSignal: options.abortController.signal,
|
|
5054
|
+
}),
|
|
5055
|
+
...(turn && { turn }),
|
|
5056
|
+
sink,
|
|
5057
|
+
})
|
|
5058
|
+
yield* streamWithChildUsage(
|
|
5059
|
+
runChatEngine(
|
|
5060
|
+
{
|
|
5061
|
+
...options,
|
|
5062
|
+
threadId,
|
|
5063
|
+
runId,
|
|
5064
|
+
...(turn && {
|
|
5065
|
+
[CHILD_RESUME_IDS]: new Set(
|
|
5066
|
+
turn.children.flatMap((child) =>
|
|
5067
|
+
child.resume.map((entry) => entry.interruptId),
|
|
5068
|
+
),
|
|
5069
|
+
),
|
|
5070
|
+
}),
|
|
5071
|
+
tools: [...(options.tools ?? []), ...synthetic],
|
|
5072
|
+
middleware: [...(options.middleware ?? []), calls.middleware],
|
|
5073
|
+
},
|
|
5074
|
+
engineRef,
|
|
5075
|
+
),
|
|
5076
|
+
sink,
|
|
5077
|
+
)
|
|
5078
|
+
return
|
|
5079
|
+
}
|
|
5080
|
+
yield* runChatEngine(options, engineRef)
|
|
5081
|
+
}
|
|
5082
|
+
|
|
5083
|
+
/** Add child usage to the next RUN_FINISHED of the parent. */
|
|
5084
|
+
async function* streamWithChildUsage(
|
|
5085
|
+
stream: AsyncIterable<StreamChunk>,
|
|
5086
|
+
sink: SubagentSink,
|
|
5087
|
+
): AsyncIterable<StreamChunk> {
|
|
5088
|
+
for await (const chunk of stream) {
|
|
5089
|
+
yield chunk.type === EventType.RUN_FINISHED
|
|
5090
|
+
? withChildUsage(chunk, sink)
|
|
5091
|
+
: chunk
|
|
5092
|
+
}
|
|
5093
|
+
}
|
|
5094
|
+
|
|
5095
|
+
async function* runRoutedSubagents(
|
|
5096
|
+
options: RuntimeTextActivityOptions<AnyTextAdapter, undefined, boolean>,
|
|
5097
|
+
engineRef: DeliveryEngineRef,
|
|
5098
|
+
): AsyncIterable<StreamChunk> {
|
|
5099
|
+
const bag = options.subagents
|
|
5100
|
+
if (!bag?.router) {
|
|
5101
|
+
yield* runChatEngine(options, engineRef)
|
|
5102
|
+
return
|
|
5103
|
+
}
|
|
5104
|
+
const threadId = options.threadId ?? `thread-${Date.now()}`
|
|
5105
|
+
const runId = options.runId ?? `run-${Date.now()}`
|
|
5106
|
+
const messages = options.messages ?? []
|
|
5107
|
+
const turn = readSubagentTurn(messages, options.resume)
|
|
5108
|
+
if (!turn && (options.resume?.length ?? 0) > 0) {
|
|
5109
|
+
// No child owns these answers. Main raised them, after a main pick or a
|
|
5110
|
+
// handoff. Run main alone.
|
|
5111
|
+
yield* runChatEngine(
|
|
5112
|
+
{ ...options, threadId, runId, subagents: undefined },
|
|
5113
|
+
engineRef,
|
|
5114
|
+
)
|
|
5115
|
+
return
|
|
5116
|
+
}
|
|
5117
|
+
const abortSignal = options.abortController?.signal
|
|
5118
|
+
const turnMessages = turn?.before ?? messages
|
|
5119
|
+
const subagentPersistence = readRoutedSubagentPersistence(options.middleware)
|
|
5120
|
+
await subagentPersistence?.start({
|
|
5121
|
+
threadId,
|
|
5122
|
+
runId,
|
|
5123
|
+
messages,
|
|
5124
|
+
...(options.resume && { resume: options.resume }),
|
|
5125
|
+
})
|
|
5126
|
+
try {
|
|
5127
|
+
// A resume reuses the plan that the first run put on each child's
|
|
5128
|
+
// SUBAGENT_STARTED metadata. Children that finished keep their result.
|
|
5129
|
+
// Suspended children continue.
|
|
5130
|
+
const plan =
|
|
5131
|
+
savedPlan(turn?.plan, bag.agents, {
|
|
5132
|
+
threadId,
|
|
5133
|
+
interruptedRunId: options.parentRunId ?? runId,
|
|
5134
|
+
interruptIds: options.resume?.map((entry) => entry.interruptId) ?? [],
|
|
5135
|
+
}) ??
|
|
5136
|
+
normalizeRouterPick(
|
|
5137
|
+
await bag.router({
|
|
5138
|
+
messages: turnMessages,
|
|
5139
|
+
agents: bag.agents,
|
|
5140
|
+
...(abortSignal && { abortSignal }),
|
|
5141
|
+
}),
|
|
5142
|
+
bag.agents,
|
|
5143
|
+
)
|
|
5144
|
+
// The run was stopped while the router decided. Start nothing. No
|
|
5145
|
+
// RUN_STARTED went out, so the stream ends without a terminal, but the
|
|
5146
|
+
// record start() opened still has to settle. Otherwise it stays `running`
|
|
5147
|
+
// and reconstructChat hands the client a run to tail that never emits.
|
|
5148
|
+
if (abortSignal?.aborted) {
|
|
5149
|
+
const error = new Error('Aborted')
|
|
5150
|
+
error.name = 'AbortError'
|
|
5151
|
+
await subagentPersistence?.abort({ threadId, runId, error })
|
|
5152
|
+
return
|
|
5153
|
+
}
|
|
5154
|
+
const onlyStep = plan.steps.length === 1 ? plan.steps[0] : undefined
|
|
5155
|
+
if (onlyStep?.names.length === 1 && onlyStep.names[0] === 'main') {
|
|
5156
|
+
// Earlier children own answers in this resume. Commit them first.
|
|
5157
|
+
if (turn) await subagentPersistence?.finish({ threadId, runId })
|
|
5158
|
+
yield* runChatEngine(
|
|
5159
|
+
{
|
|
5160
|
+
...options,
|
|
5161
|
+
threadId,
|
|
5162
|
+
runId,
|
|
5163
|
+
subagents: undefined,
|
|
5164
|
+
...(turn && { resume: turn.rest }),
|
|
5165
|
+
},
|
|
5166
|
+
engineRef,
|
|
5167
|
+
)
|
|
5168
|
+
return
|
|
5169
|
+
}
|
|
5170
|
+
|
|
5171
|
+
yield {
|
|
5172
|
+
type: EventType.RUN_STARTED,
|
|
5173
|
+
threadId,
|
|
5174
|
+
runId,
|
|
5175
|
+
timestamp: Date.now(),
|
|
5176
|
+
}
|
|
5177
|
+
|
|
5178
|
+
const earlier = [...(turn?.children ?? [])]
|
|
5179
|
+
const takeEarlier = (name: string) => {
|
|
5180
|
+
const index = earlier.findIndex((child) => child.name === name)
|
|
5181
|
+
return index === -1 ? undefined : earlier.splice(index, 1)[0]
|
|
5182
|
+
}
|
|
5183
|
+
const sink = createSubagentSink()
|
|
5184
|
+
const stepTexts: Array<string> = []
|
|
5185
|
+
let stepMessages = turnMessages
|
|
5186
|
+
let failure:
|
|
5187
|
+
| Extract<StreamChunk, { type: EventType.SUBAGENT_ERROR }>
|
|
5188
|
+
| undefined
|
|
5189
|
+
for (const step of plan.steps) {
|
|
5190
|
+
const entries: Array<SpawnEntry> = []
|
|
5191
|
+
const textByName = new Map<string, string>()
|
|
5192
|
+
for (const name of step.names) {
|
|
5193
|
+
const prior = takeEarlier(name)
|
|
5194
|
+
if (prior?.status === 'finished') {
|
|
5195
|
+
textByName.set(name, prior.text)
|
|
5196
|
+
continue
|
|
5197
|
+
}
|
|
5198
|
+
entries.push(
|
|
5199
|
+
prior?.status === 'suspended'
|
|
5200
|
+
? {
|
|
5201
|
+
name,
|
|
5202
|
+
resume: {
|
|
5203
|
+
subagentRunId: prior.subagentRunId,
|
|
5204
|
+
messages: prior.messages,
|
|
5205
|
+
entries: prior.resume,
|
|
5206
|
+
text: prior.text,
|
|
5207
|
+
},
|
|
5208
|
+
}
|
|
5209
|
+
: { name },
|
|
5210
|
+
)
|
|
5211
|
+
}
|
|
5212
|
+
const stepChunks: Array<StreamChunk> = []
|
|
5213
|
+
if (entries.length > 0) {
|
|
5214
|
+
for await (const chunk of spawnNamedAgents(
|
|
5215
|
+
entries,
|
|
5216
|
+
{ ...bag, order: step.order ?? bag.order },
|
|
5217
|
+
{
|
|
5218
|
+
messages: stepMessages,
|
|
5219
|
+
...(abortSignal && { abortSignal }),
|
|
5220
|
+
threadId,
|
|
5221
|
+
parentRunId: runId,
|
|
5222
|
+
...(options.parentRunId !== undefined && {
|
|
5223
|
+
interruptedRunId: options.parentRunId,
|
|
5224
|
+
}),
|
|
5225
|
+
},
|
|
5226
|
+
sink,
|
|
5227
|
+
)) {
|
|
5228
|
+
const tagged = withPlan(chunk, plan)
|
|
5229
|
+
stepChunks.push(tagged)
|
|
5230
|
+
await subagentPersistence?.chunk({ threadId, runId, chunk: tagged })
|
|
5231
|
+
yield tagged
|
|
5232
|
+
}
|
|
5233
|
+
}
|
|
5234
|
+
failure = stepChunks.find(
|
|
5235
|
+
(chunk) => chunk.type === EventType.SUBAGENT_ERROR,
|
|
5236
|
+
)
|
|
5237
|
+
if (failure) break
|
|
5238
|
+
if (sink.interrupts.length > 0) break
|
|
5239
|
+
for (const entry of entries) {
|
|
5240
|
+
const text = collectNamedText(stepChunks, [entry.name])
|
|
5241
|
+
textByName.set(
|
|
5242
|
+
entry.name,
|
|
5243
|
+
[entry.resume?.text, text].filter(Boolean).join('\n\n'),
|
|
5244
|
+
)
|
|
5245
|
+
}
|
|
5246
|
+
const text = step.names
|
|
5247
|
+
.map((name) => textByName.get(name)?.trim() ?? '')
|
|
5248
|
+
.filter((block) => block !== '')
|
|
5249
|
+
.join('\n\n')
|
|
5250
|
+
if (text) {
|
|
5251
|
+
stepTexts.push(text)
|
|
5252
|
+
stepMessages = [...stepMessages, { role: 'assistant', content: text }]
|
|
5253
|
+
}
|
|
5254
|
+
}
|
|
5255
|
+
|
|
5256
|
+
if (abortSignal?.aborted) {
|
|
5257
|
+
const error = new Error('Aborted')
|
|
5258
|
+
error.name = 'AbortError'
|
|
5259
|
+
await subagentPersistence?.abort({ threadId, runId, error })
|
|
5260
|
+
yield withChildUsage(
|
|
5261
|
+
{
|
|
5262
|
+
type: EventType.RUN_FINISHED,
|
|
5263
|
+
threadId,
|
|
5264
|
+
runId,
|
|
5265
|
+
outcome: { type: 'cancelled' },
|
|
5266
|
+
timestamp: Date.now(),
|
|
5267
|
+
},
|
|
5268
|
+
sink,
|
|
5269
|
+
)
|
|
5270
|
+
return
|
|
5271
|
+
}
|
|
5272
|
+
|
|
5273
|
+
if (failure) {
|
|
5274
|
+
// Carry the child's own cause, not a generic label, and keep the usage
|
|
5275
|
+
// the children already spent: the run failed, but the tokens were real.
|
|
5276
|
+
const message = failure.message || 'A subagent failed'
|
|
5277
|
+
await subagentPersistence?.abort({
|
|
5278
|
+
threadId,
|
|
5279
|
+
runId,
|
|
5280
|
+
error: new Error(message),
|
|
5281
|
+
})
|
|
5282
|
+
yield withChildUsage(
|
|
5283
|
+
{
|
|
5284
|
+
type: EventType.RUN_ERROR,
|
|
5285
|
+
threadId,
|
|
5286
|
+
runId,
|
|
5287
|
+
message,
|
|
5288
|
+
...(failure.code !== undefined ? { code: failure.code } : {}),
|
|
5289
|
+
timestamp: Date.now(),
|
|
5290
|
+
},
|
|
5291
|
+
sink,
|
|
5292
|
+
)
|
|
5293
|
+
return
|
|
5294
|
+
}
|
|
5295
|
+
|
|
5296
|
+
if (sink.interrupts.length > 0) {
|
|
5297
|
+
const interrupts = rebindInterrupts(sink.interrupts, runId)
|
|
5298
|
+
await subagentPersistence?.suspend?.({ threadId, runId, interrupts })
|
|
5299
|
+
yield withChildUsage(
|
|
5300
|
+
{
|
|
5301
|
+
type: EventType.RUN_FINISHED,
|
|
5302
|
+
threadId,
|
|
5303
|
+
runId,
|
|
5304
|
+
outcome: { type: 'interrupt', interrupts },
|
|
5305
|
+
timestamp: Date.now(),
|
|
5306
|
+
},
|
|
5307
|
+
sink,
|
|
5308
|
+
)
|
|
5309
|
+
return
|
|
5310
|
+
}
|
|
5311
|
+
|
|
5312
|
+
const strategy = bag.strategy ?? 'exclusive'
|
|
5313
|
+
if (strategy === 'handoff') {
|
|
5314
|
+
const childText = stepTexts.join('\n\n')
|
|
5315
|
+
// The children are done. Commit their answers and settle their records
|
|
5316
|
+
// before main runs. Main's own persistence takes over from here.
|
|
5317
|
+
await subagentPersistence?.finish({ threadId, runId })
|
|
5318
|
+
yield* streamWithChildUsage(
|
|
5319
|
+
runChatEngine(
|
|
5320
|
+
{
|
|
5321
|
+
...options,
|
|
5322
|
+
threadId,
|
|
5323
|
+
runId,
|
|
5324
|
+
subagents: undefined,
|
|
5325
|
+
...(turn && { resume: turn.rest }),
|
|
5326
|
+
messages: [
|
|
5327
|
+
...turnMessages,
|
|
5328
|
+
{
|
|
5329
|
+
// Same id as the recorder's parent row, so the stored thread
|
|
5330
|
+
// keeps one host message for the cards.
|
|
5331
|
+
id: subagentHostMessageId(runId),
|
|
5332
|
+
role: 'assistant',
|
|
5333
|
+
content: childText || 'Subagent finished.',
|
|
5334
|
+
metadata: { tanstack: { runId } },
|
|
5335
|
+
},
|
|
5336
|
+
],
|
|
5337
|
+
},
|
|
5338
|
+
engineRef,
|
|
5339
|
+
),
|
|
5340
|
+
sink,
|
|
5341
|
+
)
|
|
5342
|
+
return
|
|
5343
|
+
}
|
|
5344
|
+
|
|
5345
|
+
await subagentPersistence?.finish({ threadId, runId })
|
|
5346
|
+
yield withChildUsage(
|
|
5347
|
+
{
|
|
5348
|
+
type: EventType.RUN_FINISHED,
|
|
5349
|
+
threadId,
|
|
5350
|
+
runId,
|
|
5351
|
+
timestamp: Date.now(),
|
|
5352
|
+
},
|
|
5353
|
+
sink,
|
|
5354
|
+
)
|
|
5355
|
+
} catch (error) {
|
|
5356
|
+
await subagentPersistence?.abort({ threadId, runId, error })
|
|
5357
|
+
throw error
|
|
5358
|
+
}
|
|
5359
|
+
}
|
|
5360
|
+
|
|
5361
|
+
/**
|
|
5362
|
+
* The plan from a resumed turn, or undefined when it is absent. A plan that
|
|
5363
|
+
* names agents this chat does not have is stale: re-routing would strand the
|
|
5364
|
+
* suspended child that owns the answers, so fail the resume instead.
|
|
5365
|
+
*/
|
|
5366
|
+
function savedPlan(
|
|
5367
|
+
plan: unknown,
|
|
5368
|
+
agents: ReadonlyArray<DefinedAgent>,
|
|
5369
|
+
run: {
|
|
5370
|
+
threadId: string
|
|
5371
|
+
interruptedRunId: string
|
|
5372
|
+
interruptIds: ReadonlyArray<string>
|
|
5373
|
+
},
|
|
5374
|
+
): { steps: ReadonlyArray<SubagentStep> } | undefined {
|
|
5375
|
+
if (typeof plan !== 'object' || plan === null || !('steps' in plan)) {
|
|
5376
|
+
return undefined
|
|
5377
|
+
}
|
|
5378
|
+
try {
|
|
5379
|
+
// The plan comes back from the client. Check it like a router pick.
|
|
5380
|
+
return normalizeRouterPick(plan as SubagentStepsPlan, agents)
|
|
5381
|
+
} catch (error) {
|
|
5382
|
+
throw new InterruptResumeValidationError([
|
|
5383
|
+
{
|
|
5384
|
+
scope: 'batch',
|
|
5385
|
+
threadId: run.threadId,
|
|
5386
|
+
interruptedRunId: run.interruptedRunId,
|
|
5387
|
+
generation: 0,
|
|
5388
|
+
interruptIds: run.interruptIds,
|
|
5389
|
+
code: 'stale',
|
|
5390
|
+
message: `The saved subagent plan does not match the agents of this chat. ${
|
|
5391
|
+
error instanceof Error ? error.message : String(error)
|
|
5392
|
+
}`,
|
|
5393
|
+
source: 'server',
|
|
5394
|
+
retryable: false,
|
|
5395
|
+
},
|
|
5396
|
+
])
|
|
5397
|
+
}
|
|
5398
|
+
}
|
|
5399
|
+
|
|
5400
|
+
/** Put the router plan on a direct child's SUBAGENT_STARTED metadata. */
|
|
5401
|
+
function withPlan(
|
|
5402
|
+
chunk: StreamChunk,
|
|
5403
|
+
plan: { steps: ReadonlyArray<SubagentStep> },
|
|
5404
|
+
): StreamChunk {
|
|
5405
|
+
if (
|
|
5406
|
+
chunk.type !== EventType.SUBAGENT_STARTED ||
|
|
5407
|
+
chunk.parentSubagentRunId !== undefined
|
|
5408
|
+
) {
|
|
5409
|
+
return chunk
|
|
5410
|
+
}
|
|
5411
|
+
return withTanstackMetadata(chunk, { [SUBAGENT_PLAN_KEY]: plan })
|
|
5412
|
+
}
|
|
5413
|
+
|
|
5414
|
+
function readRoutedSubagentPersistence(
|
|
5415
|
+
middleware:
|
|
5416
|
+
| ReadonlyArray<{
|
|
5417
|
+
routedSubagentPersistence?: ChatMiddleware['routedSubagentPersistence']
|
|
5418
|
+
}>
|
|
5419
|
+
| undefined,
|
|
5420
|
+
) {
|
|
5421
|
+
for (const item of middleware ?? []) {
|
|
5422
|
+
if (item.routedSubagentPersistence) return item.routedSubagentPersistence
|
|
5423
|
+
}
|
|
5424
|
+
return undefined
|
|
5425
|
+
}
|
|
5426
|
+
|
|
4909
5427
|
/**
|
|
4910
5428
|
* Run non-streaming text - collects all content and returns as a string.
|
|
4911
5429
|
* Runs the full agentic loop (if tools are provided) but returns collected text.
|