Document and demo @trigger.dev/ai useChat transport

Co-authored-by: Eric Allam <eric@trigger.dev>
This commit is contained in:
Cursor Agent
2026-02-14 23:19:36 +00:00
parent 0f932eb6ef
commit af510e668a
9 changed files with 365 additions and 58 deletions
+10
View File
@@ -0,0 +1,10 @@
---
"@trigger.dev/ai": minor
---
Add a new `@trigger.dev/ai` package with:
- `ai.tool(...)` and `ai.currentToolOptions()` helpers for AI SDK tool calling ergonomics
- a typed `TriggerChatTransport` that plugs into AI SDK UI `useChat()` and runs chat backends as Trigger.dev tasks
- rich default task payloads (`chatId`, trigger metadata, messages, request context) with optional payload mapping
- reconnect-aware stream handling on top of Trigger.dev Realtime Streams v2
+3 -3
View File
@@ -81,7 +81,7 @@ await myTask.trigger({ name: "Alice", age: 30, dob: "2020-01-01" }); // this is
The `ai.tool` function allows you to create an AI tool from an existing `schemaTask` to use with the Vercel [AI SDK](https://vercel.com/docs/ai-sdk):
```ts
import { ai } from "@trigger.dev/sdk/ai";
import { ai } from "@trigger.dev/ai";
import { schemaTask } from "@trigger.dev/sdk";
import { z } from "zod";
import { generateText } from "ai";
@@ -118,7 +118,7 @@ You can also pass the `experimental_toToolResultContent` option to the `ai.tool`
```ts
import { openai } from "@ai-sdk/openai";
import { Sandbox } from "@e2b/code-interpreter";
import { ai } from "@trigger.dev/sdk/ai";
import { ai } from "@trigger.dev/ai";
import { schemaTask } from "@trigger.dev/sdk";
import { generateObject } from "ai";
import { z } from "zod";
@@ -183,7 +183,7 @@ export const chartTool = ai.tool(chartTask, {
You can access the current tool execution options inside the task run function using the `ai.currentToolOptions()` function:
```ts
import { ai } from "@trigger.dev/sdk/ai";
import { ai } from "@trigger.dev/ai";
import { schemaTask } from "@trigger.dev/sdk";
import { z } from "zod";
+89
View File
@@ -517,6 +517,95 @@ const { parts, error } = useRealtimeStream(streamDef, runId, {
});
```
## AI SDK `useChat` transport with Trigger.dev tasks
If you want to use AI SDK UI's `useChat()` on the frontend and run the backend as a Trigger.dev task,
use the `@trigger.dev/ai` transport.
### Install
```bash
npm add @trigger.dev/ai @ai-sdk/react ai
```
### Define a typed stream
```ts
// app/streams.ts
import { streams } from "@trigger.dev/sdk";
import { UIMessageChunk } from "ai";
export const aiStream = streams.define<UIMessageChunk>({
id: "ai",
});
```
### Create a task that accepts rich chat transport payload
```ts
// trigger/chat-task.ts
import { openai } from "@ai-sdk/openai";
import type { TriggerChatTransportPayload } from "@trigger.dev/ai";
import { task } from "@trigger.dev/sdk";
import { convertToModelMessages, streamText, UIMessage } from "ai";
import { aiStream } from "@/app/streams";
type ChatPayload = TriggerChatTransportPayload<UIMessage>;
export const aiChatTask = task({
id: "ai-chat",
run: async (payload: ChatPayload) => {
const result = streamText({
model: openai("gpt-4o"),
messages: convertToModelMessages(payload.messages),
});
const { waitUntilComplete } = aiStream.pipe(result.toUIMessageStream());
await waitUntilComplete();
},
});
```
### Use `useChat()` with Trigger chat transport
```tsx
"use client";
import { useChat } from "@ai-sdk/react";
import { TriggerChatTransport } from "@trigger.dev/ai";
import { aiStream } from "@/app/streams";
export function Chat({ triggerToken }: { triggerToken: string }) {
const chat = useChat({
transport: new TriggerChatTransport({
task: "ai-chat",
stream: aiStream,
accessToken: triggerToken,
timeoutInSeconds: 120,
}),
});
return (
<form
onSubmit={(event) => {
event.preventDefault();
chat.sendMessage({ text: "Hello!" });
}}
>
<button type="submit">Send</button>
</form>
);
}
```
The default payload sent to your task is a rich, typed object that includes:
- `chatId`
- `trigger` (`"submit-message"` or `"regenerate-message"`)
- `messageId`
- `messages`
- `request` (`headers`, `body`, and `metadata`)
## Complete Example: AI Streaming
### Define the stream
+93 -35
View File
@@ -2732,6 +2732,12 @@ importers:
'@ai-sdk/openai':
specifier: ^2.0.53
version: 2.0.53(zod@3.25.76)
'@ai-sdk/react':
specifier: ^3.0.83
version: 3.0.83(react@19.1.0)(zod@3.25.76)
'@trigger.dev/ai':
specifier: workspace:*
version: link:../../packages/ai
'@trigger.dev/react-hooks':
specifier: workspace:*
version: link:../../packages/react-hooks
@@ -2739,8 +2745,8 @@ importers:
specifier: workspace:*
version: link:../../packages/trigger-sdk
ai:
specifier: ^5.0.76
version: 5.0.76(zod@3.25.76)
specifier: ^6.0.81
version: 6.0.81(zod@3.25.76)
next:
specifier: 15.5.6
version: 15.5.6(@opentelemetry/api@1.9.0)(@playwright/test@1.37.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
@@ -2871,14 +2877,14 @@ packages:
peerDependencies:
zod: ^3.25.76 || ^4
'@ai-sdk/gateway@2.0.0':
resolution: {integrity: sha512-Gj0PuawK7NkZuyYgO/h5kDK/l6hFOjhLdTq3/Lli1FTl47iGmwhH1IZQpAL3Z09BeFYWakcwUmn02ovIm2wy9g==}
'@ai-sdk/gateway@3.0.2':
resolution: {integrity: sha512-giJEg9ob45htbu3iautK+2kvplY2JnTj7ir4wZzYSQWvqGatWfBBfDuNCU5wSJt9BCGjymM5ZS9ziD42JGCZBw==}
engines: {node: '>=18'}
peerDependencies:
zod: ^3.25.76 || ^4.1.8
'@ai-sdk/gateway@3.0.2':
resolution: {integrity: sha512-giJEg9ob45htbu3iautK+2kvplY2JnTj7ir4wZzYSQWvqGatWfBBfDuNCU5wSJt9BCGjymM5ZS9ziD42JGCZBw==}
'@ai-sdk/gateway@3.0.41':
resolution: {integrity: sha512-dYNhtvEomccNNGSxfSP8f4g6yPcoDHyQ6Rb7dALFE0FvvVP9UqfFWi3D2dLIz0VVKaSkiNLQAJ7lsdTVlBdRrw==}
engines: {node: '>=18'}
peerDependencies:
zod: ^3.25.76 || ^4.1.8
@@ -2961,6 +2967,12 @@ packages:
peerDependencies:
zod: ^3.25.76 || ^4.1.8
'@ai-sdk/provider-utils@4.0.14':
resolution: {integrity: sha512-7bzKd9lgiDeXM7O4U4nQ8iTxguAOkg8LZGD9AfDVZYjO5cKYRwBPwVjboFcVrxncRHu0tYxZtXZtiLKpG4pEng==}
engines: {node: '>=18'}
peerDependencies:
zod: ^3.25.76 || ^4.1.8
'@ai-sdk/provider@0.0.26':
resolution: {integrity: sha512-dQkfBDs2lTYpKM8389oopPdQgIU007GQyCbuPPrV+K6MtSII3HBfE0stUIMXUb44L+LK1t6GXPP7wjSzjO6uKg==}
engines: {node: '>=18'}
@@ -2985,6 +2997,10 @@ packages:
resolution: {integrity: sha512-m9ka3ptkPQbaHHZHqDXDF9C9B5/Mav0KTdky1k2HZ3/nrW2t1AgObxIVPyGDWQNS9FXT/FS6PIoSjpcP/No8rQ==}
engines: {node: '>=18'}
'@ai-sdk/provider@3.0.8':
resolution: {integrity: sha512-oGMAgGoQdBXbZqNG0Ze56CHjDZ1IDYOwGYxYjO5KLSlz5HiNQ9udIXsPZ61VWaHGZ5XW/jyjmr6t2xz2jGVwbQ==}
engines: {node: '>=18'}
'@ai-sdk/react@1.0.0':
resolution: {integrity: sha512-BDrZqQA07Btg64JCuhFvBgYV+tt2B8cXINzEqWknGoxqcwgdE8wSLG2gkXoLzyC2Rnj7oj0HHpOhLUxDCmoKZg==}
engines: {node: '>=18'}
@@ -3017,6 +3033,12 @@ packages:
zod:
optional: true
'@ai-sdk/react@3.0.83':
resolution: {integrity: sha512-UsHr+/N0pqGY90BwPFFpWXhY5eVYjNgcWCvSeDMRrzfPvtcd2yqHXlwR7/bveRryVB4NmJ2z6sjyLyOgOHRQdw==}
engines: {node: '>=18'}
peerDependencies:
react: ^18 || ~19.0.1 || ~19.1.2 || ^19.2.1
'@ai-sdk/ui-utils@1.0.0':
resolution: {integrity: sha512-oXBDIM/0niWeTWyw77RVl505dNxBUDLLple7bTsqo2d3i1UKwGlzBUX8XqZsh7GbY7I6V05nlG0Y8iGlWxv1Aw==}
engines: {node: '>=18'}
@@ -11134,14 +11156,14 @@ packages:
'@vanilla-extract/private@1.0.3':
resolution: {integrity: sha512-17kVyLq3ePTKOkveHxXuIJZtGYs+cSoev7BlP+Lf4916qfDhk/HBjvlYDe8egrea7LNPHKwSZJK/bzZC+Q6AwQ==}
'@vercel/oidc@3.0.3':
resolution: {integrity: sha512-yNEQvPcVrK9sIe637+I0jD6leluPxzwJKx/Haw6F4H77CdDsszUn5V3o96LPziXkSNE2B83+Z3mjqGKBK/R6Gg==}
engines: {node: '>= 20'}
'@vercel/oidc@3.0.5':
resolution: {integrity: sha512-fnYhv671l+eTTp48gB4zEsTW/YtRgRPnkI2nT7x6qw5rkI1Lq2hTmQIpHPgyThI0znLK+vX2n9XxKdXZ7BUbbw==}
engines: {node: '>= 20'}
'@vercel/oidc@3.1.0':
resolution: {integrity: sha512-Fw28YZpRnA3cAHHDlkt7xQHiJ0fcL+NRcIqsocZQUSmbzeIKRpwttJjik5ZGanXP+vlA4SbTg+AbA3bP363l+w==}
engines: {node: '>= 20'}
'@vercel/otel@1.13.0':
resolution: {integrity: sha512-esRkt470Y2jRK1B1g7S1vkt4Csu44gp83Zpu8rIyPoqy2BKgk4z7ik1uSMswzi45UogLHFl6yR5TauDurBQi4Q==}
engines: {node: '>=18'}
@@ -11157,7 +11179,7 @@ packages:
'@vercel/postgres@0.10.0':
resolution: {integrity: sha512-fSD23DxGND40IzSkXjcFcxr53t3Tiym59Is0jSYIFpG4/0f0KO9SGtcp1sXiebvPaGe7N/tU05cH4yt2S6/IPg==}
engines: {node: '>=18.14'}
deprecated: '@vercel/postgres is deprecated. You can either choose an alternate storage solution from the Vercel Marketplace if you want to set up a new database. Or you can follow this guide to migrate your existing Vercel Postgres db: https://neon.com/docs/guides/vercel-postgres-transition-guide'
deprecated: '@vercel/postgres is deprecated. If you are setting up a new database, you can choose an alternate storage solution from the Vercel Marketplace. If you had an existing Vercel Postgres database, it should have been migrated to Neon as a native Vercel integration. You can find more details and the guide to migrate to Neon''s SDKs here: https://neon.com/docs/guides/vercel-postgres-transition-guide'
'@vercel/sdk@1.19.1':
resolution: {integrity: sha512-K4rmtUT6t1vX06tiY44ot8A7W1FKN7g/tMkE7yZghCgNQ8b30SzljBd4ni8RNp2pJzM/HrZmphRDeIArO7oZuw==}
@@ -11477,14 +11499,14 @@ packages:
peerDependencies:
zod: ^3.25.76 || ^4
ai@5.0.76:
resolution: {integrity: sha512-ZCxi1vrpyCUnDbtYrO/W8GLvyacV9689f00yshTIQ3mFFphbD7eIv40a2AOZBv3GGRA7SSRYIDnr56wcS/gyQg==}
ai@6.0.3:
resolution: {integrity: sha512-OOo+/C+sEyscoLnbY3w42vjQDICioVNyS+F+ogwq6O5RJL/vgWGuiLzFwuP7oHTeni/MkmX8tIge48GTdaV7QQ==}
engines: {node: '>=18'}
peerDependencies:
zod: ^3.25.76 || ^4.1.8
ai@6.0.3:
resolution: {integrity: sha512-OOo+/C+sEyscoLnbY3w42vjQDICioVNyS+F+ogwq6O5RJL/vgWGuiLzFwuP7oHTeni/MkmX8tIge48GTdaV7QQ==}
ai@6.0.81:
resolution: {integrity: sha512-F9EEhjl2dn1VGS5tbU64ldLbqRemV9X1WHghVblpJlPCWuyYj1xpPQsj+G0TRs/SyAGnbpG1yYpl9mkwfr1H8w==}
engines: {node: '>=18'}
peerDependencies:
zod: ^3.25.76 || ^4.1.8
@@ -14249,20 +14271,24 @@ packages:
glob@10.3.10:
resolution: {integrity: sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==}
engines: {node: '>=16 || 14 >=14.17'}
deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
hasBin: true
glob@10.3.4:
resolution: {integrity: sha512-6LFElP3A+i/Q8XQKEvZjkEWEOTgAIALR9AO2rwT8bgPhDd1anmqDJDZ6lLddI4ehxxxR1S5RIqKe1uapMQfYaQ==}
engines: {node: '>=16 || 14 >=14.17'}
deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
hasBin: true
glob@10.4.5:
resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==}
deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
hasBin: true
glob@11.0.0:
resolution: {integrity: sha512-9UiX/Bl6J2yaBbxKoEBRm4Cipxgok8kQYcOPEhScPwebu2I0HoQOuYdIO6S3hLuWoZgpDpwQZMzTFxgpkyT76g==}
engines: {node: 20 || >=22}
deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
hasBin: true
glob@7.2.3:
@@ -18980,21 +19006,22 @@ packages:
tar@6.1.13:
resolution: {integrity: sha512-jdIBIN6LTIe2jqzay/2vtYLlBHa3JF42ot3h1dW8Q0PaAG4v8rm0cvpVePtau5C6OKXGGcgO9q2AMNSWxiLqKw==}
engines: {node: '>=10'}
deprecated: Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exhorbitant rates) by contacting i@izs.me
deprecated: Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
tar@6.2.1:
resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==}
engines: {node: '>=10'}
deprecated: Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exhorbitant rates) by contacting i@izs.me
deprecated: Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
tar@7.4.3:
resolution: {integrity: sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==}
engines: {node: '>=18'}
deprecated: Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exhorbitant rates) by contacting i@izs.me
deprecated: Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
tar@7.5.6:
resolution: {integrity: sha512-xqUeu2JAIJpXyvskvU3uvQW8PAmHrtXp2KDuMJwQqW8Sqq0CaZBAQ+dKS3RBXVhU4wC5NjAdKrmh84241gO9cA==}
engines: {node: '>=18'}
deprecated: Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
tdigest@0.1.2:
resolution: {integrity: sha512-+G0LLgjjo9BZX2MfdvPfH+MKLCrxlXSYec5DaPYP1fe6Iyhf0/fSmJ0bFiZ1F8BT6cGXl2LpltQptzjXKWEkKA==}
@@ -20263,13 +20290,6 @@ snapshots:
'@ai-sdk/provider-utils': 3.0.3(zod@3.25.76)
zod: 3.25.76
'@ai-sdk/gateway@2.0.0(zod@3.25.76)':
dependencies:
'@ai-sdk/provider': 2.0.0
'@ai-sdk/provider-utils': 3.0.12(zod@3.25.76)
'@vercel/oidc': 3.0.3
zod: 3.25.76
'@ai-sdk/gateway@3.0.2(zod@3.25.76)':
dependencies:
'@ai-sdk/provider': 3.0.0
@@ -20277,6 +20297,13 @@ snapshots:
'@vercel/oidc': 3.0.5
zod: 3.25.76
'@ai-sdk/gateway@3.0.41(zod@3.25.76)':
dependencies:
'@ai-sdk/provider': 3.0.8
'@ai-sdk/provider-utils': 4.0.14(zod@3.25.76)
'@vercel/oidc': 3.1.0
zod: 3.25.76
'@ai-sdk/openai@1.0.1(zod@3.25.76)':
dependencies:
'@ai-sdk/provider': 1.0.0
@@ -20361,6 +20388,13 @@ snapshots:
eventsource-parser: 3.0.6
zod: 3.25.76
'@ai-sdk/provider-utils@4.0.14(zod@3.25.76)':
dependencies:
'@ai-sdk/provider': 3.0.8
'@standard-schema/spec': 1.1.0
eventsource-parser: 3.0.6
zod: 3.25.76
'@ai-sdk/provider@0.0.26':
dependencies:
json-schema: 0.4.0
@@ -20385,6 +20419,10 @@ snapshots:
dependencies:
json-schema: 0.4.0
'@ai-sdk/provider@3.0.8':
dependencies:
json-schema: 0.4.0
'@ai-sdk/react@1.0.0(react@18.3.1)(zod@3.25.76)':
dependencies:
'@ai-sdk/provider-utils': 2.0.0(zod@3.25.76)
@@ -20415,6 +20453,16 @@ snapshots:
optionalDependencies:
zod: 3.25.76
'@ai-sdk/react@3.0.83(react@19.1.0)(zod@3.25.76)':
dependencies:
'@ai-sdk/provider-utils': 4.0.14(zod@3.25.76)
ai: 6.0.81(zod@3.25.76)
react: 19.1.0
swr: 2.2.5(react@19.1.0)
throttleit: 2.1.0
transitivePeerDependencies:
- zod
'@ai-sdk/ui-utils@1.0.0(zod@3.25.76)':
dependencies:
'@ai-sdk/provider': 1.0.0
@@ -31468,10 +31516,10 @@ snapshots:
'@vanilla-extract/private@1.0.3': {}
'@vercel/oidc@3.0.3': {}
'@vercel/oidc@3.0.5': {}
'@vercel/oidc@3.1.0': {}
'@vercel/otel@1.13.0(@opentelemetry/api-logs@0.203.0)(@opentelemetry/api@1.9.0)(@opentelemetry/instrumentation@0.203.0(@opentelemetry/api@1.9.0))(@opentelemetry/resources@2.2.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-logs@0.203.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-metrics@2.0.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.0.1(@opentelemetry/api@1.9.0))':
dependencies:
'@opentelemetry/api': 1.9.0
@@ -31899,14 +31947,6 @@ snapshots:
'@opentelemetry/api': 1.9.0
zod: 3.25.76
ai@5.0.76(zod@3.25.76):
dependencies:
'@ai-sdk/gateway': 2.0.0(zod@3.25.76)
'@ai-sdk/provider': 2.0.0
'@ai-sdk/provider-utils': 3.0.12(zod@3.25.76)
'@opentelemetry/api': 1.9.0
zod: 3.25.76
ai@6.0.3(zod@3.25.76):
dependencies:
'@ai-sdk/gateway': 3.0.2(zod@3.25.76)
@@ -31915,6 +31955,14 @@ snapshots:
'@opentelemetry/api': 1.9.0
zod: 3.25.76
ai@6.0.81(zod@3.25.76):
dependencies:
'@ai-sdk/gateway': 3.0.41(zod@3.25.76)
'@ai-sdk/provider': 3.0.8
'@ai-sdk/provider-utils': 4.0.14(zod@3.25.76)
'@opentelemetry/api': 1.9.0
zod: 3.25.76
ajv-formats@2.1.1(ajv@8.17.1):
optionalDependencies:
ajv: 8.17.1
@@ -40920,6 +40968,12 @@ snapshots:
react: 19.0.0
use-sync-external-store: 1.2.2(react@19.0.0)
swr@2.2.5(react@19.1.0):
dependencies:
client-only: 0.0.1
react: 19.1.0
use-sync-external-store: 1.2.2(react@19.1.0)
sync-content@2.0.1:
dependencies:
glob: 11.0.0
@@ -41862,6 +41916,10 @@ snapshots:
dependencies:
react: 19.0.0
use-sync-external-store@1.2.2(react@19.1.0):
dependencies:
react: 19.1.0
util-deprecate@1.0.2: {}
util@0.12.5:
+3 -1
View File
@@ -11,9 +11,11 @@
},
"dependencies": {
"@ai-sdk/openai": "^2.0.53",
"@ai-sdk/react": "^3.0.83",
"@trigger.dev/ai": "workspace:*",
"@trigger.dev/react-hooks": "workspace:*",
"@trigger.dev/sdk": "workspace:*",
"ai": "^5.0.76",
"ai": "^6.0.81",
"next": "15.5.6",
"react": "19.1.0",
"react-dom": "19.1.0",
@@ -44,9 +44,15 @@ export async function triggerStreamTask(
}
export async function triggerAIChatTask(messages: UIMessage[]) {
const chatId = `chat_${Date.now()}`;
// Trigger the AI chat task
const handle = await tasks.trigger<typeof aiChatTask>("ai-chat", {
chatId,
trigger: "submit-message",
messageId: undefined,
messages,
request: {},
});
console.log("Triggered AI chat run:", handle.id);
+12 -6
View File
@@ -1,7 +1,15 @@
import { AISdkChat } from "@/components/ai-sdk-chat";
import { TriggerButton } from "@/components/trigger-button";
import { AIChatButton } from "@/components/ai-chat-button";
import { auth } from "@trigger.dev/sdk";
export const dynamic = "force-dynamic";
export default async function Home() {
const triggerToken = await auth.createTriggerPublicToken("ai-chat", {
multipleUse: true,
expirationTime: "1h",
});
export default function Home() {
return (
<div className="font-sans grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20">
<main className="flex flex-col gap-[32px] row-start-2 items-center sm:items-start">
@@ -13,10 +21,8 @@ export default function Home() {
<div className="mt-8 pt-8 border-t border-gray-300 w-full">
<h2 className="text-xl font-semibold mb-4">AI Chat Stream (AI SDK v5)</h2>
<p className="text-sm text-gray-600 mb-4">
Test AI SDK v5&apos;s streamText with toUIMessageStream()
</p>
<AIChatButton />
<p className="text-sm text-gray-600 mb-4">Test useChat with Trigger.dev task transport</p>
<AISdkChat triggerToken={triggerToken} />
</div>
<div className="flex flex-col gap-4">
@@ -0,0 +1,138 @@
"use client";
import { aiStream } from "@/app/streams";
import { TriggerChatTransport } from "@trigger.dev/ai";
import { useChat } from "@ai-sdk/react";
import type { UIMessage } from "ai";
import type { TriggerChatRunState } from "@trigger.dev/ai";
import { Streamdown } from "streamdown";
import { useMemo, useState } from "react";
export function AISdkChat({ triggerToken }: { triggerToken: string }) {
const [input, setInput] = useState("");
const [lastRunId, setLastRunId] = useState<string | undefined>(undefined);
const transport = useMemo(function createTransport() {
return new TriggerChatTransport<UIMessage>({
task: "ai-chat",
stream: aiStream,
accessToken: triggerToken,
baseURL: process.env.NEXT_PUBLIC_TRIGGER_API_URL,
timeoutInSeconds: 120,
onTriggeredRun: function onTriggeredRun(state: TriggerChatRunState) {
setLastRunId(state.runId);
},
});
}, [triggerToken]);
const chat = useChat({
transport,
});
function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
event.preventDefault();
const trimmedInput = input.trim();
if (!trimmedInput || chat.status === "submitted" || chat.status === "streaming") {
return;
}
chat.sendMessage({
text: trimmedInput,
});
setInput("");
}
return (
<div className="w-full border border-gray-200 rounded-lg p-6 bg-white space-y-4">
<div className="flex items-center justify-between">
<div>
<h3 className="text-lg font-semibold">AI SDK useChat + Trigger.dev task transport</h3>
<p className="text-sm text-gray-600">
This chat uses <code>@trigger.dev/ai</code> + Realtime Streams v2
</p>
</div>
<span className="text-xs px-2 py-1 bg-blue-100 text-blue-800 rounded-full">
{chat.status}
</span>
</div>
{lastRunId ? (
<div className="text-xs text-gray-500">
Latest run: <code>{lastRunId}</code>
</div>
) : null}
<div className="min-h-[220px] max-h-[420px] overflow-y-auto rounded-md border border-gray-100 p-4 space-y-4">
{chat.messages.length === 0 ? (
<p className="text-sm text-gray-500">
Ask anything to start. Messages are streamed through a Trigger.dev task.
</p>
) : (
chat.messages.map(function renderMessage(message) {
const messageText = getMessageText(message);
return (
<div
key={message.id}
className={`rounded-lg p-3 ${
message.role === "user"
? "bg-gray-900 text-white ml-10"
: "bg-gray-50 text-gray-900 mr-10 border border-gray-200"
}`}
>
<div className="text-[11px] uppercase tracking-wide opacity-70 mb-2">
{message.role}
</div>
{messageText ? (
<div className="prose prose-sm max-w-none">
<Streamdown isAnimating={message.role === "assistant"}>
{messageText}
</Streamdown>
</div>
) : (
<p className="text-sm opacity-70">No text content</p>
)}
</div>
);
})
)}
</div>
<form onSubmit={handleSubmit} className="flex gap-2">
<input
value={input}
onChange={function onChange(event) {
setInput(event.target.value);
}}
placeholder="Ask a question..."
className="flex-1 px-4 py-2 rounded-md border border-gray-300 text-sm"
/>
<button
type="submit"
disabled={chat.status === "submitted" || chat.status === "streaming"}
className="px-4 py-2 rounded-md bg-purple-600 text-white text-sm disabled:bg-gray-400"
>
Send
</button>
</form>
</div>
);
}
function getMessageText(message: UIMessage): string {
let text = "";
for (const part of message.parts) {
if (part.type === "text") {
text += part.text;
continue;
}
if (part.type === "reasoning") {
text += part.text;
}
}
return text;
}
@@ -1,9 +1,9 @@
import { aiStream } from "@/app/streams";
import { openai } from "@ai-sdk/openai";
import { logger, streams, task } from "@trigger.dev/sdk";
import type { TriggerChatTransportPayload } from "@trigger.dev/ai";
import { logger, task } from "@trigger.dev/sdk";
import {
convertToModelMessages,
readUIMessageStream,
stepCountIs,
streamText,
tool,
@@ -11,22 +11,25 @@ import {
} from "ai";
import { z } from "zod/v4";
export type AIChatPayload = {
messages: UIMessage[];
};
export type AIChatPayload = TriggerChatTransportPayload<UIMessage>;
export const aiChatTask = task({
id: "ai-chat",
run: async (payload: AIChatPayload) => {
logger.info("Starting AI chat stream", {
messageCount: payload.messages.length,
chatId: payload.chatId,
trigger: payload.trigger,
messageId: payload.messageId,
});
const modelMessages = await convertToModelMessages(payload.messages);
// Stream text from OpenAI
const result = streamText({
model: openai("gpt-4o"),
system: "You are a helpful assistant.",
messages: convertToModelMessages(payload.messages),
messages: modelMessages,
stopWhen: stepCountIs(20),
tools: {
getCommonUseCases: tool({
@@ -58,13 +61,7 @@ export const aiChatTask = task({
const uiMessageStream = result.toUIMessageStream();
// Append the stream to metadata
const { waitUntilComplete, stream } = aiStream.pipe(uiMessageStream);
for await (const uiMessage of readUIMessageStream({
stream: stream,
})) {
logger.log("Current message state", { uiMessage });
}
const { waitUntilComplete } = aiStream.pipe(uiMessageStream);
// Wait for the stream to complete
await waitUntilComplete();
@@ -74,6 +71,7 @@ export const aiChatTask = task({
return {
message: "AI chat stream completed successfully",
messageCount: payload.messages.length,
chatId: payload.chatId,
};
},
});