@trigger.dev/sdk 0.0.0-prerelease-20260306173130 → 0.0.0-prerelease-20260309160514

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.
@@ -1,9 +1,24 @@
1
1
  import { AnyTask, Task, type inferSchemaIn, type inferSchemaOut, type TaskIdentifier, type TaskOptions, type TaskSchema, type TaskWithSchema } from "@trigger.dev/core/v3";
2
- import type { ModelMessage, UIMessage } from "ai";
3
- import { Tool, ToolCallOptions } from "ai";
2
+ import type { ModelMessage, UIMessage, UIMessageChunk } from "ai";
3
+ import { Tool } from "ai";
4
4
  import { locals } from "./locals.js";
5
5
  import { CHAT_MESSAGES_STREAM_ID, CHAT_STOP_STREAM_ID } from "./chat-constants.js";
6
- export type ToolCallExecutionOptions = Omit<ToolCallOptions, "abortSignal">;
6
+ export type ToolCallExecutionOptions = {
7
+ toolCallId: string;
8
+ experimental_context?: unknown;
9
+ /** Chat context — only present when the tool runs inside a chat.task turn. */
10
+ chatId?: string;
11
+ turn?: number;
12
+ continuation?: boolean;
13
+ clientData?: unknown;
14
+ };
15
+ /** Chat context stored in locals during each chat.task turn for auto-detection. */
16
+ type ChatTurnContext<TClientData = unknown> = {
17
+ chatId: string;
18
+ turn: number;
19
+ continuation: boolean;
20
+ clientData?: TClientData;
21
+ };
7
22
  type ToolResultContent = Array<{
8
23
  type: "text";
9
24
  text: string;
@@ -18,9 +33,43 @@ export type ToolOptions<TResult> = {
18
33
  declare function toolFromTask<TIdentifier extends string, TInput = void, TOutput = unknown>(task: Task<TIdentifier, TInput, TOutput>, options?: ToolOptions<TOutput>): Tool<TInput, TOutput>;
19
34
  declare function toolFromTask<TIdentifier extends string, TTaskSchema extends TaskSchema | undefined = undefined, TOutput = unknown>(task: TaskWithSchema<TIdentifier, TTaskSchema, TOutput>, options?: ToolOptions<TOutput>): Tool<inferSchemaIn<TTaskSchema>, TOutput>;
20
35
  declare function getToolOptionsFromMetadata(): ToolCallExecutionOptions | undefined;
36
+ /**
37
+ * Get the current tool call ID from inside a subtask invoked via `ai.tool()`.
38
+ * Returns `undefined` if not running as a tool subtask.
39
+ */
40
+ declare function getToolCallId(): string | undefined;
41
+ /**
42
+ * Get the chat context from inside a subtask invoked via `ai.tool()` within a `chat.task`.
43
+ * Pass `typeof yourChatTask` as the type parameter to get typed `clientData`.
44
+ * Returns `undefined` if the parent is not a chat task.
45
+ *
46
+ * @example
47
+ * ```ts
48
+ * const ctx = ai.chatContext<typeof myChat>();
49
+ * // ctx?.clientData is typed based on myChat's clientDataSchema
50
+ * ```
51
+ */
52
+ declare function getToolChatContext<TChatTask extends AnyTask = AnyTask>(): ChatTurnContext<InferChatClientData<TChatTask>> | undefined;
53
+ /**
54
+ * Get the chat context from inside a subtask, throwing if not in a chat context.
55
+ * Pass `typeof yourChatTask` as the type parameter to get typed `clientData`.
56
+ *
57
+ * @example
58
+ * ```ts
59
+ * const ctx = ai.chatContextOrThrow<typeof myChat>();
60
+ * // ctx.chatId, ctx.clientData are guaranteed non-null
61
+ * ```
62
+ */
63
+ declare function getToolChatContextOrThrow<TChatTask extends AnyTask = AnyTask>(): ChatTurnContext<InferChatClientData<TChatTask>>;
21
64
  export declare const ai: {
22
65
  tool: typeof toolFromTask;
23
66
  currentToolOptions: typeof getToolOptionsFromMetadata;
67
+ /** Get the tool call ID from inside a subtask invoked via `ai.tool()`. */
68
+ toolCallId: typeof getToolCallId;
69
+ /** Get chat context (chatId, turn, clientData, etc.) from inside a subtask of a `chat.task`. Returns undefined if not in a chat context. */
70
+ chatContext: typeof getToolChatContext;
71
+ /** Get chat context or throw if not in a chat context. Pass `typeof yourChatTask` for typed clientData. */
72
+ chatContextOrThrow: typeof getToolChatContextOrThrow;
24
73
  };
25
74
  /**
26
75
  * Creates a public access token for a chat task.
@@ -47,6 +96,21 @@ declare function createChatAccessToken<TTask extends AnyTask>(taskId: TaskIdenti
47
96
  */
48
97
  export declare const CHAT_STREAM_KEY = "chat";
49
98
  export { CHAT_MESSAGES_STREAM_ID, CHAT_STOP_STREAM_ID };
99
+ /**
100
+ * The wire payload shape sent by `TriggerChatTransport`.
101
+ * Uses `metadata` to match the AI SDK's `ChatRequestOptions` field name.
102
+ */
103
+ export type ChatTaskWirePayload<TMessage extends UIMessage = UIMessage, TMetadata = unknown> = {
104
+ messages: TMessage[];
105
+ chatId: string;
106
+ trigger: "submit-message" | "regenerate-message" | "preload";
107
+ messageId?: string;
108
+ metadata?: TMetadata;
109
+ /** Whether this run is continuing an existing chat whose previous run ended. */
110
+ continuation?: boolean;
111
+ /** The run ID of the previous run (only set when `continuation` is true). */
112
+ previousRunId?: string;
113
+ };
50
114
  /**
51
115
  * The payload shape passed to the `chatTask` run function.
52
116
  *
@@ -66,12 +130,19 @@ export type ChatTaskPayload<TClientData = unknown> = {
66
130
  * The trigger type:
67
131
  * - `"submit-message"`: A new user message
68
132
  * - `"regenerate-message"`: Regenerate the last assistant response
133
+ * - `"preload"`: Run was preloaded before the first message (only on turn 0)
69
134
  */
70
- trigger: "submit-message" | "regenerate-message";
135
+ trigger: "submit-message" | "regenerate-message" | "preload";
71
136
  /** The ID of the message to regenerate (only for `"regenerate-message"`) */
72
137
  messageId?: string;
73
138
  /** Custom data from the frontend (passed via `metadata` on `sendMessage()` or the transport). */
74
139
  clientData?: TClientData;
140
+ /** Whether this run is continuing an existing chat (previous run timed out or was cancelled). False for brand new chats. */
141
+ continuation: boolean;
142
+ /** The run ID of the previous run (only set when `continuation` is true). */
143
+ previousRunId?: string;
144
+ /** Whether this run was preloaded before the first message. */
145
+ preloaded: boolean;
75
146
  };
76
147
  /**
77
148
  * Abort signals provided to the `chatTask` run function.
@@ -168,6 +239,19 @@ declare function pipeChat(source: UIMessageStreamable | AsyncIterable<unknown> |
168
239
  * emits a control chunk and suspends via `messagesInput.wait()`. The frontend
169
240
  * transport resumes the same run by sending the next message via input streams.
170
241
  */
242
+ /**
243
+ * Event passed to the `onPreload` callback.
244
+ */
245
+ export type PreloadEvent<TClientData = unknown> = {
246
+ /** The unique identifier for the chat session. */
247
+ chatId: string;
248
+ /** The Trigger.dev run ID for this conversation. */
249
+ runId: string;
250
+ /** A scoped access token for this chat run. */
251
+ chatAccessToken: string;
252
+ /** Custom data from the frontend. */
253
+ clientData?: TClientData;
254
+ };
171
255
  /**
172
256
  * Event passed to the `onChatStart` callback.
173
257
  */
@@ -182,6 +266,12 @@ export type ChatStartEvent<TClientData = unknown> = {
182
266
  runId: string;
183
267
  /** A scoped access token for this chat run. Persist this for frontend reconnection. */
184
268
  chatAccessToken: string;
269
+ /** Whether this run is continuing an existing chat (previous run timed out or was cancelled). False for brand new chats. */
270
+ continuation: boolean;
271
+ /** The run ID of the previous run (only set when `continuation` is true). */
272
+ previousRunId?: string;
273
+ /** Whether this run was preloaded before the first message. */
274
+ preloaded: boolean;
185
275
  };
186
276
  /**
187
277
  * Event passed to the `onTurnStart` callback.
@@ -201,6 +291,12 @@ export type TurnStartEvent<TClientData = unknown> = {
201
291
  chatAccessToken: string;
202
292
  /** Custom data from the frontend. */
203
293
  clientData?: TClientData;
294
+ /** Whether this run is continuing an existing chat (previous run timed out or was cancelled). False for brand new chats. */
295
+ continuation: boolean;
296
+ /** The run ID of the previous run (only set when `continuation` is true). */
297
+ previousRunId?: string;
298
+ /** Whether this run was preloaded before the first message. */
299
+ preloaded: boolean;
204
300
  };
205
301
  /**
206
302
  * Event passed to the `onTurnComplete` callback.
@@ -225,8 +321,14 @@ export type TurnCompleteEvent<TClientData = unknown> = {
225
321
  * Useful for inserting individual message records instead of overwriting the full history.
226
322
  */
227
323
  newUIMessages: UIMessage[];
228
- /** The assistant's response for this turn (undefined if `pipeChat` was used manually). */
324
+ /** The assistant's response for this turn, with aborted parts cleaned up when `stopped` is true. Undefined if `pipeChat` was used manually. */
229
325
  responseMessage: UIMessage | undefined;
326
+ /**
327
+ * The raw assistant response before abort cleanup. Includes incomplete tool parts
328
+ * (`input-available`, `partial-call`) and streaming reasoning/text parts.
329
+ * Use this if you need custom cleanup logic. Same as `responseMessage` when not stopped.
330
+ */
331
+ rawResponseMessage: UIMessage | undefined;
230
332
  /** The turn number (0-indexed). */
231
333
  turn: number;
232
334
  /** The Trigger.dev run ID for this conversation. */
@@ -237,6 +339,14 @@ export type TurnCompleteEvent<TClientData = unknown> = {
237
339
  lastEventId?: string;
238
340
  /** Custom data from the frontend. */
239
341
  clientData?: TClientData;
342
+ /** Whether the user stopped generation during this turn. */
343
+ stopped: boolean;
344
+ /** Whether this run is continuing an existing chat (previous run timed out or was cancelled). False for brand new chats. */
345
+ continuation: boolean;
346
+ /** The run ID of the previous run (only set when `continuation` is true). */
347
+ previousRunId?: string;
348
+ /** Whether this run was preloaded before the first message. */
349
+ preloaded: boolean;
240
350
  };
241
351
  export type ChatTaskOptions<TIdentifier extends string, TClientDataSchema extends TaskSchema | undefined = undefined> = Omit<TaskOptions<TIdentifier, ChatTaskWirePayload, unknown>, "run"> & {
242
352
  /**
@@ -268,6 +378,21 @@ export type ChatTaskOptions<TIdentifier extends string, TClientDataSchema extend
268
378
  * the stream is automatically piped to the frontend.
269
379
  */
270
380
  run: (payload: ChatTaskRunPayload<inferSchemaOut<TClientDataSchema>>) => Promise<unknown>;
381
+ /**
382
+ * Called when a preloaded run starts, before the first message arrives.
383
+ *
384
+ * Use this to initialize state, create DB records, and load context early —
385
+ * so everything is ready when the user's first message comes through.
386
+ *
387
+ * @example
388
+ * ```ts
389
+ * onPreload: async ({ chatId, clientData }) => {
390
+ * await db.chat.create({ data: { id: chatId } });
391
+ * userContext.init(await loadUser(clientData.userId));
392
+ * }
393
+ * ```
394
+ */
395
+ onPreload?: (event: PreloadEvent<inferSchemaOut<TClientDataSchema>>) => Promise<void> | void;
271
396
  /**
272
397
  * Called on the first turn (turn 0) of a new run, before the `run` function executes.
273
398
  *
@@ -346,6 +471,24 @@ export type ChatTaskOptions<TIdentifier extends string, TClientDataSchema extend
346
471
  * @default "1h"
347
472
  */
348
473
  chatAccessTokenTTL?: string;
474
+ /**
475
+ * How long (in seconds) to keep the run warm after `onPreload` fires,
476
+ * waiting for the first message before suspending.
477
+ *
478
+ * Only applies to preloaded runs (triggered via `transport.preload()`).
479
+ *
480
+ * @default Same as `warmTimeoutInSeconds`
481
+ */
482
+ preloadWarmTimeoutInSeconds?: number;
483
+ /**
484
+ * How long to wait (suspended) for the first message after a preloaded run starts.
485
+ * If no message arrives within this time, the run ends.
486
+ *
487
+ * Only applies to preloaded runs.
488
+ *
489
+ * @default Same as `turnTimeout`
490
+ */
491
+ preloadTimeout?: string;
349
492
  };
350
493
  /**
351
494
  * Creates a Trigger.dev task pre-configured for AI SDK chat.
@@ -427,6 +570,71 @@ declare function setTurnTimeoutInSeconds(seconds: number): void;
427
570
  * ```
428
571
  */
429
572
  declare function setWarmTimeoutInSeconds(seconds: number): void;
573
+ /**
574
+ * Check whether the user stopped generation during the current turn.
575
+ *
576
+ * Works from **anywhere** inside a `chat.task` run — including inside
577
+ * `streamText`'s `onFinish` callback — without needing to thread the
578
+ * `stopSignal` through closures.
579
+ *
580
+ * This is especially useful when the AI SDK's `isAborted` flag is unreliable
581
+ * (e.g. when using `createUIMessageStream` + `writer.merge()`).
582
+ *
583
+ * @example
584
+ * ```ts
585
+ * onFinish: ({ isAborted }) => {
586
+ * const wasStopped = isAborted || chat.isStopped();
587
+ * if (wasStopped) {
588
+ * // handle stop
589
+ * }
590
+ * }
591
+ * ```
592
+ */
593
+ declare function isStopped(): boolean;
594
+ /**
595
+ * Register a promise that runs in the background during the current turn.
596
+ *
597
+ * Use this to move non-blocking work (DB writes, analytics, etc.) out of
598
+ * the critical path. The promise runs in parallel with streaming and is
599
+ * awaited (with a 5 s timeout) before `onTurnComplete` fires.
600
+ *
601
+ * @example
602
+ * ```ts
603
+ * onTurnStart: async ({ chatId, uiMessages }) => {
604
+ * // Persist messages without blocking the LLM call
605
+ * chat.defer(db.chat.update({ where: { id: chatId }, data: { messages: uiMessages } }));
606
+ * },
607
+ * ```
608
+ */
609
+ declare function chatDefer(promise: Promise<unknown>): void;
610
+ /**
611
+ * Clean up a UIMessage that was captured during an aborted/stopped turn.
612
+ *
613
+ * When generation is stopped mid-stream, the captured message may contain:
614
+ * - Tool parts stuck in incomplete states (`partial-call`, `input-available`,
615
+ * `input-streaming`) that cause permanent UI spinners
616
+ * - Reasoning parts with `state: "streaming"` instead of `"done"`
617
+ * - Text parts with `state: "streaming"` instead of `"done"`
618
+ *
619
+ * This function returns a cleaned copy with:
620
+ * - Incomplete tool parts removed entirely
621
+ * - Reasoning and text parts marked as `"done"`
622
+ *
623
+ * `chat.task` calls this automatically when stop is detected before passing
624
+ * the response to `onTurnComplete`. Use this manually when calling `pipeChat`
625
+ * directly and capturing response messages yourself.
626
+ *
627
+ * @example
628
+ * ```ts
629
+ * onTurnComplete: async ({ responseMessage, stopped }) => {
630
+ * // Already cleaned automatically by chat.task — but if you captured
631
+ * // your own message via pipeChat, clean it manually:
632
+ * const cleaned = chat.cleanupAbortedParts(myMessage);
633
+ * await db.messages.save(cleaned);
634
+ * }
635
+ * ```
636
+ */
637
+ declare function cleanupAbortedParts(message: UIMessage): UIMessage;
430
638
  /**
431
639
  * A Proxy-backed, run-scoped data object that appears as `T` to users.
432
640
  * Includes helper methods for initialization, dirty tracking, and serialization.
@@ -451,12 +659,16 @@ export type ChatLocal<T extends Record<string, unknown>> = T & {
451
659
  *
452
660
  * Multiple locals can coexist — each gets its own isolated run-scoped storage.
453
661
  *
662
+ * The `id` is required and must be unique across all `chat.local()` calls in
663
+ * your project. It's used to serialize values into subtask metadata so that
664
+ * `ai.tool()` subtasks can auto-hydrate parent locals (read-only).
665
+ *
454
666
  * @example
455
667
  * ```ts
456
668
  * import { chat } from "@trigger.dev/sdk/ai";
457
669
  *
458
- * const userPrefs = chat.local<{ theme: string; language: string }>();
459
- * const gameState = chat.local<{ score: number; streak: number }>();
670
+ * const userPrefs = chat.local<{ theme: string; language: string }>({ id: "userPrefs" });
671
+ * const gameState = chat.local<{ score: number; streak: number }>({ id: "gameState" });
460
672
  *
461
673
  * export const myChat = chat.task({
462
674
  * id: "my-chat",
@@ -480,7 +692,9 @@ export type ChatLocal<T extends Record<string, unknown>> = T & {
480
692
  * });
481
693
  * ```
482
694
  */
483
- declare function chatLocal<T extends Record<string, unknown>>(): ChatLocal<T>;
695
+ declare function chatLocal<T extends Record<string, unknown>>(options: {
696
+ id: string;
697
+ }): ChatLocal<T>;
484
698
  /**
485
699
  * Extracts the client data (metadata) type from a chat task.
486
700
  * Use this to type the `metadata` option on the transport.
@@ -510,4 +724,12 @@ export declare const chat: {
510
724
  setTurnTimeoutInSeconds: typeof setTurnTimeoutInSeconds;
511
725
  /** Override the warm timeout at runtime. See {@link setWarmTimeoutInSeconds}. */
512
726
  setWarmTimeoutInSeconds: typeof setWarmTimeoutInSeconds;
727
+ /** Check if the current turn was stopped by the user. See {@link isStopped}. */
728
+ isStopped: typeof isStopped;
729
+ /** Clean up aborted parts from a UIMessage. See {@link cleanupAbortedParts}. */
730
+ cleanupAbortedParts: typeof cleanupAbortedParts;
731
+ /** Register background work that runs in parallel with streaming. See {@link chatDefer}. */
732
+ defer: typeof chatDefer;
733
+ /** Typed chat output stream for writing custom chunks or piping from subtasks. */
734
+ stream: import("@trigger.dev/core/v3").RealtimeDefinedStream<UIMessageChunk>;
513
735
  };