Skip to main content
1

Define a chat task

Use chat.task from @trigger.dev/sdk/ai to define a task 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.
trigger/chat.ts
import { chat } from "@trigger.dev/sdk/ai";
import { streamText } from "ai";
import { openai } from "@ai-sdk/openai";

export const myChat = chat.task({
  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,
    });
  },
});
2

Generate an access token

On your server (e.g. a Next.js server action), create a trigger public token scoped to your chat task:
app/actions.ts
"use server";

import { chat } from "@trigger.dev/sdk/ai";
import type { myChat } from "@/trigger/chat";

export const getChatToken = () =>
  chat.createAccessToken<typeof myChat>("my-chat");
3

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:
app/components/chat.tsx
"use client";

import { useChat } from "@ai-sdk/react";
import { useTriggerChatTransport } from "@trigger.dev/sdk/chat/react";
import type { myChat } from "@/trigger/chat";
import { getChatToken } from "@/app/actions";

export function Chat() {
  const transport = useTriggerChatTransport<typeof myChat>({
    task: "my-chat",
    accessToken: getChatToken,
  });

  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
  • Features — Per-run data, deferred work, streaming, subtasks