# Quick Start

> Source: https://trigger.dev/docs/ai-chat/quick-start

The AI Agents and Prompts surface ships as part of the **v4.5 release candidate**. Install with `@trigger.dev/sdk@rc` (or pin `4.5.0-rc.0` or later) to use these features — they aren’t yet on the latest stable, and APIs may still change before the 4.5.0 GA. See [supported AI SDK versions](https://trigger.dev/docs/ai-chat/reference#compatibility)
 and the [AI chat changelog](https://trigger.dev/docs/ai-chat/changelog)
 for details.

These steps assume you already have a Trigger.dev project with the SDK installed and the CLI authenticated — if you don’t, follow [Manual setup](https://trigger.dev/docs/manual-setup)
 (or `npx trigger.dev@latest init` in an existing project) first. You should be able to run `pnpm exec trigger dev` from your project root before continuing. The chat surface works with Vercel AI SDK **v5, v6, or v7**; install whichever major you want. On **v7**, also install `@ai-sdk/otel` so your model calls are traced (the SDK registers it for you). See [compatibility](https://trigger.dev/docs/ai-chat/reference#compatibility)
 for the full matrix.

1

[](https://trigger.dev/docs/ai-chat/quick-start#)

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.

trigger/chat.ts

    import { chat } from "@trigger.dev/sdk/ai";
    import { streamText, stepCountIs } from "ai";
    import { anthropic } from "@ai-sdk/anthropic";
    
    export const myChat = chat.agent({
      id: "my-chat",
      run: async ({ messages, signal }) => {
        return streamText({
          // Spread chat.toStreamTextOptions() FIRST — it wires up
          // prepareStep (compaction, steering, background injection),
          // the system prompt set via chat.prompt(), and telemetry.
          // Skipping this is the single most common cause of subtle
          // bugs (silent broken compaction, missing steering, etc.).
          ...chat.toStreamTextOptions(),
          model: anthropic("claude-sonnet-4-5"),
          messages,
          abortSignal: signal,
          stopWhen: stepCountIs(15),
        });
      },
    });
    

**Always spread `chat.toStreamTextOptions()` into your `streamText` call.** It wires up the `prepareStep` callback that drives compaction, mid-turn steering, and background injection — features that silently no-op if the spread is missing. Spread it **first** so any explicit overrides (e.g. a custom `prepareStep`) win.

For a **custom** [`UIMessage`](https://sdk.vercel.ai/docs/reference/ai-sdk-core/ui-message)
 subtype (typed `data-*` parts, tool map, etc.), define the agent with [`chat.withUIMessage<...>().agent({...})`](https://trigger.dev/docs/ai-chat/types)
 instead of `chat.agent`.

2

[](https://trigger.dev/docs/ai-chat/quick-start#)

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.

app/actions.ts

    "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.

3

[](https://trigger.dev/docs/ai-chat/quick-start#)

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.The example below uses the Next.js `@/*` path alias for imports from `@/trigger/chat` and `@/app/actions`. If you’re not using Next.js (or haven’t configured the alias), swap them for relative imports.

app/components/chat.tsx

    "use client";
    
    import { useState } from "react";
    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, clientData }) =>
      startChatSession({ chatId, clientData }),
      });
    
      const { messages, sendMessage, stop, status } = useChat({ transport });
      const [input, setInput] = useState("");
    
      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();
              if (input.trim()) {
                sendMessage({ text: input });
                setInput("");
              }
            }}
          >
            <input
              value={input}
              onChange={(e) => setInput(e.target.value)}
              placeholder="Type a message..."
            />
            <button type="submit" disabled={status === "streaming"}>
              Send
            </button>
            {status === "streaming" && (
              <button type="button" onClick={stop}>
                Stop
              </button>
            )}
          </form>
        </div>
      );
    }
    

[​](https://trigger.dev/docs/ai-chat/quick-start#next-steps)

Next steps
--------------------------------------------------------------------------

*   [Backend](https://trigger.dev/docs/ai-chat/backend)
     — Lifecycle hooks, persistence, session iterator, raw task primitives
*   [Tools](https://trigger.dev/docs/ai-chat/tools)
    : Declare tools so `toModelOutput` survives across turns, typed in `run()`
*   [Frontend](https://trigger.dev/docs/ai-chat/frontend)
     — Session management, client data, reconnection
*   [Types](https://trigger.dev/docs/ai-chat/types)
     — `chat.withUIMessage`, `InferChatUIMessage`, and related typing
*   [`chat.local`](https://trigger.dev/docs/ai-chat/chat-local)
     — Per-run typed state across hooks, run, tools, subtasks
*   [Sub-agents pattern](https://trigger.dev/docs/ai-chat/patterns/sub-agents)
     — Subtask-as-tool, `target: "root"` streaming, `ai.toolExecute` helpers
*   [Background injection](https://trigger.dev/docs/ai-chat/background-injection)
     — `chat.inject()` and `chat.defer()` for between-turn work

Was this page helpful?

YesNo

[Previous](https://trigger.dev/docs/ai-chat/overview)
[AnatomyThe moving parts of a chat agent — the agent task, the session, the frontend transport — and which page covers each.\
\
Next](https://trigger.dev/docs/ai-chat/anatomy)

⌘I

Assistant

Responses are generated using AI and may contain mistakes.
