ai 7.0.152 → 7.0.154

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 (44) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/dist/index.d.ts +224 -9
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +440 -120
  5. package/dist/index.js.map +1 -1
  6. package/dist/internal/index.d.ts +52 -4
  7. package/dist/internal/index.d.ts.map +1 -1
  8. package/dist/internal/index.js +26 -20
  9. package/dist/internal/index.js.map +1 -1
  10. package/docs/02-getting-started/02-nextjs-app-router.mdx +2 -2
  11. package/docs/02-getting-started/08-angular.mdx +327 -0
  12. package/docs/02-getting-started/{03-nextjs-pages-router.mdx → 10-nextjs-pages-router.mdx} +12 -20
  13. package/docs/02-getting-started/index.mdx +2 -0
  14. package/docs/03-agents/01-overview.mdx +1 -0
  15. package/docs/03-agents/04-loop-control.mdx +117 -0
  16. package/docs/03-agents/06-tool-approvals.mdx +1 -0
  17. package/docs/03-agents/09-chat-platforms.mdx +164 -0
  18. package/docs/03-agents/index.mdx +6 -0
  19. package/docs/03-ai-sdk-core/60-telemetry.mdx +16 -3
  20. package/docs/03-ai-sdk-core/65-lifecycle-callbacks.mdx +10 -0
  21. package/docs/04-ai-sdk-ui/21-transport.mdx +86 -0
  22. package/docs/07-reference/01-ai-sdk-core/01-generate-text.mdx +46 -0
  23. package/docs/07-reference/01-ai-sdk-core/02-stream-text.mdx +46 -0
  24. package/docs/07-reference/01-ai-sdk-core/16-tool-loop-agent.mdx +35 -0
  25. package/package.json +12 -12
  26. package/src/agent/create-agent-ui-stream-response.ts +3 -0
  27. package/src/agent/create-agent-ui-stream.ts +16 -0
  28. package/src/agent/pipe-agent-ui-stream-to-response.ts +3 -0
  29. package/src/agent/tool-loop-agent-settings.ts +37 -1
  30. package/src/agent/tool-loop-agent.ts +43 -7
  31. package/src/generate-object/generate-object.ts +1 -1
  32. package/src/generate-text/generate-text-events.ts +37 -0
  33. package/src/generate-text/generate-text.ts +528 -374
  34. package/src/generate-text/index.ts +8 -0
  35. package/src/generate-text/prepare-step.ts +12 -0
  36. package/src/generate-text/restricted-telemetry-dispatcher.ts +31 -18
  37. package/src/generate-text/steering.ts +293 -0
  38. package/src/generate-text/step-result.ts +18 -0
  39. package/src/generate-text/stream-text.ts +664 -473
  40. package/src/middleware/extract-json-middleware.ts +8 -2
  41. package/src/middleware/extract-reasoning-middleware.ts +42 -30
  42. package/src/telemetry/create-telemetry-dispatcher.ts +3 -0
  43. package/src/telemetry/telemetry.ts +15 -1
  44. package/src/util/is-caused-by-abort.ts +20 -0
@@ -0,0 +1,164 @@
1
+ ---
2
+ title: Chat Platforms
3
+ description: Run AI SDK agents in Slack, Microsoft Teams, Discord, and other chat platforms with Chat SDK.
4
+ ---
5
+
6
+ # Chat Platforms
7
+
8
+ [Chat SDK](https://chat-sdk.dev) is a TypeScript SDK for building bots that run on Slack, Microsoft Teams, Google Chat, Discord, Telegram, GitHub, Linear, WhatsApp, and more. You write your bot logic once, and platform adapters handle webhook verification, message parsing, and native formatting for each platform.
9
+
10
+ Chat SDK works with the AI SDK out of the box:
11
+
12
+ - `thread.post()` accepts an agent's `fullStream` and streams the reply into the conversation.
13
+ - `toAiMessages` from `chat/ai` converts a thread's messages into AI SDK `ModelMessage[]`.
14
+ - `createChatTools` from `chat/ai` gives an agent tools for reading and acting in the conversation.
15
+
16
+ This page shows how to connect a [`ToolLoopAgent`](/docs/agents/building-agents) to Slack. The same code works on every platform Chat SDK supports.
17
+
18
+ ## Installation
19
+
20
+ Install Chat SDK, a platform adapter, and a state adapter. The state adapter stores thread subscriptions and locks. This example uses Slack and Redis:
21
+
22
+ <InstallPackages packages="ai chat @chat-adapter/slack @chat-adapter/state-redis" />
23
+
24
+ The Slack adapter reads `SLACK_BOT_TOKEN` and `SLACK_SIGNING_SECRET` from the environment, and the Redis state adapter reads `REDIS_URL`. See the [Slack adapter docs](https://chat-sdk.dev/adapters/official/slack) for the Slack app manifest. The examples on this page set `agentView: true` to match a manifest that uses Slack's Agent messaging experience (`agent_view`).
25
+
26
+ ## Create a Bot
27
+
28
+ Create a `Chat` instance and an agent. When someone mentions the bot, subscribe to the thread and stream the agent's reply into it:
29
+
30
+ ```ts filename="lib/bot.ts"
31
+ import { Chat } from 'chat';
32
+ import { createSlackAdapter } from '@chat-adapter/slack';
33
+ import { createRedisState } from '@chat-adapter/state-redis';
34
+ import { ToolLoopAgent } from 'ai';
35
+
36
+ export const bot = new Chat({
37
+ userName: 'mybot',
38
+ adapters: {
39
+ slack: createSlackAdapter({ agentView: true }),
40
+ },
41
+ state: createRedisState(),
42
+ });
43
+
44
+ const agent = new ToolLoopAgent({
45
+ model: 'anthropic/claude-sonnet-5.5',
46
+ instructions:
47
+ 'You are a helpful assistant in a team chat. Keep replies short.',
48
+ });
49
+
50
+ bot.onNewMention(async (thread, message) => {
51
+ await thread.subscribe();
52
+
53
+ const result = await agent.stream({
54
+ prompt: message.text,
55
+ abortSignal: thread.signal,
56
+ });
57
+
58
+ await thread.post(result.fullStream);
59
+ });
60
+ ```
61
+
62
+ `thread.signal` is aborted when the platform cancels the turn (for example, when a user clicks Slack's stop button), so passing it as `abortSignal` stops generation too.
63
+
64
+ Then route the platform's webhooks to the bot:
65
+
66
+ ```ts filename="app/api/webhooks/slack/route.ts"
67
+ import { after } from 'next/server';
68
+ import { bot } from '@/lib/bot';
69
+
70
+ export async function POST(request: Request): Promise<Response> {
71
+ return bot.webhooks.slack(request, {
72
+ waitUntil: task => after(() => task),
73
+ });
74
+ }
75
+ ```
76
+
77
+ `waitUntil` keeps the function running after the webhook responds, so the agent can finish streaming its reply.
78
+
79
+ ### Why `fullStream`?
80
+
81
+ Pass `fullStream` instead of `textStream`. When an agent calls tools between text steps, `textStream` joins the text from each step without separators. `fullStream` includes `finish-step` events, which Chat SDK uses to add paragraph breaks between steps.
82
+
83
+ ## Use Thread History
84
+
85
+ After `thread.subscribe()`, later messages in the thread go to `onSubscribedMessage`. Load the thread history and convert it with `toAiMessages` to give the agent the full conversation:
86
+
87
+ ```ts filename="lib/bot.ts"
88
+ import { toAiMessages } from 'chat/ai';
89
+
90
+ bot.onSubscribedMessage(async thread => {
91
+ const { messages } = await bot.history.thread.list(thread.id, {
92
+ limit: 20,
93
+ });
94
+
95
+ const result = await agent.stream({
96
+ messages: await toAiMessages(messages),
97
+ abortSignal: thread.signal,
98
+ });
99
+
100
+ await thread.post(result.fullStream);
101
+ });
102
+ ```
103
+
104
+ `toAiMessages` maps the bot's own messages to `assistant` and all others to `user`, sorts them oldest first, and includes supported attachments such as images and text files. Pass `{ includeNames: true }` to prefix each user message with the sender's name in multi-user threads.
105
+
106
+ ## Give the Agent Chat Tools
107
+
108
+ `createChatTools` returns AI SDK tools that let the agent read and act in the chat: fetch messages, look up channels, post, react, and more. Use a preset to limit the tools to what the agent needs:
109
+
110
+ ```ts filename="lib/bot.ts"
111
+ import { ToolLoopAgent } from 'ai';
112
+ import { createChatTools } from 'chat/ai';
113
+
114
+ const agent = new ToolLoopAgent({
115
+ model: 'anthropic/claude-sonnet-5.5',
116
+ instructions:
117
+ 'You are a helpful assistant in a team chat. You can read messages in the current channel.',
118
+ tools: createChatTools({ chat: bot, preset: 'reader' }),
119
+ toolApproval: {
120
+ getUser: {
121
+ type: 'denied',
122
+ reason: 'Profile lookups are disabled for this bot',
123
+ },
124
+ },
125
+ });
126
+ ```
127
+
128
+ The presets are `reader` (read-only), `messenger` (read, post, DM, and react), and `moderator` (all tools, including edit and delete).
129
+
130
+ When a tool runs inside a Chat SDK handler, it is limited to the conversation being handled. This stops the model from reading or posting in other channels the bot can access. See [Limiting what the agent can reach](https://chat-sdk.dev/docs/ai/ai-sdk-tools#limiting-what-the-agent-can-reach) for the `scope` and `strictScope` options.
131
+
132
+ Chat SDK marks write tools and `getUser` as needing approval by default. A `toolApproval` rule on the agent takes precedence over those defaults, so you can approve or deny calls automatically when your bot has no approval UI. The example above denies `getUser` profile lookups. See [Tool Approvals](/docs/agents/tool-approvals) for all approval options.
133
+
134
+ ## Add More Platforms
135
+
136
+ Register more adapters to run the same handlers on other platforms:
137
+
138
+ ```ts filename="lib/bot.ts"
139
+ import { Chat } from 'chat';
140
+ import { createSlackAdapter } from '@chat-adapter/slack';
141
+ import { createTeamsAdapter } from '@chat-adapter/teams';
142
+ import { createDiscordAdapter } from '@chat-adapter/discord';
143
+ import { createRedisState } from '@chat-adapter/state-redis';
144
+
145
+ export const bot = new Chat({
146
+ userName: 'mybot',
147
+ adapters: {
148
+ slack: createSlackAdapter({ agentView: true }),
149
+ teams: createTeamsAdapter(),
150
+ discord: createDiscordAdapter(),
151
+ },
152
+ state: createRedisState(),
153
+ });
154
+ ```
155
+
156
+ Each adapter gets its own webhook route, such as `bot.webhooks.teams`. Chat SDK normalizes messages and threads across platforms, so your handlers and agent stay the same.
157
+
158
+ To serve a browser chat from the same bot, use the Chat SDK Web adapter with `useChat`. See [Chat SDK Web Adapter](/docs/ai-sdk-ui/transport#chat-sdk-web-adapter).
159
+
160
+ ## Next Steps
161
+
162
+ - **[Slackbot Agent Guide](/cookbook/guides/slackbot)** - Build and deploy a Slack agent with tools step by step
163
+ - **[Tool Approvals](/docs/agents/tool-approvals)** - Approve or deny tool calls automatically or with a human in the loop
164
+ - **[Chat SDK documentation](https://chat-sdk.dev/docs)** - Adapters, cards, modals, slash commands, and more
@@ -68,5 +68,11 @@ The following section shows you how to build agents with the AI SDK - systems wh
68
68
  'Run ToolLoopAgent instances in an interactive terminal interface.',
69
69
  href: '/docs/agents/terminal-ui',
70
70
  },
71
+ {
72
+ title: 'Chat Platforms',
73
+ description:
74
+ 'Run agents in Slack, Microsoft Teams, Discord, and more with Chat SDK.',
75
+ href: '/docs/agents/chat-platforms',
76
+ },
71
77
  ]}
72
78
  />
@@ -319,14 +319,21 @@ context across provider calls and tool executions.
319
319
  import { tracingChannel } from 'node:diagnostics_channel';
320
320
  import {
321
321
  AI_SDK_TELEMETRY_TRACING_CHANNEL,
322
+ type InferTelemetryEvent,
322
323
  type TelemetryTracingChannelMessage,
323
324
  } from 'ai';
324
325
 
325
326
  tracingChannel(AI_SDK_TELEMETRY_TRACING_CHANNEL).subscribe({
326
327
  start(message) {
327
- const telemetryMessage = message as TelemetryTracingChannelMessage;
328
-
329
- if (telemetryMessage.type === 'onStart') {
328
+ const telemetryMessage = message as TelemetryTracingChannelMessage<
329
+ InferTelemetryEvent<unknown>
330
+ >;
331
+
332
+ if (
333
+ telemetryMessage.type === 'generateText' ||
334
+ telemetryMessage.type === 'streamText'
335
+ ) {
336
+ console.log('AI SDK version:', telemetryMessage.event.aiVersion);
330
337
  // Inspect telemetryMessage.event and forward it to your provider.
331
338
  }
332
339
  },
@@ -431,6 +438,12 @@ export function myIntegration(): Telemetry {
431
438
  type: '(event: GenerateTextStepEndEvent) => void | PromiseLike<void>',
432
439
  description: 'Called when a step (LLM call) completes.',
433
440
  },
441
+ {
442
+ name: 'onStepError',
443
+ type: '(event: GenerateTextStepErrorEvent) => void | PromiseLike<void>',
444
+ description:
445
+ 'Called when a step that started fails before it produces a step result. Use it to close step-level spans. The call then ends with `onError`.',
446
+ },
434
447
  {
435
448
  name: 'onEmbedEnd',
436
449
  type: '(event: EmbeddingModelCallEndEvent) => void | PromiseLike<void>',
@@ -299,6 +299,10 @@ A multi-step generation with local tool execution usually runs like this:
299
299
  `onLanguageModelCallStart` and `onLanguageModelCallEnd` describe only the model call inside that step.
300
300
  This distinction matters when the step includes local tool execution: the step duration can be longer than the model response duration.
301
301
 
302
+ When a step that started fails before it produces a step result, `onStepError` is called instead of `onStepEnd`. This happens when the provider call throws or rejects, or when the model stream ends without output. A provider `error` chunk does not fail the step: the step still ends with `onStepEnd` and `finishReason: 'error'`.
303
+
304
+ Aborts and timeouts end the step differently in each function. In `generateText` and `agent.generate()`, they end the step with `onStepError`. Its `error` is the abort or timeout error, or a provider error that has it as its `cause`. In `streamText` and `agent.stream()`, they end the step with `onAbort`.
305
+
302
306
  ## Runtime and Tool Context
303
307
 
304
308
  Generation and step lifecycle callbacks receive the full `runtimeContext` and
@@ -403,6 +407,12 @@ const result = await generateText({
403
407
  description:
404
408
  'Called after each generation step completes. Receives the step result, including usage and performance.',
405
409
  },
410
+ {
411
+ name: 'onStepError',
412
+ type: '(event: GenerateTextStepErrorEvent) => void | Promise<void>',
413
+ description:
414
+ 'Called when a step that started fails before it produces a step result: the provider call throws or rejects, or the model stream ends without output. Receives the `onStepStart` event of the failed step plus `error`. In `generateText`, also called on abort or timeout. In `streamText`, aborts and timeouts call `onAbort` instead.',
415
+ },
406
416
  {
407
417
  name: 'onEnd',
408
418
  type: '(event: GenerateTextEndEvent) => void | Promise<void>',
@@ -271,6 +271,92 @@ WorkflowAgent guide.
271
271
 
272
272
  For the full API reference, see [`WorkflowChatTransport`](/docs/reference/ai-sdk-workflow/workflow-chat-transport). For server-side endpoint setup, see the [WorkflowAgent guide](/docs/agents/workflow-agent#resumable-streaming-with-workflowchattransport).
273
273
 
274
+ ## Chat SDK Web Adapter
275
+
276
+ The [Chat SDK](https://chat-sdk.dev) Web adapter (`@chat-adapter/web`) serves a `useChat` endpoint from a Chat SDK bot. It speaks the [UI message stream protocol](/docs/ai-sdk-ui/stream-protocol), so the default transport works without changes. Use it when the same bot also runs on Slack, Microsoft Teams, Discord, or other [chat platforms](/docs/agents/chat-platforms), and you want one set of handlers for all of them.
277
+
278
+ <InstallPackages packages="ai @ai-sdk/react chat @chat-adapter/web @chat-adapter/state-redis" />
279
+
280
+ On the server, register the Web adapter and handle messages with `onDirectMessage`. Each browser conversation is a direct message thread:
281
+
282
+ ```ts filename="lib/bot.ts"
283
+ import { Chat } from 'chat';
284
+ import { toAiMessages } from 'chat/ai';
285
+ import { createWebAdapter } from '@chat-adapter/web';
286
+ import { createRedisState } from '@chat-adapter/state-redis';
287
+ import { ToolLoopAgent } from 'ai';
288
+ import { getSession } from './session'; // your app's auth
289
+
290
+ const agent = new ToolLoopAgent({
291
+ model: 'anthropic/claude-sonnet-5.5',
292
+ instructions: 'You are a helpful assistant.',
293
+ });
294
+
295
+ export const bot = new Chat({
296
+ userName: 'mybot',
297
+ adapters: {
298
+ web: createWebAdapter({
299
+ userName: 'mybot',
300
+ // Identify the caller from your app's session. Returning null responds with 401.
301
+ getUser: async () => {
302
+ const session = await getSession();
303
+ return session ? { id: session.userId } : null;
304
+ },
305
+ }),
306
+ },
307
+ state: createRedisState(),
308
+ });
309
+
310
+ bot.onDirectMessage(async thread => {
311
+ const { messages } = await bot.history.thread.list(thread.id, {
312
+ limit: 20,
313
+ });
314
+
315
+ const result = await agent.stream({
316
+ messages: await toAiMessages(messages),
317
+ abortSignal: thread.signal,
318
+ });
319
+
320
+ await thread.post(result.fullStream);
321
+ });
322
+ ```
323
+
324
+ ```ts filename="app/api/chat/route.ts"
325
+ import { after } from 'next/server';
326
+ import { bot } from '@/lib/bot';
327
+
328
+ export async function POST(request: Request): Promise<Response> {
329
+ return bot.webhooks.web(request, {
330
+ waitUntil: task => after(() => task),
331
+ });
332
+ }
333
+ ```
334
+
335
+ On the client, point the default transport at the route:
336
+
337
+ ```tsx filename="app/page.tsx"
338
+ 'use client';
339
+
340
+ import { useChat } from '@ai-sdk/react';
341
+ import { DefaultChatTransport } from 'ai';
342
+
343
+ export default function Chat() {
344
+ const { messages, sendMessage } = useChat({
345
+ transport: new DefaultChatTransport({ api: '/api/chat' }),
346
+ });
347
+
348
+ // ... render chat UI
349
+ }
350
+ ```
351
+
352
+ Things to know:
353
+
354
+ - **Authentication**: browser requests have no platform signature, so `getUser` is the security boundary. Resolve the user from your app's session and return `null` to reject the request.
355
+ - **Conversation history**: the adapter ignores the message history in the request body. It stores each conversation's messages in the state adapter, and `bot.history.thread.list()` reads them back. The chat `id` from `useChat` identifies the conversation.
356
+ - **Stopping**: the adapter honors the request's abort signal, so calling `stop()` in `useChat` stops the handler's stream.
357
+
358
+ For all options, including the `ag-ui` protocol and `useChat` wrappers for React, Vue, and Svelte, see the [Web adapter docs](https://chat-sdk.dev/adapters/official/web).
359
+
274
360
  ## Building Custom Transports
275
361
 
276
362
  To understand how to build your own transport, refer to the source code of the default implementation:
@@ -658,6 +658,13 @@ To see `generateText` in action, check out [these examples](#examples).
658
658
  description:
659
659
  'The messages that will be sent to the model for the current step. If prepareStep returns a messages override, those messages carry forward to later steps.',
660
660
  },
661
+ {
662
+ name: 'experimental_steeringMessages',
663
+ type: 'Array<UserModelMessage> | undefined',
664
+ isOptional: true,
665
+ description:
666
+ 'The user messages that were sent with `experimental_steering` and added to `messages` for the current step. Undefined when the step received no steering messages. `messages` already contains them. If you return a `messages` override that is rebuilt from other fields, add them back together with the steering messages of previous steps from `steps`.',
667
+ },
661
668
  {
662
669
  name: 'runtimeContext',
663
670
  type: 'CONTEXT',
@@ -845,6 +852,13 @@ To see `generateText` in action, check out [these examples](#examples).
845
852
  description:
846
853
  'Experimental sandbox environment that is passed through to `prepareStep`, tool description functions, and tool execution. Tools can access it from their description function options and execution options.',
847
854
  },
855
+ {
856
+ name: 'experimental_steering',
857
+ type: 'Experimental_Steering',
858
+ isOptional: true,
859
+ description:
860
+ "Experimental steering object created with `experimental_createSteering()`. Messages sent with `steering.steer()` are added as user messages at the next step boundary, after the current step and its tool calls are done. A waiting message keeps the loop going for another step unless a stop condition is met. `steer()` resolves with `{ type: 'delivered', id, message, stepNumber }` or `{ type: 'not-delivered', id, message, reason }`. See [Steering](/docs/agents/loop-control#steering).",
861
+ },
848
862
  {
849
863
  name: 'experimental_download',
850
864
  type: '(requestedDownloads: Array<{ url: URL; isUrlSupportedByModel: boolean }>) => Promise<Array<null | { data: Uint8Array; mediaType?: string }>>',
@@ -1999,6 +2013,31 @@ To see `generateText` in action, check out [these examples](#examples).
1999
2013
  },
2000
2014
  ],
2001
2015
  },
2016
+ {
2017
+ name: 'onStepError',
2018
+ type: '(event: GenerateTextStepErrorEvent) => Promise<void> | void',
2019
+ isOptional: true,
2020
+ description:
2021
+ 'Callback that is called when a step that started fails before it produces a step result, i.e. when the provider call throws or rejects, or when the call is aborted or times out. `onStepEnd` is not called for such a step. For aborts and timeouts, `error` is the abort or timeout error, or a provider error that has it as its `cause`.',
2022
+ properties: [
2023
+ {
2024
+ type: 'GenerateTextStepErrorEvent',
2025
+ parameters: [
2026
+ {
2027
+ name: '...GenerateTextStepStartEvent',
2028
+ type: 'GenerateTextStepStartEvent',
2029
+ description:
2030
+ 'All properties of the `onStepStart` event of the step that failed, such as `callId`, `stepNumber`, `steps`, `messages`, `runtimeContext`, and `toolsContext`.',
2031
+ },
2032
+ {
2033
+ name: 'error',
2034
+ type: 'unknown',
2035
+ description: 'The error that ended the step.',
2036
+ },
2037
+ ],
2038
+ },
2039
+ ],
2040
+ },
2002
2041
  {
2003
2042
  name: 'onStepFinish',
2004
2043
  type: 'GenerateTextOnStepFinishCallback<TOOLS>',
@@ -3226,6 +3265,13 @@ To see `generateText` in action, check out [these examples](#examples).
3226
3265
  description:
3227
3266
  'Additional provider-specific metadata. They are passed through from the provider to the AI SDK and enable provider-specific results that can be fully encapsulated in the provider.',
3228
3267
  },
3268
+ {
3269
+ name: 'experimental_steeringMessages',
3270
+ type: 'Array<UserModelMessage> | undefined',
3271
+ isOptional: true,
3272
+ description:
3273
+ 'The user messages that were sent with `experimental_steering` and added to the input of this step. Undefined when the step received no steering messages.',
3274
+ },
3229
3275
  ],
3230
3276
  },
3231
3277
  ],
@@ -715,6 +715,13 @@ retain the content their contracts require.
715
715
  description:
716
716
  'The messages that will be sent to the model for the current step. If prepareStep returns a messages override, those messages carry forward to later steps.',
717
717
  },
718
+ {
719
+ name: 'experimental_steeringMessages',
720
+ type: 'Array<UserModelMessage> | undefined',
721
+ isOptional: true,
722
+ description:
723
+ 'The user messages that were sent with `experimental_steering` and added to `messages` for the current step. Undefined when the step received no steering messages. `messages` already contains them. If you return a `messages` override that is rebuilt from other fields, add them back together with the steering messages of previous steps from `steps`.',
724
+ },
718
725
  {
719
726
  name: 'runtimeContext',
720
727
  type: 'CONTEXT',
@@ -902,6 +909,13 @@ retain the content their contracts require.
902
909
  description:
903
910
  'Experimental sandbox environment that is passed through to `prepareStep`, tool description functions, and tool execution. Tools can access it from their description function options and execution options.',
904
911
  },
912
+ {
913
+ name: 'experimental_steering',
914
+ type: 'Experimental_Steering',
915
+ isOptional: true,
916
+ description:
917
+ "Experimental steering object created with `experimental_createSteering()`. Messages sent with `steering.steer()` are added as user messages at the next step boundary, after the current step and its tool calls are done. A waiting message keeps the loop going for another step unless a stop condition is met. `steer()` resolves with `{ type: 'delivered', id, message, stepNumber }` or `{ type: 'not-delivered', id, message, reason }`. See [Steering](/docs/agents/loop-control#steering).",
918
+ },
905
919
  {
906
920
  name: 'experimental_download',
907
921
  type: '(requestedDownloads: Array<{ url: URL; isUrlSupportedByModel: boolean }>) => Promise<Array<null | { data: Uint8Array; mediaType?: string }>>',
@@ -1727,6 +1741,31 @@ retain the content their contracts require.
1727
1741
  },
1728
1742
  ],
1729
1743
  },
1744
+ {
1745
+ name: 'onStepError',
1746
+ type: '(event: GenerateTextStepErrorEvent) => Promise<void> | void',
1747
+ isOptional: true,
1748
+ description:
1749
+ "Callback that is called when a step that started fails before it produces a step result, i.e. when the provider call throws or rejects, or when the model stream ends without output. `onStepEnd` is not called for such a step. A provider `error` chunk does not fail the step: it still ends with `onStepEnd` and `finishReason: 'error'`. Aborts and timeouts end the step with `onAbort` instead.",
1750
+ properties: [
1751
+ {
1752
+ type: 'GenerateTextStepErrorEvent',
1753
+ parameters: [
1754
+ {
1755
+ name: '...GenerateTextStepStartEvent',
1756
+ type: 'GenerateTextStepStartEvent',
1757
+ description:
1758
+ 'All properties of the `onStepStart` event of the step that failed, such as `callId`, `stepNumber`, `steps`, `messages`, `runtimeContext`, and `toolsContext`.',
1759
+ },
1760
+ {
1761
+ name: 'error',
1762
+ type: 'unknown',
1763
+ description: 'The error that ended the step.',
1764
+ },
1765
+ ],
1766
+ },
1767
+ ],
1768
+ },
1730
1769
  {
1731
1770
  name: 'onStepFinish',
1732
1771
  type: 'GenerateTextOnStepFinishCallback<TOOLS>',
@@ -3350,6 +3389,13 @@ retain the content their contracts require.
3350
3389
  description:
3351
3390
  'Optional metadata from the provider. The outer key is the provider name. The inner values are the metadata. Details depend on the provider.',
3352
3391
  },
3392
+ {
3393
+ name: 'experimental_steeringMessages',
3394
+ type: 'Array<UserModelMessage> | undefined',
3395
+ isOptional: true,
3396
+ description:
3397
+ 'The user messages that were sent with `experimental_steering` and added to the input of this step. Undefined when the step received no steering messages.',
3398
+ },
3353
3399
  ],
3354
3400
  },
3355
3401
  ],
@@ -519,6 +519,27 @@ To see `ToolLoopAgent` in action, check out [these examples](#examples).
519
519
  description:
520
520
  'Callback invoked after each agent step (LLM/tool call) completes. If also specified in `generate()` or `stream()`, both callbacks are called (constructor first).',
521
521
  },
522
+ {
523
+ name: 'onStepError',
524
+ type: '(event: GenerateTextStepErrorEvent) => Promise<void> | void',
525
+ isOptional: true,
526
+ description:
527
+ "Callback invoked when an agent step that started fails before it produces a step result, i.e. when the provider call throws or rejects, or when the model stream ends without output. `onStepEnd` is not called for such a step. A provider `error` chunk does not fail the step: it still ends with `onStepEnd` and `finishReason: 'error'`. The event contains all properties of the `onStepStart` event of the step that failed, plus `error`. In `generate()`, aborts and timeouts also end the step here. In `stream()`, they end the step with `onAbort` instead. Only available in the constructor.",
528
+ },
529
+ {
530
+ name: 'onError',
531
+ type: '(event: { error: unknown }) => Promise<void> | void',
532
+ isOptional: true,
533
+ description:
534
+ 'Callback invoked when an error occurs. For `stream()` it receives the errors of the stream, for `generate()` it receives the error before it is thrown. Aborts and timeouts are not reported. When not set, `stream()` logs errors to the console. Only available in the constructor.',
535
+ },
536
+ {
537
+ name: 'onAbort',
538
+ type: '(event: GenerateTextAbortEvent) => Promise<void> | void',
539
+ isOptional: true,
540
+ description:
541
+ 'Callback invoked when `stream()` is aborted or times out. Receives the steps that finished before the abort and the abort reason. `generate()` does not call it: an aborted or timed out `generate()` call rejects, and its step ends with `onStepError`. Only available in the constructor.',
542
+ },
522
543
  {
523
544
  name: 'onStepFinish',
524
545
  type: 'GenerateTextOnStepFinishCallback',
@@ -742,6 +763,13 @@ const result = await agent.generate({
742
763
  description:
743
764
  'Experimental sandbox environment that is passed through to `prepareStep`, tool description functions, and tool execution. Tools can access it from their description function options and execution options.',
744
765
  },
766
+ {
767
+ name: 'experimental_steering',
768
+ type: 'Experimental_Steering',
769
+ isOptional: true,
770
+ description:
771
+ "Experimental steering object created with `experimental_createSteering()`. Messages sent with `steering.steer()` are added as user messages at the next step boundary, after the current step and its tool calls are done. A waiting message keeps the loop going for another step unless a stop condition is met. `steer()` resolves with `{ type: 'delivered', id, message, stepNumber }` or `{ type: 'not-delivered', id, message, reason }`. See [Steering](/docs/agents/loop-control#steering).",
772
+ },
745
773
  {
746
774
  name: 'options',
747
775
  type: 'CALL_OPTIONS',
@@ -858,6 +886,13 @@ for await (const chunk of stream.textStream) {
858
886
  description:
859
887
  'Experimental sandbox environment that is passed through to `prepareStep`, tool description functions, and tool execution. Tools can access it from their description function options and execution options.',
860
888
  },
889
+ {
890
+ name: 'experimental_steering',
891
+ type: 'Experimental_Steering',
892
+ isOptional: true,
893
+ description:
894
+ "Experimental steering object created with `experimental_createSteering()`. Messages sent with `steering.steer()` are added as user messages at the next step boundary, after the current step and its tool calls are done. A waiting message keeps the loop going for another step unless a stop condition is met. `steer()` resolves with `{ type: 'delivered', id, message, stepNumber }` or `{ type: 'not-delivered', id, message, reason }`. See [Steering](/docs/agents/loop-control#steering).",
895
+ },
861
896
  {
862
897
  name: 'options',
863
898
  type: 'CALL_OPTIONS',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai",
3
- "version": "7.0.152",
3
+ "version": "7.0.154",
4
4
  "type": "module",
5
5
  "description": "AI SDK by Vercel - build apps like ChatGPT, Claude, Gemini, and more with a single interface for any model using the Vercel AI Gateway or go direct to OpenAI, Anthropic, Google, or any other model provider.",
6
6
  "license": "Apache-2.0",
@@ -42,20 +42,20 @@
42
42
  }
43
43
  },
44
44
  "dependencies": {
45
- "@ai-sdk/gateway": "4.0.125",
46
- "@ai-sdk/provider": "4.0.33",
47
- "@ai-sdk/provider-utils": "5.0.67"
45
+ "@ai-sdk/gateway": "4.0.127",
46
+ "@ai-sdk/provider": "4.0.34",
47
+ "@ai-sdk/provider-utils": "5.0.69"
48
48
  },
49
49
  "devDependencies": {
50
- "@ai-sdk/amazon-bedrock": "5.0.123",
51
- "@ai-sdk/deepseek": "3.0.72",
52
- "@ai-sdk/google": "4.0.104",
53
- "@ai-sdk/groq": "4.0.69",
54
- "@ai-sdk/huggingface": "2.0.77",
55
- "@ai-sdk/moonshotai": "3.0.77",
56
- "@ai-sdk/openai": "4.0.101",
50
+ "@ai-sdk/amazon-bedrock": "5.0.125",
51
+ "@ai-sdk/deepseek": "3.0.74",
52
+ "@ai-sdk/google": "4.0.106",
53
+ "@ai-sdk/groq": "4.0.71",
54
+ "@ai-sdk/huggingface": "2.0.79",
55
+ "@ai-sdk/moonshotai": "3.0.79",
56
+ "@ai-sdk/openai": "4.0.103",
57
57
  "@ai-sdk/test-server": "2.0.4",
58
- "@ai-sdk/xai": "5.0.29",
58
+ "@ai-sdk/xai": "5.0.31",
59
59
  "@edge-runtime/vm": "^5.0.0",
60
60
  "@smithy/eventstream-codec": "^4.3.3",
61
61
  "@smithy/util-utf8": "^4.3.3",
@@ -16,6 +16,7 @@ import {
16
16
  createAgentUIStream,
17
17
  type AgentUIMessageStreamOptions,
18
18
  } from './create-agent-ui-stream';
19
+ import type { Steering } from '../generate-text/steering';
19
20
 
20
21
  /**
21
22
  * Runs the agent and returns a response object with a UI message stream.
@@ -26,6 +27,7 @@ import {
26
27
  * @param abortSignal - Abort signal. Optional.
27
28
  * @param timeout - Timeout in milliseconds. Optional.
28
29
  * @param experimental_sandbox - The sandbox environment that is passed through to tool execution. Optional.
30
+ * @param experimental_steering - A steering object that adds user messages to the run at the next step boundary. Only supported by `ToolLoopAgent`. Optional.
29
31
  * @param options - The options for the agent. Optional.
30
32
  * @param experimental_transform - Stream transformations. Optional.
31
33
  * @param onStepEnd - Callback that is called when each step ends. Optional.
@@ -70,6 +72,7 @@ export async function createAgentUIStreamResponse<
70
72
  abortSignal?: AbortSignal;
71
73
  timeout?: TimeoutConfiguration<TOOLS>;
72
74
  experimental_sandbox?: SandboxSession;
75
+ experimental_steering?: Steering;
73
76
  options?: CALL_OPTIONS;
74
77
  experimental_transform?: Arrayable<StreamTextTransform<TOOLS>>;
75
78
  } & UIMessageStreamResponseInit &
@@ -28,7 +28,10 @@ import {
28
28
  createAsyncIterableStream,
29
29
  type AsyncIterableStream,
30
30
  } from '../util/async-iterable-stream';
31
+ import { InvalidArgumentError } from '../error/invalid-argument-error';
32
+ import type { Steering } from '../generate-text/steering';
31
33
  import type { Agent } from './agent';
34
+ import { ToolLoopAgent } from './tool-loop-agent';
32
35
 
33
36
  /**
34
37
  * UI stream options with separate generation and UI message step callbacks.
@@ -56,6 +59,7 @@ export type AgentUIMessageStreamOptions<
56
59
  * @param abortSignal - The abort signal. Optional.
57
60
  * @param timeout - Timeout in milliseconds. Optional.
58
61
  * @param experimental_sandbox - The sandbox environment that is passed through to tool execution. Optional.
62
+ * @param experimental_steering - A steering object that adds user messages to the run at the next step boundary. Only supported by `ToolLoopAgent`. Optional.
59
63
  * @param options - The options for the agent.
60
64
  * @param experimental_transform - The stream transformations. Optional.
61
65
  * @param onStepEnd - Callback that is called when each step ends. Optional.
@@ -83,6 +87,7 @@ export async function createAgentUIStream<
83
87
  abortSignal,
84
88
  timeout,
85
89
  experimental_sandbox: sandbox,
90
+ experimental_steering: steering,
86
91
  experimental_transform,
87
92
  onStepEnd,
88
93
  onStepFinish,
@@ -101,12 +106,22 @@ export async function createAgentUIStream<
101
106
  abortSignal?: AbortSignal;
102
107
  timeout?: TimeoutConfiguration<TOOLS>;
103
108
  experimental_sandbox?: SandboxSession;
109
+ experimental_steering?: Steering;
104
110
  options?: CALL_OPTIONS;
105
111
  experimental_transform?: Arrayable<StreamTextTransform<TOOLS>>;
106
112
  // TODO `originalMessages` is part of this for bc, omit in v7
107
113
  } & AgentUIMessageStreamOptions<TOOLS, UI_MESSAGE>): Promise<
108
114
  AsyncIterableStream<InferUIMessageChunk<UI_MESSAGE>>
109
115
  > {
116
+ // other agents would ignore the steering object and never resolve its messages:
117
+ if (steering != null && !(agent instanceof ToolLoopAgent)) {
118
+ throw new InvalidArgumentError({
119
+ parameter: 'experimental_steering',
120
+ value: steering,
121
+ message: 'Steering is only supported by ToolLoopAgent.',
122
+ });
123
+ }
124
+
110
125
  const validatedMessages = await validateUIMessagesForAgent<UI_MESSAGE>({
111
126
  messages: uiMessages,
112
127
  // tools are compatible; the casting is required because the context param is
@@ -130,6 +145,7 @@ export async function createAgentUIStream<
130
145
  abortSignal,
131
146
  timeout,
132
147
  experimental_sandbox: sandbox,
148
+ experimental_steering: steering,
133
149
  experimental_transform,
134
150
  onStepEnd: onStepEnd ?? onStepFinish,
135
151
  });