Skip to content

Commit bcfcc36

Browse files
committed
docs: chat.agent on Sessions-as-run-manager
Bring the AI Chat documentation in line with the Sessions-as-run-manager release. Public surface (chat.agent({...}), useTriggerChatTransport, AgentChat, chat.store / chat.defer / chat.history) is unchanged in shape; the wiring docs around it changed enough that every transport example, every server-action snippet, and the auth/session model needed updating. New: - ai-chat/upgrade-guide.mdx — step-by-step migration for prerelease customers (replace getStartToken / getChatToken with startSession + accessToken, drop runId from ChatSession persistence, etc.). Updated transport-shape group (these all share the same callback shape and ship together): - quick-start, frontend, backend, server-chat, types, reference Updated peripherals: - overview, changelog, client-protocol, features, error-handling, testing, patterns/version-upgrades, patterns/database-persistence Outside ai-chat: - realtime/backend/{streams,input-streams}: callout cross-references no longer point at the deleted Sessions docs. Removed: - docs/sessions/{overview,quick-start,channels,reference}.mdx — the standalone-Sessions surface predates the task-bound model and was giving stale guidance. We'll re-introduce Sessions docs once the primitive ships a non-chat.agent customer flow worth documenting. - The "Sessions" group in docs.json's AI nav.
1 parent 82aebe4 commit bcfcc36

22 files changed

Lines changed: 641 additions & 947 deletions

docs/ai-chat/backend.mdx

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ The highest-level approach. Handles message accumulation, stop signals, turn lif
1313
</Tip>
1414

1515
<Info>
16-
Every `chat.agent` conversation is backed by a [Session](/sessions/overview)`externalId` is your `chatId`, `type` is `"chat.agent"`. The session outlives any single run, which is why chats can resume across days or deploys without losing identity. You rarely need to touch the session directly (`chat.stream`, `chat.messages`, `chat.stopSignal` wrap everything), but `payload.sessionId` is available if you want to reach in — e.g. `sessions.open(payload.sessionId)` to write from a sub-agent or from outside the turn loop.
16+
Every `chat.agent` conversation is backed by a durable Session — `externalId` is your `chatId`, `type` is `"chat.agent"`, `taskIdentifier` is the agent's task ID. The session is the run manager: it owns the chat's runs, persists across run lifecycles, and orchestrates handoffs (idle continuation, `chat.requestUpgrade`). You rarely need to touch the session directly (`chat.stream`, `chat.messages`, `chat.stopSignal` wrap everything), but `payload.sessionId` is available if you want to reach in — e.g. `sessions.open(payload.sessionId)` to write from a sub-agent or from outside the turn loop.
1717
</Info>
1818

1919
### Simple: return a StreamTextResult
@@ -674,11 +674,18 @@ export const myChat = chat.agent({
674674
```ts app/actions.ts
675675
"use server";
676676

677+
import { auth } from "@trigger.dev/sdk";
677678
import { chat } from "@trigger.dev/sdk/ai";
678-
import type { myChat } from "@/trigger/chat";
679679
import { db } from "@/lib/db";
680680

681-
export const getChatToken = () => chat.createAccessToken<typeof myChat>("my-chat");
681+
export const startChatSession = chat.createStartSessionAction("my-chat");
682+
683+
export async function mintChatAccessToken(chatId: string) {
684+
return auth.createPublicToken({
685+
scopes: { read: { sessions: chatId }, write: { sessions: chatId } },
686+
expirationTime: "1h",
687+
});
688+
}
682689

683690
export async function getChatMessages(chatId: string) {
684691
const found = await db.chat.findUnique({ where: { id: chatId } });
@@ -690,14 +697,12 @@ export async function getAllSessions() {
690697
const result: Record<
691698
string,
692699
{
693-
runId: string;
694700
publicAccessToken: string;
695701
lastEventId?: string;
696702
}
697703
> = {};
698704
for (const s of sessions) {
699705
result[s.id] = {
700-
runId: s.runId,
701706
publicAccessToken: s.publicAccessToken,
702707
lastEventId: s.lastEventId ?? undefined,
703708
};
@@ -716,12 +721,14 @@ export async function deleteSession(chatId: string) {
716721
import { useChat } from "@ai-sdk/react";
717722
import { useTriggerChatTransport } from "@trigger.dev/sdk/chat/react";
718723
import type { myChat } from "@/trigger/chat";
719-
import { getChatToken, deleteSession } from "@/app/actions";
724+
import { mintChatAccessToken, startChatSession, deleteSession } from "@/app/actions";
720725

721726
export function Chat({ chatId, initialMessages, initialSessions }) {
722727
const transport = useTriggerChatTransport<typeof myChat>({
723728
task: "my-chat",
724-
accessToken: getChatToken,
729+
accessToken: ({ chatId }) => mintChatAccessToken(chatId),
730+
startSession: ({ chatId, taskId, clientData }) =>
731+
startChatSession({ chatId, taskId, clientData }),
725732
clientData: { userId: currentUser.id }, // Type-checked against clientDataSchema
726733
sessions: initialSessions,
727734
onSessionChange: (id, session) => {

docs/ai-chat/changelog.mdx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ description: "Pre-release updates for AI chat agents."
88

99
## `chat.agent` now runs on Sessions
1010

11-
Every chat is backed by a [Session](/sessions/overview) — a new public, durable, bidirectional I/O primitive that outlives any single run. `externalId` = your chat ID, `type` = `"chat.agent"`. Under the hood:
11+
Every chat is backed by a durable Session row that outlives any single run. `externalId` = your chat ID, `type` = `"chat.agent"`. Under the hood:
1212

1313
- Output chunks stream on `session.out` (was a run-scoped `streams.writer("chat")`).
1414
- Client messages and stops land on `session.in` as a [`ChatInputChunk`](/ai-chat/reference#chatinputchunk) tagged union (was two run-scoped `streams.input` definitions).
@@ -21,7 +21,7 @@ Public surface (`chat.agent()`, `TriggerChatTransport`, `AgentChat`, `chat.strea
2121
- **`TriggerChatTaskResult.sessionId`** + **`ChatTaskRunPayload.sessionId`** — you can reach into the raw session via `sessions.open(payload.sessionId)` for advanced cases (writing from a sub-agent, custom transport).
2222
- **Dashboard Agent tab** resolves via `sessionId` and stays in sync with the live stream across runs.
2323

24-
See the new [Sessions docs](/sessions/overview) for the underlying primitive.
24+
The full wire-level protocol (session create, channel routes, JWT scopes) is documented in [Client Protocol](/ai-chat/client-protocol).
2525

2626
## `X-Session-Settled` — fast reconnect on idle chats
2727

docs/ai-chat/client-protocol.mdx

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ This page documents the protocol that chat clients use to communicate with `chat
1212

1313
## Overview
1414

15-
`chat.agent` is built on [Sessions](/sessions/overview)a durable, bidirectional I/O primitive that outlives a single run. A conversation is one session; a session can host many runs over its lifetime.
15+
`chat.agent` is built on a durable Session row — the unit of state that owns the chat's runs across their full lifecycle. A conversation is one session; a session can host many runs over its lifetime.
1616

1717
The protocol has four parts:
1818

@@ -75,7 +75,7 @@ Response:
7575

7676
`id` is the `session_*` friendly ID — persist it alongside your chat state. `isCached: true` means the server returned an existing session for this `externalId` (safe to ignore).
7777

78-
See [`POST /api/v1/sessions`](/sessions/reference#create) for the full request / response schema.
78+
`POST /api/v1/sessions` is documented inline in the wire-protocol section below.
7979

8080
## Step 2: Trigger a run
8181

@@ -485,17 +485,13 @@ A client needs to track per-conversation:
485485
| Create session (`POST /api/v1/sessions`) | Secret API key or JWT with `write:sessions` |
486486
| Close session (`POST /api/v1/sessions/{id}/close`) | Secret API key or JWT with `admin:sessions:{id}` |
487487
| Trigger task | Secret API key or JWT with `write:tasks` |
488-
| `.in` append | JWT with `write:sessions:{id}` (or `write:sessions:{id}:in`) |
488+
| `.in` append | JWT with `write:sessions:{id}` |
489489
| `.out` subscribe | JWT with `read:sessions:{id}` |
490490
491-
The transport-facing `publicAccessToken` returned from the trigger response carries both `read:sessions:{id}` and `write:sessions:{id}` for the session, plus `read:runs:{runId}` + `write:inputStreams:{runId}` for the run. Use it for all session operations.
492-
493-
See [Session reference — Token scopes](/sessions/reference#token-scopes) for the full scope list.
491+
The transport-facing `publicAccessToken` returned from `POST /api/v1/sessions` carries both `read:sessions:{id}` and `write:sessions:{id}` for the session — use it for all session operations. A token minted for either the externalId form or the friendlyId form authorizes both URL forms on every read and write route.
494492
495493
## See also
496494
497-
- [Sessions Overview](/sessions/overview) — The durable primitive chat.agent is built on
498-
- [Sessions Reference](/sessions/reference) — Full `sessions.*` API and wire endpoints
499495
- [`TriggerChatTransport`](/ai-chat/frontend) — Built-in browser transport (implements this protocol)
500496
- [`AgentChat`](/ai-chat/server-chat) — Built-in server-side client
501497
- [Backend lifecycle](/ai-chat/backend#lifecycle-hooks) — What the agent does on each event

docs/ai-chat/error-handling.mdx

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -300,7 +300,12 @@ const { messages, error, status } = useChat({
300300

301301
```tsx
302302
function Chat() {
303-
const transport = useTriggerChatTransport({ task: "my-chat", accessToken });
303+
const transport = useTriggerChatTransport({
304+
task: "my-chat",
305+
accessToken: ({ chatId }) => mintChatAccessToken(chatId),
306+
startSession: ({ chatId, taskId, clientData }) =>
307+
startChatSession({ chatId, taskId, clientData }),
308+
});
304309
const { messages, error, sendMessage } = useChat({ transport });
305310

306311
return (
@@ -344,6 +349,30 @@ uiMessageStreamOptions: {
344349
For richer error structures, use [`chat.response.write()`](/ai-chat/features#custom-data-parts) with a custom `data-error` part type. This lets you ship structured error metadata (codes, retry hints, etc.) instead of stringly-typed messages.
345350
</Tip>
346351

352+
### Errors from `accessToken` / `startSession`
353+
354+
If your `accessToken` or `startSession` callback throws (auth failure, DB write failure, network error), the rejection surfaces through `useChat`'s `error` state — same as a stream error. The transport doesn't retry the callback automatically; the customer is responsible for handling it.
355+
356+
```tsx
357+
const transport = useTriggerChatTransport({
358+
task: "my-chat",
359+
accessToken: async ({ chatId }) => {
360+
try {
361+
return await mintChatAccessToken(chatId);
362+
} catch (err) {
363+
// Customer's server action failed (e.g. user lost auth).
364+
// Re-throw to surface as a useChat error, or return a sentinel
365+
// your UI can detect and prompt re-auth.
366+
throw new Error(`AUTH_REFRESH: ${err.message}`);
367+
}
368+
},
369+
startSession: ({ chatId, taskId, clientData }) =>
370+
startChatSession({ chatId, taskId, clientData }),
371+
});
372+
```
373+
374+
`startSession` failures most commonly mean the customer's authorization layer rejected the request (no plan, quota exceeded, user not allowed to chat with this agent). The customer's server should produce a meaningful error message; the transport propagates it verbatim to `useChat`'s `error` state.
375+
347376
## Run-level retries
348377

349378
`chat.agent` uses `retry: { maxAttempts: 1 }` — the run **never retries** on unhandled failure. This is intentional: each turn is conversation-preserving, so a true run failure is severe and shouldn't silently retry (which could send duplicate API calls or mutate state twice).

docs/ai-chat/features.mdx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -428,13 +428,17 @@ import { useChat } from "@ai-sdk/react";
428428
export function Chat({ chatId }) {
429429
const transport = useTriggerChatTransport({
430430
task: "my-chat",
431-
accessToken: getChatToken,
431+
accessToken: ({ chatId }) => mintChatAccessToken(chatId),
432+
startSession: ({ chatId, taskId, clientData }) =>
433+
startChatSession({ chatId, taskId, clientData }),
432434
clientData: { userId: currentUser.id },
433435
});
434436

435-
// Preload on mount — run starts before the user types anything
437+
// Preload on mount — run starts before the user types anything.
438+
// Trigger config (idleTimeoutInSeconds, machine, tags) lives in the
439+
// server action that wraps `chat.createStartSessionAction`.
436440
useEffect(() => {
437-
transport.preload(chatId, { idleTimeoutInSeconds: 60 });
441+
transport.preload(chatId);
438442
}, [chatId]);
439443

440444
const { messages, sendMessage } = useChat({ id: chatId, transport });

0 commit comments

Comments
 (0)