@tanstack/ai 0.46.0 → 0.47.1

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 (66) hide show
  1. package/dist/esm/activities/chat/index.d.ts +36 -11
  2. package/dist/esm/activities/chat/index.js +435 -76
  3. package/dist/esm/activities/chat/index.js.map +1 -1
  4. package/dist/esm/activities/chat/messages.d.ts +1 -0
  5. package/dist/esm/activities/chat/messages.js +12 -7
  6. package/dist/esm/activities/chat/messages.js.map +1 -1
  7. package/dist/esm/activities/chat/middleware/builder.d.ts +7 -2
  8. package/dist/esm/activities/chat/middleware/builder.js.map +1 -1
  9. package/dist/esm/activities/chat/middleware/compose.d.ts +10 -3
  10. package/dist/esm/activities/chat/middleware/compose.js +55 -0
  11. package/dist/esm/activities/chat/middleware/compose.js.map +1 -1
  12. package/dist/esm/activities/chat/middleware/define.d.ts +6 -3
  13. package/dist/esm/activities/chat/middleware/define.js.map +1 -1
  14. package/dist/esm/activities/chat/middleware/generic-interrupts.d.ts +13 -0
  15. package/dist/esm/activities/chat/middleware/generic-interrupts.js +8 -0
  16. package/dist/esm/activities/chat/middleware/generic-interrupts.js.map +1 -0
  17. package/dist/esm/activities/chat/middleware/index.d.ts +4 -1
  18. package/dist/esm/activities/chat/middleware/types.d.ts +54 -3
  19. package/dist/esm/activities/chat/middleware/types.js +16 -0
  20. package/dist/esm/activities/chat/middleware/types.js.map +1 -0
  21. package/dist/esm/activities/chat/stream/processor.js +18 -5
  22. package/dist/esm/activities/chat/stream/processor.js.map +1 -1
  23. package/dist/esm/adapter-internals.d.ts +6 -0
  24. package/dist/esm/adapter-internals.js +4 -1
  25. package/dist/esm/client.d.ts +4 -0
  26. package/dist/esm/client.js +3 -1
  27. package/dist/esm/client.js.map +1 -1
  28. package/dist/esm/generic-interrupt-continuation.d.ts +45 -0
  29. package/dist/esm/generic-interrupt-continuation.js +80 -0
  30. package/dist/esm/generic-interrupt-continuation.js.map +1 -0
  31. package/dist/esm/index.d.ts +6 -1
  32. package/dist/esm/index.js +4 -1
  33. package/dist/esm/interrupt-definition.d.ts +113 -0
  34. package/dist/esm/interrupt-definition.js +169 -0
  35. package/dist/esm/interrupt-definition.js.map +1 -0
  36. package/dist/esm/interrupt-resume.d.ts +3 -0
  37. package/dist/esm/interrupt-resume.js +77 -16
  38. package/dist/esm/interrupt-resume.js.map +1 -1
  39. package/dist/esm/interrupts.d.ts +12 -3
  40. package/dist/esm/interrupts.js.map +1 -1
  41. package/dist/esm/types.d.ts +11 -3
  42. package/dist/esm/utilities/chat-params.js +10 -1
  43. package/dist/esm/utilities/chat-params.js.map +1 -1
  44. package/package.json +1 -1
  45. package/skills/ai-core/media-generation/SKILL.md +4 -1
  46. package/skills/ai-core/middleware/SKILL.md +53 -44
  47. package/skills/ai-core/structured-outputs/SKILL.md +59 -55
  48. package/skills/ai-core/tool-calling/SKILL.md +54 -1
  49. package/src/activities/chat/index.ts +1024 -203
  50. package/src/activities/chat/messages.ts +11 -3
  51. package/src/activities/chat/middleware/builder.ts +29 -4
  52. package/src/activities/chat/middleware/compose.ts +95 -5
  53. package/src/activities/chat/middleware/define.ts +13 -3
  54. package/src/activities/chat/middleware/generic-interrupts.ts +26 -0
  55. package/src/activities/chat/middleware/index.ts +15 -0
  56. package/src/activities/chat/middleware/types.ts +127 -2
  57. package/src/activities/chat/stream/processor.ts +21 -0
  58. package/src/adapter-internals.ts +20 -0
  59. package/src/client.ts +20 -0
  60. package/src/generic-interrupt-continuation.ts +162 -0
  61. package/src/index.ts +34 -0
  62. package/src/interrupt-definition.ts +581 -0
  63. package/src/interrupt-resume.ts +156 -25
  64. package/src/interrupts.ts +13 -3
  65. package/src/types.ts +11 -3
  66. package/src/utilities/chat-params.ts +16 -3
@@ -11,19 +11,22 @@ import { canonicalInterruptJson, digestInterruptJson } from "../../interrupt-ser
11
11
  import "../../interrupts.js";
12
12
  import { convertSchemaForStructuredOutput, convertSchemaToJsonSchema, isStandardSchema, parseWithStandardSchema } from "./tools/schema-converter.js";
13
13
  import { hashSchemaInput, normalizeApprovalSchema } from "./tools/approval-schema.js";
14
- import { INTERRUPT_BINDING_METADATA_KEY, InterruptResumeValidationError, readUnopenedInterruptBinding, validateInterruptResumeBatch } from "../../interrupt-resume.js";
14
+ import { INTERRUPT_BINDING_METADATA_KEY, InterruptResumeValidationError, readInterruptBinding, readUnopenedInterruptBinding, validateInterruptResumeBatch } from "../../interrupt-resume.js";
15
+ import { INTERRUPT_PAYLOAD_METADATA_KEY, createInterruptBinding, rehydrateInterruptRequest } from "../../interrupt-definition.js";
16
+ import { readGenericInterruptContinuation } from "../../generic-interrupt-continuation.js";
15
17
  import { normalizeToolResult } from "../../utilities/tool-result.js";
16
18
  import { isProviderExecutedToolCall } from "../../utilities/provider-executed.js";
17
19
  import { LazyToolManager } from "./tools/lazy-tool-manager.js";
18
20
  import { assertUniqueToolNames } from "./tools/unique-tool-names.js";
19
21
  import { MiddlewareAbortError, ToolCallManager, executeToolCalls } from "./tools/tool-calls.js";
20
22
  import { maxIterations } from "./agent-loop-strategies.js";
21
- import { convertMessagesToModelMessages, generateMessageId, modelMessageToUIMessage } from "./messages.js";
23
+ import { convertMessagesToModelMessages, generateMessageId, modelMessageToUIMessage, safeJsonStringify } from "./messages.js";
22
24
  import { MiddlewareRunner } from "./middleware/compose.js";
23
25
  import { provideSandboxRuntime } from "./middleware/sandbox-runtime.js";
24
26
  import { provideRunDisconnect } from "./middleware/run-disconnect.js";
25
27
  import { validateCapabilities } from "./middleware/validate.js";
26
28
  import { MCPManager } from "./mcp/manager.js";
29
+ import { provideGenericInterruptDefinitionRegistry } from "./middleware/generic-interrupts.js";
27
30
  import "./adapter.js";
28
31
  import { devtoolsMiddleware } from "@tanstack/ai-event-client";
29
32
  import { undoNullWidening } from "@tanstack/ai-utils";
@@ -51,38 +54,11 @@ function structuralInterruptFailure(error) {
51
54
  };
52
55
  }
53
56
  function normalizePublicInterruptBinding(value, expectedInterruptId) {
54
- if (value === null || typeof value !== "object" || Array.isArray(value)) return;
55
- const binding = Object.fromEntries(Object.entries(value));
56
- if (binding.interruptId !== expectedInterruptId || binding.v !== void 0 && binding.v !== 1 || typeof binding.interruptedRunId !== "string" || typeof binding.generation !== "number" || !Number.isInteger(binding.generation) || binding.generation < 0 || typeof binding.responseSchemaHash !== "string" || binding.expiresAt !== void 0 && typeof binding.expiresAt !== "string") return;
57
- const base = {
58
- v: 1,
59
- interruptId: binding.interruptId,
60
- interruptedRunId: binding.interruptedRunId,
61
- generation: binding.generation,
62
- responseSchemaHash: binding.responseSchemaHash,
63
- ...typeof binding.expiresAt === "string" ? { expiresAt: binding.expiresAt } : {}
64
- };
65
- if (binding.kind === "generic") return {
66
- kind: binding.kind,
67
- ...base
68
- };
69
- if (typeof binding.toolName !== "string" || typeof binding.toolCallId !== "string") return;
70
- if (binding.kind === "client-tool-execution" && typeof binding.outputSchemaHash === "string") return {
71
- kind: binding.kind,
72
- ...base,
73
- toolName: binding.toolName,
74
- toolCallId: binding.toolCallId,
75
- outputSchemaHash: binding.outputSchemaHash
76
- };
77
- if (binding.kind === "tool-approval" && Object.prototype.hasOwnProperty.call(binding, "originalArgs") && typeof binding.inputSchemaHash === "string" && typeof binding.approvalSchemaHash === "string") return {
78
- kind: binding.kind,
79
- ...base,
80
- toolName: binding.toolName,
81
- toolCallId: binding.toolCallId,
82
- originalArgs: binding.originalArgs,
83
- inputSchemaHash: binding.inputSchemaHash,
84
- approvalSchemaHash: binding.approvalSchemaHash
85
- };
57
+ return readInterruptBinding({
58
+ id: expectedInterruptId,
59
+ reason: "",
60
+ metadata: { [INTERRUPT_BINDING_METADATA_KEY]: value }
61
+ });
86
62
  }
87
63
  /**
88
64
  * Create typed options for the chat() function without executing.
@@ -120,12 +96,15 @@ function combineAbortSignals(a, b) {
120
96
  }
121
97
  var TextEngine = class {
122
98
  adapter;
99
+ interruptDefinitions;
123
100
  params;
124
101
  systemPrompts;
125
102
  tools;
126
103
  loopStrategy;
127
104
  toolCallManager;
128
105
  lazyToolManager;
106
+ /** A public interruption terminal must always have this run's start event. */
107
+ hasPublicRunStarted = false;
129
108
  initialMessageCount;
130
109
  requestId;
131
110
  streamId;
@@ -155,6 +134,8 @@ var TextEngine = class {
155
134
  finishedEvent = null;
156
135
  streamedToolErrorResults = /* @__PURE__ */ new Map();
157
136
  deferredToolCallRunFinishedChunks = [];
137
+ /** The model terminal is held until afterModel can choose an interrupt. */
138
+ deferredModelRunFinishedChunks = [];
158
139
  earlyTermination = false;
159
140
  toolPhase = "continue";
160
141
  cyclePhase = "processText";
@@ -164,6 +145,8 @@ var TextEngine = class {
164
145
  resumeClientToolResults = /* @__PURE__ */ new Map();
165
146
  resumeDeniedToolResults = /* @__PURE__ */ new Map();
166
147
  resumeCancelledToolCallIds = /* @__PURE__ */ new Set();
148
+ resumeGenericInterrupts = /* @__PURE__ */ new Map();
149
+ resumeGenericInterruptRequests = /* @__PURE__ */ new Map();
167
150
  threadId;
168
151
  runIdOverride;
169
152
  parentRunIdOverride;
@@ -185,6 +168,8 @@ var TextEngine = class {
185
168
  disconnectListeners = [];
186
169
  logger;
187
170
  structuredOutputResult = null;
171
+ structuredOutputMessageId = null;
172
+ structuredOutputMessageCreatedAt = null;
188
173
  combinedStartEmitted = false;
189
174
  combinedStructuredMessageId = null;
190
175
  validatedStructuredOutput = void 0;
@@ -195,6 +180,7 @@ var TextEngine = class {
195
180
  constructor(config, logger) {
196
181
  this.logger = logger;
197
182
  this.adapter = config.adapter;
183
+ this.interruptDefinitions = new Map((config.params.interrupts ?? []).map((definition) => [definition.id, definition]));
198
184
  this.finalStructuredOutput = config.finalStructuredOutput;
199
185
  this.params = config.params;
200
186
  this.systemPrompts = config.params.systemPrompts || [];
@@ -266,6 +252,7 @@ var TextEngine = class {
266
252
  this.disconnectListeners.push(listener);
267
253
  if (this.disconnected) this.runDisconnectListener(listener);
268
254
  } });
255
+ provideGenericInterruptDefinitionRegistry(this.middlewareCtx, { definitions: this.interruptDefinitions });
269
256
  provideSandboxRuntime(this.middlewareCtx, {
270
257
  logger: this.logger,
271
258
  emit: (event) => {
@@ -324,7 +311,21 @@ var TextEngine = class {
324
311
  const transformedConfig = await this.middlewareRunner.runOnConfig(this.middlewareCtx, initialConfig);
325
312
  this.applyMiddlewareConfig(transformedConfig);
326
313
  await this.applyEphemeralInterruptResume(transformedConfig);
314
+ await this.applyDurableGenericInterruptResolution();
327
315
  await this.middlewareRunner.runOnStart(this.middlewareCtx);
316
+ if (this.earlyTermination) {
317
+ yield* this.emitSuccessfulEarlyTermination();
318
+ if (!this.terminalHookCalled) {
319
+ this.terminalHookCalled = true;
320
+ await this.middlewareRunner.runOnFinish(this.middlewareCtx, {
321
+ finishReason: this.lastFinishReason,
322
+ duration: Date.now() - this.streamStartTime,
323
+ content: this.accumulatedContent,
324
+ usage: this.finishedEvent?.usage
325
+ });
326
+ }
327
+ return;
328
+ }
328
329
  if ((yield* this.checkForPendingToolCalls()) === "wait") return;
329
330
  if (!(!!this.finalStructuredOutput && this.tools.length === 0 && this.finalStructuredOutput.nativeCombined !== true)) do {
330
331
  if (this.earlyTermination || this.isCancelled()) return;
@@ -336,7 +337,19 @@ var TextEngine = class {
336
337
  const iterConfig = this.buildMiddlewareConfig();
337
338
  const iterTransformedConfig = await this.middlewareRunner.runOnConfig(this.middlewareCtx, iterConfig);
338
339
  this.applyMiddlewareConfig(iterTransformedConfig);
340
+ if (yield* this.emitBoundaryInterrupts("beforeModel", this.createSyntheticFinishedEvent())) {
341
+ this.setToolPhase("wait");
342
+ return;
343
+ }
339
344
  yield* this.streamModelResponse();
345
+ if (yield* this.emitBoundaryInterrupts("afterModel", this.finishedEvent ?? this.createSyntheticFinishedEvent())) {
346
+ this.setToolPhase("wait");
347
+ return;
348
+ }
349
+ if (this.shouldExecuteToolPhase()) {
350
+ this.deferredToolCallRunFinishedChunks.push(...this.deferredModelRunFinishedChunks);
351
+ this.deferredModelRunFinishedChunks = [];
352
+ } else yield* this.flushDeferredModelRunFinishedChunks();
340
353
  } else yield* this.processToolCalls();
341
354
  this.endCycle();
342
355
  } while (await this.shouldContinue());
@@ -358,7 +371,7 @@ var TextEngine = class {
358
371
  duration: Date.now() - this.streamStartTime
359
372
  });
360
373
  } else {
361
- this.addTerminalReasoningMessage();
374
+ this.addTerminalAssistantMessages();
362
375
  this.terminalHookCalled = true;
363
376
  await this.middlewareRunner.runOnFinish(this.middlewareCtx, {
364
377
  finishReason: this.lastFinishReason,
@@ -513,7 +526,10 @@ var TextEngine = class {
513
526
  if (chunk.type === EventType.CUSTOM && chunk.name === "structured-output.start") {
514
527
  this.combinedStartEmitted = true;
515
528
  const startValue = chunk.value;
516
- if (startValue && typeof startValue === "object" && "messageId" in startValue && typeof startValue.messageId === "string") this.combinedStructuredMessageId = startValue.messageId;
529
+ if (startValue && typeof startValue === "object" && "messageId" in startValue && typeof startValue.messageId === "string") {
530
+ this.combinedStructuredMessageId = startValue.messageId;
531
+ this.captureStructuredOutputMessageIdentity(startValue.messageId);
532
+ }
517
533
  }
518
534
  let outboundChunk = chunk;
519
535
  if (this.finalStructuredOutput?.source === "event" && chunk.type === EventType.CUSTOM && chunk.name === "structured-output.complete") {
@@ -526,6 +542,11 @@ var TextEngine = class {
526
542
  };
527
543
  this.combinedCompleteEmitted = true;
528
544
  const value = chunk.value;
545
+ const completeMessageId = readCustomEventMessageId(value);
546
+ if (completeMessageId) {
547
+ this.combinedStructuredMessageId = completeMessageId;
548
+ this.captureStructuredOutputMessageIdentity(completeMessageId);
549
+ }
529
550
  if (object !== parsed.object && value && typeof value === "object") outboundChunk = {
530
551
  ...chunk,
531
552
  value: {
@@ -558,10 +579,15 @@ var TextEngine = class {
558
579
  const suppressAgentLifecycle = !!this.finalStructuredOutput && this.finalStructuredOutput.yieldChunks && this.finalStructuredOutput.nativeCombined !== true;
559
580
  for (const outputChunk of outputChunks) {
560
581
  if (suppressAgentLifecycle && (outputChunk.type === EventType.RUN_STARTED || outputChunk.type === EventType.RUN_FINISHED)) continue;
582
+ if (outputChunk.type === EventType.RUN_FINISHED) {
583
+ this.deferredModelRunFinishedChunks.push(outputChunk);
584
+ continue;
585
+ }
561
586
  if (this.shouldDeferToolCallRunFinished(outputChunk)) {
562
587
  this.deferredToolCallRunFinishedChunks.push(outputChunk);
563
588
  continue;
564
589
  }
590
+ if (outputChunk.type === EventType.RUN_STARTED) this.hasPublicRunStarted = true;
565
591
  this.logger.output(`type=${outputChunk.type}`, { chunk: outputChunk });
566
592
  yield outputChunk;
567
593
  this.middlewareCtx.chunkIndex++;
@@ -617,6 +643,10 @@ var TextEngine = class {
617
643
  this.streamIdentityCaptured = true;
618
644
  }
619
645
  }
646
+ captureStructuredOutputMessageIdentity(messageId) {
647
+ this.structuredOutputMessageId = messageId;
648
+ this.structuredOutputMessageCreatedAt ??= /* @__PURE__ */ new Date();
649
+ }
620
650
  handleToolCallStartEvent(chunk) {
621
651
  if (typeof chunk.parentMessageId === "string" && chunk.parentMessageId !== "") this.captureStreamMessageIdentity(chunk.parentMessageId);
622
652
  this.toolCallManager.addToolCallStartEvent(chunk);
@@ -714,6 +744,11 @@ var TextEngine = class {
714
744
  if (deferredErrorResults.length > 0) for (const chunk of this.buildToolResultChunks(deferredErrorResults, finishEvent, argsMap)) yield* this.pipeThroughMiddleware(chunk);
715
745
  return "continue";
716
746
  }
747
+ this.middlewareCtx.phase = "beforeTools";
748
+ if (yield* this.emitBoundaryInterrupts("beforeTools", finishEvent, executablePendingCalls)) {
749
+ this.setToolPhase("wait");
750
+ return "wait";
751
+ }
717
752
  const { approvals, clientToolResults } = this.collectClientState();
718
753
  const generator = executeToolCalls(executablePendingCalls, this.resolveExecutableTools(executablePendingCalls), approvals, clientToolResults, (eventName, data) => this.createCustomEventChunk(eventName, data), {
719
754
  onBeforeToolCall: async (toolCall, tool, args) => {
@@ -802,6 +837,10 @@ var TextEngine = class {
802
837
  return;
803
838
  }
804
839
  this.middlewareCtx.phase = "beforeTools";
840
+ if (yield* this.emitBoundaryInterrupts("beforeTools", finishEvent, executableToolCalls)) {
841
+ this.setToolPhase("wait");
842
+ return;
843
+ }
805
844
  const { approvals, clientToolResults } = this.collectClientState();
806
845
  const generator = executeToolCalls(executableToolCalls, this.resolveExecutableTools(executableToolCalls), approvals, clientToolResults, (eventName, data) => this.createCustomEventChunk(eventName, data), {
807
846
  onBeforeToolCall: async (toolCall, tool, args) => {
@@ -842,14 +881,22 @@ var TextEngine = class {
842
881
  needsApproval: executionResult.needsApproval,
843
882
  needsClientExecution: executionResult.needsClientExecution
844
883
  });
884
+ const afterToolBoundaryChunks = this.buildToolResultChunks(allResults, finishEvent);
885
+ const afterToolRequests = await this.middlewareRunner.runOnInterruptBoundary(this.middlewareCtx);
886
+ if (afterToolRequests.length > 0) {
887
+ for (const chunk of afterToolBoundaryChunks) yield* this.pipeThroughMiddleware(chunk);
888
+ yield* this.emitBoundaryInterrupts("afterTools", finishEvent, toolCalls, afterToolRequests);
889
+ this.setToolPhase("wait");
890
+ return;
891
+ }
845
892
  if (executionResult.needsApproval.length > 0 || executionResult.needsClientExecution.length > 0) {
846
- if (allResults.length > 0) for (const chunk of this.buildToolResultChunks(allResults, finishEvent)) yield* this.pipeThroughMiddleware(chunk);
893
+ if (allResults.length > 0) for (const chunk of afterToolBoundaryChunks) yield* this.pipeThroughMiddleware(chunk);
847
894
  const emitted = yield* this.emitActionableInterruptBoundary(finishEvent, executionResult.needsApproval, executionResult.needsClientExecution);
848
895
  this.setToolPhase(emitted ? "wait" : "stop");
849
896
  return;
850
897
  }
851
898
  yield* this.flushDeferredToolCallRunFinishedChunks();
852
- const toolResultChunks = this.buildToolResultChunks(allResults, finishEvent);
899
+ const toolResultChunks = afterToolBoundaryChunks;
853
900
  for (const chunk of toolResultChunks) yield* this.pipeThroughMiddleware(chunk);
854
901
  if (this.lazyToolManager.hasNewlyDiscoveredTools()) {
855
902
  this.tools = this.lazyToolManager.getActiveTools();
@@ -871,6 +918,37 @@ var TextEngine = class {
871
918
  }
872
919
  this.deferredToolCallRunFinishedChunks = [];
873
920
  }
921
+ *flushDeferredModelRunFinishedChunks() {
922
+ for (const chunk of this.deferredModelRunFinishedChunks) {
923
+ this.logger.output(`type=${chunk.type}`, { chunk });
924
+ yield chunk;
925
+ this.middlewareCtx.chunkIndex++;
926
+ }
927
+ this.deferredModelRunFinishedChunks = [];
928
+ }
929
+ async *emitSyntheticRunStarted(finishEvent) {
930
+ if (this.hasPublicRunStarted) return;
931
+ yield* this.pipeThroughMiddleware({
932
+ type: EventType.RUN_STARTED,
933
+ runId: finishEvent.runId,
934
+ threadId: finishEvent.threadId,
935
+ model: finishEvent.model,
936
+ timestamp: Date.now()
937
+ });
938
+ }
939
+ async *emitSuccessfulEarlyTermination() {
940
+ this.lastFinishReason = "stop";
941
+ const finishEvent = {
942
+ ...this.createSyntheticFinishedEvent(),
943
+ finishReason: "stop"
944
+ };
945
+ yield* this.emitSyntheticRunStarted(finishEvent);
946
+ yield* this.pipeThroughMiddleware({
947
+ ...finishEvent,
948
+ timestamp: Date.now(),
949
+ outcome: { type: "success" }
950
+ });
951
+ }
874
952
  discardDeferredToolCallRunFinishedChunks() {
875
953
  this.deferredToolCallRunFinishedChunks = [];
876
954
  }
@@ -889,18 +967,61 @@ var TextEngine = class {
889
967
  }];
890
968
  this.middlewareCtx.messages = this.messages;
891
969
  }
892
- addTerminalReasoningMessage() {
970
+ addTerminalAssistantMessages() {
893
971
  this.finalizeCurrentThinkingStep();
894
- if (this.accumulatedThinking.length === 0) return;
895
- const messages = this.middlewareCtx.messages;
896
- if (messages.some((message) => message.role === "assistant" && message.id === this.currentMessageId)) return;
897
- this.messages = [...messages, {
898
- role: "assistant",
899
- content: this.accumulatedContent || null,
900
- id: this.currentMessageId ?? void 0,
901
- createdAt: this.currentMessageCreatedAt ?? void 0,
902
- thinking: this.accumulatedThinking
903
- }];
972
+ const structuredResult = this.structuredOutputResult;
973
+ const raw = structuredResult ? structuredResult.rawText || safeJsonStringify(structuredResult.data) : "";
974
+ const structuredOutput = structuredResult ? {
975
+ type: "structured-output",
976
+ status: "complete",
977
+ data: structuredResult.data,
978
+ partial: structuredResult.data,
979
+ raw,
980
+ ...structuredResult.reasoning !== void 0 ? { reasoning: structuredResult.reasoning } : {}
981
+ } : void 0;
982
+ const nativeCombined = this.finalStructuredOutput?.nativeCombined === true;
983
+ const eventSourced = this.finalStructuredOutput?.source === "event";
984
+ const structuredId = this.structuredOutputMessageId ?? this.combinedStructuredMessageId ?? this.currentMessageId ?? this.createId("msg");
985
+ const splitStructuredMessage = Boolean(structuredOutput) && (!nativeCombined || eventSourced) && this.currentMessageId != null && structuredId !== this.currentMessageId;
986
+ const messages = [...this.middlewareCtx.messages];
987
+ const existingStructuredIndex = messages.findIndex((message) => message.role === "assistant" && message.id === structuredId);
988
+ const currentTurnAlreadyRecorded = messages.some((message) => message.role === "assistant" && message.id === this.currentMessageId);
989
+ const thinking = this.accumulatedThinking.length > 0 ? this.accumulatedThinking : void 0;
990
+ const startedLength = messages.length;
991
+ if (structuredOutput && existingStructuredIndex >= 0) {
992
+ const existing = messages[existingStructuredIndex];
993
+ if (existing) messages[existingStructuredIndex] = {
994
+ ...existing,
995
+ content: raw || existing.content,
996
+ structuredOutput
997
+ };
998
+ } else if (structuredOutput && !splitStructuredMessage) {
999
+ if (!currentTurnAlreadyRecorded) messages.push({
1000
+ role: "assistant",
1001
+ content: this.accumulatedContent || raw || null,
1002
+ id: structuredId,
1003
+ createdAt: this.currentMessageCreatedAt ?? this.structuredOutputMessageCreatedAt ?? /* @__PURE__ */ new Date(),
1004
+ structuredOutput,
1005
+ ...thinking ? { thinking } : {}
1006
+ });
1007
+ } else {
1008
+ if (!currentTurnAlreadyRecorded && (this.accumulatedContent !== "" || thinking)) messages.push({
1009
+ role: "assistant",
1010
+ content: this.accumulatedContent || null,
1011
+ id: this.currentMessageId ?? this.createId("msg"),
1012
+ createdAt: this.currentMessageCreatedAt ?? /* @__PURE__ */ new Date(),
1013
+ ...thinking ? { thinking } : {}
1014
+ });
1015
+ if (structuredOutput) messages.push({
1016
+ role: "assistant",
1017
+ content: raw || null,
1018
+ id: structuredId,
1019
+ createdAt: this.structuredOutputMessageCreatedAt ?? /* @__PURE__ */ new Date(),
1020
+ structuredOutput
1021
+ });
1022
+ }
1023
+ if (messages.length === startedLength && existingStructuredIndex < 0) return;
1024
+ this.messages = messages;
904
1025
  this.middlewareCtx.messages = this.messages;
905
1026
  }
906
1027
  /**
@@ -943,7 +1064,10 @@ var TextEngine = class {
943
1064
  clientToolResults
944
1065
  };
945
1066
  }
946
- buildActionableInterrupts(approvals, clientRequests) {
1067
+ genericInterruptId() {
1068
+ return this.createId("interrupt");
1069
+ }
1070
+ buildActionableInterrupts(approvals, clientRequests, genericRequests = [], genericInterruptIds = []) {
947
1071
  const interrupts = [];
948
1072
  for (const approval of approvals) {
949
1073
  const tool = this.tools.find((candidate) => candidate.name === approval.toolName);
@@ -997,15 +1121,47 @@ var TextEngine = class {
997
1121
  }
998
1122
  });
999
1123
  }
1124
+ for (const [index, request] of genericRequests.entries()) {
1125
+ const batchIndex = interrupts.length;
1126
+ const id = genericInterruptIds[index];
1127
+ if (!id) throw new Error("Generic interrupt id is unavailable.");
1128
+ const preEmission = createInterruptBinding(request, { batchIndex });
1129
+ interrupts.push({
1130
+ id,
1131
+ reason: request.reason,
1132
+ message: request.message,
1133
+ ...preEmission.descriptor.responseSchemaCanonicalJson !== void 0 ? { responseSchema: JSON.parse(preEmission.descriptor.responseSchemaCanonicalJson) } : {},
1134
+ ...request.expiresAt !== void 0 ? { expiresAt: request.expiresAt } : {},
1135
+ metadata: {
1136
+ [interruptBindingMetadataKey]: {
1137
+ v: 1,
1138
+ kind: "generic",
1139
+ interruptId: id,
1140
+ definitionId: preEmission.descriptor.definitionId,
1141
+ key: preEmission.descriptor.key,
1142
+ batchIndex,
1143
+ ...request.expiresAt !== void 0 ? { expiresAt: request.expiresAt } : {},
1144
+ ...preEmission.descriptor.payloadSchemaHash ? { payloadSchemaHash: preEmission.descriptor.payloadSchemaHash } : {},
1145
+ ...preEmission.descriptor.responseSchemaHash !== void 0 ? { responseSchemaHash: preEmission.descriptor.responseSchemaHash } : {}
1146
+ },
1147
+ ...preEmission.payload !== void 0 ? { [INTERRUPT_PAYLOAD_METADATA_KEY]: preEmission.payload } : {}
1148
+ }
1149
+ });
1150
+ }
1151
+ const ids = /* @__PURE__ */ new Set();
1152
+ for (const interrupt of interrupts) {
1153
+ if (ids.has(interrupt.id)) throw new Error(`Duplicate interrupt id in final batch: ${interrupt.id}`);
1154
+ ids.add(interrupt.id);
1155
+ }
1000
1156
  return interrupts;
1001
1157
  }
1002
- buildInterruptFinishedChunk(finishEvent, approvals, clientRequests) {
1158
+ buildInterruptFinishedChunk(finishEvent, approvals, clientRequests, genericRequests = [], genericInterruptIds) {
1003
1159
  return {
1004
1160
  ...finishEvent,
1005
1161
  timestamp: Date.now(),
1006
1162
  outcome: {
1007
1163
  type: "interrupt",
1008
- interrupts: this.buildActionableInterrupts(approvals, clientRequests)
1164
+ interrupts: this.buildActionableInterrupts(approvals, clientRequests, genericRequests, genericInterruptIds)
1009
1165
  }
1010
1166
  };
1011
1167
  }
@@ -1013,7 +1169,7 @@ var TextEngine = class {
1013
1169
  const messages = this.messages.map((message, index) => {
1014
1170
  const content = typeof message.content === "string" ? message.content : message.content === null ? void 0 : JSON.stringify(message.content);
1015
1171
  const id = message.id || `snapshot_${this.runIdOverride ?? this.requestId}_${index}`;
1016
- const parts = message.role === "assistant" && message.thinking?.length ? modelMessageToUIMessage(message, id).parts : void 0;
1172
+ const parts = message.role === "assistant" && (message.thinking?.length || message.structuredOutput) ? modelMessageToUIMessage(message, id).parts : void 0;
1017
1173
  return {
1018
1174
  id,
1019
1175
  role: message.role,
@@ -1097,8 +1253,10 @@ var TextEngine = class {
1097
1253
  };
1098
1254
  yield* this.pipeThroughMiddleware(this.buildInterruptRunErrorChunk(error));
1099
1255
  }
1100
- async *emitActionableInterruptBoundary(finishEvent, approvals, clientRequests) {
1101
- const terminal = this.completeEphemeralInterruptBindings(this.buildInterruptFinishedChunk(finishEvent, approvals, clientRequests));
1256
+ async *emitActionableInterruptBoundary(finishEvent, approvals, clientRequests, genericRequests = []) {
1257
+ yield* this.emitSyntheticRunStarted(finishEvent);
1258
+ const genericInterruptIds = genericRequests.map(() => this.genericInterruptId());
1259
+ const terminal = this.completeEphemeralInterruptBindings(this.buildInterruptFinishedChunk(finishEvent, approvals, clientRequests, genericRequests, genericInterruptIds));
1102
1260
  let terminalOutputs;
1103
1261
  try {
1104
1262
  terminalOutputs = await this.middlewareRunner.runOnChunk(this.middlewareCtx, terminal);
@@ -1119,6 +1277,59 @@ var TextEngine = class {
1119
1277
  }
1120
1278
  return true;
1121
1279
  }
1280
+ async *emitBoundaryInterrupts(phase, finishEvent, toolCalls = [], requests) {
1281
+ this.middlewareCtx.phase = phase;
1282
+ const boundaryRequests = requests ?? await this.middlewareRunner.runOnInterruptBoundary(this.middlewareCtx);
1283
+ if (boundaryRequests.length === 0) return false;
1284
+ for (const request of boundaryRequests) if (this.interruptDefinitions.get(request.definition.id) !== request.definition) throw new Error(`Generic interrupt definition ${request.definition.id} is not registered on this chat.`);
1285
+ if (phase === "afterModel") {
1286
+ if (this.toolCallManager.hasToolCalls()) this.addAssistantToolCallMessage(this.toolCallManager.getToolCalls());
1287
+ else this.addAssistantTextMessageForInterrupt();
1288
+ }
1289
+ const actionable = this.getBoundaryActionableToolRequests(toolCalls);
1290
+ yield* this.emitActionableInterruptBoundary(finishEvent, actionable.approvals, actionable.clientRequests, boundaryRequests);
1291
+ return true;
1292
+ }
1293
+ addAssistantTextMessageForInterrupt() {
1294
+ if (this.accumulatedContent.length === 0) return;
1295
+ this.messages = [...this.messages, {
1296
+ role: "assistant",
1297
+ content: this.accumulatedContent
1298
+ }];
1299
+ this.middlewareCtx.messages = this.messages;
1300
+ }
1301
+ getBoundaryActionableToolRequests(toolCalls) {
1302
+ const { approvals, clientToolResults } = this.collectClientState();
1303
+ const approvalRequests = [];
1304
+ const clientRequests = [];
1305
+ for (const toolCall of toolCalls) {
1306
+ const tool = this.resolveExecutableTools([toolCall]).find((candidate) => candidate.name === toolCall.function.name);
1307
+ if (!tool) continue;
1308
+ let input = {};
1309
+ try {
1310
+ const parsed = JSON.parse(toolCall.function.arguments.trim() || "{}");
1311
+ input = parsed && typeof parsed === "object" ? parsed : {};
1312
+ } catch {
1313
+ input = {};
1314
+ }
1315
+ const approvalId = `approval_${toolCall.id}`;
1316
+ if (tool.needsApproval && !approvals.has(approvalId)) approvalRequests.push({
1317
+ toolCallId: toolCall.id,
1318
+ toolName: toolCall.function.name,
1319
+ input,
1320
+ approvalId
1321
+ });
1322
+ else if (!tool.execute && !clientToolResults.has(toolCall.id) && !this.resumeCancelledToolCallIds.has(toolCall.id)) clientRequests.push({
1323
+ toolCallId: toolCall.id,
1324
+ toolName: toolCall.function.name,
1325
+ input
1326
+ });
1327
+ }
1328
+ return {
1329
+ approvals: approvalRequests,
1330
+ clientRequests
1331
+ };
1332
+ }
1122
1333
  completeEphemeralInterruptBindings(chunk) {
1123
1334
  if (chunk.type !== EventType.RUN_FINISHED || chunk.outcome?.type !== "interrupt") return chunk;
1124
1335
  const interruptedRunId = this.runIdOverride ?? this.requestId;
@@ -1265,7 +1476,7 @@ var TextEngine = class {
1265
1476
  createSyntheticFinishedEvent() {
1266
1477
  return {
1267
1478
  type: "RUN_FINISHED",
1268
- runId: this.createId("pending"),
1479
+ runId: this.runIdOverride ?? this.requestId,
1269
1480
  threadId: this.threadId,
1270
1481
  model: this.params.model,
1271
1482
  timestamp: Date.now(),
@@ -1451,6 +1662,7 @@ var TextEngine = class {
1451
1662
  const buildSynthesizedStart = (timestamp = Date.now()) => {
1452
1663
  const idForStart = structuredMessageId ?? generateMessageId();
1453
1664
  structuredMessageId = idForStart;
1665
+ this.captureStructuredOutputMessageIdentity(idForStart);
1454
1666
  return {
1455
1667
  type: EventType.CUSTOM,
1456
1668
  name: "structured-output.start",
@@ -1468,7 +1680,10 @@ var TextEngine = class {
1468
1680
  if (!startEmitted && chunk.type === EventType.CUSTOM && chunk.name === "structured-output.start") startEmitted = true;
1469
1681
  if (!structuredMessageId) {
1470
1682
  const extracted = extractMessageId(chunk);
1471
- if (extracted) structuredMessageId = extracted;
1683
+ if (extracted) {
1684
+ structuredMessageId = extracted;
1685
+ this.captureStructuredOutputMessageIdentity(extracted);
1686
+ }
1472
1687
  }
1473
1688
  if (this.finalStructuredOutput.yieldChunks) {
1474
1689
  if (!startEmitted && (chunk.type === EventType.TEXT_MESSAGE_START || chunk.type === EventType.TEXT_MESSAGE_CONTENT || chunk.type === EventType.TEXT_MESSAGE_END)) {
@@ -1495,7 +1710,8 @@ var TextEngine = class {
1495
1710
  const object = this.finalStructuredOutput.normalize ? this.finalStructuredOutput.normalize(parsed.object) : parsed.object;
1496
1711
  this.structuredOutputResult = {
1497
1712
  data: object,
1498
- rawText: parsed.raw
1713
+ rawText: parsed.raw,
1714
+ ...parsed.reasoning !== void 0 ? { reasoning: parsed.reasoning } : {}
1499
1715
  };
1500
1716
  const value = chunk.value;
1501
1717
  if (object !== parsed.object && value && typeof value === "object") outboundChunk = {
@@ -1752,6 +1968,7 @@ var TextEngine = class {
1752
1968
  input: toolInputs.get(toolCall.id) ?? {}
1753
1969
  });
1754
1970
  }
1971
+ const genericPending = this.getGenericContinuationPending(interruptedRunId);
1755
1972
  const pending = this.buildActionableInterrupts(approvalRequests, clientRequests).flatMap((descriptor) => {
1756
1973
  const unopened = readUnopenedInterruptBinding(descriptor);
1757
1974
  return unopened ? [{
@@ -1764,6 +1981,7 @@ var TextEngine = class {
1764
1981
  }
1765
1982
  }] : [];
1766
1983
  });
1984
+ pending.push(...genericPending);
1767
1985
  const validated = await validateInterruptResumeBatch({
1768
1986
  threadId: this.threadId,
1769
1987
  interruptedRunId,
@@ -1779,12 +1997,124 @@ var TextEngine = class {
1779
1997
  ...validated.resumeToolState,
1780
1998
  approvals
1781
1999
  });
2000
+ const genericResolutions = validated.resumeToolState.genericInterrupts;
2001
+ if (genericPending.length > 0 && genericResolutions) {
2002
+ const resolutions = genericPending.sort((left, right) => {
2003
+ return (left.binding.kind === "generic" ? left.binding.batchIndex ?? 0 : 0) - (right.binding.kind === "generic" ? right.binding.batchIndex ?? 0 : 0);
2004
+ }).flatMap((record) => {
2005
+ const resolution = genericResolutions.get(record.interruptId);
2006
+ if (!resolution || !record.genericRequest) return [];
2007
+ return [resolution.status === "resolved" ? {
2008
+ request: record.genericRequest,
2009
+ status: "resolved",
2010
+ response: resolution.payload
2011
+ } : {
2012
+ request: record.genericRequest,
2013
+ status: "cancelled"
2014
+ }];
2015
+ });
2016
+ const policy = await this.middlewareRunner.runOnInterruptResolution(this.middlewareCtx, {
2017
+ for: (definition) => resolutions.filter((resolution) => resolution.request.definition === definition),
2018
+ all: (...definitions) => definitions.length === 0 ? resolutions : resolutions.filter((resolution) => definitions.includes(resolution.request.definition))
2019
+ });
2020
+ if (policy.toolResume === "stop") this.earlyTermination = true;
2021
+ else if (policy.toolResume === "cancel") for (const request of pendingToolCalls) this.resumeCancelledToolCallIds.add(request.id);
2022
+ }
2023
+ }
2024
+ getGenericContinuationPending(interruptedRunId) {
2025
+ const fail = (message) => {
2026
+ throw new InterruptResumeValidationError([{
2027
+ scope: "batch",
2028
+ threadId: this.threadId,
2029
+ interruptedRunId,
2030
+ generation: 0,
2031
+ interruptIds: [],
2032
+ code: "stale",
2033
+ message,
2034
+ source: "server",
2035
+ retryable: false
2036
+ }]);
2037
+ };
2038
+ const pending = [];
2039
+ const ids = /* @__PURE__ */ new Set();
2040
+ const batchIndexes = /* @__PURE__ */ new Set();
2041
+ for (const resumeItem of this.params.resume ?? []) {
2042
+ const parsed = readGenericInterruptContinuation(resumeItem.metadata);
2043
+ if (parsed.status === "absent") continue;
2044
+ if (parsed.status === "invalid") return fail(parsed.message);
2045
+ const entry = parsed.value;
2046
+ const id = resumeItem.interruptId;
2047
+ const definition = this.interruptDefinitions.get(entry.definitionId);
2048
+ if (!definition) return fail(`Generic interrupt definition ${entry.definitionId} is unavailable.`);
2049
+ if (ids.has(id) || batchIndexes.has(entry.batchIndex)) return fail("Generic interrupt continuation contains duplicate entries.");
2050
+ ids.add(id);
2051
+ batchIndexes.add(entry.batchIndex);
2052
+ let request;
2053
+ try {
2054
+ request = rehydrateInterruptRequest(definition, {
2055
+ key: entry.key,
2056
+ reason: entry.reason,
2057
+ message: entry.message,
2058
+ ...typeof entry.expiresAt === "string" ? { expiresAt: entry.expiresAt } : {},
2059
+ ...Object.prototype.hasOwnProperty.call(entry, "payload") ? { payload: entry.payload } : {}
2060
+ });
2061
+ } catch (error) {
2062
+ return fail(`Generic interrupt continuation ${id} is invalid: ${error instanceof Error ? error.message : String(error)}`);
2063
+ }
2064
+ const emitted = createInterruptBinding(request, { batchIndex: entry.batchIndex });
2065
+ if (entry.responseSchemaHash !== emitted.descriptor.responseSchemaHash || entry.payloadSchemaHash !== emitted.descriptor.payloadSchemaHash) return fail(`Generic interrupt continuation ${id} does not match its definition.`);
2066
+ pending.push({
2067
+ interruptId: id,
2068
+ payload: {
2069
+ id,
2070
+ ...emitted.descriptor.responseSchemaCanonicalJson !== void 0 ? { responseSchema: JSON.parse(emitted.descriptor.responseSchemaCanonicalJson) } : {}
2071
+ },
2072
+ binding: {
2073
+ v: 1,
2074
+ kind: "generic",
2075
+ interruptId: id,
2076
+ interruptedRunId,
2077
+ generation: 0,
2078
+ definitionId: entry.definitionId,
2079
+ key: entry.key,
2080
+ batchIndex: entry.batchIndex,
2081
+ ...typeof entry.expiresAt === "string" ? { expiresAt: entry.expiresAt } : {},
2082
+ ...emitted.descriptor.payloadSchemaHash ? { payloadSchemaHash: emitted.descriptor.payloadSchemaHash } : {},
2083
+ ...entry.responseSchemaHash !== void 0 ? { responseSchemaHash: entry.responseSchemaHash } : {}
2084
+ },
2085
+ genericRequest: request
2086
+ });
2087
+ }
2088
+ return pending;
1782
2089
  }
1783
2090
  applyResumeToolState(state) {
1784
2091
  if (state?.approvals) for (const [approvalId, resolution] of state.approvals) this.resumeApprovals.set(approvalId, resolution);
1785
2092
  if (state?.clientToolResults) for (const [toolCallId, result] of state.clientToolResults) this.resumeClientToolResults.set(toolCallId, result);
1786
2093
  if (state?.deniedToolResults) for (const [toolCallId, result] of state.deniedToolResults) this.resumeDeniedToolResults.set(toolCallId, result);
1787
2094
  if (state?.cancelledToolCallIds) for (const toolCallId of state.cancelledToolCallIds) this.resumeCancelledToolCallIds.add(toolCallId);
2095
+ if (state?.genericInterrupts) for (const [interruptId, resolution] of state.genericInterrupts) this.resumeGenericInterrupts.set(interruptId, resolution);
2096
+ if (state?.genericInterruptRequests) for (const [interruptId, request] of state.genericInterruptRequests) this.resumeGenericInterruptRequests.set(interruptId, request);
2097
+ }
2098
+ async applyDurableGenericInterruptResolution() {
2099
+ if (this.resumeGenericInterruptRequests.size === 0) return;
2100
+ const resolutions = [...this.resumeGenericInterruptRequests.entries()].flatMap(([interruptId, request]) => {
2101
+ const resolution = this.resumeGenericInterrupts.get(interruptId);
2102
+ if (!resolution) return [];
2103
+ return [resolution.status === "resolved" ? {
2104
+ request,
2105
+ status: "resolved",
2106
+ response: resolution.payload
2107
+ } : {
2108
+ request,
2109
+ status: "cancelled"
2110
+ }];
2111
+ });
2112
+ const policy = await this.middlewareRunner.runOnInterruptResolution(this.middlewareCtx, {
2113
+ for: (definition) => resolutions.filter((resolution) => resolution.request.definition === definition),
2114
+ all: (...definitions) => definitions.length === 0 ? resolutions : resolutions.filter((resolution) => definitions.includes(resolution.request.definition))
2115
+ });
2116
+ if (policy.toolResume === "stop") this.earlyTermination = true;
2117
+ else if (policy.toolResume === "cancel") for (const toolCall of this.getPendingToolCallsFromMessages()) this.resumeCancelledToolCallIds.add(toolCall.id);
1788
2118
  }
1789
2119
  applyMiddlewareConfig(config) {
1790
2120
  this.applyResumeToolState(config.resumeToolState);
@@ -1813,6 +2143,7 @@ var TextEngine = class {
1813
2143
  async *pipeThroughMiddleware(chunk) {
1814
2144
  const outputChunks = await this.middlewareRunner.runOnChunk(this.middlewareCtx, chunk);
1815
2145
  for (const outputChunk of outputChunks) {
2146
+ if (outputChunk.type === EventType.RUN_STARTED) this.hasPublicRunStarted = true;
1816
2147
  yield outputChunk;
1817
2148
  this.middlewareCtx.chunkIndex++;
1818
2149
  }
@@ -1914,28 +2245,47 @@ var TextEngine = class {
1914
2245
  * ```
1915
2246
  */
1916
2247
  function chat(options) {
1917
- validateCapabilities(options.middleware ?? [], options.adapter);
2248
+ validateInterruptDefinitions(options.interrupts);
2249
+ validateCapabilities(readRuntimeMiddleware(options.middleware) ?? [], options.adapter);
1918
2250
  if (options.tools) assertUniqueToolNames(options.tools);
1919
2251
  const { outputSchema, stream } = options;
1920
- if (outputSchema && stream === true) return runStreamingStructuredOutput({
1921
- ...options,
2252
+ if (outputSchema && stream === true) return runStreamingStructuredOutput(toRuntimeTextActivityOptions(options, {
1922
2253
  outputSchema,
1923
- stream
1924
- });
1925
- if (outputSchema) return runAgenticStructuredOutput({
1926
- ...options,
1927
- outputSchema
1928
- });
1929
- if (stream === false) return runNonStreamingText({
1930
- ...options,
2254
+ stream: true
2255
+ }));
2256
+ if (outputSchema) return runAgenticStructuredOutput(toRuntimeTextActivityOptions(options, {
2257
+ outputSchema,
2258
+ stream: false
2259
+ }));
2260
+ if (stream === false) return runNonStreamingText(toRuntimeTextActivityOptions(options, {
1931
2261
  outputSchema: void 0,
1932
- stream
1933
- });
1934
- return runStreamingText({
1935
- ...options,
2262
+ stream: false
2263
+ }));
2264
+ return runStreamingText(toRuntimeTextActivityOptions(options, {
1936
2265
  outputSchema: void 0,
1937
- stream
1938
- });
2266
+ stream: true
2267
+ }));
2268
+ }
2269
+ function readRuntimeMiddleware(middleware) {
2270
+ if (middleware === void 0) return void 0;
2271
+ if (!Array.isArray(middleware)) throw new TypeError("Chat middleware must be an array.");
2272
+ return middleware;
2273
+ }
2274
+ function toRuntimeTextActivityOptions(options, overrides) {
2275
+ const { middleware, ...rest } = options;
2276
+ return {
2277
+ ...rest,
2278
+ ...overrides,
2279
+ ...middleware === void 0 ? {} : { middleware: readRuntimeMiddleware(middleware) }
2280
+ };
2281
+ }
2282
+ function validateInterruptDefinitions(definitions) {
2283
+ if (!definitions) return;
2284
+ const seen = /* @__PURE__ */ new Set();
2285
+ for (const definition of definitions) {
2286
+ if (seen.has(definition.id)) throw new Error(`Duplicate interrupt definition id: ${definition.id}`);
2287
+ seen.add(definition.id);
2288
+ }
1939
2289
  }
1940
2290
  /**
1941
2291
  * Publish both delivery-side seams for `stream`.
@@ -1996,7 +2346,10 @@ async function* streamTextChunks(options, engineRef) {
1996
2346
  * Runs the full agentic loop (if tools are provided) but returns collected text.
1997
2347
  */
1998
2348
  function runNonStreamingText(options) {
1999
- const stream = runStreamingText(options);
2349
+ const stream = runStreamingText({
2350
+ ...options,
2351
+ stream: true
2352
+ });
2000
2353
  return streamToText(stream);
2001
2354
  }
2002
2355
  /**
@@ -2064,6 +2417,12 @@ async function runAgenticStructuredOutput(options) {
2064
2417
  * Uses an `unknown`-input runtime check rather than `as` casts so the engine
2065
2418
  * stays cast-free in its hot path.
2066
2419
  */
2420
+ function readCustomEventMessageId(value) {
2421
+ if (typeof value !== "object" || value === null) return void 0;
2422
+ if (!("messageId" in value)) return void 0;
2423
+ const messageId = value.messageId;
2424
+ return typeof messageId === "string" && messageId !== "" ? messageId : void 0;
2425
+ }
2067
2426
  function readStructuredOutputCompleteValue(value) {
2068
2427
  if (typeof value !== "object" || value === null) return null;
2069
2428
  if (!("object" in value) || !("raw" in value)) return null;