chat 4.31.0 → 4.32.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.
package/README.md CHANGED
@@ -2,8 +2,8 @@
2
2
 
3
3
  > npm package: [`chat`](https://www.npmjs.com/package/chat)
4
4
 
5
- [![npm version](https://img.shields.io/npm/v/chat)](https://www.npmjs.com/package/chat)
6
- [![npm downloads](https://img.shields.io/npm/dm/chat)](https://www.npmjs.com/package/chat)
5
+ [![Agent Stack](https://img.shields.io/badge/Agent%20Stack-000?style=flat-square&logo=vercel&logoColor=FFF&labelColor=000&color=000)](https://vercel.com/kb/agent-stack)
6
+ [![MIT License](https://img.shields.io/badge/License-MIT-000?style=flat-square&logo=opensourceinitiative&logoColor=white&labelColor=000&color=000)](../../LICENSE)
7
7
 
8
8
  Universal TypeScript SDK for building multi-platform chat bots and AI agents on Slack, Teams, Google Chat, Discord, WhatsApp, and more. Provides the `Chat` class, event handlers, JSX cards, emoji helpers, and type-safe message formatting.
9
9
 
@@ -273,6 +273,20 @@ declare const ADAPTERS: {
273
273
  readonly slug: "linear";
274
274
  readonly type: "platform";
275
275
  };
276
+ readonly linq: {
277
+ readonly description: "iMessage and SMS adapter for Chat SDK, built and maintained by Linq.";
278
+ readonly env: {
279
+ readonly config: readonly ["baseURL"];
280
+ readonly required: readonly [EnvVar, EnvVar];
281
+ };
282
+ readonly factoryExport: "createLinqAdapter";
283
+ readonly group: "vendor-official";
284
+ readonly name: "Linq";
285
+ readonly packageName: "@linqapp/chat-sdk-adapter";
286
+ readonly peerDeps: readonly [];
287
+ readonly slug: "linq";
288
+ readonly type: "platform";
289
+ };
276
290
  readonly liveblocks: {
277
291
  readonly description: "Liveblocks Comments adapter for building conversational bots on top of Liveblocks rooms, threads, and comments.";
278
292
  readonly env: {
@@ -335,6 +349,20 @@ declare const ADAPTERS: {
335
349
  readonly slug: "messenger";
336
350
  readonly type: "platform";
337
351
  };
352
+ readonly novu: {
353
+ readonly description: "Multi-channel agents with one-click channel setup, identity and multi-tenancy";
354
+ readonly env: {
355
+ readonly optional: readonly [EnvVar];
356
+ readonly required: readonly [EnvVar, EnvVar];
357
+ };
358
+ readonly factoryExport: "createNovuAdapter";
359
+ readonly group: "vendor-official";
360
+ readonly name: "Novu";
361
+ readonly packageName: "@novu/chat-sdk-adapter";
362
+ readonly peerDeps: readonly [];
363
+ readonly slug: "novu";
364
+ readonly type: "platform";
365
+ };
338
366
  readonly postgres: {
339
367
  readonly description: "Production state adapter using PostgreSQL for persistence and distributed locking.";
340
368
  readonly env: {
@@ -474,7 +502,7 @@ declare const ADAPTERS: {
474
502
  readonly type: "platform";
475
503
  };
476
504
  readonly velt: {
477
- readonly description: "Velt Comments adapter for building bots that read and respond in Velt comment threads on documents, text editors, and canvases.";
505
+ readonly description: "Velt Comments adapter for bots that read, reply, mention, and start threads in anchored comments across documents, rich-text editors, canvases, PDFs, and video. Includes per-comment document context and an AI streaming-reply sample app.";
478
506
  readonly env: {
479
507
  readonly config: readonly ["botUserId", "botUserName", "webhookVersion", "resolveUsers", "selfHostingConfig"];
480
508
  readonly optional: readonly [EnvVar, EnvVar];
@@ -281,6 +281,26 @@ var ADAPTERS = {
281
281
  slug: "linear",
282
282
  type: "platform"
283
283
  },
284
+ linq: {
285
+ description: "iMessage and SMS adapter for Chat SDK, built and maintained by Linq.",
286
+ env: {
287
+ config: ["baseURL"],
288
+ required: [
289
+ secretEnv("LINQ_API_KEY", "Linq API key for outbound API calls."),
290
+ secretEnv(
291
+ "LINQ_WEBHOOK_SECRET",
292
+ "Webhook signing secret used to verify inbound HMAC-SHA256 signatures."
293
+ )
294
+ ]
295
+ },
296
+ factoryExport: "createLinqAdapter",
297
+ group: "vendor-official",
298
+ name: "Linq",
299
+ packageName: "@linqapp/chat-sdk-adapter",
300
+ peerDeps: [],
301
+ slug: "linq",
302
+ type: "platform"
303
+ },
284
304
  liveblocks: {
285
305
  description: "Liveblocks Comments adapter for building conversational bots on top of Liveblocks rooms, threads, and comments.",
286
306
  env: {
@@ -391,6 +411,34 @@ var ADAPTERS = {
391
411
  slug: "messenger",
392
412
  type: "platform"
393
413
  },
414
+ novu: {
415
+ description: "Multi-channel agents with one-click channel setup, identity and multi-tenancy",
416
+ env: {
417
+ optional: [
418
+ urlEnv(
419
+ "NOVU_API_BASE_URL",
420
+ "API base URL. Defaults to https://api.novu.co."
421
+ )
422
+ ],
423
+ required: [
424
+ secretEnv(
425
+ "NOVU_SECRET_KEY",
426
+ "Novu API key that authorizes replies and verifies the inbound HMAC. Set automatically by npx novu connect."
427
+ ),
428
+ env(
429
+ "NOVU_AGENT_IDENTIFIER",
430
+ "Bridge agent ID set automatically by npx novu connect."
431
+ )
432
+ ]
433
+ },
434
+ factoryExport: "createNovuAdapter",
435
+ group: "vendor-official",
436
+ name: "Novu",
437
+ packageName: "@novu/chat-sdk-adapter",
438
+ peerDeps: [],
439
+ slug: "novu",
440
+ type: "platform"
441
+ },
394
442
  postgres: {
395
443
  description: "Production state adapter using PostgreSQL for persistence and distributed locking.",
396
444
  env: {
@@ -616,7 +664,7 @@ var ADAPTERS = {
616
664
  type: "platform"
617
665
  },
618
666
  velt: {
619
- description: "Velt Comments adapter for building bots that read and respond in Velt comment threads on documents, text editors, and canvases.",
667
+ description: "Velt Comments adapter for bots that read, reply, mention, and start threads in anchored comments across documents, rich-text editors, canvases, PDFs, and video. Includes per-comment document context and an AI streaming-reply sample app.",
620
668
  env: {
621
669
  config: [
622
670
  "botUserId",
@@ -1,5 +1,5 @@
1
- import { x as Chat, w as ChannelVisibility } from '../chat-BjhJs_sP.js';
2
- export { d as AiAssistantMessage, e as AiFilePart, f as AiImagePart, g as AiMessage, h as AiMessagePart, i as AiTextPart, j as AiUserMessage, aZ as ToAiMessagesOptions, b7 as toAiMessages } from '../chat-BjhJs_sP.js';
1
+ import { x as Chat, w as ChannelVisibility } from '../chat-Dm1vQU3i.js';
2
+ export { d as AiAssistantMessage, e as AiFilePart, f as AiImagePart, g as AiMessage, h as AiMessagePart, i as AiTextPart, j as AiUserMessage, aZ as ToAiMessagesOptions, b7 as toAiMessages } from '../chat-Dm1vQU3i.js';
3
3
  import * as ai from 'ai';
4
4
  import { Tool } from 'ai';
5
5
  import '@workflow/serde';
@@ -3138,6 +3138,7 @@ declare class Chat<TAdapters extends Record<string, Adapter> = Record<string, Ad
3138
3138
  * subscription status, mention detection, and pattern matching.
3139
3139
  */
3140
3140
  private dispatchToHandlers;
3141
+ private setMentionFlags;
3141
3142
  private createThread;
3142
3143
  /**
3143
3144
  * Detect if the bot was mentioned in the message.
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { ay as PostableObject, b as Adapter, az as PostableObjectContext, aN as StreamChunk, aU as ThreadHistoryCache, aV as ThreadHistoryConfig, aO as StreamEvent, L as EmojiFormats, M as EmojiMapConfig, N as EmojiValue, b4 as WellKnownEmoji, G as CustomEmojiMap, c as AdapterPostableMessage } from './chat-BjhJs_sP.js';
2
- export { A as ActionEvent, a as ActionHandler, d as AiAssistantMessage, e as AiFilePart, f as AiImagePart, g as AiMessage, h as AiMessagePart, i as AiTextPart, j as AiUserMessage, k as AppHomeOpenedEvent, l as AppHomeOpenedHandler, m as AppendInput, n as AppendOptions, o as AssistantContextChangedEvent, p as AssistantContextChangedHandler, q as AssistantThreadStartedEvent, r as AssistantThreadStartedHandler, s as Attachment, t as Author, C as Channel, u as ChannelImpl, v as ChannelInfo, w as ChannelVisibility, x as Chat, y as ChatConfig, z as ChatInstance, B as ConcurrencyConfig, D as ConcurrencyStrategy, E as ConsoleLogger, F as CountQuery, H as DeleteTarget, I as DirectMessageHandler, J as DurationString, K as Emoji, O as EphemeralMessage, P as FetchDirection, Q as FetchOptions, R as FetchResult, S as FileUpload, T as FormattedContent, U as IdentityContext, V as IdentityResolver, W as LinkPreview, X as ListQuery, Y as ListThreadsOptions, Z as ListThreadsResult, _ as Lock, $ as LockScope, a0 as LockScopeContext, a1 as LogLevel, a2 as Logger, a3 as MarkdownTextChunk, a4 as MemberJoinedChannelEvent, a5 as MemberJoinedChannelHandler, a6 as MentionHandler, a7 as Message, a8 as MessageContext, a9 as MessageData, aa as MessageHandler, ab as MessageMetadata, ac as MessageSubject, ad as ModalClearResponse, ae as ModalCloseEvent, af as ModalCloseHandler, ag as ModalCloseResponse, ah as ModalErrorsResponse, ai as ModalPushResponse, aj as ModalResponse, ak as ModalSubmitEvent, al as ModalSubmitHandler, am as ModalUpdateResponse, an as OptionsLoadEvent, ao as OptionsLoadGroup, ap as OptionsLoadHandler, aq as OptionsLoadResult, ar as PlanUpdateChunk, as as PostEphemeralOptions, at as Postable, au as PostableAst, av as PostableCard, aw as PostableMarkdown, ax as PostableMessage, aA as PostableRaw, aB as QueueEntry, aC as RawMessage, aD as ReactionEvent, aE as ReactionHandler, aF as ScheduledMessage, aG as SentMessage, aH as SerializedChannel, aI as SerializedMessage, aJ as SerializedThread, aK as SlashCommandEvent, aL as SlashCommandHandler, aM as StateAdapter, aP as StreamOptions, aQ as SubscribedMessageHandler, aR as THREAD_STATE_TTL_MS, aS as TaskUpdateChunk, aT as Thread, aW as ThreadImpl, aX as ThreadInfo, aY as ThreadSummary, aZ as ToAiMessagesOptions, a_ as TranscriptEntry, a$ as TranscriptRole, b0 as TranscriptsApi, b1 as TranscriptsConfig, b2 as UserInfo, b3 as WebhookOptions, b5 as deriveChannelId, b6 as isPostableObject, b7 as toAiMessages } from './chat-BjhJs_sP.js';
1
+ import { ay as PostableObject, b as Adapter, az as PostableObjectContext, aN as StreamChunk, aU as ThreadHistoryCache, aV as ThreadHistoryConfig, aO as StreamEvent, L as EmojiFormats, M as EmojiMapConfig, N as EmojiValue, b4 as WellKnownEmoji, G as CustomEmojiMap, c as AdapterPostableMessage } from './chat-Dm1vQU3i.js';
2
+ export { A as ActionEvent, a as ActionHandler, d as AiAssistantMessage, e as AiFilePart, f as AiImagePart, g as AiMessage, h as AiMessagePart, i as AiTextPart, j as AiUserMessage, k as AppHomeOpenedEvent, l as AppHomeOpenedHandler, m as AppendInput, n as AppendOptions, o as AssistantContextChangedEvent, p as AssistantContextChangedHandler, q as AssistantThreadStartedEvent, r as AssistantThreadStartedHandler, s as Attachment, t as Author, C as Channel, u as ChannelImpl, v as ChannelInfo, w as ChannelVisibility, x as Chat, y as ChatConfig, z as ChatInstance, B as ConcurrencyConfig, D as ConcurrencyStrategy, E as ConsoleLogger, F as CountQuery, H as DeleteTarget, I as DirectMessageHandler, J as DurationString, K as Emoji, O as EphemeralMessage, P as FetchDirection, Q as FetchOptions, R as FetchResult, S as FileUpload, T as FormattedContent, U as IdentityContext, V as IdentityResolver, W as LinkPreview, X as ListQuery, Y as ListThreadsOptions, Z as ListThreadsResult, _ as Lock, $ as LockScope, a0 as LockScopeContext, a1 as LogLevel, a2 as Logger, a3 as MarkdownTextChunk, a4 as MemberJoinedChannelEvent, a5 as MemberJoinedChannelHandler, a6 as MentionHandler, a7 as Message, a8 as MessageContext, a9 as MessageData, aa as MessageHandler, ab as MessageMetadata, ac as MessageSubject, ad as ModalClearResponse, ae as ModalCloseEvent, af as ModalCloseHandler, ag as ModalCloseResponse, ah as ModalErrorsResponse, ai as ModalPushResponse, aj as ModalResponse, ak as ModalSubmitEvent, al as ModalSubmitHandler, am as ModalUpdateResponse, an as OptionsLoadEvent, ao as OptionsLoadGroup, ap as OptionsLoadHandler, aq as OptionsLoadResult, ar as PlanUpdateChunk, as as PostEphemeralOptions, at as Postable, au as PostableAst, av as PostableCard, aw as PostableMarkdown, ax as PostableMessage, aA as PostableRaw, aB as QueueEntry, aC as RawMessage, aD as ReactionEvent, aE as ReactionHandler, aF as ScheduledMessage, aG as SentMessage, aH as SerializedChannel, aI as SerializedMessage, aJ as SerializedThread, aK as SlashCommandEvent, aL as SlashCommandHandler, aM as StateAdapter, aP as StreamOptions, aQ as SubscribedMessageHandler, aR as THREAD_STATE_TTL_MS, aS as TaskUpdateChunk, aT as Thread, aW as ThreadImpl, aX as ThreadInfo, aY as ThreadSummary, aZ as ToAiMessagesOptions, a_ as TranscriptEntry, a$ as TranscriptRole, b0 as TranscriptsApi, b1 as TranscriptsConfig, b2 as UserInfo, b3 as WebhookOptions, b5 as deriveChannelId, b6 as isPostableObject, b7 as toAiMessages } from './chat-Dm1vQU3i.js';
3
3
  import { Root, List, Content, Blockquote, Code, Emphasis, InlineCode, Delete, Link, ListItem, Paragraph, Strong, TableCell, Table as Table$1, TableRow, Text } from 'mdast';
4
4
  export { Blockquote, Code, Content, Delete, Emphasis, InlineCode, Link, List, ListItem, Table as MdastTable, Nodes, Paragraph, Root, Strong, TableCell, TableRow, Text } from 'mdast';
5
5
  import { g as CardElement, C as CardChild, A as ActionsComponent, B as ButtonComponent, f as CardComponent, j as CardLinkComponent, a9 as TextComponent, D as DividerComponent, E as ExternalSelectComponent, F as FieldComponent, w as FieldsComponent, I as ImageComponent, L as LinkButtonComponent, P as ModalComponent, T as RadioSelectComponent, W as SectionComponent, Y as SelectComponent, _ as SelectOptionComponent, a3 as Table$2, ab as TextInputComponent, ah as cardChildToFallbackText$1, ai as fromReactElement$1, aj as fromReactModalElement$1, ak as isCardElement$1, am as isJSX$1, an as isModalElement$1, ar as toCardElement$1, as as toModalElement$1 } from './jsx-runtime-CzthIo1o.js';
@@ -53,6 +53,8 @@ interface StartPlanOptions {
53
53
  initialMessage: PlanContent;
54
54
  }
55
55
  interface AddTaskOptions {
56
+ /** When true (default), mark existing in_progress tasks complete before adding. */
57
+ autoCompletePrevious?: boolean;
56
58
  /** Task details/substeps. */
57
59
  children?: PlanContent;
58
60
  title: PlanContent;
@@ -74,6 +76,7 @@ interface CompletePlanOptions {
74
76
  *
75
77
  * Create a plan with `new Plan({ initialMessage: "..." })` and post it with `thread.post(plan)`.
76
78
  * After posting, use methods like `addTask()`, `updateTask()`, and `complete()` to update it.
79
+ * For parallel steps, pass `autoCompletePrevious: false` to `addTask()` and use `updateTask({ id })` to update individual tasks.
77
80
  *
78
81
  * @example
79
82
  * ```typescript
package/dist/index.js CHANGED
@@ -3020,7 +3020,7 @@ var Chat = class {
3020
3020
  author: event.user,
3021
3021
  metadata: { dateSent: /* @__PURE__ */ new Date(), edited: false },
3022
3022
  attachments: []
3023
- }) : {};
3023
+ }) : void 0;
3024
3024
  const thread = event.threadId ? await this.createThread(
3025
3025
  event.adapter,
3026
3026
  event.threadId,
@@ -3140,7 +3140,7 @@ var Chat = class {
3140
3140
  const thread = await this.createThread(
3141
3141
  event.adapter,
3142
3142
  event.threadId,
3143
- event.message ?? {},
3143
+ event.message,
3144
3144
  isSubscribed
3145
3145
  );
3146
3146
  const fullEvent = {
@@ -3228,7 +3228,7 @@ var Chat = class {
3228
3228
  );
3229
3229
  }
3230
3230
  const threadId = await adapter.openDM(userId);
3231
- return this.createThread(adapter, threadId, {}, false);
3231
+ return this.createThread(adapter, threadId, void 0, false);
3232
3232
  }
3233
3233
  /**
3234
3234
  * Look up user information by user ID.
@@ -3333,7 +3333,7 @@ var Chat = class {
3333
3333
  "ADAPTER_NOT_FOUND"
3334
3334
  );
3335
3335
  }
3336
- return this.createThread(adapter, threadId, {}, false);
3336
+ return this.createThread(adapter, threadId, void 0, false);
3337
3337
  }
3338
3338
  /**
3339
3339
  * Infer which adapter to use based on the userId format.
@@ -3754,7 +3754,7 @@ var Chat = class {
3754
3754
  * subscription status, mention detection, and pattern matching.
3755
3755
  */
3756
3756
  async dispatchToHandlers(adapter, threadId, message, context) {
3757
- message.isMention = message.isMention || this.detectMention(adapter, message);
3757
+ const hasMention = this.setMentionFlags(adapter, message, context);
3758
3758
  const isSubscribed = await this._stateAdapter.isSubscribed(threadId);
3759
3759
  this.logger.debug("Subscription check", {
3760
3760
  threadId,
@@ -3814,7 +3814,7 @@ var Chat = class {
3814
3814
  );
3815
3815
  return;
3816
3816
  }
3817
- if (message.isMention) {
3817
+ if (message.isMention || hasMention) {
3818
3818
  this.logger.debug("Bot mentioned", {
3819
3819
  threadId,
3820
3820
  text: message.text.slice(0, 100)
@@ -3850,6 +3850,15 @@ var Chat = class {
3850
3850
  });
3851
3851
  }
3852
3852
  }
3853
+ setMentionFlags(adapter, message, context) {
3854
+ message.isMention = message.isMention || this.detectMention(adapter, message);
3855
+ let hasMention = message.isMention === true;
3856
+ for (const skipped of context?.skipped ?? []) {
3857
+ skipped.isMention = skipped.isMention || this.detectMention(adapter, skipped);
3858
+ hasMention = hasMention || skipped.isMention === true;
3859
+ }
3860
+ return hasMention;
3861
+ }
3853
3862
  createThread(adapter, threadId, initialMessage, isSubscribedContext = false) {
3854
3863
  const channelId = adapter.channelIdFromThreadId(threadId);
3855
3864
  const isDM = adapter.isDM?.(threadId) ?? false;
@@ -4064,9 +4073,12 @@ var Plan = class {
4064
4073
  return null;
4065
4074
  }
4066
4075
  const title = contentToPlainText(options.title) || "Task";
4067
- for (const task of this._model.tasks) {
4068
- if (task.status === "in_progress") {
4069
- task.status = "complete";
4076
+ const autoCompletePrevious = options.autoCompletePrevious ?? true;
4077
+ if (autoCompletePrevious) {
4078
+ for (const task of this._model.tasks) {
4079
+ if (task.status === "in_progress") {
4080
+ task.status = "complete";
4081
+ }
4070
4082
  }
4071
4083
  }
4072
4084
  const nextTask = {
@@ -32,6 +32,8 @@ The `fallbackToDM` option is required and controls behavior on platforms without
32
32
  | Discord | No | DM fallback | Persists in DM |
33
33
  | Teams | No | DM fallback | Persists in DM |
34
34
 
35
+ Discord slash command responses can be made ephemeral with the Discord adapter's [`interactionFlags` option](/adapters/official/discord#interaction-flags). Outside that interaction flow, `postEphemeral` still follows the fallback behavior.
36
+
35
37
  ## Check for fallback
36
38
 
37
39
  ```typescript title="lib/bot.ts" lineNumbers
@@ -121,6 +121,8 @@ Telegram supports bot commands such as `/status` and `/status@mybot`. Register h
121
121
 
122
122
  Discord slash commands are received via [HTTP Interactions](/adapters/official/discord#http-interactions-vs-gateway) — no Gateway connection is needed. The adapter automatically sends a deferred response to Discord, then resolves it when your handler calls `event.channel.post()`.
123
123
 
124
+ To make selected Discord slash command responses ephemeral, return `DiscordInteractionResponseFlag.Ephemeral` from the Discord adapter's [`interactionFlags` option](/adapters/official/discord#interaction-flags).
125
+
124
126
  ### Subcommands
125
127
 
126
128
  Discord supports subcommand groups and subcommands. The adapter flattens these into the `event.command` path:
@@ -217,11 +217,14 @@ await plan.addTask({ title: "Summarize findings" });
217
217
  await plan.complete({ completeMessage: "Done!" });
218
218
  ```
219
219
 
220
- By default `updateTask()` mutates the most recent `in_progress` task. Pass `{ id }` to target a specific task — useful when work runs in parallel or out of order:
220
+ By default `updateTask()` mutates the most recent `in_progress` task. For parallel work, pass `autoCompletePrevious: false` to `addTask()` so earlier tasks stay in progress; then pass `{ id }` to `updateTask()` to target a specific task when updates run out of order:
221
221
 
222
222
  ```typescript
223
223
  const fetchTask = await plan.addTask({ title: "Fetch data" });
224
- const transformTask = await plan.addTask({ title: "Transform" });
224
+ const transformTask = await plan.addTask({
225
+ title: "Transform",
226
+ autoCompletePrevious: false,
227
+ });
225
228
 
226
229
  // Update a specific task by id, even if it isn't the most recent in_progress one.
227
230
  await plan.updateTask({ id: fetchTask.id, output: "Got 42 rows" });
@@ -232,7 +235,7 @@ Adapters that don't support PostableObject editing (e.g. WhatsApp) render the pl
232
235
 
233
236
  | Method | Description |
234
237
  |--------|-------------|
235
- | `addTask({ title, children? })` | Append a new task. The previous in-progress task is auto-completed |
238
+ | `addTask({ title, children?, autoCompletePrevious? })` | Append a new task. When `autoCompletePrevious` is true (default), existing in-progress tasks are marked complete first; pass `false` for parallel workflows |
236
239
  | `updateTask(input)` | Mutate the current (or `{ id }`-targeted) task's `output`, `status`, or `title` |
237
240
  | `complete({ completeMessage })` | Mark all in-progress tasks complete and update the plan title |
238
241
  | `reset({ initialMessage })` | Discard all tasks and start fresh with a new initial message — useful when re-using a plan handle for a new run |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chat",
3
- "version": "4.31.0",
3
+ "version": "4.32.0",
4
4
  "description": "Chat SDK — universal TypeScript toolkit for building multi-platform chat bots and AI agents on Slack, Teams, Google Chat, Discord, WhatsApp, and more",
5
5
  "type": "module",
6
6
  "engines": {
@@ -0,0 +1,224 @@
1
+ # Build AI agents with AI Gateway and AI SDK
2
+
3
+ **Author:** Ben Sabic
4
+
5
+ ---
6
+
7
+ An AI agent is a model that runs in a loop, using tools to gather information or take action until it completes a task. The AI SDK gives you the TypeScript primitives to build that loop, and AI Gateway gives it one endpoint and one set of credentials for hundreds of models, so you can switch providers by changing a single string instead of managing separate accounts, keys, and rate limits.
8
+
9
+ This guide takes you from your first model request to a working agent, then makes that agent reliable with model fallbacks, reachable in chat platforms with Chat SDK, and durable with the Workflow SDK.
10
+
11
+ ## Overview
12
+
13
+ In this guide, you'll learn how to:
14
+
15
+ * Set up a [Next.js](https://nextjs.org/) project and authenticate to AI Gateway with OIDC tokens
16
+
17
+ * Generate text, stream responses, and produce structured outputs
18
+
19
+ * Give your agent tools so they can act, not just respond
20
+
21
+ * Keep your agent available with model fallbacks
22
+
23
+ * Run AI-generated code safely in isolated [Vercel Sandbox](https://vercel.com/sandbox) microVMs
24
+
25
+ * Bring your agent to Slack, Teams, and other chat platforms with [Chat SDK](https://chat-sdk.dev/)
26
+
27
+ * Give your agent secure, short-lived access to third-party APIs with [Vercel Connect](https://vercel.com/connect)
28
+
29
+ * Make your agent durable and resumable with the [Workflow SDK](https://workflow-sdk.dev)
30
+
31
+
32
+ ## Prerequisites
33
+
34
+ Before you begin, make sure you have:
35
+
36
+ * A [Vercel account](https://vercel.com/signup)
37
+
38
+ * Node.js 20 or later
39
+
40
+ * [Vercel CLI](https://vercel.com/docs/cli) installed (`npm i -g vercel`)
41
+
42
+
43
+ ## How it works
44
+
45
+ AI Gateway is a single endpoint that sits in front of every supported provider. You send it a model string in the form `creator/model-name`, and the Gateway resolves the provider, authenticates, routes the request, and tracks usage. Because the AI SDK communicates with this endpoint natively, your application code remains the same whether you call Claude Opus 4.8, GPT-5.5, or Gemini 3.1 Pro. Tokens cost the same as they would from the provider directly, with no markup.
46
+
47
+ The AI SDK provides the function-level API you'll build the agent from (`generateText`, `streamText`, `generateObject`, and the tool loop), and AI Gateway provides the infrastructure underneath: authentication, usage tracking, failover, and billing. The two are built with high cohesion but loose coupling, so you can adopt the SDK on its own and add Gateway features via provider options when needed.
48
+
49
+ ## Steps
50
+
51
+ ### 1\. Create a Next.js app
52
+
53
+ Use `create-next-app` to bootstrap a new project:
54
+
55
+ `pnpm create next-app@latest ai-gateway-demo --yes cd ai-gateway-demo`
56
+
57
+ The `--yes` flag uses the recommended defaults: TypeScript, Tailwind CSS, ESLint, App Router, and Turbopack, with the `@/*` import alias. Omit the flag if you want to customize these options interactively.
58
+
59
+ ### 2\. Install the AI SDK
60
+
61
+ Add the `ai` package to your project:
62
+
63
+ `pnpm add ai`
64
+
65
+ AI Gateway works with both AI SDK v5 and v6. Check your installed version with `pnpm list ai`.
66
+
67
+ ### 3\. Authenticate with OIDC
68
+
69
+ AI Gateway authenticates requests using [Vercel OIDC tokens](https://vercel.com/docs/oidc), which Vercel generates and links to your project automatically. You don't need to create or store an API key.
70
+
71
+ First, link your local project to a Vercel project:
72
+
73
+ `vercel link`
74
+
75
+ Then pull the environment variables, which include the OIDC token:
76
+
77
+ `vercel env pull`
78
+
79
+ This writes the token to your local environment file. OIDC tokens are valid for 12 hours, so during local development, you'll need to run `vercel env pull` again to refresh the token when it expires.
80
+
81
+ When you deploy to Vercel, OIDC tokens are provisioned automatically, so no further setup is required in production.
82
+
83
+ ### 4\. Generate text
84
+
85
+ Start with the simplest request: generate a single block of text. Create an API route at `app/api/chat/route.ts`. Pass a plain string model ID to `generateText` and the AI Gateway resolves the provider and routes the request. With OIDC authentication, you don't reference a key anywhere in your code:
86
+
87
+ `import { generateText } from 'ai'; export async function GET() { const { text } = await generateText({ model: 'anthropic/claude-opus-4.8', prompt: 'Explain quantum computing in one paragraph.', }); return Response.json({ text }); }`
88
+
89
+ Start the dev server and visit the route to see the response:
90
+
91
+ `pnpm dev`
92
+
93
+ ### 5\. Stream responses
94
+
95
+ For real-time output, use `streamText` and return a streamed response. This is the pattern you'll use for chat interfaces and any response long enough that waiting for the full result would hurt the experience:
96
+
97
+ `import { streamText } from 'ai'; export async function POST(request: Request) { const { prompt } = await request.json(); const result = streamText({ model: 'openai/gpt-5.5', prompt, }); return result.toUIMessageStreamResponse(); }`
98
+
99
+ To switch models, change the model string. No other code changes are required.
100
+
101
+ ### 6\. Generate structured outputs
102
+
103
+ Use `generateObject` with a [Zod](https://zod.dev/) schema to get type-safe structured data instead of free text:
104
+
105
+ `import { generateObject } from 'ai'; import { z } from 'zod'; export async function GET() { const { object } = await generateObject({ model: 'anthropic/claude-opus-4.8', schema: z.object({ name: z.string(), age: z.number(), city: z.string(), }), prompt: 'Extract: John is 30 years old and lives in NYC.', }); return Response.json(object); // { name: 'John', age: 30, city: 'NYC' } }`
106
+
107
+ ### 7\. Give your agent tools
108
+
109
+ So far, the model has produced text and data, but it hasn't done anything. Tools change that: you define functions the model can invoke to fetch data, call an API, or act on the outside world, and the AI SDK runs the model in a loop, calling tools and feeding results back until the task is done. A model plus tools in a loop is an agent, and the AI SDK handles that loop for you.
110
+
111
+ Define a tool with a description, an input schema, and an `execute` function:
112
+
113
+ `import { generateText, tool } from 'ai'; import { z } from 'zod'; export async function GET() { const { text } = await generateText({ model: 'anthropic/claude-opus-4.8', tools: { getWeather: tool({ description: 'Get the current weather for a location', parameters: z.object({ location: z.string().describe('City name, e.g. San Francisco'), }), execute: async ({ location }) => ({ location, temperature: 72, condition: 'sunny', }), }), }, prompt: "What's the weather in Tokyo?", }); return Response.json({ text }); }`
114
+
115
+ You now have a working agent. Everything that follows makes it more capable and more reliable: fallbacks keep it available, Sandbox lets it run code safely, Chat SDK puts it in front of users, and the Workflow SDK makes it durable.
116
+
117
+ ### 8\. Keep your agent available with fallbacks
118
+
119
+ An agent makes many model calls over the course of a task, so it has many chances to hit a provider outage or error. That makes failover matter more for agents, not less. Pass a `models` array in `providerOptions.gateway` to list backup models, which the Gateway tries in order when the primary model fails:
120
+
121
+ `import { streamText } from 'ai'; export async function POST(request: Request) { const { prompt } = await request.json(); const result = streamText({ model: 'openai/gpt-5.5', // Primary model prompt, providerOptions: { gateway: { models: ['anthropic/claude-opus-4.8', 'google/gemini-3.1-pro-preview'], // Fallbacks }, }, }); return result.toUIMessageStreamResponse(); }`
122
+
123
+ In this example, the Gateway first attempts the primary model. If that fails, it tries `anthropic/claude-opus-4.7`, then `google/gemini-3.1-pro-preview`. The response comes from the first model that succeeds, and failover happens automatically without changes to your application logic.
124
+
125
+ ## Let your agent run code safely
126
+
127
+ A capable agent often needs to do more than call predefined tools: it may generate code and run it, whether to compute a result, transform data, or test its own output. Executing model-generated code on your own infrastructure is risky because the code might consume excessive resources, read sensitive files, make unwanted network requests, or run destructive commands.
128
+
129
+ [Vercel Sandbox](https://vercel.com/docs/vercel-sandbox) provides the agent with an isolated environment to run the code. Each sandbox is an ephemeral Linux microVM with resource limits and automatic timeouts, so untrusted code runs without touching your production systems. It's a standalone SDK you can call from any environment, and the same OIDC token you pulled earlier authenticates both Sandbox and AI Gateway.
130
+
131
+ Add the Sandbox SDK to your project:
132
+
133
+ `pnpm add @vercel/sandbox ms`
134
+
135
+ The pattern has two parts: generate code with the model via the AI Gateway, then write it to a fresh sandbox and run it. The sandbox is created, used, and stopped within a single request:
136
+
137
+ ``import ms from 'ms'; import { generateText } from 'ai'; import { Sandbox } from '@vercel/sandbox'; const SYSTEM_PROMPT = `You are a code generator. Write JavaScript that runs in Node.js. Output only the code, with no explanations or markdown.`; async function generateCode(task: string): Promise<string> { const { text } = await generateText({ model: 'anthropic/claude-sonnet-4.6', system: SYSTEM_PROMPT, prompt: `Write JavaScript code to: ${task}`, }); return text .replace(/^\s*```(?:javascript|js)?\s*/i, '') .replace(/\s*```\s*$/i, '') .trim(); } async function executeCode(code: string) { const sandbox = await Sandbox.create({ resources: { vcpus: 2 }, timeout: ms('2m'), runtime: 'node22', }); try { await sandbox.writeFiles([ { path: '/vercel/sandbox/code.mjs', content: Buffer.from(code) }, ]); const result = await sandbox.runCommand({ cmd: 'node', args: ['code.mjs'] }); const stdout = await result.stdout(); const stderr = await result.stderr(); return { output: stdout || stderr || '(no output)', exitCode: result.exitCode }; } finally { await sandbox.stop(); } }``
138
+
139
+ This gives the agent two layers of safety: the system prompt steers the model away from dangerous operations, and the sandbox enforces isolation regardless of what the model produces. The sandbox captures both `stdout` and `stderr`, so the agent can read failures and retry without those failures affecting your host. For a complete walkthrough, see [How to execute AI-generated code safely with Vercel Sandbox](https://vercel.com/kb/guide/how-to-execute-ai-generated-code-safely).
140
+
141
+ ## Bring your agent to your users
142
+
143
+ The agent you've built runs over an HTTP route, but your users may already be in Slack, Microsoft Teams, Discord, or Google Chat. [Chat SDK](https://chat-sdk.dev/) is a TypeScript library for building chatbots that work across these platforms from a single codebase, and it integrates directly with AI SDK. That means the same agent you call through AI Gateway can answer inside a thread without rebuilding it for each platform.
144
+
145
+ Two helpers from the `chat/ai` subpath connect the two SDKs. Importing from `chat/ai` rather than the main `chat` entrypoint keeps the optional `ai` and `zod` peer dependencies out of bundles that don't use them.
146
+
147
+ ### Feed thread history to your agent
148
+
149
+ `toAiMessages` converts an array of Chat SDK `Message` objects into the `{ role, content }[]` format the AI SDK expects. The output is structurally compatible with the AI SDK's `ModelMessage[]`, so you can pass it straight into a model call. Fetch recent messages from the thread, convert them, and use them as the agent's prompt:
150
+
151
+ `import { toAiMessages } from 'chat/ai'; bot.onSubscribedMessage(async (thread, message) => { const result = await thread.adapter.fetchMessages(thread.id, { limit: 20 }); const history = await toAiMessages(result.messages); const response = await agent.stream({ prompt: history }); await thread.post(response.fullStream); });`
152
+
153
+ `toAiMessages` maps messages authored by the bot to the `assistant` role and all others to `user`, sorts them chronologically, and includes image and text attachments as multipart content. For multi-user threads, pass `includeNames: true` so the model can tell speakers apart.
154
+
155
+ ### Let your agent act on the platform
156
+
157
+ `createChatTools` provides the agent with a set of AI SDK tools for posting messages, adding reactions, and performing other platform actions. Pass it to your Chat instance alongside an AI SDK call:
158
+
159
+ `import { Chat } from 'chat'; import { createChatTools } from 'chat/ai'; import { createSlackAdapter } from '@chat-adapter/slack'; import { createMemoryState } from '@chat-adapter/state-memory'; import { generateText } from 'ai'; const chat = new Chat({ userName: 'mybot', adapters: { slack: createSlackAdapter() }, state: createMemoryState(), }); const result = await generateText({ model: 'anthropic/claude-opus-4.8', tools: createChatTools({ chat, preset: 'messenger' }), prompt: 'Post a friendly hello in slack:C0123ABC and react to it with a thumbs up.', });`
160
+
161
+ Each tool resolves the right adapter from the id prefix you give it (`slack:`, `discord:`, `gchat:`), so one agent can drive any platform your Chat instance is wired up to. Because the model string still routes through AI Gateway, you keep the provider failover and unified billing from the earlier steps while reaching users wherever they already work.
162
+
163
+ ## Give your agent secure access to third-party APIs
164
+
165
+ Once your agent acts on outside services, such as posting to Slack, opening GitHub pull requests, or querying a data warehouse, it needs credentials for those providers. Bundling long-lived API keys into your deployment is risky: the secret sits in your environment indefinitely, applies to every request, and is hard to scope or revoke.
166
+
167
+ [Vercel Connect](https://vercel.com/docs/connect) solves this by issuing short-lived provider tokens at runtime instead. You register a connector for a provider once, link it to your projects and environments, and your code requests a scoped token only when it needs one. The same OIDC token you've used throughout authenticates the request, so no provider secret lives in your deployment.
168
+
169
+ Add the Connect SDK to your project:
170
+
171
+ `pnpm add @vercel/connect`
172
+
173
+ Request a token with `getToken`, passing the connector, the subject the token acts as, and the scopes you need:
174
+
175
+ `import { getToken } from '@vercel/connect'; const token = await getToken('slack/acme-slack', { subject: { type: 'app' }, installationId: 'inst_workspace_xyz', scopes: ['chat:write'], });`
176
+
177
+ The subject controls whose identity the token represents: `{ type: 'app' }` acts as your service, while `{ type: 'user', id: '...' }` acts on behalf of a specific user who authorized access once. The SDK caches tokens in-process and refreshes them automatically, so an agent that makes many provider calls in a single run requests a single token rather than one per call. Connect currently supports Slack, GitHub, and OAuth connectors in Beta. To set up your first connector, see [Access external APIs from your agents with Vercel Connect](https://vercel.com/kb/guide/vercel-connect).
178
+
179
+ ## Build durable agents with the Workflow SDK
180
+
181
+ The agents you've built so far run in memory. If the process crashes, the function times out, or the user refreshes the page, the agent's progress is lost. That's fine for short, single-tool interactions, but it's costly for production agents that chain several tool calls, such as booking a flight or running a research task across multiple APIs.
182
+
183
+ `WorkflowAgent` from `@ai-sdk/workflow` runs the same agent loop as the standard in-memory agent, but inside a [Vercel Workflow](https://vercel.com/docs/workflows). Each tool call becomes a durable step, so progress persists across process boundaries, and failed steps are retried from the last checkpoint instead of restarting the whole loop. Tools marked `needsApproval` can suspend the agent for hours or days until a user responds, which makes human-in-the-loop flows possible without a custom state store or polling.
184
+
185
+ To get durability, the agent runs inside a function marked `'use workflow'`, and each tool's `execute` function is marked `'use step'`:
186
+
187
+ ``import { WorkflowAgent, type ModelCallStreamPart } from '@ai-sdk/workflow'; import { convertToModelMessages, tool, type UIMessage } from 'ai'; import { getWritable } from 'workflow'; import { z } from 'zod'; async function searchFlightsStep(input: { origin: string; destination: string; date: string; }) { 'use step'; const response = await fetch(`https://api.flights.example/search?...`); return response.json(); } export async function chat(messages: UIMessage[]) { 'use workflow'; const modelMessages = await convertToModelMessages(messages); const agent = new WorkflowAgent({ model: 'anthropic/claude-sonnet-4-6', instructions: 'You are a flight booking assistant.', tools: { searchFlights: tool({ description: 'Search for available flights', inputSchema: z.object({ origin: z.string(), destination: z.string(), date: z.string(), }), execute: searchFlightsStep, }), }, }); const result = await agent.stream({ messages: modelMessages, writable: getWritable<ModelCallStreamPart>(), }); return { messages: result.messages }; }``
188
+
189
+ The model string is the same `creator/model-name` form you've used throughout, so the request still routes through AI Gateway with its failover and unified billing. What changes is the runtime: tool calls now persist, retry, and appear as discrete steps in the workflow dashboard. To add human approval, set `needsApproval: true` on a tool definition, which suspends the durable workflow until the user responds.
190
+
191
+ Start with the standard in-memory agent, and reach for `WorkflowAgent` when tool calls outlive their request, approvals exceed function timeouts, or each call should be independently retryable and traced. For a full breakdown of what `WorkflowAgent` adds, how `needsApproval` works, and how to keep chat streams resumable across timeouts, see the [What is WorkflowAgent?](https://vercel.com/kb/guide/what-is-workflowagent) guide.
192
+
193
+ ## Best practices
194
+
195
+ * **Refresh your OIDC token during local development**: Tokens are valid for 12 hours. If requests start failing locally with an authentication error, run `vercel env pull` to get a fresh token.
196
+
197
+ * **Set fallbacks for production traffic**: A single model and provider is a single point of failure. Listing two or three fallback models in `providerOptions.gateway` keeps requests available when one provider has an outage.
198
+
199
+ * **Keep model IDs in configuration**: Because switching models is a single string change, storing model IDs in environment variables or a config file lets you switch providers without editing application code.
200
+
201
+ * **Confirm your AI SDK version**: All core features are available in both v5 and v6, but v6 adds capabilities such as video generation. Run `pnpm list ai` to check, and see the [AI SDK v6 migration guide](https://ai-sdk.dev/docs/migration-guides/migration-guide-6-0) before upgrading.
202
+
203
+
204
+ ## Resources and next steps
205
+
206
+ * Learn about [model routing and fallbacks](https://vercel.com/docs/ai-gateway/models-and-providers/provider-options) for finer control over provider preference
207
+
208
+ * Read more about [OIDC authentication](https://vercel.com/docs/ai-gateway/authentication-and-byok/authentication) and how tokens work on Vercel
209
+
210
+ * Explore the [AI SDK documentation](https://ai-sdk.dev/getting-started) for advanced patterns
211
+
212
+ * Run AI-generated code safely with [Vercel Sandbox](https://vercel.com/docs/vercel-sandbox) and the [code execution guide](https://vercel.com/kb/guide/how-to-execute-ai-generated-code-safely)
213
+
214
+ * Build a cross-platform chatbot with [Chat SDK](https://chat-sdk.dev/docs) and its [AI SDK integration](https://chat-sdk.dev/docs/ai/ai-sdk-tools)
215
+
216
+ * Request short-lived provider tokens at runtime with [Vercel Connect](https://vercel.com/docs/connect)
217
+
218
+ * Make your agents durable with [WorkflowAgent](https://vercel.com/kb/guide/what-is-workflowagent) and [Workflow SDK](https://workflow-sdk.dev/)
219
+
220
+ * Browse the [model library](https://vercel.com/ai-gateway/models) to see every supported provider and model
221
+
222
+ ---
223
+
224
+ [View full KB sitemap](/kb/sitemap.md)