@tanstack/ai 0.46.0 → 0.47.2

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 +440 -79
  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 +3 -3
  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 +1030 -211
  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,10 +311,25 @@ 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
- if (this.earlyTermination || this.isCancelled()) return;
331
+ if (this.earlyTermination) break;
332
+ if (this.isCancelled()) return;
331
333
  this.logger.agentLoop(`iteration=${this.middlewareCtx.iteration}`, { iteration: this.middlewareCtx.iteration });
332
334
  await this.beginCycle();
333
335
  if (this.cyclePhase === "processText") {
@@ -336,7 +338,20 @@ var TextEngine = class {
336
338
  const iterConfig = this.buildMiddlewareConfig();
337
339
  const iterTransformedConfig = await this.middlewareRunner.runOnConfig(this.middlewareCtx, iterConfig);
338
340
  this.applyMiddlewareConfig(iterTransformedConfig);
341
+ if (yield* this.emitBoundaryInterrupts("beforeModel", this.createSyntheticFinishedEvent())) {
342
+ this.setToolPhase("wait");
343
+ return;
344
+ }
339
345
  yield* this.streamModelResponse();
346
+ if (this.earlyTermination) break;
347
+ if (yield* this.emitBoundaryInterrupts("afterModel", this.finishedEvent ?? this.createSyntheticFinishedEvent())) {
348
+ this.setToolPhase("wait");
349
+ return;
350
+ }
351
+ if (this.shouldExecuteToolPhase()) {
352
+ this.deferredToolCallRunFinishedChunks.push(...this.deferredModelRunFinishedChunks);
353
+ this.deferredModelRunFinishedChunks = [];
354
+ } else yield* this.flushDeferredModelRunFinishedChunks();
340
355
  } else yield* this.processToolCalls();
341
356
  this.endCycle();
342
357
  } while (await this.shouldContinue());
@@ -358,7 +373,7 @@ var TextEngine = class {
358
373
  duration: Date.now() - this.streamStartTime
359
374
  });
360
375
  } else {
361
- this.addTerminalReasoningMessage();
376
+ this.addTerminalAssistantMessages();
362
377
  this.terminalHookCalled = true;
363
378
  await this.middlewareRunner.runOnFinish(this.middlewareCtx, {
364
379
  finishReason: this.lastFinishReason,
@@ -513,7 +528,10 @@ var TextEngine = class {
513
528
  if (chunk.type === EventType.CUSTOM && chunk.name === "structured-output.start") {
514
529
  this.combinedStartEmitted = true;
515
530
  const startValue = chunk.value;
516
- if (startValue && typeof startValue === "object" && "messageId" in startValue && typeof startValue.messageId === "string") this.combinedStructuredMessageId = startValue.messageId;
531
+ if (startValue && typeof startValue === "object" && "messageId" in startValue && typeof startValue.messageId === "string") {
532
+ this.combinedStructuredMessageId = startValue.messageId;
533
+ this.captureStructuredOutputMessageIdentity(startValue.messageId);
534
+ }
517
535
  }
518
536
  let outboundChunk = chunk;
519
537
  if (this.finalStructuredOutput?.source === "event" && chunk.type === EventType.CUSTOM && chunk.name === "structured-output.complete") {
@@ -526,6 +544,11 @@ var TextEngine = class {
526
544
  };
527
545
  this.combinedCompleteEmitted = true;
528
546
  const value = chunk.value;
547
+ const completeMessageId = readCustomEventMessageId(value);
548
+ if (completeMessageId) {
549
+ this.combinedStructuredMessageId = completeMessageId;
550
+ this.captureStructuredOutputMessageIdentity(completeMessageId);
551
+ }
529
552
  if (object !== parsed.object && value && typeof value === "object") outboundChunk = {
530
553
  ...chunk,
531
554
  value: {
@@ -558,10 +581,15 @@ var TextEngine = class {
558
581
  const suppressAgentLifecycle = !!this.finalStructuredOutput && this.finalStructuredOutput.yieldChunks && this.finalStructuredOutput.nativeCombined !== true;
559
582
  for (const outputChunk of outputChunks) {
560
583
  if (suppressAgentLifecycle && (outputChunk.type === EventType.RUN_STARTED || outputChunk.type === EventType.RUN_FINISHED)) continue;
584
+ if (outputChunk.type === EventType.RUN_FINISHED) {
585
+ this.deferredModelRunFinishedChunks.push(outputChunk);
586
+ continue;
587
+ }
561
588
  if (this.shouldDeferToolCallRunFinished(outputChunk)) {
562
589
  this.deferredToolCallRunFinishedChunks.push(outputChunk);
563
590
  continue;
564
591
  }
592
+ if (outputChunk.type === EventType.RUN_STARTED) this.hasPublicRunStarted = true;
565
593
  this.logger.output(`type=${outputChunk.type}`, { chunk: outputChunk });
566
594
  yield outputChunk;
567
595
  this.middlewareCtx.chunkIndex++;
@@ -617,6 +645,10 @@ var TextEngine = class {
617
645
  this.streamIdentityCaptured = true;
618
646
  }
619
647
  }
648
+ captureStructuredOutputMessageIdentity(messageId) {
649
+ this.structuredOutputMessageId = messageId;
650
+ this.structuredOutputMessageCreatedAt ??= /* @__PURE__ */ new Date();
651
+ }
620
652
  handleToolCallStartEvent(chunk) {
621
653
  if (typeof chunk.parentMessageId === "string" && chunk.parentMessageId !== "") this.captureStreamMessageIdentity(chunk.parentMessageId);
622
654
  this.toolCallManager.addToolCallStartEvent(chunk);
@@ -643,8 +675,8 @@ var TextEngine = class {
643
675
  }
644
676
  handleRunErrorEvent(chunk) {
645
677
  this.earlyTermination = true;
646
- if (this.finalStructuredOutput && this.finalizationError === null) {
647
- const message = chunk.message || chunk.error?.message || "Run failed before structured output completed";
678
+ if (this.finalizationError === null) {
679
+ const message = chunk.message || chunk.error?.message || "Run failed";
648
680
  this.finalizationError = {
649
681
  message,
650
682
  ...chunk.code !== void 0 ? { code: chunk.code } : chunk.error?.code !== void 0 ? { code: chunk.error.code } : {}
@@ -714,6 +746,11 @@ var TextEngine = class {
714
746
  if (deferredErrorResults.length > 0) for (const chunk of this.buildToolResultChunks(deferredErrorResults, finishEvent, argsMap)) yield* this.pipeThroughMiddleware(chunk);
715
747
  return "continue";
716
748
  }
749
+ this.middlewareCtx.phase = "beforeTools";
750
+ if (yield* this.emitBoundaryInterrupts("beforeTools", finishEvent, executablePendingCalls)) {
751
+ this.setToolPhase("wait");
752
+ return "wait";
753
+ }
717
754
  const { approvals, clientToolResults } = this.collectClientState();
718
755
  const generator = executeToolCalls(executablePendingCalls, this.resolveExecutableTools(executablePendingCalls), approvals, clientToolResults, (eventName, data) => this.createCustomEventChunk(eventName, data), {
719
756
  onBeforeToolCall: async (toolCall, tool, args) => {
@@ -802,6 +839,10 @@ var TextEngine = class {
802
839
  return;
803
840
  }
804
841
  this.middlewareCtx.phase = "beforeTools";
842
+ if (yield* this.emitBoundaryInterrupts("beforeTools", finishEvent, executableToolCalls)) {
843
+ this.setToolPhase("wait");
844
+ return;
845
+ }
805
846
  const { approvals, clientToolResults } = this.collectClientState();
806
847
  const generator = executeToolCalls(executableToolCalls, this.resolveExecutableTools(executableToolCalls), approvals, clientToolResults, (eventName, data) => this.createCustomEventChunk(eventName, data), {
807
848
  onBeforeToolCall: async (toolCall, tool, args) => {
@@ -842,14 +883,22 @@ var TextEngine = class {
842
883
  needsApproval: executionResult.needsApproval,
843
884
  needsClientExecution: executionResult.needsClientExecution
844
885
  });
886
+ const afterToolBoundaryChunks = this.buildToolResultChunks(allResults, finishEvent);
887
+ const afterToolRequests = await this.middlewareRunner.runOnInterruptBoundary(this.middlewareCtx);
888
+ if (afterToolRequests.length > 0) {
889
+ for (const chunk of afterToolBoundaryChunks) yield* this.pipeThroughMiddleware(chunk);
890
+ yield* this.emitBoundaryInterrupts("afterTools", finishEvent, toolCalls, afterToolRequests);
891
+ this.setToolPhase("wait");
892
+ return;
893
+ }
845
894
  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);
895
+ if (allResults.length > 0) for (const chunk of afterToolBoundaryChunks) yield* this.pipeThroughMiddleware(chunk);
847
896
  const emitted = yield* this.emitActionableInterruptBoundary(finishEvent, executionResult.needsApproval, executionResult.needsClientExecution);
848
897
  this.setToolPhase(emitted ? "wait" : "stop");
849
898
  return;
850
899
  }
851
900
  yield* this.flushDeferredToolCallRunFinishedChunks();
852
- const toolResultChunks = this.buildToolResultChunks(allResults, finishEvent);
901
+ const toolResultChunks = afterToolBoundaryChunks;
853
902
  for (const chunk of toolResultChunks) yield* this.pipeThroughMiddleware(chunk);
854
903
  if (this.lazyToolManager.hasNewlyDiscoveredTools()) {
855
904
  this.tools = this.lazyToolManager.getActiveTools();
@@ -871,6 +920,37 @@ var TextEngine = class {
871
920
  }
872
921
  this.deferredToolCallRunFinishedChunks = [];
873
922
  }
923
+ *flushDeferredModelRunFinishedChunks() {
924
+ for (const chunk of this.deferredModelRunFinishedChunks) {
925
+ this.logger.output(`type=${chunk.type}`, { chunk });
926
+ yield chunk;
927
+ this.middlewareCtx.chunkIndex++;
928
+ }
929
+ this.deferredModelRunFinishedChunks = [];
930
+ }
931
+ async *emitSyntheticRunStarted(finishEvent) {
932
+ if (this.hasPublicRunStarted) return;
933
+ yield* this.pipeThroughMiddleware({
934
+ type: EventType.RUN_STARTED,
935
+ runId: finishEvent.runId,
936
+ threadId: finishEvent.threadId,
937
+ model: finishEvent.model,
938
+ timestamp: Date.now()
939
+ });
940
+ }
941
+ async *emitSuccessfulEarlyTermination() {
942
+ this.lastFinishReason = "stop";
943
+ const finishEvent = {
944
+ ...this.createSyntheticFinishedEvent(),
945
+ finishReason: "stop"
946
+ };
947
+ yield* this.emitSyntheticRunStarted(finishEvent);
948
+ yield* this.pipeThroughMiddleware({
949
+ ...finishEvent,
950
+ timestamp: Date.now(),
951
+ outcome: { type: "success" }
952
+ });
953
+ }
874
954
  discardDeferredToolCallRunFinishedChunks() {
875
955
  this.deferredToolCallRunFinishedChunks = [];
876
956
  }
@@ -889,18 +969,61 @@ var TextEngine = class {
889
969
  }];
890
970
  this.middlewareCtx.messages = this.messages;
891
971
  }
892
- addTerminalReasoningMessage() {
972
+ addTerminalAssistantMessages() {
893
973
  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
- }];
974
+ const structuredResult = this.structuredOutputResult;
975
+ const raw = structuredResult ? structuredResult.rawText || safeJsonStringify(structuredResult.data) : "";
976
+ const structuredOutput = structuredResult ? {
977
+ type: "structured-output",
978
+ status: "complete",
979
+ data: structuredResult.data,
980
+ partial: structuredResult.data,
981
+ raw,
982
+ ...structuredResult.reasoning !== void 0 ? { reasoning: structuredResult.reasoning } : {}
983
+ } : void 0;
984
+ const nativeCombined = this.finalStructuredOutput?.nativeCombined === true;
985
+ const eventSourced = this.finalStructuredOutput?.source === "event";
986
+ const structuredId = this.structuredOutputMessageId ?? this.combinedStructuredMessageId ?? this.currentMessageId ?? this.createId("msg");
987
+ const splitStructuredMessage = Boolean(structuredOutput) && (!nativeCombined || eventSourced) && this.currentMessageId != null && structuredId !== this.currentMessageId;
988
+ const messages = [...this.middlewareCtx.messages];
989
+ const existingStructuredIndex = messages.findIndex((message) => message.role === "assistant" && message.id === structuredId);
990
+ const currentTurnAlreadyRecorded = messages.some((message) => message.role === "assistant" && message.id === this.currentMessageId);
991
+ const thinking = this.accumulatedThinking.length > 0 ? this.accumulatedThinking : void 0;
992
+ const startedLength = messages.length;
993
+ if (structuredOutput && existingStructuredIndex >= 0) {
994
+ const existing = messages[existingStructuredIndex];
995
+ if (existing) messages[existingStructuredIndex] = {
996
+ ...existing,
997
+ content: raw || existing.content,
998
+ structuredOutput
999
+ };
1000
+ } else if (structuredOutput && !splitStructuredMessage) {
1001
+ if (!currentTurnAlreadyRecorded) messages.push({
1002
+ role: "assistant",
1003
+ content: this.accumulatedContent || raw || null,
1004
+ id: structuredId,
1005
+ createdAt: this.currentMessageCreatedAt ?? this.structuredOutputMessageCreatedAt ?? /* @__PURE__ */ new Date(),
1006
+ structuredOutput,
1007
+ ...thinking ? { thinking } : {}
1008
+ });
1009
+ } else {
1010
+ if (!currentTurnAlreadyRecorded && (this.accumulatedContent !== "" || thinking)) messages.push({
1011
+ role: "assistant",
1012
+ content: this.accumulatedContent || null,
1013
+ id: this.currentMessageId ?? this.createId("msg"),
1014
+ createdAt: this.currentMessageCreatedAt ?? /* @__PURE__ */ new Date(),
1015
+ ...thinking ? { thinking } : {}
1016
+ });
1017
+ if (structuredOutput) messages.push({
1018
+ role: "assistant",
1019
+ content: raw || null,
1020
+ id: structuredId,
1021
+ createdAt: this.structuredOutputMessageCreatedAt ?? /* @__PURE__ */ new Date(),
1022
+ structuredOutput
1023
+ });
1024
+ }
1025
+ if (messages.length === startedLength && existingStructuredIndex < 0) return;
1026
+ this.messages = messages;
904
1027
  this.middlewareCtx.messages = this.messages;
905
1028
  }
906
1029
  /**
@@ -943,7 +1066,10 @@ var TextEngine = class {
943
1066
  clientToolResults
944
1067
  };
945
1068
  }
946
- buildActionableInterrupts(approvals, clientRequests) {
1069
+ genericInterruptId() {
1070
+ return this.createId("interrupt");
1071
+ }
1072
+ buildActionableInterrupts(approvals, clientRequests, genericRequests = [], genericInterruptIds = []) {
947
1073
  const interrupts = [];
948
1074
  for (const approval of approvals) {
949
1075
  const tool = this.tools.find((candidate) => candidate.name === approval.toolName);
@@ -997,15 +1123,47 @@ var TextEngine = class {
997
1123
  }
998
1124
  });
999
1125
  }
1126
+ for (const [index, request] of genericRequests.entries()) {
1127
+ const batchIndex = interrupts.length;
1128
+ const id = genericInterruptIds[index];
1129
+ if (!id) throw new Error("Generic interrupt id is unavailable.");
1130
+ const preEmission = createInterruptBinding(request, { batchIndex });
1131
+ interrupts.push({
1132
+ id,
1133
+ reason: request.reason,
1134
+ message: request.message,
1135
+ ...preEmission.descriptor.responseSchemaCanonicalJson !== void 0 ? { responseSchema: JSON.parse(preEmission.descriptor.responseSchemaCanonicalJson) } : {},
1136
+ ...request.expiresAt !== void 0 ? { expiresAt: request.expiresAt } : {},
1137
+ metadata: {
1138
+ [interruptBindingMetadataKey]: {
1139
+ v: 1,
1140
+ kind: "generic",
1141
+ interruptId: id,
1142
+ definitionId: preEmission.descriptor.definitionId,
1143
+ key: preEmission.descriptor.key,
1144
+ batchIndex,
1145
+ ...request.expiresAt !== void 0 ? { expiresAt: request.expiresAt } : {},
1146
+ ...preEmission.descriptor.payloadSchemaHash ? { payloadSchemaHash: preEmission.descriptor.payloadSchemaHash } : {},
1147
+ ...preEmission.descriptor.responseSchemaHash !== void 0 ? { responseSchemaHash: preEmission.descriptor.responseSchemaHash } : {}
1148
+ },
1149
+ ...preEmission.payload !== void 0 ? { [INTERRUPT_PAYLOAD_METADATA_KEY]: preEmission.payload } : {}
1150
+ }
1151
+ });
1152
+ }
1153
+ const ids = /* @__PURE__ */ new Set();
1154
+ for (const interrupt of interrupts) {
1155
+ if (ids.has(interrupt.id)) throw new Error(`Duplicate interrupt id in final batch: ${interrupt.id}`);
1156
+ ids.add(interrupt.id);
1157
+ }
1000
1158
  return interrupts;
1001
1159
  }
1002
- buildInterruptFinishedChunk(finishEvent, approvals, clientRequests) {
1160
+ buildInterruptFinishedChunk(finishEvent, approvals, clientRequests, genericRequests = [], genericInterruptIds) {
1003
1161
  return {
1004
1162
  ...finishEvent,
1005
1163
  timestamp: Date.now(),
1006
1164
  outcome: {
1007
1165
  type: "interrupt",
1008
- interrupts: this.buildActionableInterrupts(approvals, clientRequests)
1166
+ interrupts: this.buildActionableInterrupts(approvals, clientRequests, genericRequests, genericInterruptIds)
1009
1167
  }
1010
1168
  };
1011
1169
  }
@@ -1013,7 +1171,7 @@ var TextEngine = class {
1013
1171
  const messages = this.messages.map((message, index) => {
1014
1172
  const content = typeof message.content === "string" ? message.content : message.content === null ? void 0 : JSON.stringify(message.content);
1015
1173
  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;
1174
+ const parts = message.role === "assistant" && (message.thinking?.length || message.structuredOutput) ? modelMessageToUIMessage(message, id).parts : void 0;
1017
1175
  return {
1018
1176
  id,
1019
1177
  role: message.role,
@@ -1097,8 +1255,10 @@ var TextEngine = class {
1097
1255
  };
1098
1256
  yield* this.pipeThroughMiddleware(this.buildInterruptRunErrorChunk(error));
1099
1257
  }
1100
- async *emitActionableInterruptBoundary(finishEvent, approvals, clientRequests) {
1101
- const terminal = this.completeEphemeralInterruptBindings(this.buildInterruptFinishedChunk(finishEvent, approvals, clientRequests));
1258
+ async *emitActionableInterruptBoundary(finishEvent, approvals, clientRequests, genericRequests = []) {
1259
+ yield* this.emitSyntheticRunStarted(finishEvent);
1260
+ const genericInterruptIds = genericRequests.map(() => this.genericInterruptId());
1261
+ const terminal = this.completeEphemeralInterruptBindings(this.buildInterruptFinishedChunk(finishEvent, approvals, clientRequests, genericRequests, genericInterruptIds));
1102
1262
  let terminalOutputs;
1103
1263
  try {
1104
1264
  terminalOutputs = await this.middlewareRunner.runOnChunk(this.middlewareCtx, terminal);
@@ -1119,6 +1279,59 @@ var TextEngine = class {
1119
1279
  }
1120
1280
  return true;
1121
1281
  }
1282
+ async *emitBoundaryInterrupts(phase, finishEvent, toolCalls = [], requests) {
1283
+ this.middlewareCtx.phase = phase;
1284
+ const boundaryRequests = requests ?? await this.middlewareRunner.runOnInterruptBoundary(this.middlewareCtx);
1285
+ if (boundaryRequests.length === 0) return false;
1286
+ 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.`);
1287
+ if (phase === "afterModel") {
1288
+ if (this.toolCallManager.hasToolCalls()) this.addAssistantToolCallMessage(this.toolCallManager.getToolCalls());
1289
+ else this.addAssistantTextMessageForInterrupt();
1290
+ }
1291
+ const actionable = this.getBoundaryActionableToolRequests(toolCalls);
1292
+ yield* this.emitActionableInterruptBoundary(finishEvent, actionable.approvals, actionable.clientRequests, boundaryRequests);
1293
+ return true;
1294
+ }
1295
+ addAssistantTextMessageForInterrupt() {
1296
+ if (this.accumulatedContent.length === 0) return;
1297
+ this.messages = [...this.messages, {
1298
+ role: "assistant",
1299
+ content: this.accumulatedContent
1300
+ }];
1301
+ this.middlewareCtx.messages = this.messages;
1302
+ }
1303
+ getBoundaryActionableToolRequests(toolCalls) {
1304
+ const { approvals, clientToolResults } = this.collectClientState();
1305
+ const approvalRequests = [];
1306
+ const clientRequests = [];
1307
+ for (const toolCall of toolCalls) {
1308
+ const tool = this.resolveExecutableTools([toolCall]).find((candidate) => candidate.name === toolCall.function.name);
1309
+ if (!tool) continue;
1310
+ let input = {};
1311
+ try {
1312
+ const parsed = JSON.parse(toolCall.function.arguments.trim() || "{}");
1313
+ input = parsed && typeof parsed === "object" ? parsed : {};
1314
+ } catch {
1315
+ input = {};
1316
+ }
1317
+ const approvalId = `approval_${toolCall.id}`;
1318
+ if (tool.needsApproval && !approvals.has(approvalId)) approvalRequests.push({
1319
+ toolCallId: toolCall.id,
1320
+ toolName: toolCall.function.name,
1321
+ input,
1322
+ approvalId
1323
+ });
1324
+ else if (!tool.execute && !clientToolResults.has(toolCall.id) && !this.resumeCancelledToolCallIds.has(toolCall.id)) clientRequests.push({
1325
+ toolCallId: toolCall.id,
1326
+ toolName: toolCall.function.name,
1327
+ input
1328
+ });
1329
+ }
1330
+ return {
1331
+ approvals: approvalRequests,
1332
+ clientRequests
1333
+ };
1334
+ }
1122
1335
  completeEphemeralInterruptBindings(chunk) {
1123
1336
  if (chunk.type !== EventType.RUN_FINISHED || chunk.outcome?.type !== "interrupt") return chunk;
1124
1337
  const interruptedRunId = this.runIdOverride ?? this.requestId;
@@ -1265,7 +1478,7 @@ var TextEngine = class {
1265
1478
  createSyntheticFinishedEvent() {
1266
1479
  return {
1267
1480
  type: "RUN_FINISHED",
1268
- runId: this.createId("pending"),
1481
+ runId: this.runIdOverride ?? this.requestId,
1269
1482
  threadId: this.threadId,
1270
1483
  model: this.params.model,
1271
1484
  timestamp: Date.now(),
@@ -1451,6 +1664,7 @@ var TextEngine = class {
1451
1664
  const buildSynthesizedStart = (timestamp = Date.now()) => {
1452
1665
  const idForStart = structuredMessageId ?? generateMessageId();
1453
1666
  structuredMessageId = idForStart;
1667
+ this.captureStructuredOutputMessageIdentity(idForStart);
1454
1668
  return {
1455
1669
  type: EventType.CUSTOM,
1456
1670
  name: "structured-output.start",
@@ -1468,7 +1682,10 @@ var TextEngine = class {
1468
1682
  if (!startEmitted && chunk.type === EventType.CUSTOM && chunk.name === "structured-output.start") startEmitted = true;
1469
1683
  if (!structuredMessageId) {
1470
1684
  const extracted = extractMessageId(chunk);
1471
- if (extracted) structuredMessageId = extracted;
1685
+ if (extracted) {
1686
+ structuredMessageId = extracted;
1687
+ this.captureStructuredOutputMessageIdentity(extracted);
1688
+ }
1472
1689
  }
1473
1690
  if (this.finalStructuredOutput.yieldChunks) {
1474
1691
  if (!startEmitted && (chunk.type === EventType.TEXT_MESSAGE_START || chunk.type === EventType.TEXT_MESSAGE_CONTENT || chunk.type === EventType.TEXT_MESSAGE_END)) {
@@ -1495,7 +1712,8 @@ var TextEngine = class {
1495
1712
  const object = this.finalStructuredOutput.normalize ? this.finalStructuredOutput.normalize(parsed.object) : parsed.object;
1496
1713
  this.structuredOutputResult = {
1497
1714
  data: object,
1498
- rawText: parsed.raw
1715
+ rawText: parsed.raw,
1716
+ ...parsed.reasoning !== void 0 ? { reasoning: parsed.reasoning } : {}
1499
1717
  };
1500
1718
  const value = chunk.value;
1501
1719
  if (object !== parsed.object && value && typeof value === "object") outboundChunk = {
@@ -1752,6 +1970,7 @@ var TextEngine = class {
1752
1970
  input: toolInputs.get(toolCall.id) ?? {}
1753
1971
  });
1754
1972
  }
1973
+ const genericPending = this.getGenericContinuationPending(interruptedRunId);
1755
1974
  const pending = this.buildActionableInterrupts(approvalRequests, clientRequests).flatMap((descriptor) => {
1756
1975
  const unopened = readUnopenedInterruptBinding(descriptor);
1757
1976
  return unopened ? [{
@@ -1764,6 +1983,7 @@ var TextEngine = class {
1764
1983
  }
1765
1984
  }] : [];
1766
1985
  });
1986
+ pending.push(...genericPending);
1767
1987
  const validated = await validateInterruptResumeBatch({
1768
1988
  threadId: this.threadId,
1769
1989
  interruptedRunId,
@@ -1779,12 +1999,124 @@ var TextEngine = class {
1779
1999
  ...validated.resumeToolState,
1780
2000
  approvals
1781
2001
  });
2002
+ const genericResolutions = validated.resumeToolState.genericInterrupts;
2003
+ if (genericPending.length > 0 && genericResolutions) {
2004
+ const resolutions = genericPending.sort((left, right) => {
2005
+ return (left.binding.kind === "generic" ? left.binding.batchIndex ?? 0 : 0) - (right.binding.kind === "generic" ? right.binding.batchIndex ?? 0 : 0);
2006
+ }).flatMap((record) => {
2007
+ const resolution = genericResolutions.get(record.interruptId);
2008
+ if (!resolution || !record.genericRequest) return [];
2009
+ return [resolution.status === "resolved" ? {
2010
+ request: record.genericRequest,
2011
+ status: "resolved",
2012
+ response: resolution.payload
2013
+ } : {
2014
+ request: record.genericRequest,
2015
+ status: "cancelled"
2016
+ }];
2017
+ });
2018
+ const policy = await this.middlewareRunner.runOnInterruptResolution(this.middlewareCtx, {
2019
+ for: (definition) => resolutions.filter((resolution) => resolution.request.definition === definition),
2020
+ all: (...definitions) => definitions.length === 0 ? resolutions : resolutions.filter((resolution) => definitions.includes(resolution.request.definition))
2021
+ });
2022
+ if (policy.toolResume === "stop") this.earlyTermination = true;
2023
+ else if (policy.toolResume === "cancel") for (const request of pendingToolCalls) this.resumeCancelledToolCallIds.add(request.id);
2024
+ }
2025
+ }
2026
+ getGenericContinuationPending(interruptedRunId) {
2027
+ const fail = (message) => {
2028
+ throw new InterruptResumeValidationError([{
2029
+ scope: "batch",
2030
+ threadId: this.threadId,
2031
+ interruptedRunId,
2032
+ generation: 0,
2033
+ interruptIds: [],
2034
+ code: "stale",
2035
+ message,
2036
+ source: "server",
2037
+ retryable: false
2038
+ }]);
2039
+ };
2040
+ const pending = [];
2041
+ const ids = /* @__PURE__ */ new Set();
2042
+ const batchIndexes = /* @__PURE__ */ new Set();
2043
+ for (const resumeItem of this.params.resume ?? []) {
2044
+ const parsed = readGenericInterruptContinuation(resumeItem.metadata);
2045
+ if (parsed.status === "absent") continue;
2046
+ if (parsed.status === "invalid") return fail(parsed.message);
2047
+ const entry = parsed.value;
2048
+ const id = resumeItem.interruptId;
2049
+ const definition = this.interruptDefinitions.get(entry.definitionId);
2050
+ if (!definition) return fail(`Generic interrupt definition ${entry.definitionId} is unavailable.`);
2051
+ if (ids.has(id) || batchIndexes.has(entry.batchIndex)) return fail("Generic interrupt continuation contains duplicate entries.");
2052
+ ids.add(id);
2053
+ batchIndexes.add(entry.batchIndex);
2054
+ let request;
2055
+ try {
2056
+ request = rehydrateInterruptRequest(definition, {
2057
+ key: entry.key,
2058
+ reason: entry.reason,
2059
+ message: entry.message,
2060
+ ...typeof entry.expiresAt === "string" ? { expiresAt: entry.expiresAt } : {},
2061
+ ...Object.prototype.hasOwnProperty.call(entry, "payload") ? { payload: entry.payload } : {}
2062
+ });
2063
+ } catch (error) {
2064
+ return fail(`Generic interrupt continuation ${id} is invalid: ${error instanceof Error ? error.message : String(error)}`);
2065
+ }
2066
+ const emitted = createInterruptBinding(request, { batchIndex: entry.batchIndex });
2067
+ if (entry.responseSchemaHash !== emitted.descriptor.responseSchemaHash || entry.payloadSchemaHash !== emitted.descriptor.payloadSchemaHash) return fail(`Generic interrupt continuation ${id} does not match its definition.`);
2068
+ pending.push({
2069
+ interruptId: id,
2070
+ payload: {
2071
+ id,
2072
+ ...emitted.descriptor.responseSchemaCanonicalJson !== void 0 ? { responseSchema: JSON.parse(emitted.descriptor.responseSchemaCanonicalJson) } : {}
2073
+ },
2074
+ binding: {
2075
+ v: 1,
2076
+ kind: "generic",
2077
+ interruptId: id,
2078
+ interruptedRunId,
2079
+ generation: 0,
2080
+ definitionId: entry.definitionId,
2081
+ key: entry.key,
2082
+ batchIndex: entry.batchIndex,
2083
+ ...typeof entry.expiresAt === "string" ? { expiresAt: entry.expiresAt } : {},
2084
+ ...emitted.descriptor.payloadSchemaHash ? { payloadSchemaHash: emitted.descriptor.payloadSchemaHash } : {},
2085
+ ...entry.responseSchemaHash !== void 0 ? { responseSchemaHash: entry.responseSchemaHash } : {}
2086
+ },
2087
+ genericRequest: request
2088
+ });
2089
+ }
2090
+ return pending;
1782
2091
  }
1783
2092
  applyResumeToolState(state) {
1784
2093
  if (state?.approvals) for (const [approvalId, resolution] of state.approvals) this.resumeApprovals.set(approvalId, resolution);
1785
2094
  if (state?.clientToolResults) for (const [toolCallId, result] of state.clientToolResults) this.resumeClientToolResults.set(toolCallId, result);
1786
2095
  if (state?.deniedToolResults) for (const [toolCallId, result] of state.deniedToolResults) this.resumeDeniedToolResults.set(toolCallId, result);
1787
2096
  if (state?.cancelledToolCallIds) for (const toolCallId of state.cancelledToolCallIds) this.resumeCancelledToolCallIds.add(toolCallId);
2097
+ if (state?.genericInterrupts) for (const [interruptId, resolution] of state.genericInterrupts) this.resumeGenericInterrupts.set(interruptId, resolution);
2098
+ if (state?.genericInterruptRequests) for (const [interruptId, request] of state.genericInterruptRequests) this.resumeGenericInterruptRequests.set(interruptId, request);
2099
+ }
2100
+ async applyDurableGenericInterruptResolution() {
2101
+ if (this.resumeGenericInterruptRequests.size === 0) return;
2102
+ const resolutions = [...this.resumeGenericInterruptRequests.entries()].flatMap(([interruptId, request]) => {
2103
+ const resolution = this.resumeGenericInterrupts.get(interruptId);
2104
+ if (!resolution) return [];
2105
+ return [resolution.status === "resolved" ? {
2106
+ request,
2107
+ status: "resolved",
2108
+ response: resolution.payload
2109
+ } : {
2110
+ request,
2111
+ status: "cancelled"
2112
+ }];
2113
+ });
2114
+ const policy = await this.middlewareRunner.runOnInterruptResolution(this.middlewareCtx, {
2115
+ for: (definition) => resolutions.filter((resolution) => resolution.request.definition === definition),
2116
+ all: (...definitions) => definitions.length === 0 ? resolutions : resolutions.filter((resolution) => definitions.includes(resolution.request.definition))
2117
+ });
2118
+ if (policy.toolResume === "stop") this.earlyTermination = true;
2119
+ else if (policy.toolResume === "cancel") for (const toolCall of this.getPendingToolCallsFromMessages()) this.resumeCancelledToolCallIds.add(toolCall.id);
1788
2120
  }
1789
2121
  applyMiddlewareConfig(config) {
1790
2122
  this.applyResumeToolState(config.resumeToolState);
@@ -1813,6 +2145,7 @@ var TextEngine = class {
1813
2145
  async *pipeThroughMiddleware(chunk) {
1814
2146
  const outputChunks = await this.middlewareRunner.runOnChunk(this.middlewareCtx, chunk);
1815
2147
  for (const outputChunk of outputChunks) {
2148
+ if (outputChunk.type === EventType.RUN_STARTED) this.hasPublicRunStarted = true;
1816
2149
  yield outputChunk;
1817
2150
  this.middlewareCtx.chunkIndex++;
1818
2151
  }
@@ -1914,28 +2247,47 @@ var TextEngine = class {
1914
2247
  * ```
1915
2248
  */
1916
2249
  function chat(options) {
1917
- validateCapabilities(options.middleware ?? [], options.adapter);
2250
+ validateInterruptDefinitions(options.interrupts);
2251
+ validateCapabilities(readRuntimeMiddleware(options.middleware) ?? [], options.adapter);
1918
2252
  if (options.tools) assertUniqueToolNames(options.tools);
1919
2253
  const { outputSchema, stream } = options;
1920
- if (outputSchema && stream === true) return runStreamingStructuredOutput({
1921
- ...options,
2254
+ if (outputSchema && stream === true) return runStreamingStructuredOutput(toRuntimeTextActivityOptions(options, {
1922
2255
  outputSchema,
1923
- stream
1924
- });
1925
- if (outputSchema) return runAgenticStructuredOutput({
1926
- ...options,
1927
- outputSchema
1928
- });
1929
- if (stream === false) return runNonStreamingText({
1930
- ...options,
2256
+ stream: true
2257
+ }));
2258
+ if (outputSchema) return runAgenticStructuredOutput(toRuntimeTextActivityOptions(options, {
2259
+ outputSchema,
2260
+ stream: false
2261
+ }));
2262
+ if (stream === false) return runNonStreamingText(toRuntimeTextActivityOptions(options, {
1931
2263
  outputSchema: void 0,
1932
- stream
1933
- });
1934
- return runStreamingText({
1935
- ...options,
2264
+ stream: false
2265
+ }));
2266
+ return runStreamingText(toRuntimeTextActivityOptions(options, {
1936
2267
  outputSchema: void 0,
1937
- stream
1938
- });
2268
+ stream: true
2269
+ }));
2270
+ }
2271
+ function readRuntimeMiddleware(middleware) {
2272
+ if (middleware === void 0) return void 0;
2273
+ if (!Array.isArray(middleware)) throw new TypeError("Chat middleware must be an array.");
2274
+ return middleware;
2275
+ }
2276
+ function toRuntimeTextActivityOptions(options, overrides) {
2277
+ const { middleware, ...rest } = options;
2278
+ return {
2279
+ ...rest,
2280
+ ...overrides,
2281
+ ...middleware === void 0 ? {} : { middleware: readRuntimeMiddleware(middleware) }
2282
+ };
2283
+ }
2284
+ function validateInterruptDefinitions(definitions) {
2285
+ if (!definitions) return;
2286
+ const seen = /* @__PURE__ */ new Set();
2287
+ for (const definition of definitions) {
2288
+ if (seen.has(definition.id)) throw new Error(`Duplicate interrupt definition id: ${definition.id}`);
2289
+ seen.add(definition.id);
2290
+ }
1939
2291
  }
1940
2292
  /**
1941
2293
  * Publish both delivery-side seams for `stream`.
@@ -1996,7 +2348,10 @@ async function* streamTextChunks(options, engineRef) {
1996
2348
  * Runs the full agentic loop (if tools are provided) but returns collected text.
1997
2349
  */
1998
2350
  function runNonStreamingText(options) {
1999
- const stream = runStreamingText(options);
2351
+ const stream = runStreamingText({
2352
+ ...options,
2353
+ stream: true
2354
+ });
2000
2355
  return streamToText(stream);
2001
2356
  }
2002
2357
  /**
@@ -2064,6 +2419,12 @@ async function runAgenticStructuredOutput(options) {
2064
2419
  * Uses an `unknown`-input runtime check rather than `as` casts so the engine
2065
2420
  * stays cast-free in its hot path.
2066
2421
  */
2422
+ function readCustomEventMessageId(value) {
2423
+ if (typeof value !== "object" || value === null) return void 0;
2424
+ if (!("messageId" in value)) return void 0;
2425
+ const messageId = value.messageId;
2426
+ return typeof messageId === "string" && messageId !== "" ? messageId : void 0;
2427
+ }
2067
2428
  function readStructuredOutputCompleteValue(value) {
2068
2429
  if (typeof value !== "object" || value === null) return null;
2069
2430
  if (!("object" in value) || !("raw" in value)) return null;