From af510e668aa130102fc55cb95f2ee7fca2368cd9 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sat, 14 Feb 2026 23:19:36 +0000 Subject: [PATCH] Document and demo @trigger.dev/ai useChat transport Co-authored-by: Eric Allam --- .changeset/curly-radios-visit.md | 10 ++ docs/tasks/schemaTask.mdx | 6 +- docs/tasks/streams.mdx | 89 +++++++++++ pnpm-lock.yaml | 128 +++++++++++----- references/realtime-streams/package.json | 4 +- .../realtime-streams/src/app/actions.ts | 6 + references/realtime-streams/src/app/page.tsx | 18 ++- .../src/components/ai-sdk-chat.tsx | 138 ++++++++++++++++++ .../realtime-streams/src/trigger/ai-chat.ts | 24 ++- 9 files changed, 365 insertions(+), 58 deletions(-) create mode 100644 .changeset/curly-radios-visit.md create mode 100644 references/realtime-streams/src/components/ai-sdk-chat.tsx diff --git a/.changeset/curly-radios-visit.md b/.changeset/curly-radios-visit.md new file mode 100644 index 000000000..eae5d349f --- /dev/null +++ b/.changeset/curly-radios-visit.md @@ -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 diff --git a/docs/tasks/schemaTask.mdx b/docs/tasks/schemaTask.mdx index 3692d1d70..921f83d64 100644 --- a/docs/tasks/schemaTask.mdx +++ b/docs/tasks/schemaTask.mdx @@ -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"; diff --git a/docs/tasks/streams.mdx b/docs/tasks/streams.mdx index 2d494977a..64b846459 100644 --- a/docs/tasks/streams.mdx +++ b/docs/tasks/streams.mdx @@ -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({ + 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; + +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 ( +
{ + event.preventDefault(); + chat.sendMessage({ text: "Hello!" }); + }} + > + +
+ ); +} +``` + +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 diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 09d30a98d..b3f39f4c3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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: diff --git a/references/realtime-streams/package.json b/references/realtime-streams/package.json index 965443153..37b624d4b 100644 --- a/references/realtime-streams/package.json +++ b/references/realtime-streams/package.json @@ -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", diff --git a/references/realtime-streams/src/app/actions.ts b/references/realtime-streams/src/app/actions.ts index 2c18d11e6..32c16f127 100644 --- a/references/realtime-streams/src/app/actions.ts +++ b/references/realtime-streams/src/app/actions.ts @@ -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("ai-chat", { + chatId, + trigger: "submit-message", + messageId: undefined, messages, + request: {}, }); console.log("Triggered AI chat run:", handle.id); diff --git a/references/realtime-streams/src/app/page.tsx b/references/realtime-streams/src/app/page.tsx index 76beed7a2..438c43950 100644 --- a/references/realtime-streams/src/app/page.tsx +++ b/references/realtime-streams/src/app/page.tsx @@ -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 (
@@ -13,10 +21,8 @@ export default function Home() {

AI Chat Stream (AI SDK v5)

-

- Test AI SDK v5's streamText with toUIMessageStream() -

- +

Test useChat with Trigger.dev task transport

+
diff --git a/references/realtime-streams/src/components/ai-sdk-chat.tsx b/references/realtime-streams/src/components/ai-sdk-chat.tsx new file mode 100644 index 000000000..a7fb9d76f --- /dev/null +++ b/references/realtime-streams/src/components/ai-sdk-chat.tsx @@ -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(undefined); + + const transport = useMemo(function createTransport() { + return new TriggerChatTransport({ + 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) { + event.preventDefault(); + + const trimmedInput = input.trim(); + if (!trimmedInput || chat.status === "submitted" || chat.status === "streaming") { + return; + } + + chat.sendMessage({ + text: trimmedInput, + }); + setInput(""); + } + + return ( +
+
+
+

AI SDK useChat + Trigger.dev task transport

+

+ This chat uses @trigger.dev/ai + Realtime Streams v2 +

+
+ + {chat.status} + +
+ + {lastRunId ? ( +
+ Latest run: {lastRunId} +
+ ) : null} + +
+ {chat.messages.length === 0 ? ( +

+ Ask anything to start. Messages are streamed through a Trigger.dev task. +

+ ) : ( + chat.messages.map(function renderMessage(message) { + const messageText = getMessageText(message); + + return ( +
+
+ {message.role} +
+ {messageText ? ( +
+ + {messageText} + +
+ ) : ( +

No text content

+ )} +
+ ); + }) + )} +
+ +
+ + +
+
+ ); +} + +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; +} diff --git a/references/realtime-streams/src/trigger/ai-chat.ts b/references/realtime-streams/src/trigger/ai-chat.ts index d5c681d07..6635e30a5 100644 --- a/references/realtime-streams/src/trigger/ai-chat.ts +++ b/references/realtime-streams/src/trigger/ai-chat.ts @@ -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; 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, }; }, });