@tanstack/ai 0.57.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 +345 -19
- 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 +50 -3
- 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 +56 -7
- 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 +7 -4
- package/dist/esm/middlewares/content-guard.js.map +1 -1
- package/dist/esm/stream-to-response.js +12 -6
- package/dist/esm/stream-to-response.js.map +1 -1
- package/dist/esm/strip-to-spec-middleware.js +2 -1
- package/dist/esm/strip-to-spec-middleware.js.map +1 -1
- package/dist/esm/types.d.ts +128 -89
- 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/durability-batch.d.ts +8 -0
- package/dist/esm/utilities/durability-batch.js +45 -0
- package/dist/esm/utilities/durability-batch.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/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 +4 -4
- 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 +540 -16
- 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 +55 -2
- package/src/activities/chat/stream/processor.ts +428 -8
- package/src/activities/chat/tools/tool-calls.ts +92 -16
- 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/stream-to-response.ts +16 -6
- package/src/strip-to-spec-middleware.ts +2 -1
- package/src/types.ts +177 -95
- 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/durability-batch.ts +48 -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
|
@@ -6,6 +6,7 @@ import { publishRunDisconnectHandler } from "../../delivery-disconnect.js";
|
|
|
6
6
|
import { EventType } from "../../types.js";
|
|
7
7
|
import { rebuildTokenUsage } from "../../utilities/ag-ui-usage.js";
|
|
8
8
|
import { tanstackMetadata, withTanstackMetadata } from "../../utilities/merge-metadata.js";
|
|
9
|
+
import { withDurabilityBatchHint } from "../../utilities/durability-batch.js";
|
|
9
10
|
import { normalizeStreamChunk } from "../../utilities/normalize-stream-chunk.js";
|
|
10
11
|
import { resolveDebugOption } from "../../logger/resolve.js";
|
|
11
12
|
import { streamToText } from "../../stream-to-response.js";
|
|
@@ -17,6 +18,7 @@ import { INTERRUPT_BINDING_METADATA_KEY, InterruptResumeValidationError, readInt
|
|
|
17
18
|
import { INTERRUPT_PAYLOAD_METADATA_KEY, createInterruptBinding, rehydrateInterruptRequest } from "../../interrupt-definition.js";
|
|
18
19
|
import { readGenericInterruptContinuation } from "../../generic-interrupt-continuation.js";
|
|
19
20
|
import { normalizeToolResult } from "../../utilities/tool-result.js";
|
|
21
|
+
import { subagentHostMessageId } from "../../utilities/subagent-wire.js";
|
|
20
22
|
import { isProviderExecutedToolCall } from "../../utilities/provider-executed.js";
|
|
21
23
|
import { appendUiResourceToModelMessages, convertMessagesToModelMessages, generateMessageId, modelMessagesToUIMessages, safeJsonStringify, uiResourcePartFromCustomValue } from "./messages.js";
|
|
22
24
|
import { uiMessagesToWire } from "../../utilities/ag-ui-wire.js";
|
|
@@ -24,6 +26,8 @@ import { restorePublicUsage } from "../../utilities/restore-inbound-chunk.js";
|
|
|
24
26
|
import { LazyToolManager } from "./tools/lazy-tool-manager.js";
|
|
25
27
|
import { assertUniqueToolNames } from "./tools/unique-tool-names.js";
|
|
26
28
|
import { MiddlewareAbortError, ToolCallManager, executeToolCalls } from "./tools/tool-calls.js";
|
|
29
|
+
import { collectNamedText, createSubagentSink, createSyntheticSubagentTools, normalizeRouterPick, rebindInterrupts, spawnNamedAgents, subagentCallMessages, withChildUsage } from "./agents/spawn.js";
|
|
30
|
+
import { SUBAGENT_PLAN_KEY, readSubagentTurn } from "./agents/turn.js";
|
|
27
31
|
import { maxIterations } from "./agent-loop-strategies.js";
|
|
28
32
|
import { MiddlewareRunner } from "./middleware/compose.js";
|
|
29
33
|
import { provideSandboxRuntime } from "./middleware/sandbox-runtime.js";
|
|
@@ -44,6 +48,14 @@ import { undoNullWidening } from "@tanstack/ai-utils";
|
|
|
44
48
|
/** The adapter kind this activity handles */
|
|
45
49
|
var kind = "text";
|
|
46
50
|
var interruptBindingMetadataKey = INTERRUPT_BINDING_METADATA_KEY;
|
|
51
|
+
/** Resume entries a subagent tool call owns. The parent run skips them. */
|
|
52
|
+
var CHILD_RESUME_IDS = Symbol("tanstack.ai.childResumeIds");
|
|
53
|
+
function assertNoFileSources(adapterName, messages) {
|
|
54
|
+
for (const message of messages) {
|
|
55
|
+
if (!Array.isArray(message.content)) continue;
|
|
56
|
+
for (const part of message.content) if ("source" in part && part.source.type === "file") throw new Error(`${adapterName} does not support provider file-handle sources ({ type: 'file' }). Pass a data or url source.`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
47
59
|
function isInterruptSubmissionError(value) {
|
|
48
60
|
if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
|
|
49
61
|
if (!("scope" in value) || !("code" in value) || !("message" in value) || !("source" in value) || !("retryable" in value) || !("threadId" in value) || !("interruptedRunId" in value) || !("generation" in value) || typeof value.code !== "string" || typeof value.message !== "string" || typeof value.retryable !== "boolean" || typeof value.threadId !== "string" || typeof value.interruptedRunId !== "string" || typeof value.generation !== "number") return false;
|
|
@@ -154,6 +166,7 @@ var TextEngine = class {
|
|
|
154
166
|
threadId;
|
|
155
167
|
runIdOverride;
|
|
156
168
|
parentRunIdOverride;
|
|
169
|
+
subagentRunIdOverride;
|
|
157
170
|
middlewareRunner;
|
|
158
171
|
middlewareCtx;
|
|
159
172
|
sandboxFileQueue = [];
|
|
@@ -209,6 +222,7 @@ var TextEngine = class {
|
|
|
209
222
|
this.threadId = config.params.threadId || config.params.conversationId || this.createId("thread");
|
|
210
223
|
this.runIdOverride = config.params.runId;
|
|
211
224
|
this.parentRunIdOverride = config.params.parentRunId;
|
|
225
|
+
this.subagentRunIdOverride = config.params.subagentRunId;
|
|
212
226
|
const allMiddleware = [devtoolsMiddleware(), ...config.middleware || []];
|
|
213
227
|
this.middlewareRunner = new MiddlewareRunner(allMiddleware, logger);
|
|
214
228
|
this.middlewareAbortController = new AbortController();
|
|
@@ -218,6 +232,7 @@ var TextEngine = class {
|
|
|
218
232
|
streamId: this.streamId,
|
|
219
233
|
runId: this.runIdOverride ?? this.requestId,
|
|
220
234
|
parentRunId: this.parentRunIdOverride,
|
|
235
|
+
subagentRunId: this.subagentRunIdOverride,
|
|
221
236
|
threadId: this.threadId,
|
|
222
237
|
conversationId: this.threadId,
|
|
223
238
|
phase: "init",
|
|
@@ -228,8 +243,8 @@ var TextEngine = class {
|
|
|
228
243
|
this.abortReason = reason;
|
|
229
244
|
this.middlewareAbortController?.abort(reason);
|
|
230
245
|
},
|
|
231
|
-
emitCustomEvent: (name, value) => {
|
|
232
|
-
this.middlewareCustomQueue.push(this.createCustomEventChunk(name, value));
|
|
246
|
+
emitCustomEvent: (name, value, options) => {
|
|
247
|
+
this.middlewareCustomQueue.push(this.createCustomEventChunk(name, value, options));
|
|
233
248
|
const waiters = this.middlewareCustomWaiters;
|
|
234
249
|
this.middlewareCustomWaiters = [];
|
|
235
250
|
for (const waiter of waiters) waiter();
|
|
@@ -516,6 +531,7 @@ var TextEngine = class {
|
|
|
516
531
|
const { approvals } = this.collectClientState();
|
|
517
532
|
const adapterApprovals = /* @__PURE__ */ new Map();
|
|
518
533
|
for (const [approvalId, resolution] of approvals) adapterApprovals.set(approvalId, typeof resolution === "boolean" ? resolution : resolution.approved);
|
|
534
|
+
assertNoFileSources(this.adapter.name, this.messages);
|
|
519
535
|
for await (const raw of this.adapter.chatStream({
|
|
520
536
|
model: this.params.model,
|
|
521
537
|
messages: this.providerMessages,
|
|
@@ -796,7 +812,7 @@ var TextEngine = class {
|
|
|
796
812
|
return "wait";
|
|
797
813
|
}
|
|
798
814
|
const { approvals, clientToolResults } = this.collectClientState();
|
|
799
|
-
const generator = executeToolCalls(executablePendingCalls, this.resolveExecutableTools(executablePendingCalls), approvals, clientToolResults, (eventName, data) => this.createCustomEventChunk(eventName, data), {
|
|
815
|
+
const generator = executeToolCalls(executablePendingCalls, this.resolveExecutableTools(executablePendingCalls), approvals, clientToolResults, (eventName, data, options) => this.createCustomEventChunk(eventName, data, options), {
|
|
800
816
|
onBeforeToolCall: async (toolCall, tool, args) => {
|
|
801
817
|
this.logger.tools(`phase=before name=${toolCall.function.name}`, {
|
|
802
818
|
name: toolCall.function.name,
|
|
@@ -834,10 +850,10 @@ var TextEngine = class {
|
|
|
834
850
|
needsApproval: executionResult.needsApproval,
|
|
835
851
|
needsClientExecution: executionResult.needsClientExecution
|
|
836
852
|
}));
|
|
837
|
-
if (executionResult.needsApproval.length > 0 || executionResult.needsClientExecution.length > 0) {
|
|
853
|
+
if (executionResult.needsApproval.length > 0 || executionResult.needsClientExecution.length > 0 || executionResult.subagentInterrupts.length > 0) {
|
|
838
854
|
this.discardDeferredToolCallRunFinishedChunks();
|
|
839
855
|
if (allResults.length > 0) for (const chunk of this.buildToolResultChunks(allResults, finishEvent)) yield* this.pipeThroughMiddleware(chunk);
|
|
840
|
-
const emitted = yield* this.emitActionableInterruptBoundary(finishEvent, executionResult.needsApproval, executionResult.needsClientExecution);
|
|
856
|
+
const emitted = yield* this.emitActionableInterruptBoundary(finishEvent, executionResult.needsApproval, executionResult.needsClientExecution, [], executionResult.subagentInterrupts);
|
|
841
857
|
this.setToolPhase(emitted ? "wait" : "stop");
|
|
842
858
|
return emitted ? "wait" : "stop";
|
|
843
859
|
}
|
|
@@ -888,7 +904,7 @@ var TextEngine = class {
|
|
|
888
904
|
return;
|
|
889
905
|
}
|
|
890
906
|
const { approvals, clientToolResults } = this.collectClientState();
|
|
891
|
-
const generator = executeToolCalls(executableToolCalls, this.resolveExecutableTools(executableToolCalls), approvals, clientToolResults, (eventName, data) => this.createCustomEventChunk(eventName, data), {
|
|
907
|
+
const generator = executeToolCalls(executableToolCalls, this.resolveExecutableTools(executableToolCalls), approvals, clientToolResults, (eventName, data, options) => this.createCustomEventChunk(eventName, data, options), {
|
|
892
908
|
onBeforeToolCall: async (toolCall, tool, args) => {
|
|
893
909
|
this.logger.tools(`phase=before name=${toolCall.function.name}`, {
|
|
894
910
|
name: toolCall.function.name,
|
|
@@ -935,9 +951,9 @@ var TextEngine = class {
|
|
|
935
951
|
this.setToolPhase("wait");
|
|
936
952
|
return;
|
|
937
953
|
}
|
|
938
|
-
if (executionResult.needsApproval.length > 0 || executionResult.needsClientExecution.length > 0) {
|
|
954
|
+
if (executionResult.needsApproval.length > 0 || executionResult.needsClientExecution.length > 0 || executionResult.subagentInterrupts.length > 0) {
|
|
939
955
|
if (allResults.length > 0) for (const chunk of afterToolBoundaryChunks) yield* this.pipeThroughMiddleware(chunk);
|
|
940
|
-
const emitted = yield* this.emitActionableInterruptBoundary(finishEvent, executionResult.needsApproval, executionResult.needsClientExecution);
|
|
956
|
+
const emitted = yield* this.emitActionableInterruptBoundary(finishEvent, executionResult.needsApproval, executionResult.needsClientExecution, [], executionResult.subagentInterrupts);
|
|
941
957
|
this.setToolPhase(emitted ? "wait" : "stop");
|
|
942
958
|
return;
|
|
943
959
|
}
|
|
@@ -1109,7 +1125,7 @@ var TextEngine = class {
|
|
|
1109
1125
|
genericInterruptId() {
|
|
1110
1126
|
return this.createId("interrupt");
|
|
1111
1127
|
}
|
|
1112
|
-
buildActionableInterrupts(approvals, clientRequests, genericRequests = [], genericInterruptIds = []) {
|
|
1128
|
+
buildActionableInterrupts(approvals, clientRequests, genericRequests = [], genericInterruptIds = [], childInterrupts = []) {
|
|
1113
1129
|
const interrupts = [];
|
|
1114
1130
|
for (const approval of approvals) {
|
|
1115
1131
|
const tool = this.tools.find((candidate) => candidate.name === approval.toolName);
|
|
@@ -1190,6 +1206,7 @@ var TextEngine = class {
|
|
|
1190
1206
|
}
|
|
1191
1207
|
});
|
|
1192
1208
|
}
|
|
1209
|
+
interrupts.push(...childInterrupts);
|
|
1193
1210
|
const ids = /* @__PURE__ */ new Set();
|
|
1194
1211
|
for (const interrupt of interrupts) {
|
|
1195
1212
|
if (ids.has(interrupt.id)) throw new Error(`Duplicate interrupt id in final batch: ${interrupt.id}`);
|
|
@@ -1197,13 +1214,13 @@ var TextEngine = class {
|
|
|
1197
1214
|
}
|
|
1198
1215
|
return interrupts;
|
|
1199
1216
|
}
|
|
1200
|
-
buildInterruptFinishedChunk(finishEvent, approvals, clientRequests, genericRequests = [], genericInterruptIds) {
|
|
1217
|
+
buildInterruptFinishedChunk(finishEvent, approvals, clientRequests, genericRequests = [], genericInterruptIds, childInterrupts) {
|
|
1201
1218
|
return {
|
|
1202
1219
|
...finishEvent,
|
|
1203
1220
|
timestamp: Date.now(),
|
|
1204
1221
|
outcome: {
|
|
1205
1222
|
type: "interrupt",
|
|
1206
|
-
interrupts: this.buildActionableInterrupts(approvals, clientRequests, genericRequests, genericInterruptIds)
|
|
1223
|
+
interrupts: this.buildActionableInterrupts(approvals, clientRequests, genericRequests, genericInterruptIds, childInterrupts)
|
|
1207
1224
|
}
|
|
1208
1225
|
};
|
|
1209
1226
|
}
|
|
@@ -1297,10 +1314,10 @@ var TextEngine = class {
|
|
|
1297
1314
|
};
|
|
1298
1315
|
yield* this.pipeThroughMiddleware(this.buildInterruptRunErrorChunk(error));
|
|
1299
1316
|
}
|
|
1300
|
-
async *emitActionableInterruptBoundary(finishEvent, approvals, clientRequests, genericRequests = []) {
|
|
1317
|
+
async *emitActionableInterruptBoundary(finishEvent, approvals, clientRequests, genericRequests = [], childInterrupts = []) {
|
|
1301
1318
|
yield* this.emitSyntheticRunStarted(finishEvent);
|
|
1302
1319
|
const genericInterruptIds = genericRequests.map(() => this.genericInterruptId());
|
|
1303
|
-
const terminal = this.completeEphemeralInterruptBindings(this.buildInterruptFinishedChunk(finishEvent, approvals, clientRequests, genericRequests, genericInterruptIds));
|
|
1320
|
+
const terminal = this.completeEphemeralInterruptBindings(this.buildInterruptFinishedChunk(finishEvent, approvals, clientRequests, genericRequests, genericInterruptIds, childInterrupts));
|
|
1304
1321
|
let terminalOutputs;
|
|
1305
1322
|
try {
|
|
1306
1323
|
terminalOutputs = [...this.emitPublicChunks(await this.middlewareRunner.runOnChunk(this.middlewareCtx, terminal))];
|
|
@@ -1665,6 +1682,7 @@ var TextEngine = class {
|
|
|
1665
1682
|
tools: baseConfig.tools
|
|
1666
1683
|
}));
|
|
1667
1684
|
this.applyMiddlewareConfig(postOnConfig);
|
|
1685
|
+
assertNoFileSources(this.adapter.name, this.messages);
|
|
1668
1686
|
const structuredCallOptions = {
|
|
1669
1687
|
chatOptions: {
|
|
1670
1688
|
model: this.params.model,
|
|
@@ -1906,7 +1924,16 @@ var TextEngine = class {
|
|
|
1906
1924
|
modelOptions: this.params.modelOptions
|
|
1907
1925
|
};
|
|
1908
1926
|
}
|
|
1909
|
-
|
|
1927
|
+
/** Resume entries this run answers itself. A subagent tool owns the rest. */
|
|
1928
|
+
ownResume(resume) {
|
|
1929
|
+
const childIds = this.params[CHILD_RESUME_IDS];
|
|
1930
|
+
return childIds ? resume?.filter((entry) => !childIds.has(entry.interruptId)) : resume;
|
|
1931
|
+
}
|
|
1932
|
+
async applyEphemeralInterruptResume(middlewareConfig) {
|
|
1933
|
+
const config = {
|
|
1934
|
+
...middlewareConfig,
|
|
1935
|
+
resume: this.ownResume(middlewareConfig.resume)
|
|
1936
|
+
};
|
|
1910
1937
|
if ((config.resume?.length ?? 0) === 0) return;
|
|
1911
1938
|
const interruptedRunId = this.parentRunIdOverride;
|
|
1912
1939
|
if (!interruptedRunId) throw new InterruptResumeValidationError([{
|
|
@@ -2025,7 +2052,7 @@ var TextEngine = class {
|
|
|
2025
2052
|
const pending = [];
|
|
2026
2053
|
const ids = /* @__PURE__ */ new Set();
|
|
2027
2054
|
const batchIndexes = /* @__PURE__ */ new Set();
|
|
2028
|
-
for (const resumeItem of this.params.resume ?? []) {
|
|
2055
|
+
for (const resumeItem of this.ownResume(this.params.resume) ?? []) {
|
|
2029
2056
|
const parsed = readGenericInterruptContinuation(resumeItem.metadata);
|
|
2030
2057
|
if (parsed.status === "absent") continue;
|
|
2031
2058
|
if (parsed.status === "invalid") return fail(parsed.message);
|
|
@@ -2220,13 +2247,14 @@ var TextEngine = class {
|
|
|
2220
2247
|
pending = generator.next();
|
|
2221
2248
|
}
|
|
2222
2249
|
}
|
|
2223
|
-
createCustomEventChunk(eventName, value) {
|
|
2224
|
-
|
|
2250
|
+
createCustomEventChunk(eventName, value, options) {
|
|
2251
|
+
const chunk = {
|
|
2225
2252
|
type: EventType.CUSTOM,
|
|
2226
2253
|
timestamp: Date.now(),
|
|
2227
2254
|
name: eventName,
|
|
2228
2255
|
value
|
|
2229
2256
|
};
|
|
2257
|
+
return options?.batch ? withDurabilityBatchHint(chunk) : chunk;
|
|
2230
2258
|
}
|
|
2231
2259
|
createId(prefix) {
|
|
2232
2260
|
return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
|
@@ -2298,6 +2326,7 @@ function chat(options) {
|
|
|
2298
2326
|
validateCapabilities(readRuntimeMiddleware(options.middleware) ?? [], options.adapter);
|
|
2299
2327
|
if (options.tools) assertUniqueToolNames(options.tools);
|
|
2300
2328
|
const { outputSchema, stream } = options;
|
|
2329
|
+
if (outputSchema && (options.subagents?.agents.length ?? 0) > 0) throw new Error("chat() does not support subagents together with outputSchema. Put outputSchema on a child chat() instead.");
|
|
2301
2330
|
if (outputSchema && stream === true) return runStreamingStructuredOutput(toRuntimeTextActivityOptions(options, {
|
|
2302
2331
|
outputSchema,
|
|
2303
2332
|
stream: true
|
|
@@ -2366,8 +2395,8 @@ function runStreamingText(options) {
|
|
|
2366
2395
|
publishDeliverySeams(stream, engineRef);
|
|
2367
2396
|
return stream;
|
|
2368
2397
|
}
|
|
2369
|
-
async function*
|
|
2370
|
-
const { adapter, middleware, context, debug, mcp, ...textOptions } = options;
|
|
2398
|
+
async function* runChatEngine(options, engineRef) {
|
|
2399
|
+
const { adapter, middleware, context, debug, mcp, subagents: _subagents, ...textOptions } = options;
|
|
2371
2400
|
const model = adapter.model;
|
|
2372
2401
|
const logger = resolveDebugOption(debug);
|
|
2373
2402
|
const mcpManager = MCPManager.from(mcp);
|
|
@@ -2390,6 +2419,303 @@ async function* streamTextChunks(options, engineRef) {
|
|
|
2390
2419
|
await mcpManager.dispose();
|
|
2391
2420
|
}
|
|
2392
2421
|
}
|
|
2422
|
+
async function* streamTextChunks(options, engineRef) {
|
|
2423
|
+
const bag = options.subagents;
|
|
2424
|
+
const agents = bag?.agents ?? [];
|
|
2425
|
+
if (bag && agents.length > 0 && bag.router) {
|
|
2426
|
+
yield* runRoutedSubagents(options, engineRef);
|
|
2427
|
+
return;
|
|
2428
|
+
}
|
|
2429
|
+
if (bag && agents.length > 0) {
|
|
2430
|
+
const threadId = options.threadId ?? `thread-${Date.now()}`;
|
|
2431
|
+
const runId = options.runId ?? `run-${Date.now()}`;
|
|
2432
|
+
const messages = options.messages ?? [];
|
|
2433
|
+
const turn = readSubagentTurn(messages, options.resume);
|
|
2434
|
+
const sink = createSubagentSink();
|
|
2435
|
+
const calls = subagentCallMessages(new Set(bag.agents.map((agent) => agent.name)));
|
|
2436
|
+
const synthetic = createSyntheticSubagentTools(bag, {
|
|
2437
|
+
messages: turn?.before ?? messages,
|
|
2438
|
+
messagesFor: calls.messagesFor,
|
|
2439
|
+
threadId,
|
|
2440
|
+
runId,
|
|
2441
|
+
...options.parentRunId !== void 0 && { interruptedRunId: options.parentRunId },
|
|
2442
|
+
...options.abortController && { abortSignal: options.abortController.signal },
|
|
2443
|
+
...turn && { turn },
|
|
2444
|
+
sink
|
|
2445
|
+
});
|
|
2446
|
+
yield* streamWithChildUsage(runChatEngine({
|
|
2447
|
+
...options,
|
|
2448
|
+
threadId,
|
|
2449
|
+
runId,
|
|
2450
|
+
...turn && { [CHILD_RESUME_IDS]: new Set(turn.children.flatMap((child) => child.resume.map((entry) => entry.interruptId))) },
|
|
2451
|
+
tools: [...options.tools ?? [], ...synthetic],
|
|
2452
|
+
middleware: [...options.middleware ?? [], calls.middleware]
|
|
2453
|
+
}, engineRef), sink);
|
|
2454
|
+
return;
|
|
2455
|
+
}
|
|
2456
|
+
yield* runChatEngine(options, engineRef);
|
|
2457
|
+
}
|
|
2458
|
+
/** Add child usage to the next RUN_FINISHED of the parent. */
|
|
2459
|
+
async function* streamWithChildUsage(stream, sink) {
|
|
2460
|
+
for await (const chunk of stream) yield chunk.type === EventType.RUN_FINISHED ? withChildUsage(chunk, sink) : chunk;
|
|
2461
|
+
}
|
|
2462
|
+
async function* runRoutedSubagents(options, engineRef) {
|
|
2463
|
+
const bag = options.subagents;
|
|
2464
|
+
if (!bag?.router) {
|
|
2465
|
+
yield* runChatEngine(options, engineRef);
|
|
2466
|
+
return;
|
|
2467
|
+
}
|
|
2468
|
+
const threadId = options.threadId ?? `thread-${Date.now()}`;
|
|
2469
|
+
const runId = options.runId ?? `run-${Date.now()}`;
|
|
2470
|
+
const messages = options.messages ?? [];
|
|
2471
|
+
const turn = readSubagentTurn(messages, options.resume);
|
|
2472
|
+
if (!turn && (options.resume?.length ?? 0) > 0) {
|
|
2473
|
+
yield* runChatEngine({
|
|
2474
|
+
...options,
|
|
2475
|
+
threadId,
|
|
2476
|
+
runId,
|
|
2477
|
+
subagents: void 0
|
|
2478
|
+
}, engineRef);
|
|
2479
|
+
return;
|
|
2480
|
+
}
|
|
2481
|
+
const abortSignal = options.abortController?.signal;
|
|
2482
|
+
const turnMessages = turn?.before ?? messages;
|
|
2483
|
+
const subagentPersistence = readRoutedSubagentPersistence(options.middleware);
|
|
2484
|
+
await subagentPersistence?.start({
|
|
2485
|
+
threadId,
|
|
2486
|
+
runId,
|
|
2487
|
+
messages,
|
|
2488
|
+
...options.resume && { resume: options.resume }
|
|
2489
|
+
});
|
|
2490
|
+
try {
|
|
2491
|
+
const plan = savedPlan(turn?.plan, bag.agents, {
|
|
2492
|
+
threadId,
|
|
2493
|
+
interruptedRunId: options.parentRunId ?? runId,
|
|
2494
|
+
interruptIds: options.resume?.map((entry) => entry.interruptId) ?? []
|
|
2495
|
+
}) ?? normalizeRouterPick(await bag.router({
|
|
2496
|
+
messages: turnMessages,
|
|
2497
|
+
agents: bag.agents,
|
|
2498
|
+
...abortSignal && { abortSignal }
|
|
2499
|
+
}), bag.agents);
|
|
2500
|
+
if (abortSignal?.aborted) {
|
|
2501
|
+
const error = /* @__PURE__ */ new Error("Aborted");
|
|
2502
|
+
error.name = "AbortError";
|
|
2503
|
+
await subagentPersistence?.abort({
|
|
2504
|
+
threadId,
|
|
2505
|
+
runId,
|
|
2506
|
+
error
|
|
2507
|
+
});
|
|
2508
|
+
return;
|
|
2509
|
+
}
|
|
2510
|
+
const onlyStep = plan.steps.length === 1 ? plan.steps[0] : void 0;
|
|
2511
|
+
if (onlyStep?.names.length === 1 && onlyStep.names[0] === "main") {
|
|
2512
|
+
if (turn) await subagentPersistence?.finish({
|
|
2513
|
+
threadId,
|
|
2514
|
+
runId
|
|
2515
|
+
});
|
|
2516
|
+
yield* runChatEngine({
|
|
2517
|
+
...options,
|
|
2518
|
+
threadId,
|
|
2519
|
+
runId,
|
|
2520
|
+
subagents: void 0,
|
|
2521
|
+
...turn && { resume: turn.rest }
|
|
2522
|
+
}, engineRef);
|
|
2523
|
+
return;
|
|
2524
|
+
}
|
|
2525
|
+
yield {
|
|
2526
|
+
type: EventType.RUN_STARTED,
|
|
2527
|
+
threadId,
|
|
2528
|
+
runId,
|
|
2529
|
+
timestamp: Date.now()
|
|
2530
|
+
};
|
|
2531
|
+
const earlier = [...turn?.children ?? []];
|
|
2532
|
+
const takeEarlier = (name) => {
|
|
2533
|
+
const index = earlier.findIndex((child) => child.name === name);
|
|
2534
|
+
return index === -1 ? void 0 : earlier.splice(index, 1)[0];
|
|
2535
|
+
};
|
|
2536
|
+
const sink = createSubagentSink();
|
|
2537
|
+
const stepTexts = [];
|
|
2538
|
+
let stepMessages = turnMessages;
|
|
2539
|
+
let failure;
|
|
2540
|
+
for (const step of plan.steps) {
|
|
2541
|
+
const entries = [];
|
|
2542
|
+
const textByName = /* @__PURE__ */ new Map();
|
|
2543
|
+
for (const name of step.names) {
|
|
2544
|
+
const prior = takeEarlier(name);
|
|
2545
|
+
if (prior?.status === "finished") {
|
|
2546
|
+
textByName.set(name, prior.text);
|
|
2547
|
+
continue;
|
|
2548
|
+
}
|
|
2549
|
+
entries.push(prior?.status === "suspended" ? {
|
|
2550
|
+
name,
|
|
2551
|
+
resume: {
|
|
2552
|
+
subagentRunId: prior.subagentRunId,
|
|
2553
|
+
messages: prior.messages,
|
|
2554
|
+
entries: prior.resume,
|
|
2555
|
+
text: prior.text
|
|
2556
|
+
}
|
|
2557
|
+
} : { name });
|
|
2558
|
+
}
|
|
2559
|
+
const stepChunks = [];
|
|
2560
|
+
if (entries.length > 0) for await (const chunk of spawnNamedAgents(entries, {
|
|
2561
|
+
...bag,
|
|
2562
|
+
order: step.order ?? bag.order
|
|
2563
|
+
}, {
|
|
2564
|
+
messages: stepMessages,
|
|
2565
|
+
...abortSignal && { abortSignal },
|
|
2566
|
+
threadId,
|
|
2567
|
+
parentRunId: runId,
|
|
2568
|
+
...options.parentRunId !== void 0 && { interruptedRunId: options.parentRunId }
|
|
2569
|
+
}, sink)) {
|
|
2570
|
+
const tagged = withPlan(chunk, plan);
|
|
2571
|
+
stepChunks.push(tagged);
|
|
2572
|
+
await subagentPersistence?.chunk({
|
|
2573
|
+
threadId,
|
|
2574
|
+
runId,
|
|
2575
|
+
chunk: tagged
|
|
2576
|
+
});
|
|
2577
|
+
yield tagged;
|
|
2578
|
+
}
|
|
2579
|
+
failure = stepChunks.find((chunk) => chunk.type === EventType.SUBAGENT_ERROR);
|
|
2580
|
+
if (failure) break;
|
|
2581
|
+
if (sink.interrupts.length > 0) break;
|
|
2582
|
+
for (const entry of entries) {
|
|
2583
|
+
const text = collectNamedText(stepChunks, [entry.name]);
|
|
2584
|
+
textByName.set(entry.name, [entry.resume?.text, text].filter(Boolean).join("\n\n"));
|
|
2585
|
+
}
|
|
2586
|
+
const text = step.names.map((name) => textByName.get(name)?.trim() ?? "").filter((block) => block !== "").join("\n\n");
|
|
2587
|
+
if (text) {
|
|
2588
|
+
stepTexts.push(text);
|
|
2589
|
+
stepMessages = [...stepMessages, {
|
|
2590
|
+
role: "assistant",
|
|
2591
|
+
content: text
|
|
2592
|
+
}];
|
|
2593
|
+
}
|
|
2594
|
+
}
|
|
2595
|
+
if (abortSignal?.aborted) {
|
|
2596
|
+
const error = /* @__PURE__ */ new Error("Aborted");
|
|
2597
|
+
error.name = "AbortError";
|
|
2598
|
+
await subagentPersistence?.abort({
|
|
2599
|
+
threadId,
|
|
2600
|
+
runId,
|
|
2601
|
+
error
|
|
2602
|
+
});
|
|
2603
|
+
yield withChildUsage({
|
|
2604
|
+
type: EventType.RUN_FINISHED,
|
|
2605
|
+
threadId,
|
|
2606
|
+
runId,
|
|
2607
|
+
outcome: { type: "cancelled" },
|
|
2608
|
+
timestamp: Date.now()
|
|
2609
|
+
}, sink);
|
|
2610
|
+
return;
|
|
2611
|
+
}
|
|
2612
|
+
if (failure) {
|
|
2613
|
+
const message = failure.message || "A subagent failed";
|
|
2614
|
+
await subagentPersistence?.abort({
|
|
2615
|
+
threadId,
|
|
2616
|
+
runId,
|
|
2617
|
+
error: new Error(message)
|
|
2618
|
+
});
|
|
2619
|
+
yield withChildUsage({
|
|
2620
|
+
type: EventType.RUN_ERROR,
|
|
2621
|
+
threadId,
|
|
2622
|
+
runId,
|
|
2623
|
+
message,
|
|
2624
|
+
...failure.code !== void 0 ? { code: failure.code } : {},
|
|
2625
|
+
timestamp: Date.now()
|
|
2626
|
+
}, sink);
|
|
2627
|
+
return;
|
|
2628
|
+
}
|
|
2629
|
+
if (sink.interrupts.length > 0) {
|
|
2630
|
+
const interrupts = rebindInterrupts(sink.interrupts, runId);
|
|
2631
|
+
await subagentPersistence?.suspend?.({
|
|
2632
|
+
threadId,
|
|
2633
|
+
runId,
|
|
2634
|
+
interrupts
|
|
2635
|
+
});
|
|
2636
|
+
yield withChildUsage({
|
|
2637
|
+
type: EventType.RUN_FINISHED,
|
|
2638
|
+
threadId,
|
|
2639
|
+
runId,
|
|
2640
|
+
outcome: {
|
|
2641
|
+
type: "interrupt",
|
|
2642
|
+
interrupts
|
|
2643
|
+
},
|
|
2644
|
+
timestamp: Date.now()
|
|
2645
|
+
}, sink);
|
|
2646
|
+
return;
|
|
2647
|
+
}
|
|
2648
|
+
if ((bag.strategy ?? "exclusive") === "handoff") {
|
|
2649
|
+
const childText = stepTexts.join("\n\n");
|
|
2650
|
+
await subagentPersistence?.finish({
|
|
2651
|
+
threadId,
|
|
2652
|
+
runId
|
|
2653
|
+
});
|
|
2654
|
+
yield* streamWithChildUsage(runChatEngine({
|
|
2655
|
+
...options,
|
|
2656
|
+
threadId,
|
|
2657
|
+
runId,
|
|
2658
|
+
subagents: void 0,
|
|
2659
|
+
...turn && { resume: turn.rest },
|
|
2660
|
+
messages: [...turnMessages, {
|
|
2661
|
+
id: subagentHostMessageId(runId),
|
|
2662
|
+
role: "assistant",
|
|
2663
|
+
content: childText || "Subagent finished.",
|
|
2664
|
+
metadata: { tanstack: { runId } }
|
|
2665
|
+
}]
|
|
2666
|
+
}, engineRef), sink);
|
|
2667
|
+
return;
|
|
2668
|
+
}
|
|
2669
|
+
await subagentPersistence?.finish({
|
|
2670
|
+
threadId,
|
|
2671
|
+
runId
|
|
2672
|
+
});
|
|
2673
|
+
yield withChildUsage({
|
|
2674
|
+
type: EventType.RUN_FINISHED,
|
|
2675
|
+
threadId,
|
|
2676
|
+
runId,
|
|
2677
|
+
timestamp: Date.now()
|
|
2678
|
+
}, sink);
|
|
2679
|
+
} catch (error) {
|
|
2680
|
+
await subagentPersistence?.abort({
|
|
2681
|
+
threadId,
|
|
2682
|
+
runId,
|
|
2683
|
+
error
|
|
2684
|
+
});
|
|
2685
|
+
throw error;
|
|
2686
|
+
}
|
|
2687
|
+
}
|
|
2688
|
+
/**
|
|
2689
|
+
* The plan from a resumed turn, or undefined when it is absent. A plan that
|
|
2690
|
+
* names agents this chat does not have is stale: re-routing would strand the
|
|
2691
|
+
* suspended child that owns the answers, so fail the resume instead.
|
|
2692
|
+
*/
|
|
2693
|
+
function savedPlan(plan, agents, run) {
|
|
2694
|
+
if (typeof plan !== "object" || plan === null || !("steps" in plan)) return;
|
|
2695
|
+
try {
|
|
2696
|
+
return normalizeRouterPick(plan, agents);
|
|
2697
|
+
} catch (error) {
|
|
2698
|
+
throw new InterruptResumeValidationError([{
|
|
2699
|
+
scope: "batch",
|
|
2700
|
+
threadId: run.threadId,
|
|
2701
|
+
interruptedRunId: run.interruptedRunId,
|
|
2702
|
+
generation: 0,
|
|
2703
|
+
interruptIds: run.interruptIds,
|
|
2704
|
+
code: "stale",
|
|
2705
|
+
message: `The saved subagent plan does not match the agents of this chat. ${error instanceof Error ? error.message : String(error)}`,
|
|
2706
|
+
source: "server",
|
|
2707
|
+
retryable: false
|
|
2708
|
+
}]);
|
|
2709
|
+
}
|
|
2710
|
+
}
|
|
2711
|
+
/** Put the router plan on a direct child's SUBAGENT_STARTED metadata. */
|
|
2712
|
+
function withPlan(chunk, plan) {
|
|
2713
|
+
if (chunk.type !== EventType.SUBAGENT_STARTED || chunk.parentSubagentRunId !== void 0) return chunk;
|
|
2714
|
+
return withTanstackMetadata(chunk, { [SUBAGENT_PLAN_KEY]: plan });
|
|
2715
|
+
}
|
|
2716
|
+
function readRoutedSubagentPersistence(middleware) {
|
|
2717
|
+
for (const item of middleware ?? []) if (item.routedSubagentPersistence) return item.routedSubagentPersistence;
|
|
2718
|
+
}
|
|
2393
2719
|
/**
|
|
2394
2720
|
* Run non-streaming text - collects all content and returns as a string.
|
|
2395
2721
|
* Runs the full agentic loop (if tools are provided) but returns collected text.
|