@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.
Files changed (144) hide show
  1. package/dist/esm/activities/chat/adapter.d.ts +9 -0
  2. package/dist/esm/activities/chat/adapter.js +1 -0
  3. package/dist/esm/activities/chat/adapter.js.map +1 -1
  4. package/dist/esm/activities/chat/agents/define-agent.d.ts +81 -0
  5. package/dist/esm/activities/chat/agents/define-agent.js +34 -0
  6. package/dist/esm/activities/chat/agents/define-agent.js.map +1 -0
  7. package/dist/esm/activities/chat/agents/route.d.ts +53 -0
  8. package/dist/esm/activities/chat/agents/route.js +59 -0
  9. package/dist/esm/activities/chat/agents/route.js.map +1 -0
  10. package/dist/esm/activities/chat/agents/spawn.d.ts +124 -0
  11. package/dist/esm/activities/chat/agents/spawn.js +490 -0
  12. package/dist/esm/activities/chat/agents/spawn.js.map +1 -0
  13. package/dist/esm/activities/chat/agents/turn.d.ts +36 -0
  14. package/dist/esm/activities/chat/agents/turn.js +78 -0
  15. package/dist/esm/activities/chat/agents/turn.js.map +1 -0
  16. package/dist/esm/activities/chat/index.d.ts +13 -3
  17. package/dist/esm/activities/chat/index.js +462 -26
  18. package/dist/esm/activities/chat/index.js.map +1 -1
  19. package/dist/esm/activities/chat/messages.d.ts +7 -1
  20. package/dist/esm/activities/chat/messages.js +99 -19
  21. package/dist/esm/activities/chat/messages.js.map +1 -1
  22. package/dist/esm/activities/chat/middleware/run-store.d.ts +43 -7
  23. package/dist/esm/activities/chat/middleware/run-store.js +8 -1
  24. package/dist/esm/activities/chat/middleware/run-store.js.map +1 -1
  25. package/dist/esm/activities/chat/middleware/types.d.ts +47 -1
  26. package/dist/esm/activities/chat/middleware/types.js.map +1 -1
  27. package/dist/esm/activities/chat/stream/message-updaters.js +9 -2
  28. package/dist/esm/activities/chat/stream/message-updaters.js.map +1 -1
  29. package/dist/esm/activities/chat/stream/processor.d.ts +46 -1
  30. package/dist/esm/activities/chat/stream/processor.js +294 -18
  31. package/dist/esm/activities/chat/stream/processor.js.map +1 -1
  32. package/dist/esm/activities/chat/tools/tool-calls.d.ts +17 -3
  33. package/dist/esm/activities/chat/tools/tool-calls.js +56 -5
  34. package/dist/esm/activities/chat/tools/tool-calls.js.map +1 -1
  35. package/dist/esm/activities/embed/adapter.d.ts +7 -0
  36. package/dist/esm/activities/embed/adapter.js +1 -0
  37. package/dist/esm/activities/embed/adapter.js.map +1 -1
  38. package/dist/esm/activities/embed/index.js +2 -0
  39. package/dist/esm/activities/embed/index.js.map +1 -1
  40. package/dist/esm/activities/files/adapter.d.ts +97 -0
  41. package/dist/esm/activities/files/adapter.js +45 -0
  42. package/dist/esm/activities/files/adapter.js.map +1 -0
  43. package/dist/esm/activities/files/index.d.ts +66 -0
  44. package/dist/esm/activities/files/index.js +78 -0
  45. package/dist/esm/activities/files/index.js.map +1 -0
  46. package/dist/esm/activities/generateAudio/index.js +1 -1
  47. package/dist/esm/activities/generateImage/adapter.d.ts +8 -0
  48. package/dist/esm/activities/generateImage/adapter.js +1 -0
  49. package/dist/esm/activities/generateImage/adapter.js.map +1 -1
  50. package/dist/esm/activities/generateImage/index.js +3 -1
  51. package/dist/esm/activities/generateImage/index.js.map +1 -1
  52. package/dist/esm/activities/generateLiveVideo/index.js +1 -1
  53. package/dist/esm/activities/generateSpeech/index.js +1 -1
  54. package/dist/esm/activities/generateTranscription/index.js +1 -1
  55. package/dist/esm/activities/generateVideo/adapter.d.ts +8 -0
  56. package/dist/esm/activities/generateVideo/adapter.js +1 -0
  57. package/dist/esm/activities/generateVideo/adapter.js.map +1 -1
  58. package/dist/esm/activities/generateVideo/index.js +3 -0
  59. package/dist/esm/activities/generateVideo/index.js.map +1 -1
  60. package/dist/esm/activities/generateVoice/index.js +1 -1
  61. package/dist/esm/activities/generateWorld/adapter.d.ts +4 -2
  62. package/dist/esm/activities/generateWorld/adapter.js.map +1 -1
  63. package/dist/esm/activities/generateWorld/index.d.ts +4 -3
  64. package/dist/esm/activities/generateWorld/index.js +6 -5
  65. package/dist/esm/activities/generateWorld/index.js.map +1 -1
  66. package/dist/esm/activities/index.d.ts +9 -3
  67. package/dist/esm/activities/index.js +17 -13
  68. package/dist/esm/activities/summarize/chat-stream-summarize.d.ts +2 -0
  69. package/dist/esm/activities/summarize/chat-stream-summarize.js +8 -8
  70. package/dist/esm/activities/summarize/chat-stream-summarize.js.map +1 -1
  71. package/dist/esm/activities/summarize/index.js +1 -1
  72. package/dist/esm/client.d.ts +7 -36
  73. package/dist/esm/client.js +5 -37
  74. package/dist/esm/client.js.map +1 -1
  75. package/dist/esm/index.d.ts +8 -2
  76. package/dist/esm/index.js +9 -4
  77. package/dist/esm/middlewares/content-guard.js.map +1 -1
  78. package/dist/esm/types.d.ts +179 -98
  79. package/dist/esm/utilities/adapter-yield-chunk.d.ts +5 -1
  80. package/dist/esm/utilities/ag-ui-usage.d.ts +9 -9
  81. package/dist/esm/utilities/ag-ui-usage.js +66 -3
  82. package/dist/esm/utilities/ag-ui-usage.js.map +1 -1
  83. package/dist/esm/utilities/ag-ui-wire.js +90 -13
  84. package/dist/esm/utilities/ag-ui-wire.js.map +1 -1
  85. package/dist/esm/utilities/content-source.d.ts +60 -0
  86. package/dist/esm/utilities/content-source.js +85 -0
  87. package/dist/esm/utilities/content-source.js.map +1 -0
  88. package/dist/esm/utilities/normalize-stream-chunk.js +7 -2
  89. package/dist/esm/utilities/normalize-stream-chunk.js.map +1 -1
  90. package/dist/esm/utilities/provider-executed.d.ts +7 -0
  91. package/dist/esm/utilities/provider-executed.js +10 -1
  92. package/dist/esm/utilities/provider-executed.js.map +1 -1
  93. package/dist/esm/utilities/spec-event-keys.js +13 -8
  94. package/dist/esm/utilities/spec-event-keys.js.map +1 -1
  95. package/dist/esm/utilities/subagent-wire.d.ts +36 -0
  96. package/dist/esm/utilities/subagent-wire.js +131 -0
  97. package/dist/esm/utilities/subagent-wire.js.map +1 -0
  98. package/dist/esm/utilities/tool-result.d.ts +12 -2
  99. package/dist/esm/utilities/tool-result.js +23 -3
  100. package/dist/esm/utilities/tool-result.js.map +1 -1
  101. package/package.json +3 -3
  102. package/skills/ai-core/adapter-configuration/SKILL.md +62 -0
  103. package/skills/ai-core/adapter-configuration/references/grok-adapter.md +1 -1
  104. package/skills/ai-core/chat-experience/SKILL.md +14 -0
  105. package/skills/ai-core/media-generation/SKILL.md +10 -2
  106. package/skills/ai-core/middleware/SKILL.md +7 -4
  107. package/src/activities/chat/adapter.ts +10 -0
  108. package/src/activities/chat/agents/define-agent.ts +121 -0
  109. package/src/activities/chat/agents/route.ts +115 -0
  110. package/src/activities/chat/agents/spawn.ts +806 -0
  111. package/src/activities/chat/agents/turn.ts +151 -0
  112. package/src/activities/chat/index.ts +734 -30
  113. package/src/activities/chat/messages.ts +137 -16
  114. package/src/activities/chat/middleware/run-store.ts +56 -7
  115. package/src/activities/chat/middleware/types.ts +47 -0
  116. package/src/activities/chat/stream/message-updaters.ts +24 -2
  117. package/src/activities/chat/stream/processor.ts +452 -30
  118. package/src/activities/chat/tools/tool-calls.ts +83 -11
  119. package/src/activities/embed/adapter.ts +7 -0
  120. package/src/activities/embed/index.ts +5 -0
  121. package/src/activities/files/adapter.ts +120 -0
  122. package/src/activities/files/index.ts +113 -0
  123. package/src/activities/generateImage/adapter.ts +8 -0
  124. package/src/activities/generateImage/index.ts +4 -0
  125. package/src/activities/generateVideo/adapter.ts +8 -0
  126. package/src/activities/generateVideo/index.ts +7 -0
  127. package/src/activities/generateWorld/adapter.ts +4 -2
  128. package/src/activities/generateWorld/index.ts +7 -6
  129. package/src/activities/index.ts +40 -1
  130. package/src/activities/summarize/chat-stream-summarize.ts +22 -12
  131. package/src/client.ts +29 -35
  132. package/src/index.ts +39 -0
  133. package/src/middlewares/content-guard.ts +7 -5
  134. package/src/types.ts +226 -103
  135. package/src/utilities/adapter-yield-chunk.ts +10 -2
  136. package/src/utilities/ag-ui-usage.test.ts +38 -0
  137. package/src/utilities/ag-ui-usage.ts +98 -11
  138. package/src/utilities/ag-ui-wire.ts +134 -16
  139. package/src/utilities/content-source.ts +138 -0
  140. package/src/utilities/normalize-stream-chunk.ts +10 -2
  141. package/src/utilities/provider-executed.ts +13 -0
  142. package/src/utilities/spec-event-keys.ts +34 -7
  143. package/src/utilities/subagent-wire.ts +184 -0
  144. 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 !== "") this.currentThinkingSignature = 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
- this.messages = [...this.messages, {
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)) messages.push({
1052
- role: "assistant",
1053
- content: this.accumulatedContent || null,
1054
- id: this.currentMessageId ?? this.createId("msg"),
1055
- createdAt: this.currentMessageCreatedAt ?? /* @__PURE__ */ new Date(),
1056
- ...thinking ? { thinking } : {}
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
- if ((this.finalStructuredOutput.source ?? "text") === "event") {
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
- async applyEphemeralInterruptResume(config) {
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* streamTextChunks(options, engineRef) {
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.