@trigger.dev/sdk 0.0.0-chat-prerelease-20260401212829 → 0.0.0-chat-prerelease-20260403201229

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 +79 -2
  2. package/dist/commonjs/v3/ai.js +116 -6
  3. package/dist/commonjs/v3/ai.js.map +1 -1
  4. package/dist/commonjs/v3/chat-client.d.ts +120 -147
  5. package/dist/commonjs/v3/chat-client.js +263 -327
  6. package/dist/commonjs/v3/chat-client.js.map +1 -1
  7. package/dist/commonjs/v3/chat.d.ts +1 -1
  8. package/dist/commonjs/v3/chat.js +57 -9
  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 +79 -2
  20. package/dist/esm/v3/ai.js +116 -6
  21. package/dist/esm/v3/ai.js.map +1 -1
  22. package/dist/esm/v3/chat-client.d.ts +120 -147
  23. package/dist/esm/v3/chat-client.js +261 -324
  24. package/dist/esm/v3/chat-client.js.map +1 -1
  25. package/dist/esm/v3/chat.d.ts +1 -1
  26. package/dist/esm/v3/chat.js +56 -7
  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.
@@ -1554,6 +1593,36 @@ declare function setUIMessageStreamOptions(options: ChatUIMessageStreamOptions<U
1554
1593
  * ```
1555
1594
  */
1556
1595
  declare function isStopped(): boolean;
1596
+ /**
1597
+ * Request that the current run exits so the next message starts on the latest
1598
+ * deployed version (via the standard continuation mechanism).
1599
+ *
1600
+ * When called from `onTurnStart` or `onValidateMessages`, `run()` is skipped
1601
+ * entirely — the run exits immediately and the transport re-triggers the
1602
+ * same message on the new version.
1603
+ *
1604
+ * When called from `run()` or `chat.defer()`, the current turn completes
1605
+ * normally and the run exits afterward instead of waiting for the next message.
1606
+ *
1607
+ * Call from `onTurnStart`, `onValidateMessages`, `onChatResume`, `run()`,
1608
+ * or inside `chat.defer()`.
1609
+ *
1610
+ * @example
1611
+ * ```ts
1612
+ * const SUPPORTED_VERSIONS = new Set(["v2", "v3"]);
1613
+ *
1614
+ * chat.agent({
1615
+ * id: "my-chat",
1616
+ * onTurnStart: async ({ clientData }) => {
1617
+ * if (clientData?.protocolVersion && !SUPPORTED_VERSIONS.has(clientData.protocolVersion)) {
1618
+ * chat.requestUpgrade();
1619
+ * }
1620
+ * },
1621
+ * run: async ({ messages }) => { ... },
1622
+ * });
1623
+ * ```
1624
+ */
1625
+ declare function requestUpgrade(): void;
1557
1626
  /**
1558
1627
  * Register a promise that runs in the background during the current turn.
1559
1628
  *
@@ -1564,12 +1633,18 @@ declare function isStopped(): boolean;
1564
1633
  * @example
1565
1634
  * ```ts
1566
1635
  * onTurnStart: async ({ chatId, uiMessages }) => {
1567
- * // Persist messages without blocking the LLM call
1636
+ * // Pass a promise directly
1568
1637
  * chat.defer(db.chat.update({ where: { id: chatId }, data: { messages: uiMessages } }));
1638
+ *
1639
+ * // Or pass an async function — cleaner for multi-step work
1640
+ * chat.defer(async () => {
1641
+ * const flags = await getFeatureFlags();
1642
+ * if (flags.forceUpgrade) chat.requestUpgrade();
1643
+ * });
1569
1644
  * },
1570
1645
  * ```
1571
1646
  */
1572
- declare function chatDefer(promise: Promise<unknown>): void;
1647
+ declare function chatDefer(promiseOrFn: Promise<unknown> | (() => Promise<unknown>)): void;
1573
1648
  /**
1574
1649
  * Queue model messages for injection at the next `prepareStep` boundary.
1575
1650
  *
@@ -2010,6 +2085,8 @@ export declare const chat: {
2010
2085
  setUIMessageStreamOptions: typeof setUIMessageStreamOptions;
2011
2086
  /** Check if the current turn was stopped by the user. See {@link isStopped}. */
2012
2087
  isStopped: typeof isStopped;
2088
+ /** Request that the run exits after the current turn so the next message starts on the latest version. See {@link requestUpgrade}. */
2089
+ requestUpgrade: typeof requestUpgrade;
2013
2090
  /** Clean up aborted parts from a UIMessage. See {@link cleanupAbortedParts}. */
2014
2091
  cleanupAbortedParts: typeof cleanupAbortedParts;
2015
2092
  /** 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 },
@@ -1236,7 +1238,27 @@ function chatAgent(options) {
1236
1238
  // useChat state may still contain assistant messages with tool parts
1237
1239
  // in partial/input-available state. These cause API errors (e.g.
1238
1240
  // Anthropic requires every tool_use to have a matching tool_result).
1239
- const cleanedUIMessages = uiMessages.map((msg) => msg.role === "assistant" ? cleanupAbortedParts(msg) : msg);
1241
+ let cleanedUIMessages = uiMessages.map((msg) => msg.role === "assistant" ? cleanupAbortedParts(msg) : msg);
1242
+ // Validate/transform UIMessages before conversion — catches malformed
1243
+ // messages from storage or untrusted input before they reach the model.
1244
+ if (onValidateMessages) {
1245
+ cleanedUIMessages = await tracer_js_1.tracer.startActiveSpan("onValidateMessages()", async () => {
1246
+ return onValidateMessages({
1247
+ messages: cleanedUIMessages,
1248
+ chatId: currentWirePayload.chatId,
1249
+ turn,
1250
+ trigger: currentWirePayload.trigger,
1251
+ });
1252
+ }, {
1253
+ attributes: {
1254
+ [v3_1.SemanticInternalAttributes.STYLE_ICON]: "task-hook-onStart",
1255
+ [v3_1.SemanticInternalAttributes.COLLAPSED]: true,
1256
+ "chat.id": currentWirePayload.chatId,
1257
+ "chat.turn": turn + 1,
1258
+ "chat.messages.count": cleanedUIMessages.length,
1259
+ },
1260
+ });
1261
+ }
1240
1262
  // Convert the incoming UIMessages to model messages and update the accumulator.
1241
1263
  // Turn 1: full history from the frontend → replaces the accumulator.
1242
1264
  // Turn 2+: only the new message(s) → appended to the accumulator.
@@ -1360,6 +1382,13 @@ function chatAgent(options) {
1360
1382
  },
1361
1383
  });
1362
1384
  }
1385
+ // chat.requestUpgrade() called in onTurnStart (or onValidateMessages) —
1386
+ // skip run() and signal the transport to re-trigger the same message
1387
+ // on the new version.
1388
+ if (locals_js_1.locals.get(chatUpgradeRequestedKey)) {
1389
+ await writeUpgradeRequiredChunk();
1390
+ return "exit";
1391
+ }
1363
1392
  // Captured by the onFinish callback below — works even on abort/stop.
1364
1393
  let capturedResponseMessage;
1365
1394
  // Promise that resolves when the AI SDK's onFinish fires.
@@ -1762,6 +1791,11 @@ function chatAgent(options) {
1762
1791
  currentWirePayload = pendingMessages[0];
1763
1792
  return "continue";
1764
1793
  }
1794
+ // chat.requestUpgrade() was called — exit the loop so the
1795
+ // transport triggers a new run on the latest version.
1796
+ if (locals_js_1.locals.get(chatUpgradeRequestedKey)) {
1797
+ return "exit";
1798
+ }
1765
1799
  // Wait for the next message — stay idle briefly, then suspend
1766
1800
  const effectiveIdleTimeout = metadata_js_1.metadata.get(IDLE_TIMEOUT_METADATA_KEY) ??
1767
1801
  idleTimeoutInSeconds;
@@ -1854,6 +1888,10 @@ function chatAgent(options) {
1854
1888
  catch {
1855
1889
  // Best-effort — if stream write fails, let the run continue anyway
1856
1890
  }
1891
+ // chat.requestUpgrade() — exit after error turn too
1892
+ if (locals_js_1.locals.get(chatUpgradeRequestedKey)) {
1893
+ return;
1894
+ }
1857
1895
  // Wait for the next message — same as after a successful turn
1858
1896
  const effectiveIdleTimeout = metadata_js_1.metadata.get(IDLE_TIMEOUT_METADATA_KEY) ??
1859
1897
  idleTimeoutInSeconds;
@@ -2187,6 +2225,41 @@ function isStopped() {
2187
2225
  return controller?.signal.aborted ?? false;
2188
2226
  }
2189
2227
  // ---------------------------------------------------------------------------
2228
+ // Version upgrade
2229
+ // ---------------------------------------------------------------------------
2230
+ /**
2231
+ * Request that the current run exits so the next message starts on the latest
2232
+ * deployed version (via the standard continuation mechanism).
2233
+ *
2234
+ * When called from `onTurnStart` or `onValidateMessages`, `run()` is skipped
2235
+ * entirely — the run exits immediately and the transport re-triggers the
2236
+ * same message on the new version.
2237
+ *
2238
+ * When called from `run()` or `chat.defer()`, the current turn completes
2239
+ * normally and the run exits afterward instead of waiting for the next message.
2240
+ *
2241
+ * Call from `onTurnStart`, `onValidateMessages`, `onChatResume`, `run()`,
2242
+ * or inside `chat.defer()`.
2243
+ *
2244
+ * @example
2245
+ * ```ts
2246
+ * const SUPPORTED_VERSIONS = new Set(["v2", "v3"]);
2247
+ *
2248
+ * chat.agent({
2249
+ * id: "my-chat",
2250
+ * onTurnStart: async ({ clientData }) => {
2251
+ * if (clientData?.protocolVersion && !SUPPORTED_VERSIONS.has(clientData.protocolVersion)) {
2252
+ * chat.requestUpgrade();
2253
+ * }
2254
+ * },
2255
+ * run: async ({ messages }) => { ... },
2256
+ * });
2257
+ * ```
2258
+ */
2259
+ function requestUpgrade() {
2260
+ locals_js_1.locals.set(chatUpgradeRequestedKey, true);
2261
+ }
2262
+ // ---------------------------------------------------------------------------
2190
2263
  // Per-turn deferred work
2191
2264
  // ---------------------------------------------------------------------------
2192
2265
  /**
@@ -2199,15 +2272,21 @@ function isStopped() {
2199
2272
  * @example
2200
2273
  * ```ts
2201
2274
  * onTurnStart: async ({ chatId, uiMessages }) => {
2202
- * // Persist messages without blocking the LLM call
2275
+ * // Pass a promise directly
2203
2276
  * chat.defer(db.chat.update({ where: { id: chatId }, data: { messages: uiMessages } }));
2277
+ *
2278
+ * // Or pass an async function — cleaner for multi-step work
2279
+ * chat.defer(async () => {
2280
+ * const flags = await getFeatureFlags();
2281
+ * if (flags.forceUpgrade) chat.requestUpgrade();
2282
+ * });
2204
2283
  * },
2205
2284
  * ```
2206
2285
  */
2207
- function chatDefer(promise) {
2286
+ function chatDefer(promiseOrFn) {
2208
2287
  const promises = locals_js_1.locals.get(chatDeferKey);
2209
2288
  if (promises) {
2210
- promises.add(promise);
2289
+ promises.add(typeof promiseOrFn === "function" ? promiseOrFn() : promiseOrFn);
2211
2290
  }
2212
2291
  }
2213
2292
  // ---------------------------------------------------------------------------
@@ -2640,6 +2719,11 @@ function createChatSession(payload, options) {
2640
2719
  }
2641
2720
  // Subsequent turns: wait for the next message
2642
2721
  if (turn > 0) {
2722
+ // chat.requestUpgrade() — exit before waiting
2723
+ if (locals_js_1.locals.get(chatUpgradeRequestedKey)) {
2724
+ stop.cleanup();
2725
+ return { done: true, value: undefined };
2726
+ }
2643
2727
  const next = await messagesInput.waitWithIdleTimeout({
2644
2728
  idleTimeoutInSeconds,
2645
2729
  timeout,
@@ -2702,6 +2786,13 @@ function createChatSession(payload, options) {
2702
2786
  });
2703
2787
  // Accumulate messages
2704
2788
  const messages = await accumulator.addIncoming(currentPayload.messages, currentPayload.trigger, turn);
2789
+ // chat.requestUpgrade() called before this turn — signal transport and exit
2790
+ if (locals_js_1.locals.get(chatUpgradeRequestedKey)) {
2791
+ await writeUpgradeRequiredChunk();
2792
+ sessionMsgSub.off();
2793
+ stop.cleanup();
2794
+ return { done: true, value: undefined };
2795
+ }
2705
2796
  const combinedSignal = AbortSignal.any([runSignal, stop.signal]);
2706
2797
  const turnObj = {
2707
2798
  number: turn,
@@ -3107,6 +3198,8 @@ exports.chat = {
3107
3198
  setUIMessageStreamOptions,
3108
3199
  /** Check if the current turn was stopped by the user. See {@link isStopped}. */
3109
3200
  isStopped,
3201
+ /** Request that the run exits after the current turn so the next message starts on the latest version. See {@link requestUpgrade}. */
3202
+ requestUpgrade,
3110
3203
  /** Clean up aborted parts from a UIMessage. See {@link cleanupAbortedParts}. */
3111
3204
  cleanupAbortedParts,
3112
3205
  /** Register background work that runs in parallel with streaming. See {@link chatDefer}. */
@@ -3166,13 +3259,30 @@ async function writeTurnCompleteChunk(chatId, publicAccessToken) {
3166
3259
  collapsed: true,
3167
3260
  execute: ({ write }) => {
3168
3261
  write({
3169
- type: "__trigger_turn_complete",
3262
+ type: "trigger:turn-complete",
3170
3263
  ...(publicAccessToken ? { publicAccessToken } : {}),
3171
3264
  });
3172
3265
  },
3173
3266
  });
3174
3267
  return await waitUntilComplete();
3175
3268
  }
3269
+ /**
3270
+ * Writes an upgrade-required control chunk to the chat output stream.
3271
+ * The transport intercepts this to re-trigger the same message on the latest version.
3272
+ * @internal
3273
+ */
3274
+ async function writeUpgradeRequiredChunk() {
3275
+ const { waitUntilComplete } = streams_js_1.streams.writer(exports.CHAT_STREAM_KEY, {
3276
+ spanName: "upgrade required",
3277
+ collapsed: true,
3278
+ execute: ({ write }) => {
3279
+ write({
3280
+ type: "trigger:upgrade-required",
3281
+ });
3282
+ },
3283
+ });
3284
+ return await waitUntilComplete();
3285
+ }
3176
3286
  /**
3177
3287
  * Extracts the text content of the last user message from a UIMessage array.
3178
3288
  * Returns undefined if no user message is found.