@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
|
@@ -17,14 +17,18 @@ import { hashSchemaInput, normalizeApprovalSchema } from "./tools/approval-schem
|
|
|
17
17
|
import { INTERRUPT_BINDING_METADATA_KEY, InterruptResumeValidationError, readInterruptBinding, readUnopenedInterruptBinding, validateInterruptResumeBatch } from "../../interrupt-resume.js";
|
|
18
18
|
import { INTERRUPT_PAYLOAD_METADATA_KEY, createInterruptBinding, rehydrateInterruptRequest } from "../../interrupt-definition.js";
|
|
19
19
|
import { readGenericInterruptContinuation } from "../../generic-interrupt-continuation.js";
|
|
20
|
-
import { normalizeToolResult } from "../../utilities/tool-result.js";
|
|
21
20
|
import { isProviderExecutedToolCall } from "../../utilities/provider-executed.js";
|
|
21
|
+
import { normalizeToolResult, parseToolOutput, toolResultErrorText } from "../../utilities/tool-result.js";
|
|
22
|
+
import { subagentHostMessageId } from "../../utilities/subagent-wire.js";
|
|
22
23
|
import { appendUiResourceToModelMessages, convertMessagesToModelMessages, generateMessageId, modelMessagesToUIMessages, safeJsonStringify, uiResourcePartFromCustomValue } from "./messages.js";
|
|
23
24
|
import { uiMessagesToWire } from "../../utilities/ag-ui-wire.js";
|
|
24
25
|
import { restorePublicUsage } from "../../utilities/restore-inbound-chunk.js";
|
|
26
|
+
import { assertMessagesFileSourceSupport } from "../../utilities/content-source.js";
|
|
25
27
|
import { LazyToolManager } from "./tools/lazy-tool-manager.js";
|
|
26
28
|
import { assertUniqueToolNames } from "./tools/unique-tool-names.js";
|
|
27
29
|
import { MiddlewareAbortError, ToolCallManager, executeToolCalls } from "./tools/tool-calls.js";
|
|
30
|
+
import { collectNamedText, createSubagentSink, createSyntheticSubagentTools, normalizeRouterPick, rebindInterrupts, spawnNamedAgents, subagentCallMessages, withChildUsage } from "./agents/spawn.js";
|
|
31
|
+
import { SUBAGENT_PLAN_KEY, readSubagentTurn } from "./agents/turn.js";
|
|
28
32
|
import { maxIterations } from "./agent-loop-strategies.js";
|
|
29
33
|
import { MiddlewareRunner } from "./middleware/compose.js";
|
|
30
34
|
import { provideSandboxRuntime } from "./middleware/sandbox-runtime.js";
|
|
@@ -45,6 +49,8 @@ import { undoNullWidening } from "@tanstack/ai-utils";
|
|
|
45
49
|
/** The adapter kind this activity handles */
|
|
46
50
|
var kind = "text";
|
|
47
51
|
var interruptBindingMetadataKey = INTERRUPT_BINDING_METADATA_KEY;
|
|
52
|
+
/** Resume entries a subagent tool call owns. The parent run skips them. */
|
|
53
|
+
var CHILD_RESUME_IDS = Symbol("tanstack.ai.childResumeIds");
|
|
48
54
|
function isInterruptSubmissionError(value) {
|
|
49
55
|
if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
|
|
50
56
|
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;
|
|
@@ -132,6 +138,15 @@ var TextEngine = class {
|
|
|
132
138
|
streamIdentityCaptured = false;
|
|
133
139
|
accumulatedContent = "";
|
|
134
140
|
accumulatedThinking = [];
|
|
141
|
+
/**
|
|
142
|
+
* Arrival order of this iteration's thinking steps, text and tool calls.
|
|
143
|
+
* A ModelMessage keeps `thinking` apart from `content`/`toolCalls`, so a
|
|
144
|
+
* provider turn that thinks between provider-executed tools would otherwise
|
|
145
|
+
* be recorded as "all thinking, then text, then tools" and the provider
|
|
146
|
+
* rejects the replay (signed thinking must keep its position). `null` once
|
|
147
|
+
* the order could no longer be tracked (callers fall back to one message).
|
|
148
|
+
*/
|
|
149
|
+
turnParts = [];
|
|
135
150
|
currentThinkingContent = "";
|
|
136
151
|
currentThinkingSignature = "";
|
|
137
152
|
eventOptions;
|
|
@@ -155,6 +170,7 @@ var TextEngine = class {
|
|
|
155
170
|
threadId;
|
|
156
171
|
runIdOverride;
|
|
157
172
|
parentRunIdOverride;
|
|
173
|
+
subagentRunIdOverride;
|
|
158
174
|
middlewareRunner;
|
|
159
175
|
middlewareCtx;
|
|
160
176
|
sandboxFileQueue = [];
|
|
@@ -210,6 +226,7 @@ var TextEngine = class {
|
|
|
210
226
|
this.threadId = config.params.threadId || config.params.conversationId || this.createId("thread");
|
|
211
227
|
this.runIdOverride = config.params.runId;
|
|
212
228
|
this.parentRunIdOverride = config.params.parentRunId;
|
|
229
|
+
this.subagentRunIdOverride = config.params.subagentRunId;
|
|
213
230
|
const allMiddleware = [devtoolsMiddleware(), ...config.middleware || []];
|
|
214
231
|
this.middlewareRunner = new MiddlewareRunner(allMiddleware, logger);
|
|
215
232
|
this.middlewareAbortController = new AbortController();
|
|
@@ -219,6 +236,7 @@ var TextEngine = class {
|
|
|
219
236
|
streamId: this.streamId,
|
|
220
237
|
runId: this.runIdOverride ?? this.requestId,
|
|
221
238
|
parentRunId: this.parentRunIdOverride,
|
|
239
|
+
subagentRunId: this.subagentRunIdOverride,
|
|
222
240
|
threadId: this.threadId,
|
|
223
241
|
conversationId: this.threadId,
|
|
224
242
|
phase: "init",
|
|
@@ -487,6 +505,7 @@ var TextEngine = class {
|
|
|
487
505
|
this.streamIdentityCaptured = false;
|
|
488
506
|
this.accumulatedContent = "";
|
|
489
507
|
this.accumulatedThinking = [];
|
|
508
|
+
this.turnParts = [];
|
|
490
509
|
this.currentThinkingContent = "";
|
|
491
510
|
this.currentThinkingSignature = "";
|
|
492
511
|
this.finishedEvent = null;
|
|
@@ -517,6 +536,7 @@ var TextEngine = class {
|
|
|
517
536
|
const { approvals } = this.collectClientState();
|
|
518
537
|
const adapterApprovals = /* @__PURE__ */ new Map();
|
|
519
538
|
for (const [approvalId, resolution] of approvals) adapterApprovals.set(approvalId, typeof resolution === "boolean" ? resolution : resolution.approved);
|
|
539
|
+
assertMessagesFileSourceSupport(this.adapter, this.messages);
|
|
520
540
|
for await (const raw of this.adapter.chatStream({
|
|
521
541
|
model: this.params.model,
|
|
522
542
|
messages: this.providerMessages,
|
|
@@ -651,9 +671,23 @@ var TextEngine = class {
|
|
|
651
671
|
}
|
|
652
672
|
handleTextMessageContentEvent(chunk) {
|
|
653
673
|
const extra = chunk;
|
|
674
|
+
const before = this.accumulatedContent;
|
|
654
675
|
if (typeof extra.content === "string" && extra.content !== "") this.accumulatedContent = extra.content;
|
|
655
676
|
else this.accumulatedContent += chunk.delta;
|
|
656
677
|
this.middlewareCtx.accumulatedContent = this.accumulatedContent;
|
|
678
|
+
if (!this.turnParts) return;
|
|
679
|
+
if (!this.accumulatedContent.startsWith(before)) {
|
|
680
|
+
this.turnParts = null;
|
|
681
|
+
return;
|
|
682
|
+
}
|
|
683
|
+
const delta = this.accumulatedContent.slice(before.length);
|
|
684
|
+
if (delta === "") return;
|
|
685
|
+
const last = this.turnParts[this.turnParts.length - 1];
|
|
686
|
+
if (last && last.type === "text") last.content += delta;
|
|
687
|
+
else this.turnParts.push({
|
|
688
|
+
type: "text",
|
|
689
|
+
content: delta
|
|
690
|
+
});
|
|
657
691
|
}
|
|
658
692
|
captureStreamMessageIdentity(messageId) {
|
|
659
693
|
this.currentMessageId = messageId;
|
|
@@ -670,6 +704,11 @@ var TextEngine = class {
|
|
|
670
704
|
handleToolCallStartEvent(chunk) {
|
|
671
705
|
if (typeof chunk.parentMessageId === "string" && chunk.parentMessageId !== "") this.captureStreamMessageIdentity(chunk.parentMessageId);
|
|
672
706
|
this.toolCallManager.addToolCallStartEvent(chunk);
|
|
707
|
+
if (this.turnParts && !this.turnParts.some((part) => part.type === "call" && part.id === chunk.toolCallId)) this.turnParts.push({
|
|
708
|
+
type: "call",
|
|
709
|
+
id: chunk.toolCallId,
|
|
710
|
+
providerExecuted: isProviderExecutedToolCall({ metadata: chunk.metadata })
|
|
711
|
+
});
|
|
673
712
|
const metadata = chunk.metadata;
|
|
674
713
|
const thoughtSignature = metadata != null && typeof metadata === "object" && "thoughtSignature" in metadata && typeof metadata.thoughtSignature === "string" && metadata.thoughtSignature !== "" ? metadata.thoughtSignature : void 0;
|
|
675
714
|
if (thoughtSignature === void 0) return;
|
|
@@ -731,17 +770,42 @@ var TextEngine = class {
|
|
|
731
770
|
content: this.currentThinkingContent,
|
|
732
771
|
...this.currentThinkingSignature && { signature: this.currentThinkingSignature }
|
|
733
772
|
});
|
|
773
|
+
if (this.turnParts) {
|
|
774
|
+
const placeholder = [...this.turnParts].reverse().find((part) => part.type === "thinking" && part.index === -1);
|
|
775
|
+
const index = this.accumulatedThinking.length - 1;
|
|
776
|
+
if (placeholder) placeholder.index = index;
|
|
777
|
+
else this.turnParts.push({
|
|
778
|
+
type: "thinking",
|
|
779
|
+
index
|
|
780
|
+
});
|
|
781
|
+
}
|
|
734
782
|
this.currentThinkingContent = "";
|
|
735
783
|
this.currentThinkingSignature = "";
|
|
736
784
|
}
|
|
737
785
|
}
|
|
786
|
+
/**
|
|
787
|
+
* Record where the current thinking step sits among this turn's parts. A
|
|
788
|
+
* step is finalized only when the next step starts (or the turn ends), by
|
|
789
|
+
* which time later tool calls have already arrived, so the position has to
|
|
790
|
+
* be noted when the step's first content or signature shows up.
|
|
791
|
+
*/
|
|
792
|
+
noteThinkingStepPosition() {
|
|
793
|
+
if (this.turnParts && this.currentThinkingContent === "" && this.currentThinkingSignature === "") this.turnParts.push({
|
|
794
|
+
type: "thinking",
|
|
795
|
+
index: -1
|
|
796
|
+
});
|
|
797
|
+
}
|
|
738
798
|
handleStepStartedEvent() {
|
|
739
799
|
this.finalizeCurrentThinkingStep();
|
|
740
800
|
}
|
|
741
801
|
handleStepFinishedEvent(chunk) {
|
|
742
|
-
if (typeof chunk.signature === "string" && chunk.signature !== "")
|
|
802
|
+
if (typeof chunk.signature === "string" && chunk.signature !== "") {
|
|
803
|
+
this.noteThinkingStepPosition();
|
|
804
|
+
this.currentThinkingSignature = chunk.signature;
|
|
805
|
+
}
|
|
743
806
|
}
|
|
744
807
|
handleReasoningMessageContentEvent(chunk) {
|
|
808
|
+
this.noteThinkingStepPosition();
|
|
745
809
|
this.currentThinkingContent += chunk.delta;
|
|
746
810
|
}
|
|
747
811
|
handleReasoningEncryptedValueEvent(chunk) {
|
|
@@ -753,6 +817,7 @@ var TextEngine = class {
|
|
|
753
817
|
};
|
|
754
818
|
return;
|
|
755
819
|
}
|
|
820
|
+
this.noteThinkingStepPosition();
|
|
756
821
|
this.currentThinkingSignature = chunk.encryptedValue;
|
|
757
822
|
}
|
|
758
823
|
/**
|
|
@@ -835,10 +900,10 @@ var TextEngine = class {
|
|
|
835
900
|
needsApproval: executionResult.needsApproval,
|
|
836
901
|
needsClientExecution: executionResult.needsClientExecution
|
|
837
902
|
}));
|
|
838
|
-
if (executionResult.needsApproval.length > 0 || executionResult.needsClientExecution.length > 0) {
|
|
903
|
+
if (executionResult.needsApproval.length > 0 || executionResult.needsClientExecution.length > 0 || executionResult.subagentInterrupts.length > 0) {
|
|
839
904
|
this.discardDeferredToolCallRunFinishedChunks();
|
|
840
905
|
if (allResults.length > 0) for (const chunk of this.buildToolResultChunks(allResults, finishEvent)) yield* this.pipeThroughMiddleware(chunk);
|
|
841
|
-
const emitted = yield* this.emitActionableInterruptBoundary(finishEvent, executionResult.needsApproval, executionResult.needsClientExecution);
|
|
906
|
+
const emitted = yield* this.emitActionableInterruptBoundary(finishEvent, executionResult.needsApproval, executionResult.needsClientExecution, [], executionResult.subagentInterrupts);
|
|
842
907
|
this.setToolPhase(emitted ? "wait" : "stop");
|
|
843
908
|
return emitted ? "wait" : "stop";
|
|
844
909
|
}
|
|
@@ -936,9 +1001,9 @@ var TextEngine = class {
|
|
|
936
1001
|
this.setToolPhase("wait");
|
|
937
1002
|
return;
|
|
938
1003
|
}
|
|
939
|
-
if (executionResult.needsApproval.length > 0 || executionResult.needsClientExecution.length > 0) {
|
|
1004
|
+
if (executionResult.needsApproval.length > 0 || executionResult.needsClientExecution.length > 0 || executionResult.subagentInterrupts.length > 0) {
|
|
940
1005
|
if (allResults.length > 0) for (const chunk of afterToolBoundaryChunks) yield* this.pipeThroughMiddleware(chunk);
|
|
941
|
-
const emitted = yield* this.emitActionableInterruptBoundary(finishEvent, executionResult.needsApproval, executionResult.needsClientExecution);
|
|
1006
|
+
const emitted = yield* this.emitActionableInterruptBoundary(finishEvent, executionResult.needsApproval, executionResult.needsClientExecution, [], executionResult.subagentInterrupts);
|
|
942
1007
|
this.setToolPhase(emitted ? "wait" : "stop");
|
|
943
1008
|
return;
|
|
944
1009
|
}
|
|
@@ -998,16 +1063,67 @@ var TextEngine = class {
|
|
|
998
1063
|
shouldExecuteToolPhase() {
|
|
999
1064
|
return this.lastFinishReason === "tool_calls" && this.tools.length > 0 && this.toolCallManager.hasToolCalls();
|
|
1000
1065
|
}
|
|
1066
|
+
/**
|
|
1067
|
+
* Split this iteration into assistant ModelMessages that keep the provider's
|
|
1068
|
+
* block order: a new segment starts at every thinking step that follows a
|
|
1069
|
+
* provider-executed tool call (the rule buildAssistantMessages applies to
|
|
1070
|
+
* UIMessages). Segments after the first get `${id}-segment-${n}` ids.
|
|
1071
|
+
* Returns null when no split is needed or the order could not be tracked,
|
|
1072
|
+
* so callers fall back to the single-message shape.
|
|
1073
|
+
*/
|
|
1074
|
+
buildOrderedAssistantSegments(toolCalls, id, createdAt) {
|
|
1075
|
+
const parts = this.turnParts;
|
|
1076
|
+
if (!parts) return null;
|
|
1077
|
+
const providerCallIds = new Set(parts.flatMap((part) => part.type === "call" && part.providerExecuted ? [part.id] : []));
|
|
1078
|
+
let current = {
|
|
1079
|
+
thinking: [],
|
|
1080
|
+
text: "",
|
|
1081
|
+
callIds: []
|
|
1082
|
+
};
|
|
1083
|
+
const segments = [current];
|
|
1084
|
+
let split = false;
|
|
1085
|
+
for (const part of parts) if (part.type === "thinking") {
|
|
1086
|
+
const thinking = this.accumulatedThinking[part.index];
|
|
1087
|
+
if (!thinking) return null;
|
|
1088
|
+
if (current.callIds.some((callId) => providerCallIds.has(callId))) {
|
|
1089
|
+
current = {
|
|
1090
|
+
thinking: [thinking],
|
|
1091
|
+
text: "",
|
|
1092
|
+
callIds: []
|
|
1093
|
+
};
|
|
1094
|
+
segments.push(current);
|
|
1095
|
+
split = true;
|
|
1096
|
+
} else current.thinking.push(thinking);
|
|
1097
|
+
} else if (part.type === "text") current.text += part.content;
|
|
1098
|
+
else current.callIds.push(part.id);
|
|
1099
|
+
if (!split) return null;
|
|
1100
|
+
if (segments.map((segment) => segment.text).join("") !== this.accumulatedContent) return null;
|
|
1101
|
+
if (segments.reduce((n, segment) => n + segment.thinking.length, 0) !== this.accumulatedThinking.length) return null;
|
|
1102
|
+
const placed = new Set(segments.flatMap((segment) => segment.callIds));
|
|
1103
|
+
for (const toolCall of toolCalls) if (!placed.has(toolCall.id)) current.callIds.push(toolCall.id);
|
|
1104
|
+
return segments.map((segment, index) => {
|
|
1105
|
+
const segmentCalls = toolCalls.filter((toolCall) => segment.callIds.includes(toolCall.id));
|
|
1106
|
+
return {
|
|
1107
|
+
role: "assistant",
|
|
1108
|
+
content: segment.text || null,
|
|
1109
|
+
...segmentCalls.length > 0 && { toolCalls: segmentCalls },
|
|
1110
|
+
id: id === void 0 ? void 0 : index === 0 ? id : `${id}-segment-${index}`,
|
|
1111
|
+
createdAt,
|
|
1112
|
+
...segment.thinking.length > 0 && { thinking: segment.thinking }
|
|
1113
|
+
};
|
|
1114
|
+
});
|
|
1115
|
+
}
|
|
1001
1116
|
addAssistantToolCallMessage(toolCalls) {
|
|
1002
1117
|
this.finalizeCurrentThinkingStep();
|
|
1003
|
-
|
|
1118
|
+
const segments = this.buildOrderedAssistantSegments(toolCalls, this.currentMessageId ?? void 0, this.currentMessageCreatedAt ?? void 0);
|
|
1119
|
+
this.messages = [...this.messages, ...segments ?? [{
|
|
1004
1120
|
role: "assistant",
|
|
1005
1121
|
content: this.accumulatedContent || null,
|
|
1006
1122
|
toolCalls,
|
|
1007
1123
|
id: this.currentMessageId ?? void 0,
|
|
1008
1124
|
createdAt: this.currentMessageCreatedAt ?? void 0,
|
|
1009
1125
|
...this.accumulatedThinking.length > 0 && { thinking: this.accumulatedThinking }
|
|
1010
|
-
}];
|
|
1126
|
+
}]];
|
|
1011
1127
|
this.middlewareCtx.messages = this.messages;
|
|
1012
1128
|
}
|
|
1013
1129
|
addTerminalAssistantMessages() {
|
|
@@ -1048,13 +1164,17 @@ var TextEngine = class {
|
|
|
1048
1164
|
...thinking ? { thinking } : {}
|
|
1049
1165
|
});
|
|
1050
1166
|
} else {
|
|
1051
|
-
if (!currentTurnAlreadyRecorded && (this.accumulatedContent !== "" || thinking))
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1167
|
+
if (!currentTurnAlreadyRecorded && (this.accumulatedContent !== "" || thinking)) {
|
|
1168
|
+
const id = this.currentMessageId ?? this.createId("msg");
|
|
1169
|
+
const createdAt = this.currentMessageCreatedAt ?? /* @__PURE__ */ new Date();
|
|
1170
|
+
messages.push(...this.buildOrderedAssistantSegments(this.toolCallManager.getToolCalls(), id, createdAt) ?? [{
|
|
1171
|
+
role: "assistant",
|
|
1172
|
+
content: this.accumulatedContent || null,
|
|
1173
|
+
id,
|
|
1174
|
+
createdAt,
|
|
1175
|
+
...thinking ? { thinking } : {}
|
|
1176
|
+
}]);
|
|
1177
|
+
}
|
|
1058
1178
|
if (structuredOutput) messages.push({
|
|
1059
1179
|
role: "assistant",
|
|
1060
1180
|
content: raw || null,
|
|
@@ -1110,7 +1230,7 @@ var TextEngine = class {
|
|
|
1110
1230
|
genericInterruptId() {
|
|
1111
1231
|
return this.createId("interrupt");
|
|
1112
1232
|
}
|
|
1113
|
-
buildActionableInterrupts(approvals, clientRequests, genericRequests = [], genericInterruptIds = []) {
|
|
1233
|
+
buildActionableInterrupts(approvals, clientRequests, genericRequests = [], genericInterruptIds = [], childInterrupts = []) {
|
|
1114
1234
|
const interrupts = [];
|
|
1115
1235
|
for (const approval of approvals) {
|
|
1116
1236
|
const tool = this.tools.find((candidate) => candidate.name === approval.toolName);
|
|
@@ -1191,6 +1311,7 @@ var TextEngine = class {
|
|
|
1191
1311
|
}
|
|
1192
1312
|
});
|
|
1193
1313
|
}
|
|
1314
|
+
interrupts.push(...childInterrupts);
|
|
1194
1315
|
const ids = /* @__PURE__ */ new Set();
|
|
1195
1316
|
for (const interrupt of interrupts) {
|
|
1196
1317
|
if (ids.has(interrupt.id)) throw new Error(`Duplicate interrupt id in final batch: ${interrupt.id}`);
|
|
@@ -1198,13 +1319,13 @@ var TextEngine = class {
|
|
|
1198
1319
|
}
|
|
1199
1320
|
return interrupts;
|
|
1200
1321
|
}
|
|
1201
|
-
buildInterruptFinishedChunk(finishEvent, approvals, clientRequests, genericRequests = [], genericInterruptIds) {
|
|
1322
|
+
buildInterruptFinishedChunk(finishEvent, approvals, clientRequests, genericRequests = [], genericInterruptIds, childInterrupts) {
|
|
1202
1323
|
return {
|
|
1203
1324
|
...finishEvent,
|
|
1204
1325
|
timestamp: Date.now(),
|
|
1205
1326
|
outcome: {
|
|
1206
1327
|
type: "interrupt",
|
|
1207
|
-
interrupts: this.buildActionableInterrupts(approvals, clientRequests, genericRequests, genericInterruptIds)
|
|
1328
|
+
interrupts: this.buildActionableInterrupts(approvals, clientRequests, genericRequests, genericInterruptIds, childInterrupts)
|
|
1208
1329
|
}
|
|
1209
1330
|
};
|
|
1210
1331
|
}
|
|
@@ -1298,10 +1419,10 @@ var TextEngine = class {
|
|
|
1298
1419
|
};
|
|
1299
1420
|
yield* this.pipeThroughMiddleware(this.buildInterruptRunErrorChunk(error));
|
|
1300
1421
|
}
|
|
1301
|
-
async *emitActionableInterruptBoundary(finishEvent, approvals, clientRequests, genericRequests = []) {
|
|
1422
|
+
async *emitActionableInterruptBoundary(finishEvent, approvals, clientRequests, genericRequests = [], childInterrupts = []) {
|
|
1302
1423
|
yield* this.emitSyntheticRunStarted(finishEvent);
|
|
1303
1424
|
const genericInterruptIds = genericRequests.map(() => this.genericInterruptId());
|
|
1304
|
-
const terminal = this.completeEphemeralInterruptBindings(this.buildInterruptFinishedChunk(finishEvent, approvals, clientRequests, genericRequests, genericInterruptIds));
|
|
1425
|
+
const terminal = this.completeEphemeralInterruptBindings(this.buildInterruptFinishedChunk(finishEvent, approvals, clientRequests, genericRequests, genericInterruptIds, childInterrupts));
|
|
1305
1426
|
let terminalOutputs;
|
|
1306
1427
|
try {
|
|
1307
1428
|
terminalOutputs = [...this.emitPublicChunks(await this.middlewareRunner.runOnChunk(this.middlewareCtx, terminal))];
|
|
@@ -1344,6 +1465,7 @@ var TextEngine = class {
|
|
|
1344
1465
|
const approvalRequests = [];
|
|
1345
1466
|
const clientRequests = [];
|
|
1346
1467
|
for (const toolCall of toolCalls) {
|
|
1468
|
+
if (isProviderExecutedToolCall(toolCall)) continue;
|
|
1347
1469
|
const tool = this.resolveExecutableTools([toolCall]).find((candidate) => candidate.name === toolCall.function.name);
|
|
1348
1470
|
if (!tool) continue;
|
|
1349
1471
|
let input = {};
|
|
@@ -1444,7 +1566,8 @@ var TextEngine = class {
|
|
|
1444
1566
|
const newToolMessage = {
|
|
1445
1567
|
role: "tool",
|
|
1446
1568
|
content,
|
|
1447
|
-
toolCallId: result.toolCallId
|
|
1569
|
+
toolCallId: result.toolCallId,
|
|
1570
|
+
...result.state === "output-error" && { error: toolResultErrorText(parseToolOutput(wireContent)) }
|
|
1448
1571
|
};
|
|
1449
1572
|
if (placeholderIdx >= 0) this.messages = [
|
|
1450
1573
|
...this.messages.slice(0, placeholderIdx),
|
|
@@ -1666,6 +1789,7 @@ var TextEngine = class {
|
|
|
1666
1789
|
tools: baseConfig.tools
|
|
1667
1790
|
}));
|
|
1668
1791
|
this.applyMiddlewareConfig(postOnConfig);
|
|
1792
|
+
assertMessagesFileSourceSupport(this.adapter, this.messages);
|
|
1669
1793
|
const structuredCallOptions = {
|
|
1670
1794
|
chatOptions: {
|
|
1671
1795
|
model: this.params.model,
|
|
@@ -1815,7 +1939,12 @@ var TextEngine = class {
|
|
|
1815
1939
|
async *harvestCombinedStructuredOutput() {
|
|
1816
1940
|
if (!this.finalStructuredOutput) throw new Error("harvestCombinedStructuredOutput called without finalStructuredOutput config");
|
|
1817
1941
|
const yieldChunks = this.finalStructuredOutput.yieldChunks;
|
|
1818
|
-
|
|
1942
|
+
const source = this.finalStructuredOutput.source ?? "text";
|
|
1943
|
+
if (this.lastFinishReason === "length") this.finalizationError = {
|
|
1944
|
+
message: "The response was cut off because the maximum token limit was reached (finish_reason=length); raise the output token limit.",
|
|
1945
|
+
code: "max_tokens"
|
|
1946
|
+
};
|
|
1947
|
+
else if (source === "event") {
|
|
1819
1948
|
if (!this.structuredOutputResult) this.finalizationError = {
|
|
1820
1949
|
message: "missing structured result",
|
|
1821
1950
|
code: "structured-output-missing-result"
|
|
@@ -1907,7 +2036,16 @@ var TextEngine = class {
|
|
|
1907
2036
|
modelOptions: this.params.modelOptions
|
|
1908
2037
|
};
|
|
1909
2038
|
}
|
|
1910
|
-
|
|
2039
|
+
/** Resume entries this run answers itself. A subagent tool owns the rest. */
|
|
2040
|
+
ownResume(resume) {
|
|
2041
|
+
const childIds = this.params[CHILD_RESUME_IDS];
|
|
2042
|
+
return childIds ? resume?.filter((entry) => !childIds.has(entry.interruptId)) : resume;
|
|
2043
|
+
}
|
|
2044
|
+
async applyEphemeralInterruptResume(middlewareConfig) {
|
|
2045
|
+
const config = {
|
|
2046
|
+
...middlewareConfig,
|
|
2047
|
+
resume: this.ownResume(middlewareConfig.resume)
|
|
2048
|
+
};
|
|
1911
2049
|
if ((config.resume?.length ?? 0) === 0) return;
|
|
1912
2050
|
const interruptedRunId = this.parentRunIdOverride;
|
|
1913
2051
|
if (!interruptedRunId) throw new InterruptResumeValidationError([{
|
|
@@ -2026,7 +2164,7 @@ var TextEngine = class {
|
|
|
2026
2164
|
const pending = [];
|
|
2027
2165
|
const ids = /* @__PURE__ */ new Set();
|
|
2028
2166
|
const batchIndexes = /* @__PURE__ */ new Set();
|
|
2029
|
-
for (const resumeItem of this.params.resume ?? []) {
|
|
2167
|
+
for (const resumeItem of this.ownResume(this.params.resume) ?? []) {
|
|
2030
2168
|
const parsed = readGenericInterruptContinuation(resumeItem.metadata);
|
|
2031
2169
|
if (parsed.status === "absent") continue;
|
|
2032
2170
|
if (parsed.status === "invalid") return fail(parsed.message);
|
|
@@ -2300,6 +2438,7 @@ function chat(options) {
|
|
|
2300
2438
|
validateCapabilities(readRuntimeMiddleware(options.middleware) ?? [], options.adapter);
|
|
2301
2439
|
if (options.tools) assertUniqueToolNames(options.tools);
|
|
2302
2440
|
const { outputSchema, stream } = options;
|
|
2441
|
+
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.");
|
|
2303
2442
|
if (outputSchema && stream === true) return runStreamingStructuredOutput(toRuntimeTextActivityOptions(options, {
|
|
2304
2443
|
outputSchema,
|
|
2305
2444
|
stream: true
|
|
@@ -2368,8 +2507,8 @@ function runStreamingText(options) {
|
|
|
2368
2507
|
publishDeliverySeams(stream, engineRef);
|
|
2369
2508
|
return stream;
|
|
2370
2509
|
}
|
|
2371
|
-
async function*
|
|
2372
|
-
const { adapter, middleware, context, debug, mcp, ...textOptions } = options;
|
|
2510
|
+
async function* runChatEngine(options, engineRef) {
|
|
2511
|
+
const { adapter, middleware, context, debug, mcp, subagents: _subagents, ...textOptions } = options;
|
|
2373
2512
|
const model = adapter.model;
|
|
2374
2513
|
const logger = resolveDebugOption(debug);
|
|
2375
2514
|
const mcpManager = MCPManager.from(mcp);
|
|
@@ -2392,6 +2531,303 @@ async function* streamTextChunks(options, engineRef) {
|
|
|
2392
2531
|
await mcpManager.dispose();
|
|
2393
2532
|
}
|
|
2394
2533
|
}
|
|
2534
|
+
async function* streamTextChunks(options, engineRef) {
|
|
2535
|
+
const bag = options.subagents;
|
|
2536
|
+
const agents = bag?.agents ?? [];
|
|
2537
|
+
if (bag && agents.length > 0 && bag.router) {
|
|
2538
|
+
yield* runRoutedSubagents(options, engineRef);
|
|
2539
|
+
return;
|
|
2540
|
+
}
|
|
2541
|
+
if (bag && agents.length > 0) {
|
|
2542
|
+
const threadId = options.threadId ?? `thread-${Date.now()}`;
|
|
2543
|
+
const runId = options.runId ?? `run-${Date.now()}`;
|
|
2544
|
+
const messages = options.messages ?? [];
|
|
2545
|
+
const turn = readSubagentTurn(messages, options.resume);
|
|
2546
|
+
const sink = createSubagentSink();
|
|
2547
|
+
const calls = subagentCallMessages(new Set(bag.agents.map((agent) => agent.name)));
|
|
2548
|
+
const synthetic = createSyntheticSubagentTools(bag, {
|
|
2549
|
+
messages: turn?.before ?? messages,
|
|
2550
|
+
messagesFor: calls.messagesFor,
|
|
2551
|
+
threadId,
|
|
2552
|
+
runId,
|
|
2553
|
+
...options.parentRunId !== void 0 && { interruptedRunId: options.parentRunId },
|
|
2554
|
+
...options.abortController && { abortSignal: options.abortController.signal },
|
|
2555
|
+
...turn && { turn },
|
|
2556
|
+
sink
|
|
2557
|
+
});
|
|
2558
|
+
yield* streamWithChildUsage(runChatEngine({
|
|
2559
|
+
...options,
|
|
2560
|
+
threadId,
|
|
2561
|
+
runId,
|
|
2562
|
+
...turn && { [CHILD_RESUME_IDS]: new Set(turn.children.flatMap((child) => child.resume.map((entry) => entry.interruptId))) },
|
|
2563
|
+
tools: [...options.tools ?? [], ...synthetic],
|
|
2564
|
+
middleware: [...options.middleware ?? [], calls.middleware]
|
|
2565
|
+
}, engineRef), sink);
|
|
2566
|
+
return;
|
|
2567
|
+
}
|
|
2568
|
+
yield* runChatEngine(options, engineRef);
|
|
2569
|
+
}
|
|
2570
|
+
/** Add child usage to the next RUN_FINISHED of the parent. */
|
|
2571
|
+
async function* streamWithChildUsage(stream, sink) {
|
|
2572
|
+
for await (const chunk of stream) yield chunk.type === EventType.RUN_FINISHED ? withChildUsage(chunk, sink) : chunk;
|
|
2573
|
+
}
|
|
2574
|
+
async function* runRoutedSubagents(options, engineRef) {
|
|
2575
|
+
const bag = options.subagents;
|
|
2576
|
+
if (!bag?.router) {
|
|
2577
|
+
yield* runChatEngine(options, engineRef);
|
|
2578
|
+
return;
|
|
2579
|
+
}
|
|
2580
|
+
const threadId = options.threadId ?? `thread-${Date.now()}`;
|
|
2581
|
+
const runId = options.runId ?? `run-${Date.now()}`;
|
|
2582
|
+
const messages = options.messages ?? [];
|
|
2583
|
+
const turn = readSubagentTurn(messages, options.resume);
|
|
2584
|
+
if (!turn && (options.resume?.length ?? 0) > 0) {
|
|
2585
|
+
yield* runChatEngine({
|
|
2586
|
+
...options,
|
|
2587
|
+
threadId,
|
|
2588
|
+
runId,
|
|
2589
|
+
subagents: void 0
|
|
2590
|
+
}, engineRef);
|
|
2591
|
+
return;
|
|
2592
|
+
}
|
|
2593
|
+
const abortSignal = options.abortController?.signal;
|
|
2594
|
+
const turnMessages = turn?.before ?? messages;
|
|
2595
|
+
const subagentPersistence = readRoutedSubagentPersistence(options.middleware);
|
|
2596
|
+
await subagentPersistence?.start({
|
|
2597
|
+
threadId,
|
|
2598
|
+
runId,
|
|
2599
|
+
messages,
|
|
2600
|
+
...options.resume && { resume: options.resume }
|
|
2601
|
+
});
|
|
2602
|
+
try {
|
|
2603
|
+
const plan = savedPlan(turn?.plan, bag.agents, {
|
|
2604
|
+
threadId,
|
|
2605
|
+
interruptedRunId: options.parentRunId ?? runId,
|
|
2606
|
+
interruptIds: options.resume?.map((entry) => entry.interruptId) ?? []
|
|
2607
|
+
}) ?? normalizeRouterPick(await bag.router({
|
|
2608
|
+
messages: turnMessages,
|
|
2609
|
+
agents: bag.agents,
|
|
2610
|
+
...abortSignal && { abortSignal }
|
|
2611
|
+
}), bag.agents);
|
|
2612
|
+
if (abortSignal?.aborted) {
|
|
2613
|
+
const error = /* @__PURE__ */ new Error("Aborted");
|
|
2614
|
+
error.name = "AbortError";
|
|
2615
|
+
await subagentPersistence?.abort({
|
|
2616
|
+
threadId,
|
|
2617
|
+
runId,
|
|
2618
|
+
error
|
|
2619
|
+
});
|
|
2620
|
+
return;
|
|
2621
|
+
}
|
|
2622
|
+
const onlyStep = plan.steps.length === 1 ? plan.steps[0] : void 0;
|
|
2623
|
+
if (onlyStep?.names.length === 1 && onlyStep.names[0] === "main") {
|
|
2624
|
+
if (turn) await subagentPersistence?.finish({
|
|
2625
|
+
threadId,
|
|
2626
|
+
runId
|
|
2627
|
+
});
|
|
2628
|
+
yield* runChatEngine({
|
|
2629
|
+
...options,
|
|
2630
|
+
threadId,
|
|
2631
|
+
runId,
|
|
2632
|
+
subagents: void 0,
|
|
2633
|
+
...turn && { resume: turn.rest }
|
|
2634
|
+
}, engineRef);
|
|
2635
|
+
return;
|
|
2636
|
+
}
|
|
2637
|
+
yield {
|
|
2638
|
+
type: EventType.RUN_STARTED,
|
|
2639
|
+
threadId,
|
|
2640
|
+
runId,
|
|
2641
|
+
timestamp: Date.now()
|
|
2642
|
+
};
|
|
2643
|
+
const earlier = [...turn?.children ?? []];
|
|
2644
|
+
const takeEarlier = (name) => {
|
|
2645
|
+
const index = earlier.findIndex((child) => child.name === name);
|
|
2646
|
+
return index === -1 ? void 0 : earlier.splice(index, 1)[0];
|
|
2647
|
+
};
|
|
2648
|
+
const sink = createSubagentSink();
|
|
2649
|
+
const stepTexts = [];
|
|
2650
|
+
let stepMessages = turnMessages;
|
|
2651
|
+
let failure;
|
|
2652
|
+
for (const step of plan.steps) {
|
|
2653
|
+
const entries = [];
|
|
2654
|
+
const textByName = /* @__PURE__ */ new Map();
|
|
2655
|
+
for (const name of step.names) {
|
|
2656
|
+
const prior = takeEarlier(name);
|
|
2657
|
+
if (prior?.status === "finished") {
|
|
2658
|
+
textByName.set(name, prior.text);
|
|
2659
|
+
continue;
|
|
2660
|
+
}
|
|
2661
|
+
entries.push(prior?.status === "suspended" ? {
|
|
2662
|
+
name,
|
|
2663
|
+
resume: {
|
|
2664
|
+
subagentRunId: prior.subagentRunId,
|
|
2665
|
+
messages: prior.messages,
|
|
2666
|
+
entries: prior.resume,
|
|
2667
|
+
text: prior.text
|
|
2668
|
+
}
|
|
2669
|
+
} : { name });
|
|
2670
|
+
}
|
|
2671
|
+
const stepChunks = [];
|
|
2672
|
+
if (entries.length > 0) for await (const chunk of spawnNamedAgents(entries, {
|
|
2673
|
+
...bag,
|
|
2674
|
+
order: step.order ?? bag.order
|
|
2675
|
+
}, {
|
|
2676
|
+
messages: stepMessages,
|
|
2677
|
+
...abortSignal && { abortSignal },
|
|
2678
|
+
threadId,
|
|
2679
|
+
parentRunId: runId,
|
|
2680
|
+
...options.parentRunId !== void 0 && { interruptedRunId: options.parentRunId }
|
|
2681
|
+
}, sink)) {
|
|
2682
|
+
const tagged = withPlan(chunk, plan);
|
|
2683
|
+
stepChunks.push(tagged);
|
|
2684
|
+
await subagentPersistence?.chunk({
|
|
2685
|
+
threadId,
|
|
2686
|
+
runId,
|
|
2687
|
+
chunk: tagged
|
|
2688
|
+
});
|
|
2689
|
+
yield tagged;
|
|
2690
|
+
}
|
|
2691
|
+
failure = stepChunks.find((chunk) => chunk.type === EventType.SUBAGENT_ERROR);
|
|
2692
|
+
if (failure) break;
|
|
2693
|
+
if (sink.interrupts.length > 0) break;
|
|
2694
|
+
for (const entry of entries) {
|
|
2695
|
+
const text = collectNamedText(stepChunks, [entry.name]);
|
|
2696
|
+
textByName.set(entry.name, [entry.resume?.text, text].filter(Boolean).join("\n\n"));
|
|
2697
|
+
}
|
|
2698
|
+
const text = step.names.map((name) => textByName.get(name)?.trim() ?? "").filter((block) => block !== "").join("\n\n");
|
|
2699
|
+
if (text) {
|
|
2700
|
+
stepTexts.push(text);
|
|
2701
|
+
stepMessages = [...stepMessages, {
|
|
2702
|
+
role: "assistant",
|
|
2703
|
+
content: text
|
|
2704
|
+
}];
|
|
2705
|
+
}
|
|
2706
|
+
}
|
|
2707
|
+
if (abortSignal?.aborted) {
|
|
2708
|
+
const error = /* @__PURE__ */ new Error("Aborted");
|
|
2709
|
+
error.name = "AbortError";
|
|
2710
|
+
await subagentPersistence?.abort({
|
|
2711
|
+
threadId,
|
|
2712
|
+
runId,
|
|
2713
|
+
error
|
|
2714
|
+
});
|
|
2715
|
+
yield withChildUsage({
|
|
2716
|
+
type: EventType.RUN_FINISHED,
|
|
2717
|
+
threadId,
|
|
2718
|
+
runId,
|
|
2719
|
+
outcome: { type: "cancelled" },
|
|
2720
|
+
timestamp: Date.now()
|
|
2721
|
+
}, sink);
|
|
2722
|
+
return;
|
|
2723
|
+
}
|
|
2724
|
+
if (failure) {
|
|
2725
|
+
const message = failure.message || "A subagent failed";
|
|
2726
|
+
await subagentPersistence?.abort({
|
|
2727
|
+
threadId,
|
|
2728
|
+
runId,
|
|
2729
|
+
error: new Error(message)
|
|
2730
|
+
});
|
|
2731
|
+
yield withChildUsage({
|
|
2732
|
+
type: EventType.RUN_ERROR,
|
|
2733
|
+
threadId,
|
|
2734
|
+
runId,
|
|
2735
|
+
message,
|
|
2736
|
+
...failure.code !== void 0 ? { code: failure.code } : {},
|
|
2737
|
+
timestamp: Date.now()
|
|
2738
|
+
}, sink);
|
|
2739
|
+
return;
|
|
2740
|
+
}
|
|
2741
|
+
if (sink.interrupts.length > 0) {
|
|
2742
|
+
const interrupts = rebindInterrupts(sink.interrupts, runId);
|
|
2743
|
+
await subagentPersistence?.suspend?.({
|
|
2744
|
+
threadId,
|
|
2745
|
+
runId,
|
|
2746
|
+
interrupts
|
|
2747
|
+
});
|
|
2748
|
+
yield withChildUsage({
|
|
2749
|
+
type: EventType.RUN_FINISHED,
|
|
2750
|
+
threadId,
|
|
2751
|
+
runId,
|
|
2752
|
+
outcome: {
|
|
2753
|
+
type: "interrupt",
|
|
2754
|
+
interrupts
|
|
2755
|
+
},
|
|
2756
|
+
timestamp: Date.now()
|
|
2757
|
+
}, sink);
|
|
2758
|
+
return;
|
|
2759
|
+
}
|
|
2760
|
+
if ((bag.strategy ?? "exclusive") === "handoff") {
|
|
2761
|
+
const childText = stepTexts.join("\n\n");
|
|
2762
|
+
await subagentPersistence?.finish({
|
|
2763
|
+
threadId,
|
|
2764
|
+
runId
|
|
2765
|
+
});
|
|
2766
|
+
yield* streamWithChildUsage(runChatEngine({
|
|
2767
|
+
...options,
|
|
2768
|
+
threadId,
|
|
2769
|
+
runId,
|
|
2770
|
+
subagents: void 0,
|
|
2771
|
+
...turn && { resume: turn.rest },
|
|
2772
|
+
messages: [...turnMessages, {
|
|
2773
|
+
id: subagentHostMessageId(runId),
|
|
2774
|
+
role: "assistant",
|
|
2775
|
+
content: childText || "Subagent finished.",
|
|
2776
|
+
metadata: { tanstack: { runId } }
|
|
2777
|
+
}]
|
|
2778
|
+
}, engineRef), sink);
|
|
2779
|
+
return;
|
|
2780
|
+
}
|
|
2781
|
+
await subagentPersistence?.finish({
|
|
2782
|
+
threadId,
|
|
2783
|
+
runId
|
|
2784
|
+
});
|
|
2785
|
+
yield withChildUsage({
|
|
2786
|
+
type: EventType.RUN_FINISHED,
|
|
2787
|
+
threadId,
|
|
2788
|
+
runId,
|
|
2789
|
+
timestamp: Date.now()
|
|
2790
|
+
}, sink);
|
|
2791
|
+
} catch (error) {
|
|
2792
|
+
await subagentPersistence?.abort({
|
|
2793
|
+
threadId,
|
|
2794
|
+
runId,
|
|
2795
|
+
error
|
|
2796
|
+
});
|
|
2797
|
+
throw error;
|
|
2798
|
+
}
|
|
2799
|
+
}
|
|
2800
|
+
/**
|
|
2801
|
+
* The plan from a resumed turn, or undefined when it is absent. A plan that
|
|
2802
|
+
* names agents this chat does not have is stale: re-routing would strand the
|
|
2803
|
+
* suspended child that owns the answers, so fail the resume instead.
|
|
2804
|
+
*/
|
|
2805
|
+
function savedPlan(plan, agents, run) {
|
|
2806
|
+
if (typeof plan !== "object" || plan === null || !("steps" in plan)) return;
|
|
2807
|
+
try {
|
|
2808
|
+
return normalizeRouterPick(plan, agents);
|
|
2809
|
+
} catch (error) {
|
|
2810
|
+
throw new InterruptResumeValidationError([{
|
|
2811
|
+
scope: "batch",
|
|
2812
|
+
threadId: run.threadId,
|
|
2813
|
+
interruptedRunId: run.interruptedRunId,
|
|
2814
|
+
generation: 0,
|
|
2815
|
+
interruptIds: run.interruptIds,
|
|
2816
|
+
code: "stale",
|
|
2817
|
+
message: `The saved subagent plan does not match the agents of this chat. ${error instanceof Error ? error.message : String(error)}`,
|
|
2818
|
+
source: "server",
|
|
2819
|
+
retryable: false
|
|
2820
|
+
}]);
|
|
2821
|
+
}
|
|
2822
|
+
}
|
|
2823
|
+
/** Put the router plan on a direct child's SUBAGENT_STARTED metadata. */
|
|
2824
|
+
function withPlan(chunk, plan) {
|
|
2825
|
+
if (chunk.type !== EventType.SUBAGENT_STARTED || chunk.parentSubagentRunId !== void 0) return chunk;
|
|
2826
|
+
return withTanstackMetadata(chunk, { [SUBAGENT_PLAN_KEY]: plan });
|
|
2827
|
+
}
|
|
2828
|
+
function readRoutedSubagentPersistence(middleware) {
|
|
2829
|
+
for (const item of middleware ?? []) if (item.routedSubagentPersistence) return item.routedSubagentPersistence;
|
|
2830
|
+
}
|
|
2395
2831
|
/**
|
|
2396
2832
|
* Run non-streaming text - collects all content and returns as a string.
|
|
2397
2833
|
* Runs the full agentic loop (if tools are provided) but returns collected text.
|