Documentation Index
Fetch the complete documentation index at: https://trigger-docs-tri-7532-ai-sdk-chat-transport-and-chat-task-s.mintlify.app/llms.txt
Use this file to discover all available pages before exploring further.
Define a chat agent
Use chat.agent from @trigger.dev/sdk/ai to define an agent that handles chat messages. The run function receives ModelMessage[] (already converted from the frontend’s UIMessage[]) — pass them directly to streamText.If you return a StreamTextResult, it’s automatically piped to the frontend.import { chat } from "@trigger.dev/sdk/ai";
import { streamText } from "ai";
import { openai } from "@ai-sdk/openai";
export const myChat = chat.agent({
id: "my-chat",
run: async ({ messages, signal }) => {
// messages is ModelMessage[] — pass directly to streamText
// signal fires on stop or run cancel
return streamText({
model: openai("gpt-4o"),
messages,
abortSignal: signal,
});
},
});
Add two server actions
On your server (e.g. as Next.js server actions), expose two helpers the transport will call: one that creates the chat session, and one that mints a fresh session-scoped access token for refresh."use server";
import { auth } from "@trigger.dev/sdk";
import { chat } from "@trigger.dev/sdk/ai";
// Creates the Session row + triggers the first run, returns the
// session PAT. Idempotent on (env, chatId) so concurrent calls
// converge to the same session.
export const startChatSession = chat.createStartSessionAction("my-chat");
// Pure mint — fresh session-scoped PAT for an existing session.
// The transport calls this on 401/403 to refresh.
export async function mintChatAccessToken(chatId: string) {
return auth.createPublicToken({
scopes: {
read: { sessions: chatId },
write: { sessions: chatId },
},
expirationTime: "1h",
});
}
The browser never holds your environment’s secret key — both helpers run on your server, where customer-side authorization (per-user, per-plan, etc.) lives alongside any DB writes you want to pair with session creation. Use in the frontend
Use the useTriggerChatTransport hook from @trigger.dev/sdk/chat/react to create a memoized transport instance, then pass it to useChat. Wire both server actions into the transport’s accessToken and startSession callbacks:"use client";
import { useChat } from "@ai-sdk/react";
import { useTriggerChatTransport } from "@trigger.dev/sdk/chat/react";
import type { myChat } from "@/trigger/chat";
import { mintChatAccessToken, startChatSession } from "@/app/actions";
export function Chat() {
const transport = useTriggerChatTransport<typeof myChat>({
task: "my-chat",
accessToken: ({ chatId }) => mintChatAccessToken(chatId),
startSession: ({ chatId, taskId, clientData }) =>
startChatSession({ chatId, taskId, clientData }),
});
const { messages, sendMessage, stop, status } = useChat({ transport });
return (
<div>
{messages.map((m) => (
<div key={m.id}>
<strong>{m.role}:</strong>
{m.parts.map((part, i) =>
part.type === "text" ? <span key={i}>{part.text}</span> : null
)}
</div>
))}
<form
onSubmit={(e) => {
e.preventDefault();
const input = e.currentTarget.querySelector("input");
if (input?.value) {
sendMessage({ text: input.value });
input.value = "";
}
}}
>
<input placeholder="Type a message..." />
<button type="submit" disabled={status === "streaming"}>
Send
</button>
{status === "streaming" && (
<button type="button" onClick={stop}>
Stop
</button>
)}
</form>
</div>
);
}
Next steps
- Backend — Lifecycle hooks, persistence, session iterator, raw task primitives
- Frontend — Session management, client data, reconnection
- Types —
chat.withUIMessage, InferChatUIMessage, and related typing
- Features — Per-run data, deferred work, streaming, subtasks