ai 7.0.0-beta.111 → 7.0.0-beta.113

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 (95) hide show
  1. package/CHANGELOG.md +60 -0
  2. package/dist/index.d.ts +2640 -2457
  3. package/dist/index.js +1136 -640
  4. package/dist/index.js.map +1 -1
  5. package/dist/internal/index.d.ts +324 -262
  6. package/dist/internal/index.js +23 -20
  7. package/dist/internal/index.js.map +1 -1
  8. package/docs/02-foundations/03-prompts.mdx +13 -10
  9. package/docs/03-agents/01-overview.mdx +16 -0
  10. package/docs/03-agents/02-building-agents.mdx +78 -14
  11. package/docs/03-agents/06-subagents.mdx +3 -1
  12. package/docs/03-agents/07-workflow-agent.mdx +130 -3
  13. package/docs/03-agents/index.mdx +4 -2
  14. package/docs/03-ai-sdk-core/05-generating-text.mdx +53 -23
  15. package/docs/03-ai-sdk-core/15-tools-and-tool-calling.mdx +154 -115
  16. package/docs/03-ai-sdk-core/17-runtime-and-tool-context.mdx +215 -0
  17. package/docs/03-ai-sdk-core/60-telemetry.mdx +115 -27
  18. package/docs/03-ai-sdk-core/65-event-listeners.mdx +176 -153
  19. package/docs/03-ai-sdk-core/index.mdx +6 -0
  20. package/docs/04-ai-sdk-ui/03-chatbot-tool-usage.mdx +46 -4
  21. package/docs/04-ai-sdk-ui/50-stream-protocol.mdx +43 -0
  22. package/docs/07-reference/01-ai-sdk-core/01-generate-text.mdx +172 -128
  23. package/docs/07-reference/01-ai-sdk-core/02-stream-text.mdx +172 -110
  24. package/docs/07-reference/01-ai-sdk-core/05-embed.mdx +4 -16
  25. package/docs/07-reference/01-ai-sdk-core/06-embed-many.mdx +4 -16
  26. package/docs/07-reference/01-ai-sdk-core/06-rerank.mdx +2 -2
  27. package/docs/07-reference/01-ai-sdk-core/15-agent.mdx +4 -4
  28. package/docs/07-reference/01-ai-sdk-core/16-tool-loop-agent.mdx +92 -126
  29. package/docs/07-reference/01-ai-sdk-core/17-create-agent-ui-stream.mdx +1 -1
  30. package/docs/07-reference/01-ai-sdk-core/18-create-agent-ui-stream-response.mdx +1 -1
  31. package/docs/07-reference/01-ai-sdk-core/18-pipe-agent-ui-stream-to-response.mdx +1 -1
  32. package/docs/07-reference/01-ai-sdk-core/20-tool.mdx +12 -1
  33. package/docs/07-reference/01-ai-sdk-core/22-dynamic-tool.mdx +1 -1
  34. package/docs/07-reference/01-ai-sdk-core/23-create-mcp-client.mdx +14 -1
  35. package/docs/07-reference/01-ai-sdk-core/29-filter-active-tools.mdx +14 -2
  36. package/docs/07-reference/01-ai-sdk-core/30-model-message.mdx +5 -2
  37. package/docs/07-reference/02-ai-sdk-ui/31-convert-to-model-messages.mdx +9 -0
  38. package/docs/07-reference/03-ai-sdk-rsc/01-stream-ui.mdx +7 -0
  39. package/docs/07-reference/04-ai-sdk-workflow/01-workflow-agent.mdx +48 -29
  40. package/docs/08-migration-guides/23-migration-guide-7-0.mdx +99 -0
  41. package/package.json +9 -7
  42. package/src/agent/agent.ts +10 -10
  43. package/src/agent/create-agent-ui-stream-response.ts +5 -4
  44. package/src/agent/create-agent-ui-stream.ts +5 -4
  45. package/src/agent/index.ts +0 -4
  46. package/src/agent/pipe-agent-ui-stream-to-response.ts +5 -4
  47. package/src/agent/tool-loop-agent-settings.ts +28 -41
  48. package/src/agent/tool-loop-agent.ts +23 -10
  49. package/src/embed/embed-events.ts +8 -44
  50. package/src/embed/embed-many.ts +14 -34
  51. package/src/embed/embed.ts +11 -23
  52. package/src/generate-object/generate-object.ts +20 -24
  53. package/src/generate-object/index.ts +4 -4
  54. package/src/generate-object/stream-object.ts +26 -27
  55. package/src/generate-object/structured-output-events.ts +4 -25
  56. package/src/generate-text/active-tools.ts +12 -0
  57. package/src/generate-text/content-part.ts +5 -3
  58. package/src/generate-text/create-execute-tools-transformation.ts +80 -38
  59. package/src/generate-text/execute-tool-call.ts +31 -37
  60. package/src/generate-text/filter-active-tools.ts +38 -0
  61. package/src/generate-text/{core-events.ts → generate-text-events.ts} +108 -113
  62. package/src/generate-text/generate-text.ts +214 -167
  63. package/src/generate-text/index.ts +29 -14
  64. package/src/generate-text/language-model-events.ts +79 -0
  65. package/src/generate-text/prepare-step.ts +2 -1
  66. package/src/generate-text/resolve-tool-approval.ts +134 -0
  67. package/src/generate-text/restricted-telemetry-dispatcher.ts +247 -0
  68. package/src/generate-text/step-result.ts +0 -9
  69. package/src/generate-text/stream-language-model-call.ts +245 -35
  70. package/src/generate-text/stream-text-result.ts +5 -0
  71. package/src/generate-text/stream-text.ts +168 -215
  72. package/src/generate-text/to-response-messages.ts +34 -2
  73. package/src/generate-text/tool-approval-configuration.ts +128 -0
  74. package/src/generate-text/tool-approval-request-output.ts +7 -0
  75. package/src/generate-text/tool-approval-response-output.ts +35 -0
  76. package/src/generate-text/tool-execution-events.ts +120 -65
  77. package/src/index.ts +1 -0
  78. package/src/prompt/convert-to-language-model-prompt.ts +1 -1
  79. package/src/prompt/prompt.ts +9 -0
  80. package/src/prompt/standardize-prompt.ts +38 -27
  81. package/src/rerank/index.ts +3 -3
  82. package/src/rerank/rerank-events.ts +8 -44
  83. package/src/rerank/rerank.ts +13 -33
  84. package/src/telemetry/{create-unified-telemetry.ts → create-telemetry-dispatcher.ts} +71 -24
  85. package/src/telemetry/index.ts +1 -1
  86. package/src/telemetry/telemetry.ts +92 -36
  87. package/src/ui/convert-to-model-messages.ts +21 -1
  88. package/src/ui/process-ui-message-stream.ts +48 -1
  89. package/src/ui/ui-messages.ts +10 -0
  90. package/src/ui/validate-ui-messages.ts +10 -0
  91. package/src/ui-message-stream/ui-message-chunks.ts +18 -0
  92. package/src/util/merge-objects.ts +5 -0
  93. package/src/generate-text/filter-active-tool.ts +0 -41
  94. package/src/generate-text/is-tool-approval-needed.ts +0 -66
  95. package/src/generate-text/tool-needs-approval-configuration.ts +0 -21
@@ -45,6 +45,14 @@ const result = await generateText({
45
45
  System prompts are the initial set of instructions given to models that help guide and constrain the models' behaviors and responses.
46
46
  You can set system prompts using the `system` property.
47
47
  System prompts work with both the `prompt` and the `messages` properties.
48
+ System messages in `prompt` or `messages` are rejected by default; use the `system` property for system instructions, or set `allowSystemInMessages: true` when you need to send existing message histories that contain system messages.
49
+
50
+ <Note type="warning">
51
+ Opting in with `allowSystemInMessages` can create a prompt injection risk
52
+ where users can override or set the system prompt by injecting system
53
+ messages. In most cases, only trusted server-side code should set system
54
+ instructions via the `system` or `instructions` property.
55
+ </Note>
48
56
 
49
57
  ```ts highlight="3-6"
50
58
  const result = await generateText({
@@ -59,11 +67,6 @@ const result = await generateText({
59
67
  });
60
68
  ```
61
69
 
62
- <Note>
63
- When you use a message prompt, you can also use system messages instead of a
64
- system prompt.
65
- </Note>
66
-
67
70
  ## Message Prompts
68
71
 
69
72
  A message prompt is an array of user, assistant, and tool messages.
@@ -118,10 +121,9 @@ const { text } = await generateText({
118
121
  For granular control over applying provider options at the message level, you can pass `providerOptions` to the message object:
119
122
 
120
123
  ```ts
121
- import { ModelMessage } from 'ai';
122
-
123
- const messages: ModelMessage[] = [
124
- {
124
+ const result = await generateText({
125
+ model: __MODEL__,
126
+ system: {
125
127
  role: 'system',
126
128
  content: 'Cached system message',
127
129
  providerOptions: {
@@ -129,7 +131,8 @@ const messages: ModelMessage[] = [
129
131
  anthropic: { cacheControl: { type: 'ephemeral' } },
130
132
  },
131
133
  },
132
- ];
134
+ prompt: 'Invent a new holiday and describe its traditions.',
135
+ });
133
136
  ```
134
137
 
135
138
  #### Message Part Level
@@ -15,6 +15,21 @@ These components work together:
15
15
  - **Context management** - Maintaining conversation history and deciding what the model sees (input) at each step
16
16
  - **Stopping conditions** - Determining when the loop (task) is complete
17
17
 
18
+ ## Agent State and Context
19
+
20
+ Agents often need server-side state that should not be placed directly in the
21
+ prompt, such as tenant settings, request IDs, feature flags, credentials, or
22
+ progress through a task.
23
+
24
+ Use `runtimeContext` as the agent's shared runtime state. It flows through the
25
+ agent loop, is available in `prepareStep` and lifecycle callbacks, and can be
26
+ updated between steps. Use `toolsContext` for per-tool values such as API keys
27
+ or scoped permissions; each tool receives only its own typed `context` based on
28
+ its `contextSchema`.
29
+
30
+ Learn more in [Runtime and Tool
31
+ Context](/docs/ai-sdk-core/runtime-and-tool-context).
32
+
18
33
  ## ToolLoopAgent Class
19
34
 
20
35
  The ToolLoopAgent class handles these three components. Here's an agent that uses multiple tools in a loop to accomplish a task:
@@ -90,5 +105,6 @@ Agents are flexible and powerful, but non-deterministic. When you need reliable,
90
105
  ## Next Steps
91
106
 
92
107
  - **[Building Agents](/docs/agents/building-agents)** - Guide to creating agents with the ToolLoopAgent
108
+ - **[Runtime and Tool Context](/docs/ai-sdk-core/runtime-and-tool-context)** - How to pass shared agent state and per-tool context
93
109
  - **[Workflow Patterns](/docs/agents/workflows)** - Structured patterns using core functions for complex workflows
94
110
  - **[Loop Control](/docs/agents/loop-control)** - Execution control with stopWhen and prepareStep
@@ -77,9 +77,73 @@ const codeAgent = new ToolLoopAgent({
77
77
  });
78
78
  ```
79
79
 
80
- You can also require approval before a tool executes. Use `needsApproval` on the
81
- tool itself for the default behavior, or set `toolNeedsApproval` on the
82
- `ToolLoopAgent` when approval should be configured per agent:
80
+ ### Context and Agent State
81
+
82
+ Use `runtimeContext` as the agent's shared runtime state. It flows through the
83
+ agent loop and is available in `prepareStep`, lifecycle callbacks, and final
84
+ results. If a tool needs server-side values such as credentials, scoped
85
+ permissions, or default settings, pass them through `toolsContext` and declare
86
+ them with the tool's `contextSchema`.
87
+
88
+ ```ts highlight="8-15,21-31,37-49"
89
+ import { ToolLoopAgent, tool } from 'ai';
90
+ import { z } from 'zod';
91
+
92
+ const agent = new ToolLoopAgent({
93
+ model: __MODEL__,
94
+ tools: {
95
+ searchTickets: tool({
96
+ description: 'Search support tickets',
97
+ inputSchema: z.object({
98
+ query: z.string(),
99
+ }),
100
+ contextSchema: z.object({
101
+ apiKey: z.string(),
102
+ accountId: z.string(),
103
+ }),
104
+ execute: async ({ query }, { context }) =>
105
+ searchTickets(query, context.accountId, context.apiKey),
106
+ }),
107
+ },
108
+ prepareStep: async ({ runtimeContext }) => {
109
+ if (runtimeContext.escalated) {
110
+ return { temperature: 0.1 };
111
+ }
112
+
113
+ return {};
114
+ },
115
+ });
116
+
117
+ const result = await agent.generate({
118
+ prompt: 'Find open billing tickets for this account.',
119
+ runtimeContext: {
120
+ requestId: 'req_abc',
121
+ escalated: false,
122
+ },
123
+ toolsContext: {
124
+ searchTickets: {
125
+ apiKey: process.env.SUPPORT_API_KEY!,
126
+ accountId: 'acct_123',
127
+ },
128
+ },
129
+ });
130
+ ```
131
+
132
+ For the full model, including sensitive context filtering and where each context
133
+ value is available, see [Runtime and Tool
134
+ Context](/docs/ai-sdk-core/runtime-and-tool-context).
135
+
136
+ You can also require approval before a tool executes. Configure approval on the
137
+ `ToolLoopAgent` with `toolApproval`. The older `needsApproval` property on
138
+ tools is deprecated for `ToolLoopAgent`. `toolApproval` can be a
139
+ `GenericToolApprovalFunction` (one callback for every tool call) or a per-tool
140
+ object of statuses and/or `SingleToolApprovalFunction` handlers. For the
141
+ default execution path, use `'not-applicable'` or return `undefined` from an
142
+ approval function. Use `'user-approval'` for manual review, or `'approved'` /
143
+ `'denied'` when you want explicit automatic approval records in the output. For
144
+ automatic approvals and denials, you can also return an object such as
145
+ `{ type: 'denied', reason: 'blocked by policy' }` to attach a reason to the
146
+ approval response:
83
147
 
84
148
  ```ts
85
149
  const agent = new ToolLoopAgent({
@@ -93,8 +157,8 @@ const agent = new ToolLoopAgent({
93
157
  execute: async ({ code }) => ({ output: code }),
94
158
  }),
95
159
  },
96
- toolNeedsApproval: {
97
- runCode: true,
160
+ toolApproval: {
161
+ runCode: 'user-approval',
98
162
  },
99
163
  });
100
164
  ```
@@ -364,21 +428,21 @@ These are useful for logging, observability, debugging, and custom telemetry.
364
428
  const result = await myAgent.generate({
365
429
  prompt: 'Research and summarize the latest AI trends',
366
430
 
367
- experimental_onStart({ model, functionId }) {
368
- console.log('Agent started', { model: model.modelId, functionId });
431
+ experimental_onStart({ modelId }) {
432
+ console.log('Agent started', { modelId });
369
433
  },
370
434
 
371
- experimental_onStepStart({ stepNumber, model }) {
372
- console.log(`Step ${stepNumber} starting`, { model: model.modelId });
435
+ experimental_onStepStart({ stepNumber, modelId }) {
436
+ console.log(`Step ${stepNumber} starting`, { modelId });
373
437
  },
374
438
 
375
439
  experimental_onToolExecutionStart({ toolCall }) {
376
440
  console.log(`Tool call starting: ${toolCall.toolName}`);
377
441
  },
378
442
 
379
- experimental_onToolExecutionEnd({ toolCall, durationMs, success }) {
443
+ experimental_onToolExecutionEnd({ toolCall, durationMs, toolOutput }) {
380
444
  console.log(`Tool call finished: ${toolCall.toolName} (${durationMs}ms)`, {
381
- success,
445
+ success: toolOutput.type === 'tool-result',
382
446
  });
383
447
  },
384
448
 
@@ -402,10 +466,10 @@ const result = await myAgent.generate({
402
466
 
403
467
  The available lifecycle callbacks are:
404
468
 
405
- - **`experimental_onStart`**: Called once when the agent operation begins, before any LLM calls. Receives model info, prompt, settings, and `runtimeContext`.
469
+ - **`experimental_onStart`**: Called once when the agent operation begins, before any LLM calls. Receives model info, messages, settings, and `runtimeContext`.
406
470
  - **`experimental_onStepStart`**: Called before each step (LLM call). Receives the step number, model, messages being sent, tools, and prior steps.
407
- - **`experimental_onToolExecutionStart`**: Called right before a tool's `execute` function runs. Receives the tool call object with tool name, call ID, and input.
408
- - **`experimental_onToolExecutionEnd`**: Called right after a tool's `execute` function completes or errors. Receives the tool call, `durationMs`, and a `success` discriminator (`output` when successful, `error` when failed).
471
+ - **`experimental_onToolExecutionStart`**: Called right before a tool's `execute` function runs. Receives the tool call object, messages, and `toolContext`.
472
+ - **`experimental_onToolExecutionEnd`**: Called right after a tool's `execute` function completes or errors. Receives the tool call, `durationMs`, and a `toolOutput` discriminated union (`type: 'tool-result'` with `output`, or `type: 'tool-error'` with `error`).
409
473
  - **`onStepFinish`**: Called after each step finishes. Receives step results including usage, finish reason, and tool calls.
410
474
  - **`onFinish`**: Called when all steps are finished and the response is complete. Receives all step results, total usage, and `runtimeContext`.
411
475
 
@@ -334,7 +334,9 @@ export function Chat() {
334
334
 
335
335
  ### No Tool Approvals in Subagents
336
336
 
337
- Subagent tools cannot use `needsApproval`. All tools must execute automatically without user confirmation.
337
+ Subagent tools cannot use approval flows such as `toolApproval` (or the
338
+ deprecated `needsApproval`). All tools must execute automatically without user
339
+ confirmation.
338
340
 
339
341
  ### Subagent Context is Isolated
340
342
 
@@ -302,7 +302,11 @@ Tools without `'use step'` still work but run as regular in-memory functions wit
302
302
 
303
303
  ## Tool Approval
304
304
 
305
- Tools can require human approval before execution. When a tool has `needsApproval` set, the agent pauses and emits an approval request to the writable stream. The workflow suspends until the user approves or denies:
305
+ For `WorkflowAgent`, human approval is configured on the tool definition with
306
+ `needsApproval`. This is specific to `WorkflowAgent`; for `generateText`,
307
+ `streamText`, and `ToolLoopAgent`, use `toolApproval` instead. When a workflow
308
+ tool has `needsApproval` set, the agent pauses and emits an approval request to
309
+ the writable stream. The workflow suspends until the user approves or denies:
306
310
 
307
311
  ```ts
308
312
  const agent = new WorkflowAgent({
@@ -426,7 +430,7 @@ Agents provide lifecycle callbacks for logging, observability, and custom teleme
426
430
  const agent = new WorkflowAgent({
427
431
  model: "anthropic/claude-sonnet-4-6",
428
432
 
429
- experimental_onStart({ model, messages }) {
433
+ experimental_onStart({ modelId, messages }) {
430
434
  console.log("Agent started");
431
435
  },
432
436
 
@@ -438,7 +442,7 @@ const agent = new WorkflowAgent({
438
442
  console.log(`Calling tool: ${toolCall.toolName}`);
439
443
  },
440
444
 
441
- experimental_onToolExecutionEnd({ toolCall, result, error }) {
445
+ experimental_onToolExecutionEnd({ toolCall, toolOutput }) {
442
446
  console.log(`Tool finished: ${toolCall.toolName}`);
443
447
  },
444
448
 
@@ -466,6 +470,129 @@ const myAgent = new WorkflowAgent({
466
470
  export type MyAgentUIMessage = InferWorkflowAgentUIMessage<typeof myAgent>;
467
471
  ```
468
472
 
473
+ ## Migrating from `DurableAgent`
474
+
475
+ `WorkflowAgent` replaces the Workflow DevKit's [`DurableAgent`](https://workflow-sdk.dev/docs/api-reference/workflow-ai/durable-agent). The two share the same core idea — a durable agent loop that runs inside a workflow — but `WorkflowAgent` moves the class into the AI SDK, tightens typing, and introduces first-class tool approval. If you are using `DurableAgent` today, follow the steps below to switch.
476
+
477
+ ### Change the import and class name
478
+
479
+ `DurableAgent` was exported from `workflow/ai`. `WorkflowAgent` is exported from `@ai-sdk/workflow`, alongside its helpers.
480
+
481
+ ```diff
482
+ - import { DurableAgent } from 'workflow/ai';
483
+ + import { WorkflowAgent, type ModelCallStreamPart } from '@ai-sdk/workflow';
484
+
485
+ - const agent = new DurableAgent({
486
+ + const agent = new WorkflowAgent({
487
+ model: 'anthropic/claude-sonnet-4-6',
488
+ instructions: 'You are a helpful assistant.',
489
+ tools: { /* ... */ },
490
+ });
491
+ ```
492
+
493
+ Install the new package alongside `workflow`:
494
+
495
+ ```bash
496
+ npm install @ai-sdk/workflow
497
+ ```
498
+
499
+ ### Write `ModelCallStreamPart`, not `UIMessageChunk`
500
+
501
+ `DurableAgent` wrote `UIMessageChunk` objects directly to the writable returned by `getWritable()`. `WorkflowAgent` writes the lower-level `ModelCallStreamPart` shape and leaves the conversion to a transform at the response boundary. This keeps the durable stream provider-shaped and avoids baking a UI protocol into the workflow payload.
502
+
503
+ ```diff
504
+ // Inside the workflow
505
+ await agent.stream({
506
+ messages,
507
+ - writable: getWritable<UIMessageChunk>(),
508
+ + writable: getWritable<ModelCallStreamPart>(),
509
+ });
510
+ ```
511
+
512
+ ```diff
513
+ // Inside the route handler
514
+ + import { createModelCallToUIChunkTransform } from '@ai-sdk/workflow';
515
+
516
+ return createUIMessageStreamResponse({
517
+ - stream: run.readable,
518
+ + stream: run.readable.pipeThrough(createModelCallToUIChunkTransform()),
519
+ });
520
+ ```
521
+
522
+ ### Replace `maxSteps` with `stopWhen`
523
+
524
+ `DurableAgent` accepted `maxSteps` directly. `WorkflowAgent` uses the AI SDK's shared `stopWhen` conditions so the same stop logic works across `ToolLoopAgent`, `generateText`, and `streamText`.
525
+
526
+ ```diff
527
+ + import { isStepCount } from 'ai';
528
+
529
+ await agent.stream({
530
+ messages,
531
+ - maxSteps: 10,
532
+ + stopWhen: isStepCount(10),
533
+ });
534
+ ```
535
+
536
+ See [Loop Control](/docs/agents/loop-control) for the full list of stop conditions.
537
+
538
+ ### Replace `experimental_output` with `output`
539
+
540
+ ```diff
541
+ + import { Output } from '@ai-sdk/workflow';
542
+
543
+ await agent.stream({
544
+ messages,
545
+ - experimental_output: Output.object({ schema }),
546
+ + output: Output.object({ schema }),
547
+ });
548
+ ```
549
+
550
+ The returned value is now on `result.output` (previously `result.experimental_output`).
551
+
552
+ ### WorkflowAgent: Use `needsApproval` for human-in-the-loop tools
553
+
554
+ With `DurableAgent`, tool approval was implemented by calling a Hook from inside the tool's `execute` function. `WorkflowAgent` makes approval a first-class tool property — the agent emits the approval request, suspends the workflow, and resumes automatically when the user responds.
555
+
556
+ ```diff
557
+ bookFlight: tool({
558
+ description: 'Book a flight',
559
+ inputSchema: z.object({ flightId: z.string() }),
560
+ + needsApproval: true,
561
+ - execute: async (input) => {
562
+ - const approved = await waitForApprovalHook(input);
563
+ - if (!approved) throw new Error('Denied');
564
+ - return bookFlightStep(input);
565
+ - },
566
+ + execute: bookFlightStep,
567
+ }),
568
+ ```
569
+
570
+ `needsApproval` also accepts an async function so you can decide per-input
571
+ whether approval is required (see [Tool Approval](#tool-approval) above).
572
+
573
+ ### `uiMessages` / `collectUIMessages` is gone
574
+
575
+ `DurableAgent.stream()` returned accumulated `uiMessages` when `collectUIMessages: true` was set. `WorkflowAgent.stream()` returns `ModelMessage[]` on `result.messages` instead — persist those and convert them at the edge with `convertToModelMessages` / `convertToUIMessages` as needed.
576
+
577
+ ```diff
578
+ const result = await agent.stream({
579
+ messages,
580
+ writable: getWritable<ModelCallStreamPart>(),
581
+ - collectUIMessages: true,
582
+ });
583
+
584
+ - return { uiMessages: result.uiMessages };
585
+ + return { messages: result.messages };
586
+ ```
587
+
588
+ ### No `generate()` method
589
+
590
+ `WorkflowAgent` only exposes `stream()`. If you were calling `agent.generate()`, switch to `stream()` and read `result.messages` / `result.output` once the promise resolves.
591
+
592
+ ### Everything else
593
+
594
+ Other options carry over with the same names: `prepareStep`, `onStepFinish`, `onFinish`, `onError`, `toolChoice`, `activeTools`, `timeout`, `experimental_context`, `experimental_repairToolCall`, and the usual generation settings (`temperature`, `maxOutputTokens`, `topP`, …). `WorkflowAgent` additionally adds `prepareCall` (runs once before the loop) and the `experimental_onStart` / `experimental_onStepStart` / `experimental_onToolExecutionStart` / `experimental_onToolExecutionEnd` lifecycle callbacks documented above.
595
+
469
596
  ## Next Steps
470
597
 
471
598
  - [WorkflowAgent API Reference](/docs/reference/ai-sdk-workflow/workflow-agent) for detailed parameter documentation
@@ -11,12 +11,14 @@ The following section shows you how to build agents with the AI SDK - systems wh
11
11
  cards={[
12
12
  {
13
13
  title: 'Overview',
14
- description: 'Learn what agents are and why to use the ToolLoopAgent.',
14
+ description:
15
+ 'Learn what agents are, how they manage state, and why to use the ToolLoopAgent.',
15
16
  href: '/docs/agents/overview',
16
17
  },
17
18
  {
18
19
  title: 'Building Agents',
19
- description: 'Complete guide to creating agents with the ToolLoopAgent.',
20
+ description:
21
+ 'Create ToolLoopAgent instances with tools, runtime context, and loop control.',
20
22
  href: '/docs/agents/building-agents',
21
23
  },
22
24
  {
@@ -127,21 +127,35 @@ const result = await generateText({
127
127
  // ... your tools
128
128
  },
129
129
 
130
- experimental_onStart({ model, settings, functionId }) {
131
- console.log('Generation started', { model, functionId });
130
+ experimental_onStart({ modelId }) {
131
+ console.log('Generation started', { modelId });
132
132
  },
133
133
 
134
- experimental_onStepStart({ stepNumber, model, promptMessages }) {
135
- console.log(`Step ${stepNumber} starting`, { model: model.modelId });
134
+ experimental_onStepStart({ stepNumber, modelId, messages }) {
135
+ console.log(`Step ${stepNumber} starting`, { modelId });
136
136
  },
137
137
 
138
- experimental_onToolExecutionStart({ toolName, toolCallId, input }) {
139
- console.log(`Tool call starting: ${toolName}`, { toolCallId });
138
+ experimental_onLanguageModelCallStart({ modelId, messages }) {
139
+ console.log('Model call starting', { modelId, messageCount: messages.length });
140
140
  },
141
141
 
142
- experimental_onToolExecutionEnd({ toolName, durationMs, error }) {
143
- console.log(`Tool call finished: ${toolName} (${durationMs}ms)`, {
144
- success: !error,
142
+ experimental_onLanguageModelCallEnd({ modelId, finishReason, content }) {
143
+ console.log('Model call finished', {
144
+ modelId,
145
+ finishReason,
146
+ contentParts: content.length,
147
+ });
148
+ },
149
+
150
+ experimental_onToolExecutionStart({ toolCall }) {
151
+ console.log(`Tool call starting: ${toolCall.toolName}`, {
152
+ toolCallId: toolCall.toolCallId,
153
+ });
154
+ },
155
+
156
+ experimental_onToolExecutionEnd({ toolCall, durationMs, toolOutput }) {
157
+ console.log(`Tool call finished: ${toolCall.toolName} (${durationMs}ms)`, {
158
+ success: toolOutput.type === 'tool-result',
145
159
  });
146
160
  },
147
161
 
@@ -153,11 +167,13 @@ const result = await generateText({
153
167
 
154
168
  The available lifecycle callbacks are:
155
169
 
156
- - **`experimental_onStart`**: Called once when the `generateText` operation begins, before any LLM calls. Receives model info, prompt, settings, and `runtimeContext`.
157
- - **`experimental_onStepStart`**: Called before each step (LLM call). Receives the step number, model, prompt messages being sent, tools, and prior steps.
158
- - **`experimental_onToolExecutionStart`**: Called right before a tool's `execute` function runs. Receives the tool name, call ID, and input.
159
- - **`experimental_onToolExecutionEnd`**: Called right after a tool's `execute` function completes or errors. Receives the tool name, call ID, input, output (or undefined on error), error (or undefined on success), and `durationMs`.
160
- - **`onStepFinish`**: Called after each step finishes. Now also includes `stepNumber` (zero-based index of the completed step).
170
+ - **`experimental_onStart`**: Called once when the `generateText` operation begins, before any LLM calls. Receives model info, messages, settings, and `runtimeContext`.
171
+ - **`experimental_onStepStart`**: Called before each step (LLM call). Receives the step number, model, messages being sent, tools, and prior steps.
172
+ - **`experimental_onLanguageModelCallStart`**: Called immediately before the provider model call begins. Useful when you want to observe the model invocation separately from later tool execution.
173
+ - **`experimental_onLanguageModelCallEnd`**: Called after the model response has been normalized and parsed, but before any client-side tool execution begins. Receives the model-call content parts, usage, and finish reason.
174
+ - **`experimental_onToolExecutionStart`**: Called right before a tool's `execute` function runs. Receives the tool call object, messages, and `toolContext`.
175
+ - **`experimental_onToolExecutionEnd`**: Called right after a tool's `execute` function completes or errors. Receives the tool call object, `durationMs`, and a `toolOutput` discriminated union (`type: 'tool-result'` with `output`, or `type: 'tool-error'` with `error`).
176
+ - **`onStepFinish`**: Called after each step finishes. Includes `stepNumber` (zero-based index of the completed step).
161
177
 
162
178
  ## `streamText`
163
179
 
@@ -319,12 +335,24 @@ const result = streamText({
319
335
  // ... your tools
320
336
  },
321
337
 
322
- experimental_onStart({ model, system, prompt, messages }) {
323
- console.log('Streaming started', { model, prompt });
338
+ experimental_onStart({ modelId, system, messages }) {
339
+ console.log('Streaming started', { modelId });
340
+ },
341
+
342
+ experimental_onStepStart({ stepNumber, modelId, messages }) {
343
+ console.log(`Step ${stepNumber} starting`, { modelId });
344
+ },
345
+
346
+ experimental_onLanguageModelCallStart({ modelId, messages }) {
347
+ console.log('Model call starting', { modelId, messageCount: messages.length });
324
348
  },
325
349
 
326
- experimental_onStepStart({ stepNumber, model, messages }) {
327
- console.log(`Step ${stepNumber} starting`, { model: model.modelId });
350
+ experimental_onLanguageModelCallEnd({ modelId, finishReason, content }) {
351
+ console.log('Model call finished', {
352
+ modelId,
353
+ finishReason,
354
+ contentParts: content.length,
355
+ });
328
356
  },
329
357
 
330
358
  experimental_onToolExecutionStart({ toolCall }) {
@@ -333,9 +361,9 @@ const result = streamText({
333
361
  });
334
362
  },
335
363
 
336
- experimental_onToolExecutionEnd({ toolCall, durationMs, success, error }) {
364
+ experimental_onToolExecutionEnd({ toolCall, durationMs, toolOutput }) {
337
365
  console.log(`Tool call finished: ${toolCall.toolName} (${durationMs}ms)`, {
338
- success,
366
+ success: toolOutput.type === 'tool-result',
339
367
  });
340
368
  },
341
369
 
@@ -347,10 +375,12 @@ const result = streamText({
347
375
 
348
376
  The available lifecycle callbacks are:
349
377
 
350
- - **`experimental_onStart`**: Called once when the `streamText` operation begins, before any LLM calls. Receives model info, prompt, settings, and `runtimeContext`.
378
+ - **`experimental_onStart`**: Called once when the `streamText` operation begins, before any LLM calls. Receives model info, messages, settings, and `runtimeContext`.
351
379
  - **`experimental_onStepStart`**: Called before each step (LLM call). Receives the step number, model, messages being sent, tools, and prior steps.
352
- - **`experimental_onToolExecutionStart`**: Called right before a tool's `execute` function runs. Receives the tool call object, messages, and the tool-specific context for that call.
353
- - **`experimental_onToolExecutionEnd`**: Called right after a tool's `execute` function completes or errors. Receives the tool call object, `durationMs`, and a discriminated union with `success`/`output` or `success`/`error`.
380
+ - **`experimental_onLanguageModelCallStart`**: Called immediately before the provider model call begins. Useful when you want to observe the model invocation separately from later tool execution.
381
+ - **`experimental_onLanguageModelCallEnd`**: Called after the model response has been normalized and parsed, but before any client-side tool execution begins. Receives the model-call content parts, usage, and finish reason.
382
+ - **`experimental_onToolExecutionStart`**: Called right before a tool's `execute` function runs. Receives the tool call object, messages, and `toolContext`.
383
+ - **`experimental_onToolExecutionEnd`**: Called right after a tool's `execute` function completes or errors. Receives the tool call object, `durationMs`, and a `toolOutput` discriminated union (`type: 'tool-result'` with `output`, or `type: 'tool-error'` with `error`).
354
384
  - **`onStepFinish`**: Called after each step finishes. Receives the finish reason, usage, and other step details.
355
385
 
356
386
  ### `fullStream` property