@trigger.dev/sdk 0.0.0-chat-prerelease-20260402124815 → 0.0.0-chat-prerelease-20260413101648

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 (37) hide show
  1. package/dist/commonjs/v3/ai.d.ts +81 -2
  2. package/dist/commonjs/v3/ai.js +132 -12
  3. package/dist/commonjs/v3/ai.js.map +1 -1
  4. package/dist/commonjs/v3/chat-client.d.ts +6 -3
  5. package/dist/commonjs/v3/chat-client.js +91 -11
  6. package/dist/commonjs/v3/chat-client.js.map +1 -1
  7. package/dist/commonjs/v3/chat.d.ts +45 -0
  8. package/dist/commonjs/v3/chat.js +136 -6
  9. package/dist/commonjs/v3/chat.js.map +1 -1
  10. package/dist/commonjs/v3/chat.test.js +13 -13
  11. package/dist/commonjs/v3/chat.test.js.map +1 -1
  12. package/dist/commonjs/v3/deployments.d.ts +26 -0
  13. package/dist/commonjs/v3/deployments.js +37 -0
  14. package/dist/commonjs/v3/deployments.js.map +1 -0
  15. package/dist/commonjs/v3/index.d.ts +1 -0
  16. package/dist/commonjs/v3/index.js +3 -1
  17. package/dist/commonjs/v3/index.js.map +1 -1
  18. package/dist/commonjs/version.js +1 -1
  19. package/dist/esm/v3/ai.d.ts +81 -2
  20. package/dist/esm/v3/ai.js +132 -12
  21. package/dist/esm/v3/ai.js.map +1 -1
  22. package/dist/esm/v3/chat-client.d.ts +6 -3
  23. package/dist/esm/v3/chat-client.js +91 -11
  24. package/dist/esm/v3/chat-client.js.map +1 -1
  25. package/dist/esm/v3/chat.d.ts +45 -0
  26. package/dist/esm/v3/chat.js +136 -6
  27. package/dist/esm/v3/chat.js.map +1 -1
  28. package/dist/esm/v3/chat.test.js +13 -13
  29. package/dist/esm/v3/chat.test.js.map +1 -1
  30. package/dist/esm/v3/deployments.d.ts +26 -0
  31. package/dist/esm/v3/deployments.js +34 -0
  32. package/dist/esm/v3/deployments.js.map +1 -0
  33. package/dist/esm/v3/index.d.ts +1 -0
  34. package/dist/esm/v3/index.js +1 -0
  35. package/dist/esm/v3/index.js.map +1 -1
  36. package/dist/esm/version.js +1 -1
  37. package/package.json +2 -2
@@ -832,6 +832,19 @@ export type ChatStartEvent<TClientData = unknown> = {
832
832
  /** Stream writer — write custom `UIMessageChunk` parts to the chat stream. Lazy: no overhead if unused. */
833
833
  writer: ChatWriter;
834
834
  };
835
+ /**
836
+ * Event passed to the `onValidateMessages` callback.
837
+ */
838
+ export type ValidateMessagesEvent<TUIM extends UIMessage = UIMessage> = {
839
+ /** The incoming UI messages for this turn (after cleanup of aborted tool parts). */
840
+ messages: TUIM[];
841
+ /** The unique identifier for the chat session. */
842
+ chatId: string;
843
+ /** The turn number (0-indexed). */
844
+ turn: number;
845
+ /** The trigger type for this turn. */
846
+ trigger: "submit-message" | "regenerate-message" | "preload" | "close";
847
+ };
835
848
  /**
836
849
  * Event passed to the `onTurnStart` callback.
837
850
  */
@@ -1055,6 +1068,32 @@ export type ChatAgentOptions<TIdentifier extends string, TClientDataSchema exten
1055
1068
  * ```
1056
1069
  */
1057
1070
  onChatStart?: (event: ChatStartEvent<inferSchemaOut<TClientDataSchema>>) => Promise<void> | void;
1071
+ /**
1072
+ * Validate or transform incoming UI messages before they are converted to model
1073
+ * messages and accumulated. Fires once per turn with the raw `UIMessage[]` from
1074
+ * the wire payload (after cleanup of aborted tool parts).
1075
+ *
1076
+ * Return the validated messages array. Throw to abort the turn with an error.
1077
+ *
1078
+ * This is the right place to call the AI SDK's `validateUIMessages` to catch
1079
+ * malformed messages from storage or untrusted input before they reach the model.
1080
+ *
1081
+ * @example
1082
+ * ```ts
1083
+ * import { validateUIMessages } from "ai";
1084
+ *
1085
+ * chat.agent({
1086
+ * id: "my-chat",
1087
+ * onValidateMessages: async ({ messages }) => {
1088
+ * return validateUIMessages({ messages, tools: chatTools });
1089
+ * },
1090
+ * run: async ({ messages }) => {
1091
+ * return streamText({ model, messages, tools: chatTools });
1092
+ * },
1093
+ * });
1094
+ * ```
1095
+ */
1096
+ onValidateMessages?: (event: ValidateMessagesEvent<TUIMessage>) => TUIMessage[] | Promise<TUIMessage[]>;
1058
1097
  /**
1059
1098
  * Called at the start of every turn, after message accumulation and `onChatStart` (turn 0),
1060
1099
  * but before the `run` function executes.
@@ -1199,6 +1238,8 @@ export type ChatAgentOptions<TIdentifier extends string, TClientDataSchema exten
1199
1238
  * waiting for the first message before suspending.
1200
1239
  *
1201
1240
  * Only applies to preloaded runs (triggered via `transport.preload()`).
1241
+ * Takes precedence over `transport.preload(..., { idleTimeoutInSeconds })`
1242
+ * and over {@link ChatAgentOptions.idleTimeoutInSeconds}.
1202
1243
  *
1203
1244
  * @default Same as `idleTimeoutInSeconds`
1204
1245
  */
@@ -1554,6 +1595,36 @@ declare function setUIMessageStreamOptions(options: ChatUIMessageStreamOptions<U
1554
1595
  * ```
1555
1596
  */
1556
1597
  declare function isStopped(): boolean;
1598
+ /**
1599
+ * Request that the current run exits so the next message starts on the latest
1600
+ * deployed version (via the standard continuation mechanism).
1601
+ *
1602
+ * When called from `onTurnStart` or `onValidateMessages`, `run()` is skipped
1603
+ * entirely — the run exits immediately and the transport re-triggers the
1604
+ * same message on the new version.
1605
+ *
1606
+ * When called from `run()` or `chat.defer()`, the current turn completes
1607
+ * normally and the run exits afterward instead of waiting for the next message.
1608
+ *
1609
+ * Call from `onTurnStart`, `onValidateMessages`, `onChatResume`, `run()`,
1610
+ * or inside `chat.defer()`.
1611
+ *
1612
+ * @example
1613
+ * ```ts
1614
+ * const SUPPORTED_VERSIONS = new Set(["v2", "v3"]);
1615
+ *
1616
+ * chat.agent({
1617
+ * id: "my-chat",
1618
+ * onTurnStart: async ({ clientData }) => {
1619
+ * if (clientData?.protocolVersion && !SUPPORTED_VERSIONS.has(clientData.protocolVersion)) {
1620
+ * chat.requestUpgrade();
1621
+ * }
1622
+ * },
1623
+ * run: async ({ messages }) => { ... },
1624
+ * });
1625
+ * ```
1626
+ */
1627
+ declare function requestUpgrade(): void;
1557
1628
  /**
1558
1629
  * Register a promise that runs in the background during the current turn.
1559
1630
  *
@@ -1564,12 +1635,18 @@ declare function isStopped(): boolean;
1564
1635
  * @example
1565
1636
  * ```ts
1566
1637
  * onTurnStart: async ({ chatId, uiMessages }) => {
1567
- * // Persist messages without blocking the LLM call
1638
+ * // Pass a promise directly
1568
1639
  * chat.defer(db.chat.update({ where: { id: chatId }, data: { messages: uiMessages } }));
1640
+ *
1641
+ * // Or pass an async function — cleaner for multi-step work
1642
+ * chat.defer(async () => {
1643
+ * const flags = await getFeatureFlags();
1644
+ * if (flags.forceUpgrade) chat.requestUpgrade();
1645
+ * });
1569
1646
  * },
1570
1647
  * ```
1571
1648
  */
1572
- declare function chatDefer(promise: Promise<unknown>): void;
1649
+ declare function chatDefer(promiseOrFn: Promise<unknown> | (() => Promise<unknown>)): void;
1573
1650
  /**
1574
1651
  * Queue model messages for injection at the next `prepareStep` boundary.
1575
1652
  *
@@ -2010,6 +2087,8 @@ export declare const chat: {
2010
2087
  setUIMessageStreamOptions: typeof setUIMessageStreamOptions;
2011
2088
  /** Check if the current turn was stopped by the user. See {@link isStopped}. */
2012
2089
  isStopped: typeof isStopped;
2090
+ /** Request that the run exits after the current turn so the next message starts on the latest version. See {@link requestUpgrade}. */
2091
+ requestUpgrade: typeof requestUpgrade;
2013
2092
  /** Clean up aborted parts from a UIMessage. See {@link cleanupAbortedParts}. */
2014
2093
  cleanupAbortedParts: typeof cleanupAbortedParts;
2015
2094
  /** Register background work that runs in parallel with streaming. See {@link chatDefer}. */
@@ -384,6 +384,8 @@ const chatOnCompactedKey = locals_js_1.locals.create("chat.onCompacted");
384
384
  /** @internal Full task `ctx` for the active `chat.agent` run (for hooks invoked from nested compaction). */
385
385
  const chatAgentRunContextKey = locals_js_1.locals.create("chat.agentRunContext");
386
386
  const chatPrepareMessagesKey = locals_js_1.locals.create("chat.prepareMessages");
387
+ /** @internal Flag set by `chat.requestUpgrade()` to exit the loop after the current turn. */
388
+ const chatUpgradeRequestedKey = locals_js_1.locals.create("chat.upgradeRequested");
387
389
  /** @internal */
388
390
  const chatAgentCompactionKey = locals_js_1.locals.create("chat.agentCompaction");
389
391
  /**
@@ -975,7 +977,7 @@ function chatCustomAgent(options) {
975
977
  return task;
976
978
  }
977
979
  function chatAgent(options) {
978
- const { run: userRun, clientDataSchema, onPreload, onChatStart, onTurnStart, onBeforeTurnComplete, onCompacted, compaction, pendingMessages: pendingMessagesConfig, prepareMessages, onTurnComplete, maxTurns = 100, turnTimeout = "1h", idleTimeoutInSeconds = 30, chatAccessTokenTTL = "1h", preloadIdleTimeoutInSeconds, preloadTimeout, uiMessageStreamOptions, onChatSuspend, onChatResume, exitAfterPreloadIdle = false, ...restOptions } = options;
980
+ const { run: userRun, clientDataSchema, onPreload, onChatStart, onValidateMessages, onTurnStart, onBeforeTurnComplete, onCompacted, compaction, pendingMessages: pendingMessagesConfig, prepareMessages, onTurnComplete, maxTurns = 100, turnTimeout = "1h", idleTimeoutInSeconds = 30, chatAccessTokenTTL = "1h", preloadIdleTimeoutInSeconds, preloadTimeout, uiMessageStreamOptions, onChatSuspend, onChatResume, exitAfterPreloadIdle = false, ...restOptions } = options;
979
981
  const parseClientData = clientDataSchema ? (0, v3_1.getSchemaParseFn)(clientDataSchema) : undefined;
980
982
  const task = (0, shared_js_1.createTask)({
981
983
  retry: { maxAttempts: 1 },
@@ -1073,8 +1075,12 @@ function chatAgent(options) {
1073
1075
  },
1074
1076
  });
1075
1077
  }
1076
- // Wait for the first real message — use preload-specific timeouts if configured
1077
- const effectivePreloadIdleTimeout = payload.idleTimeoutInSeconds ?? preloadIdleTimeoutInSeconds ?? idleTimeoutInSeconds;
1078
+ // Wait for the first real message — task-level idle settings win over
1079
+ // `transport.preload(..., { idleTimeoutInSeconds })` / wire payload so
1080
+ // `chat.agent({ idleTimeoutInSeconds, preloadIdleTimeoutInSeconds })` is authoritative.
1081
+ const effectivePreloadIdleTimeout = preloadIdleTimeoutInSeconds ??
1082
+ idleTimeoutInSeconds ??
1083
+ payload.idleTimeoutInSeconds;
1078
1084
  const effectivePreloadTimeout = metadata_js_1.metadata.get(TURN_TIMEOUT_METADATA_KEY) ??
1079
1085
  preloadTimeout ??
1080
1086
  turnTimeout;
@@ -1236,7 +1242,27 @@ function chatAgent(options) {
1236
1242
  // useChat state may still contain assistant messages with tool parts
1237
1243
  // in partial/input-available state. These cause API errors (e.g.
1238
1244
  // Anthropic requires every tool_use to have a matching tool_result).
1239
- const cleanedUIMessages = uiMessages.map((msg) => msg.role === "assistant" ? cleanupAbortedParts(msg) : msg);
1245
+ let cleanedUIMessages = uiMessages.map((msg) => msg.role === "assistant" ? cleanupAbortedParts(msg) : msg);
1246
+ // Validate/transform UIMessages before conversion — catches malformed
1247
+ // messages from storage or untrusted input before they reach the model.
1248
+ if (onValidateMessages) {
1249
+ cleanedUIMessages = await tracer_js_1.tracer.startActiveSpan("onValidateMessages()", async () => {
1250
+ return onValidateMessages({
1251
+ messages: cleanedUIMessages,
1252
+ chatId: currentWirePayload.chatId,
1253
+ turn,
1254
+ trigger: currentWirePayload.trigger,
1255
+ });
1256
+ }, {
1257
+ attributes: {
1258
+ [v3_1.SemanticInternalAttributes.STYLE_ICON]: "task-hook-onStart",
1259
+ [v3_1.SemanticInternalAttributes.COLLAPSED]: true,
1260
+ "chat.id": currentWirePayload.chatId,
1261
+ "chat.turn": turn + 1,
1262
+ "chat.messages.count": cleanedUIMessages.length,
1263
+ },
1264
+ });
1265
+ }
1240
1266
  // Convert the incoming UIMessages to model messages and update the accumulator.
1241
1267
  // Turn 1: full history from the frontend → replaces the accumulator.
1242
1268
  // Turn 2+: only the new message(s) → appended to the accumulator.
@@ -1360,6 +1386,13 @@ function chatAgent(options) {
1360
1386
  },
1361
1387
  });
1362
1388
  }
1389
+ // chat.requestUpgrade() called in onTurnStart (or onValidateMessages) —
1390
+ // skip run() and signal the transport to re-trigger the same message
1391
+ // on the new version.
1392
+ if (locals_js_1.locals.get(chatUpgradeRequestedKey)) {
1393
+ await writeUpgradeRequiredChunk();
1394
+ return "exit";
1395
+ }
1363
1396
  // Captured by the onFinish callback below — works even on abort/stop.
1364
1397
  let capturedResponseMessage;
1365
1398
  // Promise that resolves when the AI SDK's onFinish fires.
@@ -1425,8 +1458,13 @@ function chatAgent(options) {
1425
1458
  }
1426
1459
  // Wait for onFinish to fire — on abort this may resolve slightly
1427
1460
  // after pipeChat, since the stream's cancel() handler is async.
1461
+ // Race with a timeout so a stop-abort that prevents onFinish from
1462
+ // firing doesn't hang the turn loop indefinitely.
1428
1463
  if (onFinishAttached) {
1429
- await onFinishPromise;
1464
+ await Promise.race([
1465
+ onFinishPromise,
1466
+ new Promise((r) => setTimeout(r, 2_000)),
1467
+ ]);
1430
1468
  }
1431
1469
  // Capture token usage from the streamText result (if available).
1432
1470
  // totalUsage is a PromiseLike that resolves after the stream is consumed.
@@ -1714,7 +1752,7 @@ function chatAgent(options) {
1714
1752
  await tracer_js_1.tracer.startActiveSpan("onTurnComplete()", async () => {
1715
1753
  await onTurnComplete({
1716
1754
  ...turnCompleteEvent,
1717
- lastEventId: turnCompleteResult.lastEventId,
1755
+ lastEventId: turnCompleteResult?.lastEventId,
1718
1756
  });
1719
1757
  // Check if onTurnComplete replaced messages (compaction)
1720
1758
  const turnCompleteOverride = locals_js_1.locals.get(chatOverrideMessagesKey);
@@ -1762,6 +1800,11 @@ function chatAgent(options) {
1762
1800
  currentWirePayload = pendingMessages[0];
1763
1801
  return "continue";
1764
1802
  }
1803
+ // chat.requestUpgrade() was called — exit the loop so the
1804
+ // transport triggers a new run on the latest version.
1805
+ if (locals_js_1.locals.get(chatUpgradeRequestedKey)) {
1806
+ return "exit";
1807
+ }
1765
1808
  // Wait for the next message — stay idle briefly, then suspend
1766
1809
  const effectiveIdleTimeout = metadata_js_1.metadata.get(IDLE_TIMEOUT_METADATA_KEY) ??
1767
1810
  idleTimeoutInSeconds;
@@ -1854,6 +1897,10 @@ function chatAgent(options) {
1854
1897
  catch {
1855
1898
  // Best-effort — if stream write fails, let the run continue anyway
1856
1899
  }
1900
+ // chat.requestUpgrade() — exit after error turn too
1901
+ if (locals_js_1.locals.get(chatUpgradeRequestedKey)) {
1902
+ return;
1903
+ }
1857
1904
  // Wait for the next message — same as after a successful turn
1858
1905
  const effectiveIdleTimeout = metadata_js_1.metadata.get(IDLE_TIMEOUT_METADATA_KEY) ??
1859
1906
  idleTimeoutInSeconds;
@@ -2187,6 +2234,41 @@ function isStopped() {
2187
2234
  return controller?.signal.aborted ?? false;
2188
2235
  }
2189
2236
  // ---------------------------------------------------------------------------
2237
+ // Version upgrade
2238
+ // ---------------------------------------------------------------------------
2239
+ /**
2240
+ * Request that the current run exits so the next message starts on the latest
2241
+ * deployed version (via the standard continuation mechanism).
2242
+ *
2243
+ * When called from `onTurnStart` or `onValidateMessages`, `run()` is skipped
2244
+ * entirely — the run exits immediately and the transport re-triggers the
2245
+ * same message on the new version.
2246
+ *
2247
+ * When called from `run()` or `chat.defer()`, the current turn completes
2248
+ * normally and the run exits afterward instead of waiting for the next message.
2249
+ *
2250
+ * Call from `onTurnStart`, `onValidateMessages`, `onChatResume`, `run()`,
2251
+ * or inside `chat.defer()`.
2252
+ *
2253
+ * @example
2254
+ * ```ts
2255
+ * const SUPPORTED_VERSIONS = new Set(["v2", "v3"]);
2256
+ *
2257
+ * chat.agent({
2258
+ * id: "my-chat",
2259
+ * onTurnStart: async ({ clientData }) => {
2260
+ * if (clientData?.protocolVersion && !SUPPORTED_VERSIONS.has(clientData.protocolVersion)) {
2261
+ * chat.requestUpgrade();
2262
+ * }
2263
+ * },
2264
+ * run: async ({ messages }) => { ... },
2265
+ * });
2266
+ * ```
2267
+ */
2268
+ function requestUpgrade() {
2269
+ locals_js_1.locals.set(chatUpgradeRequestedKey, true);
2270
+ }
2271
+ // ---------------------------------------------------------------------------
2190
2272
  // Per-turn deferred work
2191
2273
  // ---------------------------------------------------------------------------
2192
2274
  /**
@@ -2199,15 +2281,21 @@ function isStopped() {
2199
2281
  * @example
2200
2282
  * ```ts
2201
2283
  * onTurnStart: async ({ chatId, uiMessages }) => {
2202
- * // Persist messages without blocking the LLM call
2284
+ * // Pass a promise directly
2203
2285
  * chat.defer(db.chat.update({ where: { id: chatId }, data: { messages: uiMessages } }));
2286
+ *
2287
+ * // Or pass an async function — cleaner for multi-step work
2288
+ * chat.defer(async () => {
2289
+ * const flags = await getFeatureFlags();
2290
+ * if (flags.forceUpgrade) chat.requestUpgrade();
2291
+ * });
2204
2292
  * },
2205
2293
  * ```
2206
2294
  */
2207
- function chatDefer(promise) {
2295
+ function chatDefer(promiseOrFn) {
2208
2296
  const promises = locals_js_1.locals.get(chatDeferKey);
2209
2297
  if (promises) {
2210
- promises.add(promise);
2298
+ promises.add(typeof promiseOrFn === "function" ? promiseOrFn() : promiseOrFn);
2211
2299
  }
2212
2300
  }
2213
2301
  // ---------------------------------------------------------------------------
@@ -2613,7 +2701,8 @@ class ChatMessageAccumulator {
2613
2701
  * ```
2614
2702
  */
2615
2703
  function createChatSession(payload, options) {
2616
- const { signal: runSignal, idleTimeoutInSeconds = 30, timeout = "1h", maxTurns = 100, compaction: sessionCompaction, pendingMessages: sessionPendingMessages, } = options;
2704
+ const { signal: runSignal, idleTimeoutInSeconds: sessionIdleTimeoutOpt, timeout = "1h", maxTurns = 100, compaction: sessionCompaction, pendingMessages: sessionPendingMessages, } = options;
2705
+ const idleTimeoutInSeconds = sessionIdleTimeoutOpt ?? 30;
2617
2706
  return {
2618
2707
  [Symbol.asyncIterator]() {
2619
2708
  let currentPayload = payload;
@@ -2628,7 +2717,7 @@ function createChatSession(payload, options) {
2628
2717
  // First turn: handle preload — wait for the first real message
2629
2718
  if (turn === 0 && currentPayload.trigger === "preload") {
2630
2719
  const result = await messagesInput.waitWithIdleTimeout({
2631
- idleTimeoutInSeconds: currentPayload.idleTimeoutInSeconds ?? idleTimeoutInSeconds,
2720
+ idleTimeoutInSeconds: sessionIdleTimeoutOpt ?? currentPayload.idleTimeoutInSeconds ?? 30,
2632
2721
  timeout,
2633
2722
  spanName: "waiting for first message",
2634
2723
  });
@@ -2640,6 +2729,11 @@ function createChatSession(payload, options) {
2640
2729
  }
2641
2730
  // Subsequent turns: wait for the next message
2642
2731
  if (turn > 0) {
2732
+ // chat.requestUpgrade() — exit before waiting
2733
+ if (locals_js_1.locals.get(chatUpgradeRequestedKey)) {
2734
+ stop.cleanup();
2735
+ return { done: true, value: undefined };
2736
+ }
2643
2737
  const next = await messagesInput.waitWithIdleTimeout({
2644
2738
  idleTimeoutInSeconds,
2645
2739
  timeout,
@@ -2702,6 +2796,13 @@ function createChatSession(payload, options) {
2702
2796
  });
2703
2797
  // Accumulate messages
2704
2798
  const messages = await accumulator.addIncoming(currentPayload.messages, currentPayload.trigger, turn);
2799
+ // chat.requestUpgrade() called before this turn — signal transport and exit
2800
+ if (locals_js_1.locals.get(chatUpgradeRequestedKey)) {
2801
+ await writeUpgradeRequiredChunk();
2802
+ sessionMsgSub.off();
2803
+ stop.cleanup();
2804
+ return { done: true, value: undefined };
2805
+ }
2705
2806
  const combinedSignal = AbortSignal.any([runSignal, stop.signal]);
2706
2807
  const turnObj = {
2707
2808
  number: turn,
@@ -3107,6 +3208,8 @@ exports.chat = {
3107
3208
  setUIMessageStreamOptions,
3108
3209
  /** Check if the current turn was stopped by the user. See {@link isStopped}. */
3109
3210
  isStopped,
3211
+ /** Request that the run exits after the current turn so the next message starts on the latest version. See {@link requestUpgrade}. */
3212
+ requestUpgrade,
3110
3213
  /** Clean up aborted parts from a UIMessage. See {@link cleanupAbortedParts}. */
3111
3214
  cleanupAbortedParts,
3112
3215
  /** Register background work that runs in parallel with streaming. See {@link chatDefer}. */
@@ -3166,13 +3269,30 @@ async function writeTurnCompleteChunk(chatId, publicAccessToken) {
3166
3269
  collapsed: true,
3167
3270
  execute: ({ write }) => {
3168
3271
  write({
3169
- type: "__trigger_turn_complete",
3272
+ type: "trigger:turn-complete",
3170
3273
  ...(publicAccessToken ? { publicAccessToken } : {}),
3171
3274
  });
3172
3275
  },
3173
3276
  });
3174
3277
  return await waitUntilComplete();
3175
3278
  }
3279
+ /**
3280
+ * Writes an upgrade-required control chunk to the chat output stream.
3281
+ * The transport intercepts this to re-trigger the same message on the latest version.
3282
+ * @internal
3283
+ */
3284
+ async function writeUpgradeRequiredChunk() {
3285
+ const { waitUntilComplete } = streams_js_1.streams.writer(exports.CHAT_STREAM_KEY, {
3286
+ spanName: "upgrade required",
3287
+ collapsed: true,
3288
+ execute: ({ write }) => {
3289
+ write({
3290
+ type: "trigger:upgrade-required",
3291
+ });
3292
+ },
3293
+ });
3294
+ return await waitUntilComplete();
3295
+ }
3176
3296
  /**
3177
3297
  * Extracts the text content of the last user message from a UIMessage array.
3178
3298
  * Returns undefined if no user message is found.