@trigger.dev/sdk 4.5.0-rc.5 → 4.5.0-rc.7

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 (213) hide show
  1. package/dist/commonjs/v3/ai.d.ts +178 -5
  2. package/dist/commonjs/v3/ai.js +603 -119
  3. package/dist/commonjs/v3/ai.js.map +1 -1
  4. package/dist/commonjs/v3/chat-client.js +3 -0
  5. package/dist/commonjs/v3/chat-client.js.map +1 -1
  6. package/dist/commonjs/v3/chat-react.js +10 -7
  7. package/dist/commonjs/v3/chat-react.js.map +1 -1
  8. package/dist/commonjs/v3/chat-server.d.ts +8 -0
  9. package/dist/commonjs/v3/chat-server.js +32 -10
  10. package/dist/commonjs/v3/chat-server.js.map +1 -1
  11. package/dist/commonjs/v3/chat-server.test.js +51 -0
  12. package/dist/commonjs/v3/chat-server.test.js.map +1 -1
  13. package/dist/commonjs/v3/chat.js +34 -6
  14. package/dist/commonjs/v3/chat.js.map +1 -1
  15. package/dist/commonjs/v3/chat.test.js +53 -0
  16. package/dist/commonjs/v3/chat.test.js.map +1 -1
  17. package/dist/commonjs/v3/createStartSessionAction.test.js +30 -0
  18. package/dist/commonjs/v3/createStartSessionAction.test.js.map +1 -1
  19. package/dist/commonjs/v3/sessions.d.ts +11 -6
  20. package/dist/commonjs/v3/sessions.js +10 -5
  21. package/dist/commonjs/v3/sessions.js.map +1 -1
  22. package/dist/commonjs/v3/test/mock-chat-agent.d.ts +6 -0
  23. package/dist/commonjs/v3/test/mock-chat-agent.js +1 -0
  24. package/dist/commonjs/v3/test/mock-chat-agent.js.map +1 -1
  25. package/dist/commonjs/version.js +1 -1
  26. package/dist/esm/v3/ai.d.ts +178 -5
  27. package/dist/esm/v3/ai.js +603 -120
  28. package/dist/esm/v3/ai.js.map +1 -1
  29. package/dist/esm/v3/chat-client.js +3 -0
  30. package/dist/esm/v3/chat-client.js.map +1 -1
  31. package/dist/esm/v3/chat-react.js +10 -7
  32. package/dist/esm/v3/chat-react.js.map +1 -1
  33. package/dist/esm/v3/chat-server.d.ts +8 -0
  34. package/dist/esm/v3/chat-server.js +32 -10
  35. package/dist/esm/v3/chat-server.js.map +1 -1
  36. package/dist/esm/v3/chat-server.test.js +51 -0
  37. package/dist/esm/v3/chat-server.test.js.map +1 -1
  38. package/dist/esm/v3/chat.js +34 -6
  39. package/dist/esm/v3/chat.js.map +1 -1
  40. package/dist/esm/v3/chat.test.js +53 -0
  41. package/dist/esm/v3/chat.test.js.map +1 -1
  42. package/dist/esm/v3/createStartSessionAction.test.js +30 -0
  43. package/dist/esm/v3/createStartSessionAction.test.js.map +1 -1
  44. package/dist/esm/v3/sessions.d.ts +11 -6
  45. package/dist/esm/v3/sessions.js +10 -5
  46. package/dist/esm/v3/sessions.js.map +1 -1
  47. package/dist/esm/v3/test/mock-chat-agent.d.ts +6 -0
  48. package/dist/esm/v3/test/mock-chat-agent.js +1 -0
  49. package/dist/esm/v3/test/mock-chat-agent.js.map +1 -1
  50. package/dist/esm/version.js +1 -1
  51. package/docs/ai/prompts.mdx +430 -0
  52. package/docs/ai-chat/actions.mdx +115 -0
  53. package/docs/ai-chat/anatomy.mdx +71 -0
  54. package/docs/ai-chat/backend.mdx +817 -0
  55. package/docs/ai-chat/background-injection.mdx +221 -0
  56. package/docs/ai-chat/changelog.mdx +850 -0
  57. package/docs/ai-chat/chat-local.mdx +174 -0
  58. package/docs/ai-chat/client-protocol.mdx +1081 -0
  59. package/docs/ai-chat/compaction.mdx +411 -0
  60. package/docs/ai-chat/custom-agents.mdx +364 -0
  61. package/docs/ai-chat/error-handling.mdx +415 -0
  62. package/docs/ai-chat/fast-starts.mdx +672 -0
  63. package/docs/ai-chat/frontend.mdx +580 -0
  64. package/docs/ai-chat/how-it-works.mdx +230 -0
  65. package/docs/ai-chat/lifecycle-hooks.mdx +530 -0
  66. package/docs/ai-chat/mcp.mdx +101 -0
  67. package/docs/ai-chat/overview.mdx +90 -0
  68. package/docs/ai-chat/patterns/branching-conversations.mdx +284 -0
  69. package/docs/ai-chat/patterns/code-sandbox.mdx +126 -0
  70. package/docs/ai-chat/patterns/database-persistence.mdx +414 -0
  71. package/docs/ai-chat/patterns/human-in-the-loop.mdx +275 -0
  72. package/docs/ai-chat/patterns/large-payloads.mdx +169 -0
  73. package/docs/ai-chat/patterns/oom-resilience.mdx +120 -0
  74. package/docs/ai-chat/patterns/persistence-and-replay.mdx +211 -0
  75. package/docs/ai-chat/patterns/recovery-boot.mdx +230 -0
  76. package/docs/ai-chat/patterns/skills.mdx +221 -0
  77. package/docs/ai-chat/patterns/sub-agents.mdx +383 -0
  78. package/docs/ai-chat/patterns/tool-result-auditing.mdx +148 -0
  79. package/docs/ai-chat/patterns/trusted-edge-signals.mdx +337 -0
  80. package/docs/ai-chat/patterns/version-upgrades.mdx +172 -0
  81. package/docs/ai-chat/pending-messages.mdx +343 -0
  82. package/docs/ai-chat/prompt-caching.mdx +206 -0
  83. package/docs/ai-chat/quick-start.mdx +161 -0
  84. package/docs/ai-chat/reference.mdx +909 -0
  85. package/docs/ai-chat/server-chat.mdx +263 -0
  86. package/docs/ai-chat/sessions.mdx +333 -0
  87. package/docs/ai-chat/testing.mdx +682 -0
  88. package/docs/ai-chat/tools.mdx +191 -0
  89. package/docs/ai-chat/types.mdx +242 -0
  90. package/docs/ai-chat/upgrade-guide.mdx +515 -0
  91. package/docs/apikeys.mdx +54 -0
  92. package/docs/building-with-ai.mdx +261 -0
  93. package/docs/bulk-actions.mdx +49 -0
  94. package/docs/changelog.mdx +6 -0
  95. package/docs/cli-deploy-commands.mdx +9 -0
  96. package/docs/cli-dev-commands.mdx +9 -0
  97. package/docs/cli-dev.mdx +8 -0
  98. package/docs/cli-init-commands.mdx +58 -0
  99. package/docs/cli-introduction.mdx +25 -0
  100. package/docs/cli-list-profiles-commands.mdx +42 -0
  101. package/docs/cli-login-commands.mdx +33 -0
  102. package/docs/cli-logout-commands.mdx +33 -0
  103. package/docs/cli-preview-archive.mdx +59 -0
  104. package/docs/cli-promote-commands.mdx +9 -0
  105. package/docs/cli-switch.mdx +43 -0
  106. package/docs/cli-update-commands.mdx +42 -0
  107. package/docs/cli-whoami-commands.mdx +33 -0
  108. package/docs/community.mdx +6 -0
  109. package/docs/config/config-file.mdx +602 -0
  110. package/docs/config/extensions/additionalFiles.mdx +38 -0
  111. package/docs/config/extensions/additionalPackages.mdx +40 -0
  112. package/docs/config/extensions/aptGet.mdx +34 -0
  113. package/docs/config/extensions/audioWaveform.mdx +20 -0
  114. package/docs/config/extensions/custom.mdx +380 -0
  115. package/docs/config/extensions/emitDecoratorMetadata.mdx +29 -0
  116. package/docs/config/extensions/esbuildPlugin.mdx +31 -0
  117. package/docs/config/extensions/ffmpeg.mdx +45 -0
  118. package/docs/config/extensions/lightpanda.mdx +56 -0
  119. package/docs/config/extensions/overview.mdx +67 -0
  120. package/docs/config/extensions/playwright.mdx +195 -0
  121. package/docs/config/extensions/prismaExtension.mdx +1014 -0
  122. package/docs/config/extensions/puppeteer.mdx +30 -0
  123. package/docs/config/extensions/pythonExtension.mdx +182 -0
  124. package/docs/config/extensions/syncEnvVars.mdx +291 -0
  125. package/docs/context.mdx +235 -0
  126. package/docs/database-connections.mdx +213 -0
  127. package/docs/deploy-environment-variables.mdx +435 -0
  128. package/docs/deployment/atomic-deployment.mdx +172 -0
  129. package/docs/deployment/overview.mdx +257 -0
  130. package/docs/deployment/preview-branches.mdx +224 -0
  131. package/docs/errors-retrying.mdx +379 -0
  132. package/docs/github-actions.mdx +222 -0
  133. package/docs/github-integration.mdx +136 -0
  134. package/docs/github-repo.mdx +8 -0
  135. package/docs/help-email.mdx +6 -0
  136. package/docs/help-slack.mdx +11 -0
  137. package/docs/hidden-tasks.mdx +56 -0
  138. package/docs/how-it-works.mdx +454 -0
  139. package/docs/how-to-reduce-your-spend.mdx +217 -0
  140. package/docs/idempotency.mdx +504 -0
  141. package/docs/introduction.mdx +223 -0
  142. package/docs/limits.mdx +241 -0
  143. package/docs/logging.mdx +195 -0
  144. package/docs/machines.mdx +952 -0
  145. package/docs/manual-setup.mdx +632 -0
  146. package/docs/mcp-agent-rules.mdx +41 -0
  147. package/docs/mcp-introduction.mdx +385 -0
  148. package/docs/mcp-tools.mdx +273 -0
  149. package/docs/migrating-from-v3.mdx +334 -0
  150. package/docs/observability/dashboards.mdx +102 -0
  151. package/docs/observability/query.mdx +585 -0
  152. package/docs/open-source-contributing.mdx +16 -0
  153. package/docs/open-source-self-hosting.mdx +541 -0
  154. package/docs/private-networking/aws-console-setup.mdx +304 -0
  155. package/docs/private-networking/overview.mdx +144 -0
  156. package/docs/private-networking/troubleshooting.mdx +78 -0
  157. package/docs/queue-concurrency.mdx +354 -0
  158. package/docs/quick-start.mdx +97 -0
  159. package/docs/realtime/auth.mdx +208 -0
  160. package/docs/realtime/backend/overview.mdx +45 -0
  161. package/docs/realtime/backend/streams.mdx +418 -0
  162. package/docs/realtime/backend/subscribe.mdx +225 -0
  163. package/docs/realtime/how-it-works.mdx +94 -0
  164. package/docs/realtime/overview.mdx +63 -0
  165. package/docs/realtime/react-hooks/overview.mdx +73 -0
  166. package/docs/realtime/react-hooks/streams.mdx +449 -0
  167. package/docs/realtime/react-hooks/subscribe.mdx +674 -0
  168. package/docs/realtime/react-hooks/swr.mdx +87 -0
  169. package/docs/realtime/react-hooks/triggering.mdx +194 -0
  170. package/docs/realtime/react-hooks/use-wait-token.mdx +34 -0
  171. package/docs/realtime/run-object.mdx +174 -0
  172. package/docs/replaying.mdx +72 -0
  173. package/docs/request-feature.mdx +6 -0
  174. package/docs/roadmap.mdx +6 -0
  175. package/docs/run-tests.mdx +20 -0
  176. package/docs/run-usage.mdx +113 -0
  177. package/docs/runs/heartbeats.mdx +38 -0
  178. package/docs/runs/max-duration.mdx +139 -0
  179. package/docs/runs/metadata.mdx +734 -0
  180. package/docs/runs/priority.mdx +31 -0
  181. package/docs/runs.mdx +396 -0
  182. package/docs/self-hosting/docker.mdx +458 -0
  183. package/docs/self-hosting/env/supervisor.mdx +74 -0
  184. package/docs/self-hosting/env/webapp.mdx +276 -0
  185. package/docs/self-hosting/kubernetes.mdx +601 -0
  186. package/docs/self-hosting/overview.mdx +108 -0
  187. package/docs/skills.mdx +85 -0
  188. package/docs/tags.mdx +120 -0
  189. package/docs/tasks/overview.mdx +697 -0
  190. package/docs/tasks/scheduled.mdx +382 -0
  191. package/docs/tasks/schemaTask.mdx +413 -0
  192. package/docs/tasks/streams.mdx +884 -0
  193. package/docs/triggering.mdx +1320 -0
  194. package/docs/troubleshooting-alerts.mdx +385 -0
  195. package/docs/troubleshooting-debugging-in-vscode.mdx +8 -0
  196. package/docs/troubleshooting-github-issues.mdx +6 -0
  197. package/docs/troubleshooting-uptime-status.mdx +6 -0
  198. package/docs/troubleshooting.mdx +398 -0
  199. package/docs/upgrading-packages.mdx +80 -0
  200. package/docs/vercel-integration.mdx +207 -0
  201. package/docs/versioning.mdx +56 -0
  202. package/docs/video-walkthrough.mdx +23 -0
  203. package/docs/wait-for-token.mdx +540 -0
  204. package/docs/wait-for.mdx +42 -0
  205. package/docs/wait-until.mdx +53 -0
  206. package/docs/wait.mdx +18 -0
  207. package/docs/writing-tasks-introduction.mdx +33 -0
  208. package/package.json +10 -6
  209. package/skills/trigger-authoring-chat-agent/SKILL.md +296 -0
  210. package/skills/trigger-authoring-tasks/SKILL.md +254 -0
  211. package/skills/trigger-chat-agent-advanced/SKILL.md +368 -0
  212. package/skills/trigger-cost-savings/SKILL.md +116 -0
  213. package/skills/trigger-realtime-and-frontend/SKILL.md +276 -0
@@ -0,0 +1,191 @@
1
+ ---
2
+ title: "Tools"
3
+ sidebarTitle: "Tools"
4
+ description: "Declare tools on chat.agent so toModelOutput survives across turns, get them back typed in run(), and type your messages from them."
5
+ ---
6
+
7
+ import RcBanner from "/snippets/ai-chat-rc-banner.mdx";
8
+
9
+ <RcBanner />
10
+
11
+ `chat.agent` doesn't call the model for you. Your tools still go to [`streamText`](https://sdk.vercel.ai/docs/ai-sdk-core/tools-and-tool-calling) inside `run()`. But you should **also declare them on the agent config**:
12
+
13
+ ```ts
14
+ import { chat } from "@trigger.dev/sdk/ai";
15
+ import { streamText, stepCountIs, tool } from "ai";
16
+ import { anthropic } from "@ai-sdk/anthropic";
17
+ import { z } from "zod";
18
+
19
+ const tools = {
20
+ searchDocs: tool({
21
+ description: "Search the docs.",
22
+ inputSchema: z.object({ query: z.string() }),
23
+ execute: async ({ query }) => searchIndex(query),
24
+ }),
25
+ };
26
+
27
+ export const myChat = chat.agent({
28
+ id: "my-chat",
29
+ tools, // ← declare here
30
+ run: async ({ messages, tools, signal }) =>
31
+ streamText({
32
+ ...chat.toStreamTextOptions({ tools }), // ← the same set, handed back on the payload
33
+ model: anthropic("claude-sonnet-4-5"),
34
+ messages,
35
+ abortSignal: signal,
36
+ stopWhen: stepCountIs(15),
37
+ }),
38
+ });
39
+ ```
40
+
41
+ Declaring `tools` on the config does two things you can't get by passing them to `streamText` alone:
42
+
43
+ - It threads your tools into the SDK's internal message conversion, so each tool's [`toModelOutput`](https://sdk.vercel.ai/docs/ai-sdk-core/tools-and-tool-calling#tomodeloutput) is re-applied when prior-turn history is re-converted (see [`toModelOutput` across turns](#tomodeloutput-across-turns)).
44
+ - It hands the resolved set back, typed, on the `run()` payload as `tools`, so you declare them once and don't re-import the map.
45
+
46
+ ## Where tools go
47
+
48
+ There are three places a tool set shows up. Declare once, reuse:
49
+
50
+ | Surface | What it's for |
51
+ | --- | --- |
52
+ | `chat.agent({ tools })` | Re-applies `toModelOutput` on prior-turn history; hands the set back typed on the `run()` payload. |
53
+ | `chat.toStreamTextOptions({ tools })` | Detects which tool calls need [HITL approval](/ai-chat/patterns/human-in-the-loop) (`needsApproval`) and merges any auto-injected [skill](/ai-chat/patterns/skills) tools. |
54
+ | `streamText({ tools })` | What the model actually calls. `chat.toStreamTextOptions({ tools })` already sets this, so spread it instead of passing `tools` twice. |
55
+
56
+ The canonical pattern: declare `tools` on the config, read them back from the `run()` payload, and pass that to `chat.toStreamTextOptions({ tools })`. One declaration flows everywhere.
57
+
58
+ <Tip>
59
+ Conversion only reads each tool's `inputSchema` and `toModelOutput`, never `execute`. If you keep heavy `execute` dependencies out of a module (for bundle reasons), you can declare a lightweight schema-only tool map on the config and add the executes where you call `streamText`.
60
+ </Tip>
61
+
62
+ ## `toModelOutput` across turns
63
+
64
+ `toModelOutput` transforms a tool's result before it enters the model's context, turning raw image bytes into an image content part, or compressing a long sub-agent transcript into a one-line summary. The full result still streams to the frontend; the model only sees the transformed version.
65
+
66
+ The catch is multi-turn. After each turn, `chat.agent` persists the conversation as `UIMessage[]` and re-converts it to model messages at the start of the next turn. That conversion needs your tools to find each `toModelOutput`. **If you only pass tools to `streamText` and not to the config, the transform runs on turn 1 but is skipped on every later turn.** The raw output gets stringified back into the prompt instead, and the model loses the transformed view.
67
+
68
+ Declaring `tools` on the config fixes this: the SDK threads them into the conversion, so `toModelOutput` is re-applied on every turn.
69
+
70
+ ```ts
71
+ const tools = {
72
+ renderChart: tool({
73
+ description: "Render a chart and return it as an image.",
74
+ inputSchema: z.object({ spec: z.string() }),
75
+ execute: async ({ spec }) => renderToPng(spec), // raw bytes
76
+ // The model should see an image part, not base64 bytes:
77
+ toModelOutput: ({ output }) => ({
78
+ type: "content",
79
+ value: [{ type: "media", mediaType: "image/png", data: output.base64 }],
80
+ }),
81
+ }),
82
+ };
83
+
84
+ export const chartChat = chat.agent({
85
+ id: "chart-chat",
86
+ tools, // ← without this, the image is "remembered" on turn 1 and gone from turn 2
87
+ run: async ({ messages, tools, signal }) =>
88
+ streamText({
89
+ ...chat.toStreamTextOptions({ tools }),
90
+ model: anthropic("claude-sonnet-4-5"),
91
+ messages,
92
+ abortSignal: signal,
93
+ stopWhen: stepCountIs(15),
94
+ }),
95
+ });
96
+ ```
97
+
98
+ ## Static or per-turn tools
99
+
100
+ `tools` accepts either a static `ToolSet` or a function that returns one per turn, for tools that depend on the user, a feature flag, or anything in the turn context:
101
+
102
+ ```ts
103
+ export const myChat = chat
104
+ .withClientData({ schema: z.object({ userId: z.string(), plan: z.string() }) })
105
+ .agent({
106
+ id: "my-chat",
107
+ tools: ({ clientData }) => ({
108
+ searchDocs,
109
+ ...(clientData?.plan === "pro" ? { deepResearch } : {}),
110
+ }),
111
+ run: async ({ messages, tools, signal }) =>
112
+ streamText({
113
+ ...chat.toStreamTextOptions({ tools }),
114
+ model: anthropic("claude-sonnet-4-5"),
115
+ messages,
116
+ abortSignal: signal,
117
+ stopWhen: stepCountIs(15),
118
+ }),
119
+ });
120
+ ```
121
+
122
+ The function receives a `ResolveToolsEvent` and runs once per turn (after `clientData` is parsed):
123
+
124
+ | Field | Type | Description |
125
+ | --- | --- | --- |
126
+ | `chatId` | `string` | The chat session ID. |
127
+ | `turn` | `number` | The current turn number (0-indexed). |
128
+ | `continuation` | `boolean` | Whether this run is continuing an existing chat. |
129
+ | `clientData` | `TClientData` | Parsed client data from the frontend. |
130
+
131
+ The resolved set is what lands on the `run()` payload's `tools`.
132
+
133
+ ## Typed tools in `run()`
134
+
135
+ The `run()` payload's `tools` is typed to whatever you declared, so you can pass it straight through without re-importing the map:
136
+
137
+ ```ts
138
+ run: async ({ messages, tools, signal }) => {
139
+ // `tools` is typed as your tool set, not a broad `ToolSet`
140
+ return streamText({
141
+ ...chat.toStreamTextOptions({ tools }),
142
+ model: anthropic("claude-sonnet-4-5"),
143
+ messages,
144
+ abortSignal: signal,
145
+ });
146
+ };
147
+ ```
148
+
149
+ When no `tools` are declared, the payload's `tools` is an empty object and behaves exactly as before, so declaring tools is fully opt-in.
150
+
151
+ ## Typing messages from your tools
152
+
153
+ To get typed tool parts (`tool-${name}` with typed input/output) on your `UIMessage`, in hooks like `onTurnComplete` and on the frontend, derive the message type from your tool set with `InferChatUIMessageFromTools`:
154
+
155
+ ```ts
156
+ import type { InferChatUIMessageFromTools } from "@trigger.dev/sdk/ai";
157
+
158
+ const tools = { searchDocs, renderChart };
159
+
160
+ export type ChatUiMessage = InferChatUIMessageFromTools<typeof tools>;
161
+ ```
162
+
163
+ This is shorthand for `UIMessage<unknown, UIDataTypes, InferUITools<typeof tools>>`. Pin it on the agent with [`chat.withUIMessage<ChatUiMessage>()`](/ai-chat/types#custom-uimessage-with-chat-withuimessage) and reuse it on the client. If you also have custom `data-*` parts, build the `UIMessage` generic directly instead. See [Types](/ai-chat/types).
164
+
165
+ ## Skills
166
+
167
+ [Agent skills](/ai-chat/patterns/skills) are auto-injected as tools (`loadSkill`, `readFile`, `bash`) by `chat.toStreamTextOptions()`. They're separate from your config `tools`: declare your own tools on the config (so their `toModelOutput` survives across turns), and let `toStreamTextOptions` merge the skill tools on top at call time. Skill tools don't define `toModelOutput`, so they don't need to be on the config.
168
+
169
+ ## Manual turn loops (`chat.customAgent`)
170
+
171
+ The `tools` config option belongs to the managed [`chat.agent`](/ai-chat/backend#chat-agent). When you drive the loop yourself with [`chat.customAgent`](/ai-chat/custom-agents#chat-customagent) (or build messages from `chat.history`), you own the conversion, so pass your tools to `convertToModelMessages` directly to get the same cross-turn `toModelOutput` behavior:
172
+
173
+ ```ts
174
+ import { convertToModelMessages, streamText } from "ai";
175
+
176
+ // Inside your loop, with `tools` in scope:
177
+ const uiMessages = chat.history.all();
178
+ const messages = await convertToModelMessages(uiMessages, {
179
+ tools,
180
+ ignoreIncompleteToolCalls: true,
181
+ });
182
+
183
+ return streamText({ model: anthropic("claude-sonnet-4-5"), messages, tools });
184
+ ```
185
+
186
+ ## Learn more
187
+
188
+ - [Human-in-the-loop](/ai-chat/patterns/human-in-the-loop): tools that pause for approval.
189
+ - [Sub-agents](/ai-chat/patterns/sub-agents): tools that delegate to other agents and compress their output with `toModelOutput`.
190
+ - [Tool result auditing](/ai-chat/patterns/tool-result-auditing): logging tool results as they resolve.
191
+ - [AI SDK: Tools and tool calling](https://sdk.vercel.ai/docs/ai-sdk-core/tools-and-tool-calling).
@@ -0,0 +1,242 @@
1
+ ---
2
+ title: "Types"
3
+ sidebarTitle: "Types"
4
+ description: "TypeScript types for AI Agents, UI messages, and the frontend transport."
5
+ ---
6
+
7
+ import RcBanner from "/snippets/ai-chat-rc-banner.mdx";
8
+
9
+ <RcBanner />
10
+
11
+ TypeScript patterns for [AI Chat](/ai-chat/overview). This page covers how to pin a custom AI SDK [`UIMessage`](https://sdk.vercel.ai/docs/reference/ai-sdk-core/ui-message) subtype with `chat.withUIMessage`, fix a typed `clientData` schema with `chat.withClientData`, chain builder-level hooks, and align types on the client.
12
+
13
+ ## Custom `UIMessage` with `chat.withUIMessage`
14
+
15
+ `chat.agent()` types the wire payload with the base AI SDK `UIMessage`. That is enough for many apps.
16
+
17
+ When you add **custom `data-*` parts** (via `chat.stream` / `writer`) or a **typed tool map** (e.g. `InferUITools<typeof tools>`), you want a **narrower** `UIMessage` generic so that:
18
+
19
+ - `onTurnStart`, `onTurnComplete`, and similar hooks expose correctly typed `uiMessages`
20
+ - Stream options like `sendReasoning` align with your message shape
21
+ - The frontend can treat `useChat` messages as the same subtype end-to-end
22
+
23
+ `chat.withUIMessage<YourUIMessage>(config?)` returns a [ChatBuilder](#chatbuilder) where `.agent(...)` accepts the **same options as** [`chat.agent()`](/ai-chat/backend#chat-agent) but fixes `YourUIMessage` as the UI message type for that chat agent.
24
+
25
+ ### Defining a `UIMessage` subtype
26
+
27
+ Build the type from AI SDK helpers and your tools object:
28
+
29
+ ```ts
30
+ import type { InferUITools, UIDataTypes, UIMessage } from "ai";
31
+ import { tool, stepCountIs } from "ai";
32
+ import { z } from "zod";
33
+
34
+ const myTools = {
35
+ lookup: tool({
36
+ description: "Look up a record",
37
+ inputSchema: z.object({ id: z.string() }),
38
+ execute: async ({ id }) => ({ id, label: "example" }),
39
+ }),
40
+ };
41
+
42
+ type MyChatTools = InferUITools<typeof myTools>;
43
+
44
+ type MyChatDataTypes = UIDataTypes & {
45
+ "turn-status": { status: "preparing" | "streaming" | "done" };
46
+ };
47
+
48
+ export type MyChatUIMessage = UIMessage<unknown, MyChatDataTypes, MyChatTools>;
49
+ ```
50
+
51
+ <Tip>
52
+ If you don't need custom `data-*` parts, [`InferChatUIMessageFromTools<typeof myTools>`](/ai-chat/tools#typing-messages-from-your-tools) from `@trigger.dev/sdk/ai` collapses the tools half into one line (it's shorthand for `UIMessage<unknown, UIDataTypes, InferUITools<typeof myTools>>`).
53
+ </Tip>
54
+
55
+ Task-backed tools should use AI SDK [`tool()`](https://sdk.vercel.ai/docs/ai-sdk-core/tools-and-tool-calling) with `execute: ai.toolExecute(schemaTask)` where needed — see [Task-backed AI tools](/tasks/schemaTask#task-backed-ai-tools).
56
+
57
+ ### Backend: `chat.withUIMessage(...).agent(...)`
58
+
59
+ Call `withUIMessage` **once**, then chain `.agent({ ... })` instead of `chat.agent({ ... })`. You can also chain `.withClientData()` and hook methods before `.agent()`:
60
+
61
+ ```ts
62
+ import { chat } from "@trigger.dev/sdk/ai";
63
+ import { streamText, tool } from "ai";
64
+ import { anthropic } from "@ai-sdk/anthropic";
65
+ import { z } from "zod";
66
+ import type { MyChatUIMessage } from "./my-chat-types";
67
+
68
+ const myTools = {
69
+ lookup: tool({
70
+ description: "Look up a record",
71
+ inputSchema: z.object({ id: z.string() }),
72
+ execute: async ({ id }) => ({ id, label: "example" }),
73
+ }),
74
+ };
75
+
76
+ export const myChat = chat
77
+ .withUIMessage<MyChatUIMessage>({
78
+ streamOptions: {
79
+ sendReasoning: true,
80
+ onError: (error) =>
81
+ error instanceof Error ? error.message : "Something went wrong.",
82
+ },
83
+ })
84
+ .withClientData({
85
+ schema: z.object({ userId: z.string() }),
86
+ })
87
+ .agent({
88
+ id: "my-chat",
89
+ tools: myTools,
90
+ onTurnStart: async ({ uiMessages, writer }) => {
91
+ // uiMessages is MyChatUIMessage[] — custom data parts are typed
92
+ writer.write({
93
+ type: "data-turn-status",
94
+ data: { status: "preparing" },
95
+ });
96
+ },
97
+ run: async ({ messages, tools, signal }) => {
98
+ // `tools` is myTools, typed, handed back on the payload
99
+ return streamText({
100
+ model: anthropic("claude-sonnet-4-5"),
101
+ messages,
102
+ tools,
103
+ abortSignal: signal,
104
+ stopWhen: stepCountIs(15),
105
+ });
106
+ },
107
+ });
108
+ ```
109
+
110
+ ### Default stream options
111
+
112
+ The optional `streamOptions` object becomes the **default** [`uiMessageStreamOptions`](/ai-chat/reference#chatagentoptions) for `toUIMessageStream()`.
113
+
114
+ If you also set `uiMessageStreamOptions` on the inner `.agent({ ... })`, the two objects are **shallow-merged** — keys on the **agent** win on conflicts. Per-turn overrides via [`chat.setUIMessageStreamOptions()`](/ai-chat/backend#stream-options) still apply on top.
115
+
116
+ ### Frontend: `InferChatUIMessage`
117
+
118
+ Import the helper type and pass it to `useChat` so `messages` and render logic match the backend:
119
+
120
+ ```tsx
121
+ import { useChat } from "@ai-sdk/react";
122
+ import { useTriggerChatTransport, type InferChatUIMessage } from "@trigger.dev/sdk/chat/react";
123
+ import type { myChat } from "./myChat";
124
+
125
+ type Msg = InferChatUIMessage<typeof myChat>;
126
+
127
+ export function Chat() {
128
+ const transport = useTriggerChatTransport<typeof myChat>({
129
+ task: "my-chat",
130
+ accessToken: ({ chatId }) => mintChatAccessToken(chatId),
131
+ startSession: ({ chatId, clientData }) =>
132
+ startChatSession({ chatId, clientData }),
133
+ });
134
+
135
+ const { messages } = useChat<Msg>({ transport });
136
+
137
+ return messages.map((m) => (
138
+ <div key={m.id}>{/* m.parts narrowed for your UIMessage subtype */}</div>
139
+ ));
140
+ }
141
+ ```
142
+
143
+ You can also import `InferChatUIMessage` from `@trigger.dev/sdk/ai` in non-React modules.
144
+
145
+ ## Typed client data with `chat.withClientData`
146
+
147
+ `chat.withClientData({ schema })` returns a [ChatBuilder](#chatbuilder) that fixes the client data schema. All hooks and `run` receive typed `clientData` without needing `clientDataSchema` in `.agent()` options.
148
+
149
+ ```ts
150
+ import { chat } from "@trigger.dev/sdk/ai";
151
+ import { z } from "zod";
152
+
153
+ export const myChat = chat
154
+ .withClientData({
155
+ schema: z.object({ userId: z.string(), model: z.string().optional() }),
156
+ })
157
+ .agent({
158
+ id: "my-chat",
159
+ onPreload: async ({ clientData }) => {
160
+ // clientData is typed as { userId: string; model?: string }
161
+ await initUser(clientData.userId);
162
+ },
163
+ run: async ({ messages, clientData, signal }) => {
164
+ return streamText({
165
+ model: getModel(clientData.model),
166
+ messages,
167
+ abortSignal: signal,
168
+ stopWhen: stepCountIs(15),
169
+ });
170
+ },
171
+ });
172
+ ```
173
+
174
+ ## ChatBuilder
175
+
176
+ Both `chat.withUIMessage()` and `chat.withClientData()` return a **ChatBuilder** — a chainable object that accumulates configuration before creating the agent with `.agent()`.
177
+
178
+ Builder methods can be chained in any order:
179
+
180
+ ```ts
181
+ export const myChat = chat
182
+ .withUIMessage<MyChatUIMessage>({
183
+ streamOptions: { sendReasoning: true },
184
+ })
185
+ .withClientData({
186
+ schema: z.object({ userId: z.string() }),
187
+ })
188
+ .onChatSuspend(async ({ ctx }) => {
189
+ await disposeCodeSandbox(ctx.run.id);
190
+ })
191
+ .onChatResume(async ({ ctx }) => {
192
+ warmCache(ctx.run.id);
193
+ })
194
+ .agent({
195
+ id: "my-chat",
196
+ run: async ({ messages, signal }) => {
197
+ return streamText({ model: anthropic("claude-sonnet-4-5"), messages, abortSignal: signal });
198
+ },
199
+ });
200
+ ```
201
+
202
+ ### Builder-level hooks
203
+
204
+ All [lifecycle hooks](/ai-chat/lifecycle-hooks) can be set on the builder: `onPreload`, `onChatStart`, `onTurnStart`, `onBeforeTurnComplete`, `onTurnComplete`, `onCompacted`, `onChatSuspend`, `onChatResume`.
205
+
206
+ Builder hooks and task-level hooks **coexist**. When both are defined for the same event, the builder hook runs first, then the task hook:
207
+
208
+ ```ts
209
+ chat
210
+ .withUIMessage<MyChatUIMessage>()
211
+ .onPreload(async (event) => {
212
+ // Runs first — shared setup across tasks using this builder
213
+ await initializeSharedState(event.chatId);
214
+ })
215
+ .agent({
216
+ id: "my-chat",
217
+ onPreload: async (event) => {
218
+ // Runs second — task-specific logic
219
+ await createChatRecord(event.chatId);
220
+ },
221
+ run: async ({ messages, signal }) => {
222
+ return streamText({ model: anthropic("claude-sonnet-4-5"), messages, abortSignal: signal });
223
+ },
224
+ });
225
+ ```
226
+
227
+ <Tip>
228
+ Set types first (`.withUIMessage()`, `.withClientData()`), then hooks. Hook parameters are typed based on the builder's current generics — so hooks registered after `.withClientData()` get typed `clientData`.
229
+ </Tip>
230
+
231
+ ### When plain `chat.agent()` is enough
232
+
233
+ If you do not rely on custom `UIMessage` generics (only default text, reasoning, and built-in tool UI types), **`chat.agent()` alone is fine** — no need for `withUIMessage`.
234
+
235
+ ## See also
236
+
237
+ - [Backend — `chat.agent()`](/ai-chat/backend#chat-agent)
238
+ - [Lifecycle hooks](/ai-chat/lifecycle-hooks)
239
+ - [Frontend — transport & `useChat`](/ai-chat/frontend)
240
+ - [API reference — `chat.withUIMessage`](/ai-chat/reference#chat-withuimessage)
241
+ - [API reference — `chat.withClientData`](/ai-chat/reference#chat-withclientdata)
242
+ - [Task-backed AI tools — `ai.toolExecute`](/tasks/schemaTask#task-backed-ai-tools)