diff --git a/references/ai-chat/next.config.ts b/references/ai-chat/next.config.ts
new file mode 100644
index 000000000..cb651cdc0
--- /dev/null
+++ b/references/ai-chat/next.config.ts
@@ -0,0 +1,5 @@
+import type { NextConfig } from "next";
+
+const nextConfig: NextConfig = {};
+
+export default nextConfig;
diff --git a/references/ai-chat/package.json b/references/ai-chat/package.json
new file mode 100644
index 000000000..228a09015
--- /dev/null
+++ b/references/ai-chat/package.json
@@ -0,0 +1,30 @@
+{
+ "name": "references-ai-chat",
+ "version": "0.1.0",
+ "private": true,
+ "scripts": {
+ "dev": "next dev --turbopack",
+ "build": "next build",
+ "start": "next start",
+ "dev:trigger": "trigger dev"
+ },
+ "dependencies": {
+ "@ai-sdk/openai": "^2.0.0",
+ "@ai-sdk/react": "^2.0.0",
+ "@trigger.dev/sdk": "workspace:*",
+ "ai": "^6.0.0",
+ "next": "15.3.3",
+ "react": "^19.0.0",
+ "react-dom": "^19.0.0"
+ },
+ "devDependencies": {
+ "@tailwindcss/postcss": "^4",
+ "@trigger.dev/build": "workspace:*",
+ "@types/node": "^22",
+ "@types/react": "^19",
+ "@types/react-dom": "^19",
+ "tailwindcss": "^4",
+ "trigger.dev": "workspace:*",
+ "typescript": "^5"
+ }
+}
diff --git a/references/ai-chat/postcss.config.mjs b/references/ai-chat/postcss.config.mjs
new file mode 100644
index 000000000..79bcf135d
--- /dev/null
+++ b/references/ai-chat/postcss.config.mjs
@@ -0,0 +1,8 @@
+/** @type {import('postcss-load-config').Config} */
+const config = {
+ plugins: {
+ "@tailwindcss/postcss": {},
+ },
+};
+
+export default config;
diff --git a/references/ai-chat/src/app/globals.css b/references/ai-chat/src/app/globals.css
new file mode 100644
index 000000000..f1d8c73cd
--- /dev/null
+++ b/references/ai-chat/src/app/globals.css
@@ -0,0 +1 @@
+@import "tailwindcss";
diff --git a/references/ai-chat/src/app/layout.tsx b/references/ai-chat/src/app/layout.tsx
new file mode 100644
index 000000000..f50702858
--- /dev/null
+++ b/references/ai-chat/src/app/layout.tsx
@@ -0,0 +1,15 @@
+import type { Metadata } from "next";
+import "./globals.css";
+
+export const metadata: Metadata = {
+ title: "AI Chat — Trigger.dev",
+ description: "AI SDK useChat powered by Trigger.dev durable tasks",
+};
+
+export default function RootLayout({ children }: { children: React.ReactNode }) {
+ return (
+
+
{children}
+
+ );
+}
diff --git a/references/ai-chat/src/app/page.tsx b/references/ai-chat/src/app/page.tsx
new file mode 100644
index 000000000..16f01282c
--- /dev/null
+++ b/references/ai-chat/src/app/page.tsx
@@ -0,0 +1,17 @@
+import { auth } from "@trigger.dev/sdk";
+import { Chat } from "@/components/chat";
+
+export default async function Home() {
+ const accessToken = await auth.createTriggerPublicToken("ai-chat");
+
+ return (
+
+
+
+ AI Chat — powered by Trigger.dev
+
+
+
+
+ );
+}
diff --git a/references/ai-chat/src/components/chat.tsx b/references/ai-chat/src/components/chat.tsx
new file mode 100644
index 000000000..34c68d8ba
--- /dev/null
+++ b/references/ai-chat/src/components/chat.tsx
@@ -0,0 +1,91 @@
+"use client";
+
+import { useChat } from "@ai-sdk/react";
+import { TriggerChatTransport } from "@trigger.dev/sdk/chat";
+import { useState } from "react";
+
+export function Chat({ accessToken }: { accessToken: string }) {
+ const [input, setInput] = useState("");
+
+ const { messages, sendMessage, status, error } = useChat({
+ transport: new TriggerChatTransport({
+ task: "ai-chat",
+ accessToken,
+ baseURL: process.env.NEXT_PUBLIC_TRIGGER_API_URL,
+ }),
+ });
+
+ function handleSubmit(e: React.FormEvent) {
+ e.preventDefault();
+ if (!input.trim() || status === "streaming") return;
+
+ sendMessage({ text: input });
+ setInput("");
+ }
+
+ return (
+
+ {/* Messages */}
+
+ {messages.length === 0 && (
+
Send a message to start chatting.
+ )}
+
+ {messages.map((message) => (
+
+
+ {message.parts.map((part, i) => {
+ if (part.type === "text") {
+ return {part.text};
+ }
+ return null;
+ })}
+
+
+ ))}
+
+ {status === "streaming" && (
+
+ )}
+
+
+ {/* Error */}
+ {error && (
+
+ {error.message}
+
+ )}
+
+ {/* Input */}
+
+
+ );
+}
diff --git a/references/ai-chat/src/trigger/chat.ts b/references/ai-chat/src/trigger/chat.ts
new file mode 100644
index 000000000..27a400239
--- /dev/null
+++ b/references/ai-chat/src/trigger/chat.ts
@@ -0,0 +1,14 @@
+import { chatTask } from "@trigger.dev/sdk/ai";
+import { streamText, convertToModelMessages } from "ai";
+import { openai } from "@ai-sdk/openai";
+
+export const chat = chatTask({
+ id: "ai-chat",
+ run: async ({ messages }) => {
+ return streamText({
+ model: openai("gpt-4o-mini"),
+ system: "You are a helpful assistant. Be concise and friendly.",
+ messages: convertToModelMessages(messages),
+ });
+ },
+});
diff --git a/references/ai-chat/trigger.config.ts b/references/ai-chat/trigger.config.ts
new file mode 100644
index 000000000..4412bfc93
--- /dev/null
+++ b/references/ai-chat/trigger.config.ts
@@ -0,0 +1,7 @@
+import { defineConfig } from "@trigger.dev/sdk";
+
+export default defineConfig({
+ project: process.env.TRIGGER_PROJECT_REF!,
+ dirs: ["./src/trigger"],
+ maxDuration: 300,
+});
diff --git a/references/ai-chat/tsconfig.json b/references/ai-chat/tsconfig.json
new file mode 100644
index 000000000..c1334095f
--- /dev/null
+++ b/references/ai-chat/tsconfig.json
@@ -0,0 +1,27 @@
+{
+ "compilerOptions": {
+ "target": "ES2017",
+ "lib": ["dom", "dom.iterable", "esnext"],
+ "allowJs": true,
+ "skipLibCheck": true,
+ "strict": true,
+ "noEmit": true,
+ "esModuleInterop": true,
+ "module": "esnext",
+ "moduleResolution": "bundler",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "jsx": "preserve",
+ "incremental": true,
+ "plugins": [
+ {
+ "name": "next"
+ }
+ ],
+ "paths": {
+ "@/*": ["./src/*"]
+ }
+ },
+ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
+ "exclude": ["node_modules"]
+}