@trigger.dev/sdk 4.5.0-rc.6 → 4.5.0-rc.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commonjs/v3/ai.d.ts +171 -5
- package/dist/commonjs/v3/ai.js +309 -22
- package/dist/commonjs/v3/ai.js.map +1 -1
- package/dist/commonjs/v3/chat-server.d.ts +8 -0
- package/dist/commonjs/v3/chat-server.js +32 -10
- package/dist/commonjs/v3/chat-server.js.map +1 -1
- package/dist/commonjs/v3/chat-server.test.js +51 -0
- package/dist/commonjs/v3/chat-server.test.js.map +1 -1
- package/dist/commonjs/v3/createStartSessionAction.test.js +30 -0
- package/dist/commonjs/v3/createStartSessionAction.test.js.map +1 -1
- package/dist/commonjs/v3/sessions.d.ts +3 -2
- package/dist/commonjs/v3/sessions.js +3 -2
- package/dist/commonjs/v3/sessions.js.map +1 -1
- package/dist/commonjs/version.js +1 -1
- package/dist/esm/v3/ai.d.ts +171 -5
- package/dist/esm/v3/ai.js +309 -22
- package/dist/esm/v3/ai.js.map +1 -1
- package/dist/esm/v3/chat-server.d.ts +8 -0
- package/dist/esm/v3/chat-server.js +32 -10
- package/dist/esm/v3/chat-server.js.map +1 -1
- package/dist/esm/v3/chat-server.test.js +51 -0
- package/dist/esm/v3/chat-server.test.js.map +1 -1
- package/dist/esm/v3/createStartSessionAction.test.js +30 -0
- package/dist/esm/v3/createStartSessionAction.test.js.map +1 -1
- package/dist/esm/v3/sessions.d.ts +3 -2
- package/dist/esm/v3/sessions.js +3 -2
- package/dist/esm/v3/sessions.js.map +1 -1
- package/dist/esm/version.js +1 -1
- package/docs/ai/prompts.mdx +430 -0
- package/docs/ai-chat/actions.mdx +115 -0
- package/docs/ai-chat/anatomy.mdx +71 -0
- package/docs/ai-chat/backend.mdx +817 -0
- package/docs/ai-chat/background-injection.mdx +221 -0
- package/docs/ai-chat/changelog.mdx +850 -0
- package/docs/ai-chat/chat-local.mdx +174 -0
- package/docs/ai-chat/client-protocol.mdx +1081 -0
- package/docs/ai-chat/compaction.mdx +411 -0
- package/docs/ai-chat/custom-agents.mdx +364 -0
- package/docs/ai-chat/error-handling.mdx +415 -0
- package/docs/ai-chat/fast-starts.mdx +672 -0
- package/docs/ai-chat/frontend.mdx +580 -0
- package/docs/ai-chat/how-it-works.mdx +230 -0
- package/docs/ai-chat/lifecycle-hooks.mdx +530 -0
- package/docs/ai-chat/mcp.mdx +101 -0
- package/docs/ai-chat/overview.mdx +90 -0
- package/docs/ai-chat/patterns/branching-conversations.mdx +284 -0
- package/docs/ai-chat/patterns/code-sandbox.mdx +126 -0
- package/docs/ai-chat/patterns/database-persistence.mdx +414 -0
- package/docs/ai-chat/patterns/human-in-the-loop.mdx +275 -0
- package/docs/ai-chat/patterns/large-payloads.mdx +169 -0
- package/docs/ai-chat/patterns/oom-resilience.mdx +120 -0
- package/docs/ai-chat/patterns/persistence-and-replay.mdx +211 -0
- package/docs/ai-chat/patterns/recovery-boot.mdx +230 -0
- package/docs/ai-chat/patterns/skills.mdx +221 -0
- package/docs/ai-chat/patterns/sub-agents.mdx +383 -0
- package/docs/ai-chat/patterns/tool-result-auditing.mdx +148 -0
- package/docs/ai-chat/patterns/trusted-edge-signals.mdx +337 -0
- package/docs/ai-chat/patterns/version-upgrades.mdx +172 -0
- package/docs/ai-chat/pending-messages.mdx +343 -0
- package/docs/ai-chat/prompt-caching.mdx +206 -0
- package/docs/ai-chat/quick-start.mdx +161 -0
- package/docs/ai-chat/reference.mdx +909 -0
- package/docs/ai-chat/server-chat.mdx +263 -0
- package/docs/ai-chat/sessions.mdx +333 -0
- package/docs/ai-chat/testing.mdx +682 -0
- package/docs/ai-chat/tools.mdx +191 -0
- package/docs/ai-chat/types.mdx +242 -0
- package/docs/ai-chat/upgrade-guide.mdx +515 -0
- package/docs/apikeys.mdx +54 -0
- package/docs/building-with-ai.mdx +261 -0
- package/docs/bulk-actions.mdx +49 -0
- package/docs/changelog.mdx +6 -0
- package/docs/cli-deploy-commands.mdx +9 -0
- package/docs/cli-dev-commands.mdx +9 -0
- package/docs/cli-dev.mdx +8 -0
- package/docs/cli-init-commands.mdx +58 -0
- package/docs/cli-introduction.mdx +25 -0
- package/docs/cli-list-profiles-commands.mdx +42 -0
- package/docs/cli-login-commands.mdx +33 -0
- package/docs/cli-logout-commands.mdx +33 -0
- package/docs/cli-preview-archive.mdx +59 -0
- package/docs/cli-promote-commands.mdx +9 -0
- package/docs/cli-switch.mdx +43 -0
- package/docs/cli-update-commands.mdx +42 -0
- package/docs/cli-whoami-commands.mdx +33 -0
- package/docs/community.mdx +6 -0
- package/docs/config/config-file.mdx +602 -0
- package/docs/config/extensions/additionalFiles.mdx +38 -0
- package/docs/config/extensions/additionalPackages.mdx +40 -0
- package/docs/config/extensions/aptGet.mdx +34 -0
- package/docs/config/extensions/audioWaveform.mdx +20 -0
- package/docs/config/extensions/custom.mdx +380 -0
- package/docs/config/extensions/emitDecoratorMetadata.mdx +29 -0
- package/docs/config/extensions/esbuildPlugin.mdx +31 -0
- package/docs/config/extensions/ffmpeg.mdx +45 -0
- package/docs/config/extensions/lightpanda.mdx +56 -0
- package/docs/config/extensions/overview.mdx +67 -0
- package/docs/config/extensions/playwright.mdx +195 -0
- package/docs/config/extensions/prismaExtension.mdx +1014 -0
- package/docs/config/extensions/puppeteer.mdx +30 -0
- package/docs/config/extensions/pythonExtension.mdx +182 -0
- package/docs/config/extensions/syncEnvVars.mdx +291 -0
- package/docs/context.mdx +235 -0
- package/docs/database-connections.mdx +213 -0
- package/docs/deploy-environment-variables.mdx +435 -0
- package/docs/deployment/atomic-deployment.mdx +172 -0
- package/docs/deployment/overview.mdx +257 -0
- package/docs/deployment/preview-branches.mdx +224 -0
- package/docs/errors-retrying.mdx +379 -0
- package/docs/github-actions.mdx +222 -0
- package/docs/github-integration.mdx +136 -0
- package/docs/github-repo.mdx +8 -0
- package/docs/help-email.mdx +6 -0
- package/docs/help-slack.mdx +11 -0
- package/docs/hidden-tasks.mdx +56 -0
- package/docs/how-it-works.mdx +454 -0
- package/docs/how-to-reduce-your-spend.mdx +217 -0
- package/docs/idempotency.mdx +504 -0
- package/docs/introduction.mdx +223 -0
- package/docs/limits.mdx +241 -0
- package/docs/logging.mdx +195 -0
- package/docs/machines.mdx +952 -0
- package/docs/manual-setup.mdx +632 -0
- package/docs/mcp-agent-rules.mdx +41 -0
- package/docs/mcp-introduction.mdx +385 -0
- package/docs/mcp-tools.mdx +273 -0
- package/docs/migrating-from-v3.mdx +334 -0
- package/docs/observability/dashboards.mdx +102 -0
- package/docs/observability/query.mdx +585 -0
- package/docs/open-source-contributing.mdx +16 -0
- package/docs/open-source-self-hosting.mdx +541 -0
- package/docs/private-networking/aws-console-setup.mdx +304 -0
- package/docs/private-networking/overview.mdx +144 -0
- package/docs/private-networking/troubleshooting.mdx +78 -0
- package/docs/queue-concurrency.mdx +354 -0
- package/docs/quick-start.mdx +97 -0
- package/docs/realtime/auth.mdx +208 -0
- package/docs/realtime/backend/overview.mdx +45 -0
- package/docs/realtime/backend/streams.mdx +418 -0
- package/docs/realtime/backend/subscribe.mdx +225 -0
- package/docs/realtime/how-it-works.mdx +94 -0
- package/docs/realtime/overview.mdx +63 -0
- package/docs/realtime/react-hooks/overview.mdx +73 -0
- package/docs/realtime/react-hooks/streams.mdx +449 -0
- package/docs/realtime/react-hooks/subscribe.mdx +674 -0
- package/docs/realtime/react-hooks/swr.mdx +87 -0
- package/docs/realtime/react-hooks/triggering.mdx +194 -0
- package/docs/realtime/react-hooks/use-wait-token.mdx +34 -0
- package/docs/realtime/run-object.mdx +174 -0
- package/docs/replaying.mdx +72 -0
- package/docs/request-feature.mdx +6 -0
- package/docs/roadmap.mdx +6 -0
- package/docs/run-tests.mdx +20 -0
- package/docs/run-usage.mdx +113 -0
- package/docs/runs/heartbeats.mdx +38 -0
- package/docs/runs/max-duration.mdx +139 -0
- package/docs/runs/metadata.mdx +734 -0
- package/docs/runs/priority.mdx +31 -0
- package/docs/runs.mdx +396 -0
- package/docs/self-hosting/docker.mdx +458 -0
- package/docs/self-hosting/env/supervisor.mdx +74 -0
- package/docs/self-hosting/env/webapp.mdx +276 -0
- package/docs/self-hosting/kubernetes.mdx +601 -0
- package/docs/self-hosting/overview.mdx +108 -0
- package/docs/skills.mdx +85 -0
- package/docs/tags.mdx +120 -0
- package/docs/tasks/overview.mdx +697 -0
- package/docs/tasks/scheduled.mdx +382 -0
- package/docs/tasks/schemaTask.mdx +413 -0
- package/docs/tasks/streams.mdx +884 -0
- package/docs/triggering.mdx +1320 -0
- package/docs/troubleshooting-alerts.mdx +385 -0
- package/docs/troubleshooting-debugging-in-vscode.mdx +8 -0
- package/docs/troubleshooting-github-issues.mdx +6 -0
- package/docs/troubleshooting-uptime-status.mdx +6 -0
- package/docs/troubleshooting.mdx +398 -0
- package/docs/upgrading-packages.mdx +80 -0
- package/docs/vercel-integration.mdx +207 -0
- package/docs/versioning.mdx +56 -0
- package/docs/video-walkthrough.mdx +23 -0
- package/docs/wait-for-token.mdx +540 -0
- package/docs/wait-for.mdx +42 -0
- package/docs/wait-until.mdx +53 -0
- package/docs/wait.mdx +18 -0
- package/docs/writing-tasks-introduction.mdx +33 -0
- package/package.json +8 -5
- package/skills/trigger-authoring-chat-agent/SKILL.md +296 -0
- package/skills/trigger-authoring-tasks/SKILL.md +254 -0
- package/skills/trigger-chat-agent-advanced/SKILL.md +368 -0
- package/skills/trigger-cost-savings/SKILL.md +116 -0
- package/skills/trigger-realtime-and-frontend/SKILL.md +276 -0
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Compaction"
|
|
3
|
+
sidebarTitle: "Compaction"
|
|
4
|
+
description: "Automatic context compaction to keep long conversations within token limits."
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
import RcBanner from "/snippets/ai-chat-rc-banner.mdx";
|
|
8
|
+
|
|
9
|
+
<RcBanner />
|
|
10
|
+
|
|
11
|
+
## Overview
|
|
12
|
+
|
|
13
|
+
Long conversations accumulate tokens across turns. Eventually the context window fills up, causing errors or degraded responses. Compaction solves this by automatically summarizing the conversation when token usage exceeds a threshold, then using that summary as the context for future turns.
|
|
14
|
+
|
|
15
|
+
The `compaction` option on `chat.agent()` handles this in both paths:
|
|
16
|
+
|
|
17
|
+
- **Between tool-call steps** (inner loop) — via the AI SDK's `prepareStep`, compaction runs between tool calls within a single turn
|
|
18
|
+
- **Between turns** (outer loop) — for single-step responses with no tool calls, where `prepareStep` never fires
|
|
19
|
+
|
|
20
|
+
## Basic usage
|
|
21
|
+
|
|
22
|
+
Provide `shouldCompact` to decide when to compact and `summarize` to generate the summary:
|
|
23
|
+
|
|
24
|
+
```ts
|
|
25
|
+
import { chat } from "@trigger.dev/sdk/ai";
|
|
26
|
+
import { streamText, generateText, stepCountIs } from "ai";
|
|
27
|
+
import { anthropic } from "@ai-sdk/anthropic";
|
|
28
|
+
|
|
29
|
+
export const myChat = chat.agent({
|
|
30
|
+
id: "my-chat",
|
|
31
|
+
compaction: {
|
|
32
|
+
shouldCompact: ({ totalTokens }) => (totalTokens ?? 0) > 80_000,
|
|
33
|
+
summarize: async ({ messages }) => {
|
|
34
|
+
const result = await generateText({
|
|
35
|
+
model: anthropic("claude-haiku-4-5"),
|
|
36
|
+
messages: [...messages, { role: "user", content: "Summarize this conversation concisely." }],
|
|
37
|
+
});
|
|
38
|
+
return result.text;
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
run: async ({ messages, signal }) => {
|
|
42
|
+
return streamText({
|
|
43
|
+
...chat.toStreamTextOptions({ registry }),
|
|
44
|
+
messages,
|
|
45
|
+
abortSignal: signal,
|
|
46
|
+
stopWhen: stepCountIs(15),
|
|
47
|
+
});
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
<Note>
|
|
53
|
+
The `prepareStep` for inner-loop compaction is automatically injected when you spread `chat.toStreamTextOptions()` into your `streamText` call. If you provide your own `prepareStep` after the spread, it overrides the auto-injected one.
|
|
54
|
+
</Note>
|
|
55
|
+
|
|
56
|
+
## How it works
|
|
57
|
+
|
|
58
|
+
After each turn completes:
|
|
59
|
+
|
|
60
|
+
1. `shouldCompact` is called with the current token usage
|
|
61
|
+
2. If it returns `true`, `summarize` generates a summary from the model messages
|
|
62
|
+
3. The **model messages** (sent to the LLM) are replaced with the summary
|
|
63
|
+
4. The **UI messages** (persisted and displayed) are preserved by default
|
|
64
|
+
5. The `onCompacted` hook fires if configured
|
|
65
|
+
|
|
66
|
+
On the next turn, the LLM receives the compact summary instead of the full history — dramatically reducing token usage while preserving context.
|
|
67
|
+
|
|
68
|
+
## Customizing what gets persisted
|
|
69
|
+
|
|
70
|
+
By default, compaction only affects model messages — UI messages stay intact so users see the full conversation after a page refresh. You can customize this with `compactUIMessages`:
|
|
71
|
+
|
|
72
|
+
### Summary + recent messages
|
|
73
|
+
|
|
74
|
+
Replace older messages with a summary but keep the last few exchanges visible:
|
|
75
|
+
|
|
76
|
+
```ts
|
|
77
|
+
import { generateId } from "ai";
|
|
78
|
+
|
|
79
|
+
export const myChat = chat.agent({
|
|
80
|
+
id: "my-chat",
|
|
81
|
+
compaction: {
|
|
82
|
+
shouldCompact: ({ totalTokens }) => (totalTokens ?? 0) > 80_000,
|
|
83
|
+
summarize: async ({ messages }) => {
|
|
84
|
+
return generateText({
|
|
85
|
+
model: anthropic("claude-haiku-4-5"),
|
|
86
|
+
messages: [...messages, { role: "user", content: "Summarize." }],
|
|
87
|
+
}).then((r) => r.text);
|
|
88
|
+
},
|
|
89
|
+
compactUIMessages: ({ uiMessages, summary }) => [
|
|
90
|
+
{
|
|
91
|
+
id: generateId(),
|
|
92
|
+
role: "assistant",
|
|
93
|
+
parts: [{ type: "text", text: `[Conversation summary]\n\n${summary}` }],
|
|
94
|
+
},
|
|
95
|
+
...uiMessages.slice(-4), // Keep the last 4 messages
|
|
96
|
+
],
|
|
97
|
+
},
|
|
98
|
+
run: async ({ messages, signal }) => {
|
|
99
|
+
return streamText({ model: anthropic("claude-sonnet-4-5"), messages, abortSignal: signal });
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Flatten to summary only
|
|
105
|
+
|
|
106
|
+
Replace all messages with just the summary (like the LLM sees):
|
|
107
|
+
|
|
108
|
+
```ts
|
|
109
|
+
compactUIMessages: ({ summary }) => [
|
|
110
|
+
{
|
|
111
|
+
id: generateId(),
|
|
112
|
+
role: "assistant",
|
|
113
|
+
parts: [{ type: "text", text: `[Conversation summary]\n\n${summary}` }],
|
|
114
|
+
},
|
|
115
|
+
],
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Customizing model messages
|
|
119
|
+
|
|
120
|
+
By default, model messages are replaced with a single summary message. Use `compactModelMessages` to customize what the LLM sees after compaction:
|
|
121
|
+
|
|
122
|
+
### Summary + recent context
|
|
123
|
+
|
|
124
|
+
Keep the last few model messages so the LLM has recent detail alongside the summary:
|
|
125
|
+
|
|
126
|
+
```ts
|
|
127
|
+
compactModelMessages: ({ modelMessages, summary }) => [
|
|
128
|
+
{ role: "user", content: summary },
|
|
129
|
+
...modelMessages.slice(-2), // Keep last exchange for detail
|
|
130
|
+
],
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Keep tool results
|
|
134
|
+
|
|
135
|
+
Preserve tool-call results so the LLM remembers what tools returned:
|
|
136
|
+
|
|
137
|
+
```ts
|
|
138
|
+
compactModelMessages: ({ modelMessages, summary }) => [
|
|
139
|
+
{ role: "user", content: summary },
|
|
140
|
+
...modelMessages.filter((m) => m.role === "tool"),
|
|
141
|
+
],
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## shouldCompact event
|
|
145
|
+
|
|
146
|
+
The `shouldCompact` callback receives context about the current state:
|
|
147
|
+
|
|
148
|
+
| Field | Type | Description |
|
|
149
|
+
|-------|------|-------------|
|
|
150
|
+
| `messages` | `ModelMessage[]` | Current model messages |
|
|
151
|
+
| `totalTokens` | `number \| undefined` | Total tokens from the triggering step/turn |
|
|
152
|
+
| `inputTokens` | `number \| undefined` | Input tokens |
|
|
153
|
+
| `outputTokens` | `number \| undefined` | Output tokens |
|
|
154
|
+
| `usage` | `LanguageModelUsage` | Full usage object |
|
|
155
|
+
| `totalUsage` | `LanguageModelUsage` | Cumulative usage across all turns |
|
|
156
|
+
| `chatId` | `string` | Chat session ID |
|
|
157
|
+
| `turn` | `number` | Current turn (0-indexed) |
|
|
158
|
+
| `clientData` | `unknown` | Custom data from the frontend |
|
|
159
|
+
| `source` | `"inner" \| "outer"` | Whether this is between steps or between turns |
|
|
160
|
+
| `steps` | `CompactionStep[]` | Steps array (inner loop only) |
|
|
161
|
+
| `stepNumber` | `number` | Step index (inner loop only) |
|
|
162
|
+
|
|
163
|
+
## summarize event
|
|
164
|
+
|
|
165
|
+
The `summarize` callback receives similar context:
|
|
166
|
+
|
|
167
|
+
| Field | Type | Description |
|
|
168
|
+
|-------|------|-------------|
|
|
169
|
+
| `messages` | `ModelMessage[]` | Messages to summarize |
|
|
170
|
+
| `usage` | `LanguageModelUsage` | Usage from the triggering step/turn |
|
|
171
|
+
| `totalUsage` | `LanguageModelUsage` | Cumulative usage |
|
|
172
|
+
| `chatId` | `string` | Chat session ID |
|
|
173
|
+
| `turn` | `number` | Current turn |
|
|
174
|
+
| `clientData` | `unknown` | Custom data from the frontend |
|
|
175
|
+
| `source` | `"inner" \| "outer"` | Where compaction is running |
|
|
176
|
+
| `stepNumber` | `number` | Step index (inner loop only) |
|
|
177
|
+
|
|
178
|
+
## onCompacted hook
|
|
179
|
+
|
|
180
|
+
Track compaction events for logging, billing, or analytics:
|
|
181
|
+
|
|
182
|
+
```ts
|
|
183
|
+
export const myChat = chat.agent({
|
|
184
|
+
id: "my-chat",
|
|
185
|
+
compaction: { ... },
|
|
186
|
+
onCompacted: async ({ summary, totalTokens, messageCount, chatId, turn }) => {
|
|
187
|
+
logger.info("Compacted", { chatId, turn, totalTokens, messageCount });
|
|
188
|
+
await db.compactionLog.create({
|
|
189
|
+
data: { chatId, summary, totalTokens, messageCount },
|
|
190
|
+
});
|
|
191
|
+
},
|
|
192
|
+
run: async ({ messages, signal }) => {
|
|
193
|
+
return streamText({ model: anthropic("claude-sonnet-4-5"), messages, abortSignal: signal });
|
|
194
|
+
},
|
|
195
|
+
});
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
## User-initiated compaction
|
|
199
|
+
|
|
200
|
+
Sometimes you want the user to decide when to compact — a "Summarize conversation" button, a `/compact` slash command, or a settings toggle. Wire this up with [actions](/ai-chat/actions): the frontend sends a typed action, `onAction` runs the summary, and `chat.history.set()` replaces the conversation.
|
|
201
|
+
|
|
202
|
+
### Backend
|
|
203
|
+
|
|
204
|
+
Define a `compact` action that reuses your existing `summarize` function:
|
|
205
|
+
|
|
206
|
+
```ts
|
|
207
|
+
import { chat } from "@trigger.dev/sdk/ai";
|
|
208
|
+
import { streamText, generateText, generateId, convertToModelMessages } from "ai";
|
|
209
|
+
import { anthropic } from "@ai-sdk/anthropic";
|
|
210
|
+
import { z } from "zod";
|
|
211
|
+
|
|
212
|
+
// Reusable summarize fn — also used by the automatic compaction config.
|
|
213
|
+
async function summarize(messages: ModelMessage[]) {
|
|
214
|
+
const result = await generateText({
|
|
215
|
+
model: anthropic("claude-haiku-4-5"),
|
|
216
|
+
messages: [...messages, { role: "user", content: "Summarize this conversation concisely." }],
|
|
217
|
+
});
|
|
218
|
+
return result.text;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export const myChat = chat.agent({
|
|
222
|
+
id: "my-chat",
|
|
223
|
+
|
|
224
|
+
// Automatic compaction still runs on threshold.
|
|
225
|
+
compaction: {
|
|
226
|
+
shouldCompact: ({ totalTokens }) => (totalTokens ?? 0) > 80_000,
|
|
227
|
+
summarize: async ({ messages }) => summarize(messages),
|
|
228
|
+
},
|
|
229
|
+
|
|
230
|
+
// User-initiated: the frontend sends { type: "compact" }.
|
|
231
|
+
actionSchema: z.discriminatedUnion("type", [
|
|
232
|
+
z.object({ type: z.literal("compact") }),
|
|
233
|
+
]),
|
|
234
|
+
|
|
235
|
+
onAction: async ({ action, uiMessages }) => {
|
|
236
|
+
if (action.type !== "compact") return;
|
|
237
|
+
|
|
238
|
+
const summary = await summarize(convertToModelMessages(uiMessages));
|
|
239
|
+
|
|
240
|
+
// Replace the full history with a single summary message.
|
|
241
|
+
chat.history.set([
|
|
242
|
+
{
|
|
243
|
+
id: generateId(),
|
|
244
|
+
role: "assistant",
|
|
245
|
+
parts: [{ type: "text", text: `[Conversation summary]\n\n${summary}` }],
|
|
246
|
+
},
|
|
247
|
+
]);
|
|
248
|
+
},
|
|
249
|
+
|
|
250
|
+
run: async ({ messages, signal }) => {
|
|
251
|
+
return streamText({ model: anthropic("claude-sonnet-4-5"), messages, abortSignal: signal });
|
|
252
|
+
},
|
|
253
|
+
});
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
Actions fire `onAction` only (plus `hydrateMessages` if set) — `run()` and `onTurnComplete` do not fire for actions. Persist the compacted state directly inside `onAction` after the `chat.history.set` call. See [Actions](/ai-chat/actions) for the full lifecycle.
|
|
257
|
+
|
|
258
|
+
### Frontend
|
|
259
|
+
|
|
260
|
+
Call `transport.sendAction()` from a button or slash command:
|
|
261
|
+
|
|
262
|
+
```tsx
|
|
263
|
+
import { useTriggerChatTransport } from "@trigger.dev/sdk/chat/react";
|
|
264
|
+
import { useChat } from "@ai-sdk/react";
|
|
265
|
+
|
|
266
|
+
function ChatView({ chatId }: { chatId: string }) {
|
|
267
|
+
const transport = useTriggerChatTransport({
|
|
268
|
+
task: "my-chat",
|
|
269
|
+
accessToken: ({ chatId }) => mintChatAccessToken(chatId),
|
|
270
|
+
startSession: ({ chatId, clientData }) =>
|
|
271
|
+
startChatSession({ chatId, clientData }),
|
|
272
|
+
});
|
|
273
|
+
const { messages } = useChat({ id: chatId, transport });
|
|
274
|
+
|
|
275
|
+
return (
|
|
276
|
+
<>
|
|
277
|
+
<button onClick={() => transport.sendAction(chatId, { type: "compact" })}>
|
|
278
|
+
Summarize conversation
|
|
279
|
+
</button>
|
|
280
|
+
{messages.map(/* ... */)}
|
|
281
|
+
</>
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
The call returns as soon as the backend accepts the action. Because `onTurnComplete` replaces the `uiMessages` with the summary, `useChat` receives the new state via the normal turn-complete flow — the UI updates automatically.
|
|
287
|
+
|
|
288
|
+
### Indicating compaction in the UI
|
|
289
|
+
|
|
290
|
+
For "Compacting..." feedback while the summary generates, append a transient data part from `onAction` via `chat.stream.append()`:
|
|
291
|
+
|
|
292
|
+
```ts
|
|
293
|
+
onAction: async ({ action, uiMessages }) => {
|
|
294
|
+
if (action.type !== "compact") return;
|
|
295
|
+
|
|
296
|
+
chat.stream.append({ type: "data-compaction", data: { status: "compacting" } });
|
|
297
|
+
const summary = await summarize(convertToModelMessages(uiMessages));
|
|
298
|
+
chat.stream.append({ type: "data-compaction", data: { status: "complete" } });
|
|
299
|
+
|
|
300
|
+
chat.history.set([ /* ... */ ]);
|
|
301
|
+
},
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
See [Raw streaming with `chat.stream`](/ai-chat/backend#raw-streaming-with-chat-stream) for the full API.
|
|
305
|
+
|
|
306
|
+
## Using with chat.createSession()
|
|
307
|
+
|
|
308
|
+
Pass the same `compaction` config to `chat.createSession()`. The session handles outer-loop compaction automatically inside `turn.complete()`:
|
|
309
|
+
|
|
310
|
+
```ts
|
|
311
|
+
const session = chat.createSession(payload, {
|
|
312
|
+
signal,
|
|
313
|
+
idleTimeoutInSeconds: 60,
|
|
314
|
+
timeout: "1h",
|
|
315
|
+
compaction: {
|
|
316
|
+
shouldCompact: ({ totalTokens }) => (totalTokens ?? 0) > 80_000,
|
|
317
|
+
summarize: async ({ messages }) =>
|
|
318
|
+
generateText({ model: anthropic("claude-haiku-4-5"), messages }).then((r) => r.text),
|
|
319
|
+
compactUIMessages: ({ uiMessages, summary }) => [
|
|
320
|
+
{ id: generateId(), role: "assistant",
|
|
321
|
+
parts: [{ type: "text", text: `[Summary]\n\n${summary}` }] },
|
|
322
|
+
...uiMessages.slice(-4),
|
|
323
|
+
],
|
|
324
|
+
},
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
for await (const turn of session) {
|
|
328
|
+
const result = streamText({
|
|
329
|
+
model: anthropic("claude-sonnet-4-5"),
|
|
330
|
+
messages: turn.messages,
|
|
331
|
+
abortSignal: turn.signal,
|
|
332
|
+
stopWhen: stepCountIs(15),
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
await turn.complete(result);
|
|
336
|
+
// Outer-loop compaction runs automatically after complete()
|
|
337
|
+
|
|
338
|
+
await db.chat.update({
|
|
339
|
+
where: { id: turn.chatId },
|
|
340
|
+
data: { messages: turn.uiMessages },
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
## Using with raw tasks (MessageAccumulator)
|
|
346
|
+
|
|
347
|
+
Pass `compaction` to the `MessageAccumulator` constructor. Use `prepareStep()` for inner-loop compaction and `compactIfNeeded()` for the outer loop:
|
|
348
|
+
|
|
349
|
+
```ts
|
|
350
|
+
const conversation = new chat.MessageAccumulator({
|
|
351
|
+
compaction: {
|
|
352
|
+
shouldCompact: ({ totalTokens }) => (totalTokens ?? 0) > 80_000,
|
|
353
|
+
summarize: async ({ messages }) =>
|
|
354
|
+
generateText({ model: anthropic("claude-haiku-4-5"), messages }).then((r) => r.text),
|
|
355
|
+
compactUIMessages: ({ summary }) => [
|
|
356
|
+
{ id: generateId(), role: "assistant",
|
|
357
|
+
parts: [{ type: "text", text: `[Summary]\n\n${summary}` }] },
|
|
358
|
+
],
|
|
359
|
+
},
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
for (let turn = 0; turn < 100; turn++) {
|
|
363
|
+
const messages = await conversation.addIncoming(payload.messages, payload.trigger, turn);
|
|
364
|
+
|
|
365
|
+
const result = streamText({
|
|
366
|
+
model: anthropic("claude-sonnet-4-5"),
|
|
367
|
+
messages,
|
|
368
|
+
prepareStep: conversation.prepareStep(), // Inner-loop compaction
|
|
369
|
+
stopWhen: stepCountIs(15),
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
const response = await chat.pipeAndCapture(result);
|
|
373
|
+
if (response) await conversation.addResponse(response);
|
|
374
|
+
|
|
375
|
+
// Outer-loop compaction
|
|
376
|
+
const usage = await result.totalUsage;
|
|
377
|
+
await conversation.compactIfNeeded(usage, { chatId: payload.chatId, turn });
|
|
378
|
+
|
|
379
|
+
await db.chat.update({ data: { messages: conversation.uiMessages } });
|
|
380
|
+
await chat.writeTurnComplete();
|
|
381
|
+
}
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
## Fully manual compaction
|
|
385
|
+
|
|
386
|
+
For maximum control, use `chat.compact()` directly inside a custom `prepareStep`:
|
|
387
|
+
|
|
388
|
+
```ts
|
|
389
|
+
prepareStep: async ({ messages: stepMessages, steps }) => {
|
|
390
|
+
const result = await chat.compact(stepMessages, steps, {
|
|
391
|
+
threshold: 80_000,
|
|
392
|
+
summarize: async (msgs) =>
|
|
393
|
+
generateText({ model: anthropic("claude-haiku-4-5"), messages: msgs }).then((r) => r.text),
|
|
394
|
+
});
|
|
395
|
+
return result.type === "skipped" ? undefined : result;
|
|
396
|
+
},
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
Or use the `chat.compactionStep()` factory:
|
|
400
|
+
|
|
401
|
+
```ts
|
|
402
|
+
prepareStep: chat.compactionStep({
|
|
403
|
+
threshold: 80_000,
|
|
404
|
+
summarize: async (msgs) =>
|
|
405
|
+
generateText({ model: anthropic("claude-haiku-4-5"), messages: msgs }).then((r) => r.text),
|
|
406
|
+
}),
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
<Note>
|
|
410
|
+
The fully manual APIs only handle inner-loop compaction (between tool-call steps). For outer-loop coverage, use the `compaction` option on `chat.agent()`, `chat.createSession()`, or `MessageAccumulator`.
|
|
411
|
+
</Note>
|