Skip to content

Commit 87f8df6

Browse files
committed
feat(sdk,cli): bundle agent skills + docs in the SDK for zero-drift
@trigger.dev/sdk now ships the agent skills and a curated snapshot of the docs the skills cite. The skills the CLI installs into your coding agent (.claude/skills and friends) are thin pointers that read this content directly from node_modules, so the guidance always matches the SDK version in your project instead of going stale until the next reinstall.
1 parent 034058b commit 87f8df6

12 files changed

Lines changed: 1341 additions & 979 deletions

File tree

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@trigger.dev/sdk": patch
3+
"trigger.dev": patch
4+
---
5+
6+
`@trigger.dev/sdk` now bundles the Trigger.dev agent skills and a curated snapshot of the docs those skills reference. The skills that `trigger skills` installs into your coding agent read this content from node_modules, so the guidance your AI assistant follows is pinned to the SDK version installed in your project and stays current across upgrades instead of going stale until the next reinstall.

packages/cli-v3/skills/authoring-chat-agent/SKILL.md

Lines changed: 6 additions & 241 deletions
Original file line numberDiff line numberDiff line change
@@ -10,242 +10,16 @@ description: >
1010
streamText route to chat.agent.
1111
type: core
1212
library: trigger.dev
13-
library_version: "{{TRIGGER_SDK_VERSION}}"
14-
sources:
15-
- docs/ai-chat/overview.mdx
16-
- docs/ai-chat/quick-start.mdx
17-
- docs/ai-chat/how-it-works.mdx
18-
- docs/ai-chat/backend.mdx
19-
- docs/ai-chat/frontend.mdx
20-
- docs/ai-chat/reference.mdx
21-
- docs/ai-chat/types.mdx
22-
- docs/ai-chat/tools.mdx
23-
- docs/ai-chat/lifecycle-hooks.mdx
24-
- docs/ai-chat/error-handling.mdx
2513
---
2614

27-
# Authoring a chat agent
15+
# Authoring a chat.agent
2816

29-
A `chat.agent` runs an entire conversation as one long-lived Trigger.dev task. It wakes when a
30-
message arrives, freezes when none do, and in-memory state survives page refreshes, deploys, idle
31-
gaps, and crashes. Your code is the loop you would write anyway: messages in, `streamText` out.
32-
There are no API routes. The frontend talks to the agent through a `TriggerChatTransport`, so
33-
history accumulates server-side and the client ships only the new message each turn.
17+
The full, version-pinned reference ships **inside your installed `@trigger.dev/sdk`**. Read it before writing code — it always matches the SDK version in this project, so it never drifts:
3418

35-
Works with Vercel AI SDK v5, v6, or v7. On v7 also install `@ai-sdk/otel` so model calls are traced
36-
(the SDK registers it for you).
19+
- **Skill:** `node_modules/@trigger.dev/sdk/skills/authoring-chat-agent/SKILL.md` — the per-turn run loop, `chat.toStreamTextOptions()`, the two server actions, typed tools/data parts, and the React transport.
20+
- **Docs:** `node_modules/@trigger.dev/sdk/docs/ai-chat/` — exhaustive detail. Grep for an API, e.g. `grep -rl "toStreamTextOptions" node_modules/@trigger.dev/sdk/docs/`.
3721

38-
## Setup
39-
40-
Three pieces: the agent task, two server actions, and the frontend transport.
41-
42-
### 1. Define the agent
43-
44-
```ts trigger/chat.ts
45-
import { chat } from "@trigger.dev/sdk/ai";
46-
import { streamText, stepCountIs } from "ai";
47-
import { anthropic } from "@ai-sdk/anthropic";
48-
49-
export const myChat = chat.agent({
50-
id: "my-chat",
51-
run: async ({ messages, signal }) =>
52-
streamText({
53-
// Spread this FIRST. See "Common mistakes".
54-
...chat.toStreamTextOptions(),
55-
model: anthropic("claude-sonnet-4-5"),
56-
messages,
57-
abortSignal: signal,
58-
stopWhen: stepCountIs(15),
59-
}),
60-
});
61-
```
62-
63-
`run` receives `messages` already converted to `ModelMessage[]` (the SDK converts the frontend's
64-
`UIMessage[]` for you) plus a `signal` that aborts on stop or cancel. Returning the
65-
`StreamTextResult` auto-pipes it to the frontend.
66-
67-
### 2. Add two server actions
68-
69-
Both run on your server, so the browser never holds your environment secret key. This is also
70-
where per-user / per-plan authorization and any paired DB writes live.
71-
72-
```ts app/actions.ts
73-
"use server";
74-
import { auth } from "@trigger.dev/sdk";
75-
import { chat } from "@trigger.dev/sdk/ai";
76-
77-
// Creates the Session + first run, returns a session PAT. Idempotent on (env, chatId).
78-
export const startChatSession = chat.createStartSessionAction("my-chat");
79-
80-
// Pure mint. The transport calls this on 401/403 to refresh an expired token.
81-
export async function mintChatAccessToken(chatId: string) {
82-
return auth.createPublicToken({
83-
scopes: { read: { sessions: chatId }, write: { sessions: chatId } },
84-
expirationTime: "1h",
85-
});
86-
}
87-
```
88-
89-
### 3. Wire the frontend
90-
91-
```tsx app/components/chat.tsx
92-
"use client";
93-
import { useState } from "react";
94-
import { useChat } from "@ai-sdk/react";
95-
import { useTriggerChatTransport } from "@trigger.dev/sdk/chat/react";
96-
import type { myChat } from "@/trigger/chat";
97-
import { mintChatAccessToken, startChatSession } from "@/app/actions";
98-
99-
export function Chat() {
100-
const transport = useTriggerChatTransport<typeof myChat>({
101-
task: "my-chat", // typeof myChat gives compile-time task-id validation
102-
accessToken: ({ chatId }) => mintChatAccessToken(chatId),
103-
startSession: ({ chatId, clientData }) => startChatSession({ chatId, clientData }),
104-
});
105-
106-
const { messages, sendMessage, stop, status } = useChat({ transport });
107-
const [input, setInput] = useState("");
108-
// render messages, a form that calls sendMessage({ text: input }),
109-
// and a Stop button (onClick={stop}) while status === "streaming".
110-
}
111-
```
112-
113-
The transport is memoized (created once, reused across renders). Passing `typeof myChat` flows the
114-
agent's message type through `useChat`.
115-
116-
## Core patterns
117-
118-
### 1. Return vs pipe
119-
120-
Return the `streamText` result from `run` for the simple case. When `streamText` is called deep
121-
inside nested helpers, call `await chat.pipe(result)` from anywhere in the task instead, and let
122-
`run` resolve `void`.
123-
124-
```ts
125-
export const agentChat = chat.agent({
126-
id: "agent-chat",
127-
run: async ({ messages }) => {
128-
await runAgentLoop(messages); // don't return; pipe inside
129-
},
130-
});
131-
132-
async function runAgentLoop(messages: ModelMessage[]) {
133-
const result = streamText({
134-
...chat.toStreamTextOptions(),
135-
model: anthropic("claude-sonnet-4-5"),
136-
messages,
137-
});
138-
await chat.pipe(result); // works from anywhere in the task
139-
}
140-
```
141-
142-
### 2. Typed tools (declare on config AND spread back)
143-
144-
Declare tools on `chat.agent({ tools })`, read them back typed from the `run()` payload, and pass
145-
that set to `chat.toStreamTextOptions({ tools })`. One declaration flows everywhere.
146-
147-
```ts
148-
import { tool, stepCountIs } from "ai";
149-
import { z } from "zod";
150-
151-
const tools = {
152-
searchDocs: tool({
153-
description: "Search the docs.",
154-
inputSchema: z.object({ query: z.string() }),
155-
execute: async ({ query }) => searchIndex(query),
156-
}),
157-
};
158-
159-
export const myChat = chat.agent({
160-
id: "my-chat",
161-
tools, // so toModelOutput survives across turns
162-
run: async ({ messages, tools, signal }) =>
163-
streamText({
164-
...chat.toStreamTextOptions({ tools }), // same set, handed back typed
165-
model: anthropic("claude-sonnet-4-5"),
166-
messages,
167-
abortSignal: signal,
168-
stopWhen: stepCountIs(15),
169-
}),
170-
});
171-
```
172-
173-
`tools` also accepts a function `(event) => ToolSet` resolved per turn, where `event` carries
174-
`chatId`, `turn`, `continuation`, and `clientData`.
175-
176-
### 3. Custom data parts (persisted vs transient)
177-
178-
`data-*` parts written via `chat.response.write()` in `run()` (or `writer.write()` in hooks)
179-
persist into `responseMessage.parts` and surface in `onTurnComplete`. Add `transient: true` to
180-
stream them without persisting. Writes via `chat.stream` are always ephemeral.
181-
182-
```ts
183-
// In run() - persists, surfaces in onTurnComplete's responseMessage
184-
chat.response.write({ type: "data-context", data: { searchResults } });
185-
186-
// In a hook via writer - streams but does NOT persist
187-
writer.write({ type: "data-progress", id: "search", data: { percent: 50 }, transient: true });
188-
```
189-
190-
### 4. Custom UIMessage type, client data, and builder hooks
191-
192-
For typed `data-*` parts or a tool map, build the agent through `chat.withUIMessage<T>()` and
193-
`chat.withClientData({ schema })`. Builder methods chain in any order; builder hooks run before the
194-
matching task hook. `streamOptions` becomes the default `uiMessageStreamOptions` (shallow-merged,
195-
agent wins).
196-
197-
```ts
198-
export const myChat = chat
199-
.withUIMessage<MyChatUIMessage>({ streamOptions: { sendReasoning: true } })
200-
.withClientData({ schema: z.object({ userId: z.string() }) })
201-
.agent({
202-
id: "my-chat",
203-
tools: myTools,
204-
onTurnStart: async ({ uiMessages, writer }) => {
205-
writer.write({ type: "data-turn-status", data: { status: "preparing" } });
206-
},
207-
run: async ({ messages, tools, signal }) =>
208-
streamText({ ...chat.toStreamTextOptions({ tools }), model, messages, abortSignal: signal }),
209-
});
210-
```
211-
212-
Build `MyChatUIMessage` as `UIMessage<unknown, MyDataTypes, InferUITools<typeof tools>>` (or, for
213-
tools only, `InferChatUIMessageFromTools<typeof tools>` from `@trigger.dev/sdk/ai`). On the
214-
frontend, narrow `useChat` with `InferChatUIMessage<typeof myChat>` from `@trigger.dev/sdk/chat/react`.
215-
216-
### 5. Lifecycle hooks and stop
217-
218-
`chat.agent` accepts hooks that fire in a fixed per-turn order:
219-
220-
```text
221-
onValidateMessages -> hydrateMessages -> onChatStart (chat's first message only)
222-
-> onTurnStart -> run() -> onBeforeTurnComplete -> onTurnComplete
223-
```
224-
225-
`onBoot` fires once per worker process (every fresh boot, including continuation runs) and is where
226-
`chat.local`, DB connections, and per-process state belong. `onChatStart` fires only on the chat's
227-
first message. Suspend/resume use `onChatSuspend` / `onChatResume`. Config options include
228-
`tools`, `clientDataSchema`, `maxTurns` (100), `turnTimeout` ("1h"), `idleTimeoutInSeconds` (30),
229-
`uiMessageStreamOptions`, and `exitAfterPreloadIdle`. There is no generic `retry`; `chat.agent`
230-
runs with `maxAttempts: 1` internally.
231-
232-
Stop is load-bearing: the `signal` passed to `run` aborts on stop or cancel. Forward it as
233-
`abortSignal` to `streamText`, or the Stop button updates the UI while the model keeps generating
234-
server-side.
235-
236-
```ts
237-
run: async ({ messages, signal }) =>
238-
streamText({ ...chat.toStreamTextOptions(), model, messages, abortSignal: signal, stopWhen: stepCountIs(15) });
239-
```
240-
241-
### 6. Migrating from a plain AI SDK `streamText` route
242-
243-
There is no API route in this model. The transport replaces the route round-trip, so:
244-
245-
- Delete the route handler. Move per-request auth into the two server actions from Setup step 2.
246-
- Move the `streamText` call into `run`. It already receives pre-converted `ModelMessage[]`.
247-
- Return the `StreamTextResult` (it auto-pipes) and add `...chat.toStreamTextOptions()` first.
248-
- On the client, swap the `api` URL for `useTriggerChatTransport`; `useChat` stays the same shape.
22+
If those paths don't exist, `@trigger.dev/sdk` isn't installed yet — install it first. In a non-hoisted layout, resolve the package with `node -p "require.resolve('@trigger.dev/sdk/package.json')"` and read `skills/` + `docs/` beside it.
24923

25024
## Common mistakes
25125

@@ -283,13 +57,4 @@ There is no API route in this model. The transport replaces the route round-trip
28357

28458
## References
28559

286-
- `chat-agent-advanced` skill - lifecycle hooks in depth, sessions, raw-task primitives
287-
(`chat.createSession`, `chat.customAgent`, `chat.stream`), compaction, HITL approvals, recovery.
288-
- `realtime-and-frontend` skill - Realtime hooks and frontend streaming beyond the chat transport.
289-
- `authoring-tasks` skill - base `task()` semantics, `ctx`, and standard lifecycle hooks.
290-
- Docs: /ai-chat/quick-start, /ai-chat/backend, /ai-chat/tools, /ai-chat/types, /ai-chat/frontend
291-
292-
## Version
293-
294-
Generated for `@trigger.dev/sdk` `{{TRIGGER_SDK_VERSION}}`. Re-run the trigger.dev skills installer
295-
after upgrading.
60+
Sibling skills: **chat-agent-advanced** (Sessions primitive, custom transports, sub-agents, HITL, fast starts, resilience, testing, upgrades), **authoring-tasks** and **realtime-and-frontend** (the task + frontend foundations chat builds on).

0 commit comments

Comments
 (0)