chat 4.28.1 → 4.30.0

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.
Files changed (48) hide show
  1. package/dist/ai/index.d.ts +501 -0
  2. package/dist/ai/index.js +500 -0
  3. package/dist/chat-BPjXsoIl.d.ts +3158 -0
  4. package/dist/chunk-HD375J7S.js +128 -0
  5. package/dist/index.d.ts +6 -3143
  6. package/dist/index.js +45 -139
  7. package/dist/{jsx-runtime-DxGwoLu2.d.ts → jsx-runtime-CnDs8rPr.d.ts} +1 -1
  8. package/dist/jsx-runtime.d.ts +1 -1
  9. package/docs/adapters.mdx +35 -5
  10. package/docs/ai/ai-sdk-tools.mdx +227 -0
  11. package/docs/ai/index.mdx +69 -0
  12. package/docs/ai/meta.json +4 -0
  13. package/docs/{api → ai}/to-ai-messages.mdx +16 -3
  14. package/docs/ai/types.mdx +243 -0
  15. package/docs/api/chat.mdx +50 -10
  16. package/docs/api/index.mdx +4 -6
  17. package/docs/api/message.mdx +1 -1
  18. package/docs/api/meta.json +0 -1
  19. package/docs/api/postable-message.mdx +3 -3
  20. package/docs/api/thread.mdx +1 -1
  21. package/docs/concurrency.mdx +54 -15
  22. package/docs/contributing/building.mdx +1 -1
  23. package/docs/contributing/testing.mdx +4 -0
  24. package/docs/direct-messages.mdx +10 -1
  25. package/docs/files.mdx +20 -0
  26. package/docs/getting-started.mdx +5 -1
  27. package/docs/handling-events.mdx +10 -7
  28. package/docs/index.mdx +4 -1
  29. package/docs/meta.json +4 -0
  30. package/docs/posting-messages.mdx +3 -1
  31. package/docs/slack-primitives.mdx +320 -0
  32. package/docs/slash-commands.mdx +4 -4
  33. package/docs/streaming.mdx +4 -4
  34. package/docs/subject.mdx +1 -1
  35. package/docs/testing.mdx +142 -0
  36. package/docs/threads-messages-channels.mdx +1 -1
  37. package/docs/usage.mdx +8 -4
  38. package/package.json +23 -2
  39. package/resources/guides/create-a-discord-support-bot-with-nuxt-and-redis.md +5 -1
  40. package/resources/guides/how-to-build-a-slack-bot-with-next-js-and-redis.md +5 -1
  41. package/resources/guides/how-to-build-an-ai-agent-for-slack-with-chat-sdk-and-ai-sdk.md +1 -1
  42. package/resources/guides/human-in-the-loop-with-chat-sdk-and-workflow-sdk.md +176 -0
  43. package/resources/guides/liveblocks-chat-sdk-ai-sdk.md +165 -0
  44. package/resources/guides/run-and-track-deploys-from-slack.md +7 -5
  45. package/resources/guides/ship-a-github-code-review-bot-with-hono-and-redis.md +5 -1
  46. package/resources/guides/slack-bot-vercel-blob.md +254 -0
  47. package/resources/guides/triage-form-submissions-with-chat-sdk.md +3 -1
  48. package/resources/templates.json +5 -0
@@ -0,0 +1,254 @@
1
+ # How to build a Slack bot that manages files in Vercel Blob
2
+
3
+ **Author:** Ben Sabic
4
+
5
+ ---
6
+
7
+ You can build a Slack bot that browses, reads, uploads, and deletes files in Vercel Blob by combining Chat SDK, AI SDK's `ToolLoopAgent`, and Files SDK's pre-built tool factory. Chat SDK handles the chat piece, AI SDK runs the agent loop, and Files SDK exposes your Blob store to the agent as a set of approval-gated tools. The result is a chat-first interface to your object storage, with read tools that run freely and write tools that prompt for approval by default.
8
+
9
+ This guide will walk you through building a Slack bot with Chat SDK, AI SDK's `ToolLoopAgent`, and Files SDK's `createFileTools` factory backed by [Vercel Blob](https://vercel.com/storage/blob). You'll wire up streaming responses, tool calling, and multi-turn conversation history, then configure per-tool approval gates and read-only mode to keep write operations safe in production.
10
+
11
+ ## Prerequisites
12
+
13
+ Before you begin, make sure you have:
14
+
15
+ * Node.js 18 or later
16
+
17
+ * [pnpm](https://pnpm.io/) (or npm/yarn)
18
+
19
+ * A Slack workspace where you can install apps
20
+
21
+ * A Redis instance (local or hosted, such as [Upstash](https://vercel.com/marketplace/upstash))
22
+
23
+ * A [Vercel account](https://vercel.com/signup) with AI Gateway and Vercel Blob
24
+
25
+
26
+ ## How it works
27
+
28
+ Three SDKs cover three distinct layers:
29
+
30
+ * **Chat SDK** is the platform layer. It receives Slack webhooks, normalizes them into events like `onNewMention` and `onSubscribedMessage`, and streams responses back to Slack's native streaming API.
31
+
32
+ * **AI SDK** is the reasoning layer. `ToolLoopAgent` wraps a language model with tools and runs the loop where the model picks a tool, the SDK executes it, and the result feeds back into the next step until the model finishes.
33
+
34
+ * **Files SDK** is the storage layer. It presents a single `Files` interface over Vercel Blob, S3, R2, and other providers, and ships a `createFileTools` factory that turns that interface into ready-to-use AI SDK tools.
35
+
36
+
37
+ `createFileTools` returns eight tools, split between four read tools (`listFiles`, `getFileMetadata`, `downloadFile`, `getFileUrl`) and four write tools (`uploadFile`, `deleteFile`, `copyFile`, `signUploadUrl`). Write tools require approval by default, while read tools run freely, and you decide how to surface the approval gate to your users.
38
+
39
+ Chat SDK accepts any `AsyncIterable<string>` as a message, so the agent's `fullStream` flows straight into `thread.post()` for real-time streaming in Slack.
40
+
41
+ Learn more about Chat SDK in [The Complete Guide to Chat SDK](https://vercel.com/kb/guide/the-complete-guide-to-chat-sdk).
42
+
43
+ ## Steps
44
+
45
+ ### 1\. Scaffold the project and install dependencies
46
+
47
+ Create a new Next.js app and install the Chat SDK, AI SDK, Files SDK, and other related packages:
48
+
49
+ `npx create-next-app@latest my-files-bot --typescript --app cd my-files-bot pnpm add chat @chat-adapter/slack @chat-adapter/state-redis ai zod files-sdk @vercel/blob`
50
+
51
+ The `chat` package is the Chat SDK core, and `@chat-adapter/slack` and `@chat-adapter/state-redis` are the [Slack platform adapter](https://chat-sdk.dev/adapters/official/slack) and [Redis state adapter](https://chat-sdk.dev/adapters/official/redis). The `ai` package is AI SDK, which includes the [AI Gateway provider](https://ai-sdk.dev/providers/ai-sdk-providers/ai-gateway) and `ToolLoopAgent`. `files-sdk` is the storage SDK, and `@vercel/blob` is the optional peer dependency required by the Vercel Blob adapter. `zod` is used for tool input schemas.
52
+
53
+ ### 2\. Create a Slack app
54
+
55
+ Go to [api.slack.com/apps](https://api.slack.com/apps), click **Create New App**, then **From a manifest**.
56
+
57
+ Select your workspace and paste this manifest:
58
+
59
+ `display_information: name: Files Bot description: A file management bot built with Chat SDK, AI SDK, and Files SDK features: bot_user: display_name: Files Bot always_online: true oauth_config: scopes: bot: - app_mentions:read - channels:history - channels:read - chat:write - files:read - groups:history - groups:read - im:history - im:read - mpim:history - mpim:read - reactions:read - reactions:write - users:read settings: event_subscriptions: request_url: https://your-domain.com/api/webhooks/slack bot_events: - app_mention - message.channels - message.groups - message.im - message.mpim interactivity: is_enabled: true request_url: https://your-domain.com/api/webhooks/slack org_deploy_enabled: false socket_mode_enabled: false token_rotation_enabled: false`
60
+
61
+ After creating the app:
62
+
63
+ 1. Go to **Install App** and install the app to your workspace
64
+
65
+ 2. Go to **OAuth & Permissions** > **OAuth Tokens** and copy the **Bot User OAuth Token**
66
+
67
+ 3. Go to **Basic Information** > **App Credentials** and copy the **Signing Secret**
68
+
69
+
70
+ You'll replace the `request_url` placeholders with your real domain after deploying (or a tunnel URL for local testing). The `files:read` scope is included so the bot can later read files users add to Slack threads.
71
+
72
+ ### 3\. Create a Vercel Blob store
73
+
74
+ In your Vercel dashboard, open **Storage**, click **Create Database**, and create a new **Blob** store. Connect it to the project you'll be deploying to. Vercel will add `BLOB_READ_WRITE_TOKEN` to your project’s environment variables for you, so you don’t need to manage the token yourself.
75
+
76
+ For local development, link your project and pull the token into `.env.local` with the [Vercel CLI](https://vercel.com/docs/cli):
77
+
78
+ `vercel link vercel env pull`
79
+
80
+ This writes `BLOB_READ_WRITE_TOKEN` into `.env.local`.
81
+
82
+ ### 4\. Configure environment variables
83
+
84
+ Add the remaining environment variables to `.env.local`:
85
+
86
+ `` SLACK_BOT_TOKEN=xoxb-your-bot-token SLACK_SIGNING_SECRET=your-signing-secret REDIS_URL=redis://localhost:6379 VERCEL_OIDC_TOKEN=your-OIDC-token # BLOB_READ_WRITE_TOKEN comes from `vercel env pull` ``
87
+
88
+ The Slack adapter reads `SLACK_BOT_TOKEN` and `SLACK_SIGNING_SECRET` automatically. The Redis state adapter reads `REDIS_URL`. AI SDK uses `VERCEL_OIDC_TOKEN` to authenticate with the Vercel AI Gateway with [OIDC authentication](https://vercel.com/docs/ai-gateway/authentication-and-byok/authentication#oidc-token). Files SDK will also read `BLOB_READ_WRITE_TOKEN` automatically when handling file operations.
89
+
90
+ ### 5\. Configure Files SDK with the Vercel Blob adapter
91
+
92
+ Create `lib/files.ts`:
93
+
94
+ ``import { Files } from "files-sdk"; import { vercelBlob } from "files-sdk/vercel-blob"; // BLOB_READ_WRITE_TOKEN is added automatically on Vercel and pulled into // .env.local via `vercel env pull` for local development. export const files = new Files({ adapter: vercelBlob(), });``
95
+
96
+ The `vercelBlob` adapter defaults to `access: "public"`, which matches the most common Blob usage and lets the agent return permanent CDN URLs from `getFileUrl`. For private buckets, pass `vercelBlob({ access: "private" })`, which routes uploads through Vercel's private mode and reads through the API instead of a public URL. With private access, `getFileUrl` throws because no permanent URL exists; use `downloadFile` instead.
97
+
98
+ ### 6\. Build the agent and bot
99
+
100
+ Create `lib/bot.ts`:
101
+
102
+ `import { Chat } from "chat"; import { toAiMessages } from "chat/ai"; import { createSlackAdapter } from "@chat-adapter/slack"; import { createRedisState } from "@chat-adapter/state-redis"; import { ToolLoopAgent } from "ai"; import { createFileTools } from "files-sdk/ai-sdk"; import { files } from "./files"; const agent = new ToolLoopAgent({ model: "anthropic/claude-sonnet-4.6", instructions: "You are a file management assistant in a Slack workspace. " + "Use the file tools to help users browse, read, upload, and delete " + "files in their object storage. When a write operation is rejected, " + "explain what you were about to do and ask the user to confirm.", tools: createFileTools({ files }), }); export const bot = new Chat({ userName: "files-bot", adapters: { slack: createSlackAdapter(), }, state: createRedisState(), }); // Handle first-time mentions bot.onNewMention(async (thread, message) => { await thread.subscribe(); const result = await agent.stream({ prompt: message.text }); await thread.post(result.fullStream); }); // Handle follow-up messages in subscribed threads bot.onSubscribedMessage(async (thread) => { const { messages } = await thread.adapter.fetchMessages(thread.id, { limit: 20, }); const history = await toAiMessages(messages); const result = await agent.stream({ prompt: history }); await thread.post(result.fullStream); });`
103
+
104
+ **A few things are happening here:**
105
+
106
+ * `createFileTools({ files })` returns all eight file tools. Read tools run immediately when the agent calls them; write tools (`uploadFile`, `deleteFile`, `copyFile`, `signUploadUrl`) are gated by AI SDK's tool approval flow.
107
+
108
+ * `toAiMessages` converts the most recent 20 Chat SDK messages into the AI SDK `ModelMessage[]` shape, preserving roles, attachments, and chronological order.
109
+
110
+ * `result.fullStream` is preferred over `textStream` because it preserves paragraph breaks between tool-calling steps, which Slack renders cleanly.
111
+
112
+ * `bot.onNewMention` fires the first time someone @mentions the bot in a channel. `thread.subscribe()` opts the thread into future `onSubscribedMessage` events so the bot keeps responding without further mentions.
113
+
114
+
115
+ ### 7\. Wire up the webhook route
116
+
117
+ Create `app/api/webhooks/[platform]/route.ts`:
118
+
119
+ ``import { after } from "next/server"; import { bot } from "@/lib/bot"; type Platform = keyof typeof bot.webhooks; export async function POST( request: Request, context: RouteContext<"/api/webhooks/[platform]">, ) { const { platform } = await context.params; const handler = bot.webhooks[platform as Platform]; if (!handler) { return new Response(`Unknown platform: ${platform}`, { status: 404 }); } return handler(request, { waitUntil: (task) => after(() => task), }); }``
120
+
121
+ This creates a `POST /api/webhooks/slack` endpoint. The `waitUntil` option ensures event handlers finish processing after the HTTP response is sent, which is required on Vercel where the function would otherwise terminate as soon as the response returns.
122
+
123
+ ### 8\. Test locally
124
+
125
+ 1. Start the dev server:
126
+
127
+ `pnpm dev`
128
+
129
+ 2. Expose it with a tunnel:
130
+
131
+ `npx ngrok http 3000`
132
+
133
+ 3. Copy the tunnel URL (for example, [`https://abc123.ngrok-free.dev`](https://abc123.ngrok-free.dev)) and update both **Event Subscriptions** and **Interactivity** Request URLs in your [Slack app settings](https://api.slack.com/apps) to [`https://abc123.ngrok-free.dev/api/webhooks/slack`](https://abc123.ngrok-free.dev/api/webhooks/slack).
134
+
135
+ 4. Invite the bot to a channel: `/invite @Files Bot`.
136
+
137
+ 5. @mention the bot and ask it to list files: "Show me what's in the bucket." The agent calls `listFiles` and streams the response back into the thread. To test a write operation end-to-end before building an approval flow, temporarily pass `requireApproval: false` to `createFileTools` in `lib/bot.ts` and ask the bot to "Upload a file called test.txt with the contents 'hello world'."
138
+
139
+
140
+ ### 9\. Deploy to Vercel
141
+
142
+ Link your project and add your environment variables:
143
+
144
+ `vercel env add SLACK_BOT_TOKEN vercel env add SLACK_SIGNING_SECRET vercel env add REDIS_URL`
145
+
146
+ Alternatively, add them in the Vercel dashboard under **Environment Variables**.
147
+
148
+ Reminder: `BLOB_READ_WRITE_TOKEN` is already managed by the Blob store connection from step three. You don't need to add it manually.
149
+
150
+ Then deploy to production:
151
+
152
+ `vercel --prod`
153
+
154
+ Update the **Event Subscriptions** and **Interactivity** Request URLs in your Slack app settings to your production URL, for example [`https://my-files-bot.vercel.app/api/webhooks/slack`](https://my-files-bot.vercel.app/api/webhooks/slack).
155
+
156
+ ## Configuring approval and read-only mode
157
+
158
+ The default `createFileTools({ files })` gates every write tool with approval and leaves reads open. That's a reasonable default, but you'll often want to tune it.
159
+
160
+ ### Granular approval
161
+
162
+ Pass an object to `requireApproval` to opt individual tools in or out:
163
+
164
+ `const tools = createFileTools({ files, requireApproval: { deleteFile: true, signUploadUrl: true, uploadFile: false, copyFile: false, }, });`
165
+
166
+ Unspecified entries default to `true`, so it's safe to opt in only the cases you trust. In the example above, the agent can upload and copy without prompting, but still needs approval for deletes and pre-signed upload URLs.
167
+
168
+ For a production-grade approval handler that pauses the workflow in Slack until a human clicks Approve or Deny, see [Human-in-the-Loop with Chat SDK and Workflow SDK](https://vercel.com/kb/guide/human-in-the-loop-with-chat-sdk-and-workflow-sdk). The same pattern wraps any write tool from `createFileTools`.
169
+
170
+ ### Read-only mode
171
+
172
+ For a bot that should only browse and summarize files, pass `readOnly: true`:
173
+
174
+ `const tools = createFileTools({ files, readOnly: true }); // Returns only: listFiles, getFileMetadata, downloadFile, getFileUrl`
175
+
176
+ Read-only mode drops every write tool from the toolset, so approval configuration becomes irrelevant. This is useful when the bot's job is to find a file and hand the user a download URL rather than mutate the bucket.
177
+
178
+ ### Tightening descriptions per tool
179
+
180
+ If you want to scope a tool's behavior to your domain (for example, "list files in the Acme team folder"), use `overrides` to patch the description without touching the underlying implementation:
181
+
182
+ `const tools = createFileTools({ files, overrides: { listFiles: { description: "List files in the current Slack workspace's bucket", }, deleteFile: { title: "Remove file" }, }, });`
183
+
184
+ `execute`, `inputSchema`, and `outputSchema` are intentionally not overridable. Override descriptions to improve tool selection, override titles for clearer approval UIs, and let the SDK keep ownership of the I/O contract.
185
+
186
+ ## Reading Slack uploads with `toAiMessages`
187
+
188
+ When users upload files to a Slack thread, `toAiMessages` automatically includes them in the AI SDK message stream. Images become `image` parts and supported text files (JSON, XML, YAML, plain text) become `file` parts, both with base64 data. Video and audio attachments are skipped, with a `console.warn` by default.
189
+
190
+ This means a user can drag a CSV into the thread and ask, "Upload this to reports/q4.csv," and the agent will see the file contents in its message history and can call `uploadFile` with that content. No extra wiring needed.
191
+
192
+ To customize how unsupported attachments are handled, pass `onUnsupportedAttachment`:
193
+
194
+ ``const history = await toAiMessages(messages, { onUnsupportedAttachment: (attachment, message) => { logger.warn( `Skipped ${attachment.type} in message ${message.id}`, ); }, });``
195
+
196
+ PDFs and other unrecognized MIME types are silently skipped. If you need to handle them, fetch the raw attachment via `attachment.fetchData()` in your handler and route it directly to `files.upload()` outside the agent loop.
197
+
198
+ ## Troubleshooting
199
+
200
+ ### The bot doesn't respond to mentions
201
+
202
+ Check that your Slack app has the `app_mentions:read` scope and that the **Event Subscriptions** Request URL is correct. Slack sends a challenge request when you first set the URL, so your server must be running and reachable.
203
+
204
+ ### Tool calls fail silently
205
+
206
+ If the agent calls a tool but no result appears, check your server logs for thrown errors. Common causes include a missing `BLOB_READ_WRITE_TOKEN`, an invalid file key, or a `vercelBlob({ access: "private" })` adapter trying to call `getFileUrl`. AI SDK surfaces tool execution errors back to the model, which may attempt to recover; add explicit error handling in your tools if you need to control how the model sees the failure.
207
+
208
+ ### Write operations are always rejected
209
+
210
+ By default, write tools require approval. Until you build an approval handler that resolves these requests, every write call will be denied. For development, pass `requireApproval: false` to disable the gate, or `requireApproval: { deleteFile: true }` to leave only the most destructive operations gated.
211
+
212
+ ### `getFileUrl` throws on private blobs
213
+
214
+ `vercelBlob({ access: "private" })` has no permanent public URL, so `getFileUrl` (which wraps `url()`) throws an error. Use `downloadFile` to fetch private blob contents through the API instead. If you need both public and private blobs in the same bot, construct two `Files` instances with different adapters and route the agent to the right one through separate tools.
215
+
216
+ ### Thread history grows too large
217
+
218
+ For long-running threads, the conversation history can exceed the model's context window. Limit the number of messages passed to the agent (the example above uses `limit: 20`) or summarize older messages in a separate step.
219
+
220
+ ## How to add Teams, Discord, or other platforms
221
+
222
+ Chat SDK supports multiple platforms from a single codebase. The event handlers and agent logic you've already defined work identically across all of them, since the SDK normalizes messages, threads, and reactions into a consistent format.
223
+
224
+ To add Microsoft Teams or another platform, register an additional adapter:
225
+
226
+ `import { createSlackAdapter } from "@chat-adapter/slack"; import { createTeamsAdapter } from "@chat-adapter/teams"; export const bot = new Chat({ adapters: { slack: createSlackAdapter(), teams: createTeamsAdapter(), }, state, userName: "files-bot", });`
227
+
228
+ The existing webhook route already uses a `:platform` parameter, so Teams webhooks would be handled at `/api/webhooks/teams` with no additional routing code.
229
+
230
+ Streaming behavior varies by platform. Slack uses its native streaming API for smooth real-time updates, while Teams, Discord, and Google Chat fall back to a post-then-edit pattern that throttles updates to avoid rate limits. You can adjust the update interval with the `streamingUpdateIntervalMs` option when creating your `Chat` instance.
231
+
232
+ See the [Chat SDK adapter directory](https://chat-sdk.dev/adapters) for the full list of supported platforms.
233
+
234
+ ### Other Chat SDK Guides
235
+
236
+ ## Related resources
237
+
238
+ * [Chat SDK AI utilities overview](https://chat-sdk.dev/docs/ai)
239
+
240
+ * [Files SDK AI SDK integration](https://files-sdk.dev/ai)
241
+
242
+ * [Files SDK Vercel Blob adapter](https://files-sdk.dev/adapters/vercel-blob)
243
+
244
+ * [How to build an AI agent for Slack with Chat SDK and AI SDK](https://vercel.com/kb/guide/how-to-build-an-ai-agent-for-slack-with-chat-sdk-and-ai-sdk)
245
+
246
+ * [AI SDK agents documentation](https://ai-sdk.dev/docs/agents/building-agents)
247
+
248
+ * [Vercel Blob documentation](https://vercel.com/docs/storage/vercel-blob)
249
+
250
+ * [AI Gateway documentation](https://vercel.com/docs/ai-gateway)
251
+
252
+ ---
253
+
254
+ [View full KB sitemap](/kb/sitemap.md)
@@ -18,7 +18,7 @@ If you're working with an AI coding agent like Claude Code or Cursor, you can cl
18
18
 
19
19
  ### Vercel Plugin
20
20
 
21
- Turn your agent into a Vercel expert with this [plugin](https://vercel.com/docs/agent-resources/vercel-plugin); the [Chat SDK skill](https://skills.sh/vercel/chat/chat-sdk) is included.
21
+ Turn your agent into a Vercel expert with this [plugin](https://vercel.com/docs/agent-resources/vercel-plugin); the [Chat SDK skill](https://skills.sh/vercel/chat/chat-sdk) is included. This plugin is optional; it’s not required to use Chat SDK or for this guide.
22
22
 
23
23
  `npx plugins add vercel/vercel-plugin`
24
24
 
@@ -169,6 +169,8 @@ See the [Chat SDK adapter directory](https://chat-sdk.dev/adapters) for the full
169
169
 
170
170
  * [Chat SDK GitHub](https://github.com/vercel/chat)
171
171
 
172
+ * [The Complete Guide to Chat SDK](https://vercel.com/kb/guide/the-complete-guide-to-chat-sdk)
173
+
172
174
  * [Resend documentation](https://resend.com/docs/api-reference/emails/send-email)
173
175
 
174
176
  * [Hono documentation](https://hono.dev/docs/getting-started/vercel)
@@ -5,6 +5,11 @@
5
5
  "description": "Build a bot that you can engage with inside Liveblocks.",
6
6
  "href": "https://vercel.com/templates/next.js/chat-sdk-liveblocks-bot"
7
7
  },
8
+ {
9
+ "title": "Durable iMessage Agent",
10
+ "description": "Durable iMessage agent powered by the Sendblue adapter.",
11
+ "href": "https://vercel.com/templates/nitro/durable-imessage-ai-agent"
12
+ },
8
13
  {
9
14
  "title": "Knowledge Agent",
10
15
  "description": "Open source file-system and knowledge based agent template. Build AI agents that stay up to date with your knowledge base.",