@mastra/mcp-docs-server 1.0.0-beta.11 → 1.0.0-beta.13

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 (159) hide show
  1. package/.docs/organized/changelogs/%40mastra%2Fagent-builder.md +12 -12
  2. package/.docs/organized/changelogs/%40mastra%2Fai-sdk.md +24 -24
  3. package/.docs/organized/changelogs/%40mastra%2Fclickhouse.md +45 -45
  4. package/.docs/organized/changelogs/%40mastra%2Fclient-js.md +83 -83
  5. package/.docs/organized/changelogs/%40mastra%2Fcloudflare-d1.md +39 -39
  6. package/.docs/organized/changelogs/%40mastra%2Fcloudflare.md +39 -39
  7. package/.docs/organized/changelogs/%40mastra%2Fconvex.md +38 -0
  8. package/.docs/organized/changelogs/%40mastra%2Fcore.md +174 -174
  9. package/.docs/organized/changelogs/%40mastra%2Fdeployer-cloud.md +17 -17
  10. package/.docs/organized/changelogs/%40mastra%2Fdeployer.md +27 -27
  11. package/.docs/organized/changelogs/%40mastra%2Fdynamodb.md +39 -39
  12. package/.docs/organized/changelogs/%40mastra%2Ffastembed.md +6 -0
  13. package/.docs/organized/changelogs/%40mastra%2Flance.md +39 -39
  14. package/.docs/organized/changelogs/%40mastra%2Flibsql.md +45 -45
  15. package/.docs/organized/changelogs/%40mastra%2Fmcp-docs-server.md +15 -15
  16. package/.docs/organized/changelogs/%40mastra%2Fmemory.md +13 -13
  17. package/.docs/organized/changelogs/%40mastra%2Fmongodb.md +39 -39
  18. package/.docs/organized/changelogs/%40mastra%2Fmssql.md +39 -39
  19. package/.docs/organized/changelogs/%40mastra%2Fpg.md +45 -45
  20. package/.docs/organized/changelogs/%40mastra%2Fplayground-ui.md +75 -75
  21. package/.docs/organized/changelogs/%40mastra%2Freact.md +40 -0
  22. package/.docs/organized/changelogs/%40mastra%2Fschema-compat.md +6 -0
  23. package/.docs/organized/changelogs/%40mastra%2Fserver.md +29 -29
  24. package/.docs/organized/changelogs/%40mastra%2Fupstash.md +39 -39
  25. package/.docs/organized/changelogs/create-mastra.md +29 -29
  26. package/.docs/organized/changelogs/mastra.md +35 -35
  27. package/.docs/organized/code-examples/quick-start.md +0 -4
  28. package/.docs/organized/code-examples/stock-price-tool.md +21 -2
  29. package/.docs/raw/agents/agent-approval.mdx +136 -2
  30. package/.docs/raw/agents/agent-memory.mdx +4 -4
  31. package/.docs/raw/agents/guardrails.mdx +1 -1
  32. package/.docs/raw/agents/networks.mdx +1 -1
  33. package/.docs/raw/agents/overview.mdx +2 -2
  34. package/.docs/raw/agents/using-tools.mdx +1 -1
  35. package/.docs/raw/course/01-first-agent/07-creating-your-agent.md +1 -2
  36. package/.docs/raw/course/01-first-agent/12-connecting-tool-to-agent.md +1 -1
  37. package/.docs/raw/course/01-first-agent/16-adding-memory-to-agent.md +1 -2
  38. package/.docs/raw/course/02-agent-tools-mcp/05-updating-your-agent.md +1 -1
  39. package/.docs/raw/course/02-agent-tools-mcp/10-updating-agent-instructions-zapier.md +1 -1
  40. package/.docs/raw/course/02-agent-tools-mcp/16-updating-agent-instructions-github.md +1 -1
  41. package/.docs/raw/course/02-agent-tools-mcp/21-updating-agent-instructions-hackernews.md +1 -1
  42. package/.docs/raw/course/02-agent-tools-mcp/27-updating-agent-instructions-filesystem.md +1 -1
  43. package/.docs/raw/course/02-agent-tools-mcp/31-enhancing-memory-configuration.md +2 -2
  44. package/.docs/raw/course/03-agent-memory/04-creating-basic-memory-agent.md +1 -2
  45. package/.docs/raw/course/03-agent-memory/08-configuring-conversation-history.md +1 -2
  46. package/.docs/raw/course/03-agent-memory/16-configuring-semantic-recall.md +3 -4
  47. package/.docs/raw/course/03-agent-memory/21-configuring-working-memory.md +2 -3
  48. package/.docs/raw/course/03-agent-memory/22-custom-working-memory-templates.md +2 -3
  49. package/.docs/raw/course/03-agent-memory/25-combining-memory-features.md +1 -2
  50. package/.docs/raw/course/03-agent-memory/27-creating-learning-assistant.md +2 -3
  51. package/.docs/raw/course/04-workflows/11-creating-an-ai-agent.md +2 -3
  52. package/.docs/raw/deployment/cloud-providers.mdx +20 -0
  53. package/.docs/raw/deployment/{building-mastra.mdx → mastra-server.mdx} +2 -2
  54. package/.docs/raw/deployment/monorepo.mdx +23 -44
  55. package/.docs/raw/deployment/overview.mdx +28 -53
  56. package/.docs/raw/deployment/web-framework.mdx +12 -14
  57. package/.docs/raw/getting-started/start.mdx +10 -1
  58. package/.docs/raw/getting-started/studio.mdx +1 -1
  59. package/.docs/raw/guides/build-your-ui/ai-sdk-ui.mdx +1021 -67
  60. package/.docs/raw/{deployment/cloud-providers → guides/deployment}/aws-lambda.mdx +3 -6
  61. package/.docs/raw/{deployment/cloud-providers → guides/deployment}/azure-app-services.mdx +4 -6
  62. package/.docs/raw/{deployment/cloud-providers → guides/deployment}/cloudflare-deployer.mdx +4 -0
  63. package/.docs/raw/{deployment/cloud-providers → guides/deployment}/digital-ocean.mdx +3 -6
  64. package/.docs/raw/guides/deployment/index.mdx +32 -0
  65. package/.docs/raw/{deployment/cloud-providers → guides/deployment}/netlify-deployer.mdx +4 -0
  66. package/.docs/raw/{deployment/cloud-providers → guides/deployment}/vercel-deployer.mdx +4 -0
  67. package/.docs/raw/guides/getting-started/express.mdx +71 -152
  68. package/.docs/raw/guides/getting-started/hono.mdx +227 -0
  69. package/.docs/raw/guides/getting-started/next-js.mdx +173 -63
  70. package/.docs/raw/guides/getting-started/vite-react.mdx +307 -137
  71. package/.docs/raw/guides/guide/research-assistant.mdx +4 -4
  72. package/.docs/raw/guides/migrations/upgrade-to-v1/client.mdx +17 -0
  73. package/.docs/raw/guides/migrations/upgrade-to-v1/overview.mdx +6 -0
  74. package/.docs/raw/index.mdx +1 -1
  75. package/.docs/raw/{deployment/mastra-cloud → mastra-cloud}/dashboard.mdx +2 -6
  76. package/.docs/raw/{deployment/mastra-cloud → mastra-cloud}/observability.mdx +1 -5
  77. package/.docs/raw/{deployment/mastra-cloud → mastra-cloud}/overview.mdx +2 -6
  78. package/.docs/raw/{deployment/mastra-cloud → mastra-cloud}/setting-up.mdx +3 -6
  79. package/.docs/raw/memory/overview.mdx +1 -1
  80. package/.docs/raw/memory/storage/memory-with-libsql.mdx +1 -1
  81. package/.docs/raw/memory/storage/memory-with-mongodb.mdx +1 -1
  82. package/.docs/raw/memory/storage/memory-with-pg.mdx +1 -1
  83. package/.docs/raw/memory/storage/memory-with-upstash.mdx +1 -1
  84. package/.docs/raw/{server-db/storage.mdx → memory/storage/overview.mdx} +2 -2
  85. package/.docs/raw/observability/logging.mdx +1 -1
  86. package/.docs/raw/observability/tracing/exporters/cloud.mdx +1 -1
  87. package/.docs/raw/observability/tracing/exporters/default.mdx +1 -1
  88. package/.docs/raw/rag/chunking-and-embedding.mdx +12 -25
  89. package/.docs/raw/rag/graph-rag.mdx +220 -0
  90. package/.docs/raw/rag/overview.mdx +1 -2
  91. package/.docs/raw/rag/retrieval.mdx +13 -29
  92. package/.docs/raw/rag/vector-databases.mdx +7 -3
  93. package/.docs/raw/reference/agents/getDefaultGenerateOptions.mdx +1 -1
  94. package/.docs/raw/reference/agents/getDefaultOptions.mdx +1 -1
  95. package/.docs/raw/reference/agents/getDefaultStreamOptions.mdx +1 -1
  96. package/.docs/raw/reference/agents/getInstructions.mdx +1 -1
  97. package/.docs/raw/reference/agents/getLLM.mdx +1 -1
  98. package/.docs/raw/reference/agents/getMemory.mdx +1 -1
  99. package/.docs/raw/reference/agents/getModel.mdx +1 -1
  100. package/.docs/raw/reference/agents/listScorers.mdx +1 -1
  101. package/.docs/raw/reference/ai-sdk/chat-route.mdx +1 -1
  102. package/.docs/raw/reference/ai-sdk/handle-chat-stream.mdx +1 -1
  103. package/.docs/raw/reference/ai-sdk/handle-network-stream.mdx +1 -1
  104. package/.docs/raw/reference/ai-sdk/handle-workflow-stream.mdx +1 -1
  105. package/.docs/raw/reference/ai-sdk/network-route.mdx +1 -1
  106. package/.docs/raw/reference/ai-sdk/to-ai-sdk-v4-messages.mdx +127 -0
  107. package/.docs/raw/reference/ai-sdk/to-ai-sdk-v5-messages.mdx +107 -0
  108. package/.docs/raw/reference/ai-sdk/workflow-route.mdx +1 -1
  109. package/.docs/raw/reference/auth/auth0.mdx +1 -1
  110. package/.docs/raw/reference/auth/clerk.mdx +1 -1
  111. package/.docs/raw/reference/auth/firebase.mdx +1 -1
  112. package/.docs/raw/reference/auth/jwt.mdx +1 -1
  113. package/.docs/raw/reference/auth/supabase.mdx +1 -1
  114. package/.docs/raw/reference/auth/workos.mdx +1 -1
  115. package/.docs/raw/reference/cli/mastra.mdx +1 -1
  116. package/.docs/raw/reference/client-js/mastra-client.mdx +1 -1
  117. package/.docs/raw/reference/client-js/workflows.mdx +20 -0
  118. package/.docs/raw/reference/core/getServer.mdx +2 -2
  119. package/.docs/raw/reference/core/getStorage.mdx +1 -1
  120. package/.docs/raw/reference/core/getStoredAgentById.mdx +1 -1
  121. package/.docs/raw/reference/core/listStoredAgents.mdx +1 -1
  122. package/.docs/raw/reference/core/setStorage.mdx +1 -1
  123. package/.docs/raw/reference/logging/pino-logger.mdx +1 -1
  124. package/.docs/raw/reference/rag/database-config.mdx +1 -1
  125. package/.docs/raw/reference/server/create-route.mdx +1 -1
  126. package/.docs/raw/reference/server/express-adapter.mdx +4 -4
  127. package/.docs/raw/reference/server/hono-adapter.mdx +4 -4
  128. package/.docs/raw/reference/server/mastra-server.mdx +2 -2
  129. package/.docs/raw/reference/server/routes.mdx +28 -1
  130. package/.docs/raw/reference/streaming/agents/stream.mdx +22 -0
  131. package/.docs/raw/reference/streaming/workflows/stream.mdx +33 -20
  132. package/.docs/raw/reference/tools/create-tool.mdx +23 -1
  133. package/.docs/raw/reference/tools/graph-rag-tool.mdx +3 -3
  134. package/.docs/raw/reference/tools/vector-query-tool.mdx +3 -3
  135. package/.docs/raw/reference/workflows/run-methods/startAsync.mdx +143 -0
  136. package/.docs/raw/reference/workflows/workflow-methods/create-run.mdx +35 -0
  137. package/.docs/raw/reference/workflows/workflow.mdx +14 -0
  138. package/.docs/raw/{auth → server/auth}/auth0.mdx +1 -1
  139. package/.docs/raw/{auth → server/auth}/clerk.mdx +1 -1
  140. package/.docs/raw/{auth → server/auth}/firebase.mdx +1 -1
  141. package/.docs/raw/{auth → server/auth}/index.mdx +6 -6
  142. package/.docs/raw/{auth → server/auth}/jwt.mdx +1 -1
  143. package/.docs/raw/{auth → server/auth}/supabase.mdx +1 -1
  144. package/.docs/raw/{auth → server/auth}/workos.mdx +1 -1
  145. package/.docs/raw/{server-db → server}/custom-adapters.mdx +3 -3
  146. package/.docs/raw/{server-db → server}/custom-api-routes.mdx +1 -1
  147. package/.docs/raw/{server-db → server}/mastra-client.mdx +2 -2
  148. package/.docs/raw/{server-db → server}/mastra-server.mdx +5 -5
  149. package/.docs/raw/{server-db → server}/middleware.mdx +2 -2
  150. package/.docs/raw/{server-db → server}/request-context.mdx +3 -3
  151. package/.docs/raw/{server-db → server}/server-adapters.mdx +6 -6
  152. package/.docs/raw/tools-mcp/overview.mdx +2 -2
  153. package/.docs/raw/workflows/error-handling.mdx +162 -1
  154. package/.docs/raw/workflows/overview.mdx +2 -2
  155. package/CHANGELOG.md +14 -0
  156. package/package.json +3 -3
  157. package/.docs/organized/changelogs/%40internal%2Fai-sdk-v4.md +0 -1
  158. package/.docs/raw/deployment/cloud-providers/index.mdx +0 -55
  159. /package/.docs/raw/{deployment/cloud-providers → guides/deployment}/amazon-ec2.mdx +0 -0
@@ -64,7 +64,7 @@ Once you have your API routes set up, you can use them in the [`useChat()`](#use
64
64
 
65
65
  ### Mastra's server
66
66
 
67
- Run Mastra as a standalone server and connect your frontend (e.g. using Vite + React) to its API endpoints. You'll be using Mastra's [custom API routes](/docs/v1/server-db/custom-api-routes) feature for this.
67
+ Run Mastra as a standalone server and connect your frontend (e.g. using Vite + React) to its API endpoints. You'll be using Mastra's [custom API routes](/docs/v1/server/custom-api-routes) feature for this.
68
68
 
69
69
  :::info
70
70
 
@@ -390,15 +390,582 @@ export async function POST(req: Request) {
390
390
 
391
391
  </Tabs>
392
392
 
393
+ ## Custom UI
394
+
395
+ Custom UI (also known as Generative UI) allows you to render custom React components based on data streamed from Mastra. Instead of displaying raw text or JSON, you can create visual components for tool outputs, workflow progress, agent network execution, and custom events.
396
+
397
+ Use Custom UI when you want to:
398
+
399
+ - Render tool outputs as visual components (e.g., a weather card instead of JSON)
400
+ - Display workflow step progress with status indicators
401
+ - Visualize agent network execution with step-by-step updates
402
+ - Show progress indicators or status updates during long-running operations
403
+
404
+ ### Data part types
405
+
406
+ Mastra streams data to the frontend as "parts" within messages. Each part has a `type` that determines how to render it. The `@mastra/ai-sdk` package transforms Mastra streams into AI SDK-compatible [UI Message DataParts](https://ai-sdk.dev/docs/reference/ai-sdk-core/ui-message#datauipart).
407
+
408
+ | Data Part Type | Source | Description |
409
+ |----------------|--------|-------------|
410
+ | `tool-{toolKey}` | AI SDK built-in | Tool invocation with states: `input-available`, `output-available`, `output-error` |
411
+ | `data-workflow` | `workflowRoute()` | Workflow execution with step inputs, outputs, and status |
412
+ | `data-network` | `networkRoute()` | Agent network execution with ordered steps and outputs |
413
+ | `data-tool-agent` | Nested agent in tool | Agent output streamed from within a tool's `execute()` |
414
+ | `data-tool-workflow` | Nested workflow in tool | Workflow output streamed from within a tool's `execute()` |
415
+ | `data-tool-network` | Nested network in tool | Network output streamed from within a tool's `execute()` |
416
+ | `data-{custom}` | `writer.custom()` | Custom events for progress indicators, status updates, etc. |
417
+
418
+ ### Rendering tool outputs
419
+
420
+ AI SDK automatically creates `tool-{toolKey}` parts when an agent calls a tool. These parts include the tool's state and output, which you can use to render custom components.
421
+
422
+ The tool part cycles through states:
423
+ - `input-streaming`: Tool input is being streamed (when tool call streaming is enabled)
424
+ - `input-available`: Tool has been called with complete input, waiting for execution
425
+ - `output-available`: Tool execution completed with output
426
+ - `output-error`: Tool execution failed
427
+
428
+ Here's an example of rendering a weather tool's output as a custom `WeatherCard` component.
429
+
430
+ <Tabs>
431
+
432
+ <TabItem value="backend" label="Backend">
433
+
434
+ Define a tool with an `outputSchema` so the frontend knows the shape of the data to render.
435
+
436
+ ```typescript title="src/mastra/tools/weather-tool.ts" {10-17} copy
437
+ import { createTool } from "@mastra/core/tools";
438
+ import { z } from "zod";
439
+
440
+ export const weatherTool = createTool({
441
+ id: "get-weather",
442
+ description: "Get current weather for a location",
443
+ inputSchema: z.object({
444
+ location: z.string().describe("The location to get the weather for"),
445
+ }),
446
+ outputSchema: z.object({
447
+ temperature: z.number(),
448
+ feelsLike: z.number(),
449
+ humidity: z.number(),
450
+ windSpeed: z.number(),
451
+ conditions: z.string(),
452
+ location: z.string(),
453
+ }),
454
+ execute: async ({ location }) => {
455
+ const response = await fetch(
456
+ `https://api.weatherapi.com/v1/current.json?key=${process.env.WEATHER_API_KEY}&q=${location}`
457
+ );
458
+ const data = await response.json();
459
+ return {
460
+ temperature: data.current.temp_c,
461
+ feelsLike: data.current.feelslike_c,
462
+ humidity: data.current.humidity,
463
+ windSpeed: data.current.wind_kph,
464
+ conditions: data.current.condition.text,
465
+ location: data.location.name,
466
+ };
467
+ },
468
+ });
469
+ ```
470
+
471
+ </TabItem>
472
+
473
+ <TabItem value="frontend" label="Frontend">
474
+
475
+ Check for `tool-{toolKey}` parts in the message and render a custom component based on the tool's state and output.
476
+
477
+ ```typescript title="src/components/chat.tsx" {24-35} copy
478
+ import { useChat } from "@ai-sdk/react";
479
+ import { DefaultChatTransport } from "ai";
480
+ import { WeatherCard } from "./weather-card";
481
+ import { Loader } from "./loader";
482
+
483
+ export function Chat() {
484
+ const { messages, sendMessage } = useChat({
485
+ transport: new DefaultChatTransport({
486
+ api: "http://localhost:4111/chat/weatherAgent",
487
+ }),
488
+ });
489
+
490
+ return (
491
+ <div>
492
+ {messages.map((message) => (
493
+ <div key={message.id}>
494
+ {message.parts.map((part, index) => {
495
+ // Handle user text messages
496
+ if (part.type === "text" && message.role === "user") {
497
+ return <p key={index}>{part.text}</p>;
498
+ }
499
+
500
+ // Handle weather tool output
501
+ if (part.type === "tool-weatherTool") {
502
+ switch (part.state) {
503
+ case "input-available":
504
+ return <Loader key={index} />;
505
+ case "output-available":
506
+ return <WeatherCard key={index} {...part.output} />;
507
+ case "output-error":
508
+ return <div key={index}>Error: {part.errorText}</div>;
509
+ default:
510
+ return null;
511
+ }
512
+ }
513
+
514
+ return null;
515
+ })}
516
+ </div>
517
+ ))}
518
+ </div>
519
+ );
520
+ }
521
+ ```
522
+
523
+ </TabItem>
524
+
525
+ </Tabs>
526
+
527
+ :::tip
528
+
529
+ The tool part type follows the pattern `tool-{toolKey}`, where `toolKey` is the key used when registering the tool with the agent. For example, if you register tools as `tools: { weatherTool }`, the part type will be `tool-weatherTool`.
530
+
531
+ :::
532
+
533
+ ### Rendering workflow data
534
+
535
+ When using `workflowRoute()` or `handleWorkflowStream()`, Mastra emits `data-workflow` parts that contain the workflow's execution state, including step statuses and outputs.
536
+
537
+ <Tabs>
538
+
539
+ <TabItem value="backend" label="Backend">
540
+
541
+ Define a workflow with multiple steps that will emit `data-workflow` parts as it executes.
542
+
543
+ ```typescript title="src/mastra/workflows/activities-workflow.ts" copy
544
+ import { createStep, createWorkflow } from "@mastra/core/workflows";
545
+ import { z } from "zod";
546
+
547
+ const fetchWeather = createStep({
548
+ id: "fetch-weather",
549
+ inputSchema: z.object({
550
+ location: z.string(),
551
+ }),
552
+ outputSchema: z.object({
553
+ temperature: z.number(),
554
+ conditions: z.string(),
555
+ }),
556
+ execute: async ({ inputData }) => {
557
+ // Fetch weather data...
558
+ return { temperature: 22, conditions: "Sunny" };
559
+ },
560
+ });
561
+
562
+ const planActivities = createStep({
563
+ id: "plan-activities",
564
+ inputSchema: z.object({
565
+ temperature: z.number(),
566
+ conditions: z.string(),
567
+ }),
568
+ outputSchema: z.object({
569
+ activities: z.string(),
570
+ }),
571
+ execute: async ({ inputData, mastra }) => {
572
+ const agent = mastra?.getAgent("activityAgent");
573
+ const response = await agent?.generate(
574
+ `Suggest activities for ${inputData.conditions} weather at ${inputData.temperature}°C`
575
+ );
576
+ return { activities: response?.text || "" };
577
+ },
578
+ });
579
+
580
+ export const activitiesWorkflow = createWorkflow({
581
+ id: "activities-workflow",
582
+ inputSchema: z.object({
583
+ location: z.string(),
584
+ }),
585
+ outputSchema: z.object({
586
+ activities: z.string(),
587
+ }),
588
+ })
589
+ .then(fetchWeather)
590
+ .then(planActivities);
591
+
592
+ activitiesWorkflow.commit();
593
+ ```
594
+
595
+ Register the workflow with Mastra and expose it via `workflowRoute()` to stream workflow events to the frontend.
596
+
597
+ ```typescript title="src/mastra/index.ts" copy
598
+ import { Mastra } from "@mastra/core";
599
+ import { workflowRoute } from "@mastra/ai-sdk";
600
+
601
+ export const mastra = new Mastra({
602
+ workflows: { activitiesWorkflow },
603
+ server: {
604
+ apiRoutes: [
605
+ workflowRoute({
606
+ path: "/workflow/activitiesWorkflow",
607
+ workflow: "activitiesWorkflow",
608
+ }),
609
+ ],
610
+ },
611
+ });
612
+ ```
613
+
614
+ </TabItem>
615
+
616
+ <TabItem value="frontend" label="Frontend">
617
+
618
+ Check for `data-workflow` parts and render each step's status and output using the `WorkflowDataPart` type for type safety.
619
+
620
+ ```typescript title="src/components/workflow-chat.tsx" {3,5,45-47} copy
621
+ import { useChat } from "@ai-sdk/react";
622
+ import { DefaultChatTransport } from "ai";
623
+ import type { WorkflowDataPart } from "@mastra/ai-sdk";
624
+
625
+ type WorkflowData = WorkflowDataPart["data"];
626
+ type StepStatus = "running" | "success" | "failed" | "suspended" | "waiting";
627
+
628
+ function StepIndicator({ name, status, output }: {
629
+ name: string;
630
+ status: StepStatus;
631
+ output: unknown;
632
+ }) {
633
+ return (
634
+ <div className="step">
635
+ <div className="step-header">
636
+ <span>{name}</span>
637
+ <span className={`status status-${status}`}>{status}</span>
638
+ </div>
639
+ {status === "success" && output && (
640
+ <pre>{JSON.stringify(output, null, 2)}</pre>
641
+ )}
642
+ </div>
643
+ );
644
+ }
645
+
646
+ export function WorkflowChat() {
647
+ const { messages, sendMessage, status } = useChat({
648
+ transport: new DefaultChatTransport({
649
+ api: "http://localhost:4111/workflow/activitiesWorkflow",
650
+ prepareSendMessagesRequest: ({ messages }) => ({
651
+ body: {
652
+ inputData: {
653
+ location: messages[messages.length - 1]?.parts[0]?.text,
654
+ },
655
+ },
656
+ }),
657
+ }),
658
+ });
659
+
660
+ return (
661
+ <div>
662
+ {messages.map((message) => (
663
+ <div key={message.id}>
664
+ {message.parts.map((part, index) => {
665
+ if (part.type === "data-workflow") {
666
+ const workflowData = part.data as WorkflowData;
667
+ const steps = Object.values(workflowData.steps);
668
+
669
+ return (
670
+ <div key={index} className="workflow-progress">
671
+ <h3>Workflow: {workflowData.name}</h3>
672
+ <p>Status: {workflowData.status}</p>
673
+ {steps.map((step) => (
674
+ <StepIndicator
675
+ key={step.name}
676
+ name={step.name}
677
+ status={step.status}
678
+ output={step.output}
679
+ />
680
+ ))}
681
+ </div>
682
+ );
683
+ }
684
+ return null;
685
+ })}
686
+ </div>
687
+ ))}
688
+ </div>
689
+ );
690
+ }
691
+ ```
692
+
693
+ </TabItem>
694
+
695
+ </Tabs>
696
+
697
+ For more details on workflow streaming, see [Workflow Streaming](/docs/v1/streaming/workflow-streaming).
698
+
699
+ ### Rendering network data
700
+
701
+ When using `networkRoute()` or `handleNetworkStream()`, Mastra emits `data-network` parts that contain the agent network's execution state, including which agents were called and their outputs.
702
+
703
+ <Tabs>
704
+
705
+ <TabItem value="backend" label="Backend">
706
+
707
+ Register agents with Mastra and expose the routing agent via `networkRoute()` to stream network execution events to the frontend.
708
+
709
+ ```typescript title="src/mastra/index.ts" copy
710
+ import { Mastra } from "@mastra/core";
711
+ import { networkRoute } from "@mastra/ai-sdk";
712
+
713
+ export const mastra = new Mastra({
714
+ agents: { routingAgent, researchAgent, weatherAgent },
715
+ server: {
716
+ apiRoutes: [
717
+ networkRoute({
718
+ path: "/network",
719
+ agent: "routingAgent",
720
+ }),
721
+ ],
722
+ },
723
+ });
724
+ ```
725
+
726
+ </TabItem>
727
+
728
+ <TabItem value="frontend" label="Frontend">
729
+
730
+ Check for `data-network` parts and render each agent's execution step using the `NetworkDataPart` type for type safety.
731
+
732
+ ```typescript title="src/components/network-chat.tsx" {3,5,42-44} copy
733
+ import { useChat } from "@ai-sdk/react";
734
+ import { DefaultChatTransport } from "ai";
735
+ import type { NetworkDataPart } from "@mastra/ai-sdk";
736
+
737
+ type NetworkData = NetworkDataPart["data"];
738
+
739
+ function AgentStep({ step }: { step: NetworkData["steps"][number] }) {
740
+ return (
741
+ <div className="agent-step">
742
+ <div className="step-header">
743
+ <span className="agent-name">{step.name}</span>
744
+ <span className={`status status-${step.status}`}>{step.status}</span>
745
+ </div>
746
+ {step.input && (
747
+ <div className="step-input">
748
+ <strong>Input:</strong>
749
+ <pre>{JSON.stringify(step.input, null, 2)}</pre>
750
+ </div>
751
+ )}
752
+ {step.output && (
753
+ <div className="step-output">
754
+ <strong>Output:</strong>
755
+ <pre>{typeof step.output === "string" ? step.output : JSON.stringify(step.output, null, 2)}</pre>
756
+ </div>
757
+ )}
758
+ </div>
759
+ );
760
+ }
761
+
762
+ export function NetworkChat() {
763
+ const { messages, sendMessage, status } = useChat({
764
+ transport: new DefaultChatTransport({
765
+ api: "http://localhost:4111/network",
766
+ }),
767
+ });
768
+
769
+ return (
770
+ <div>
771
+ {messages.map((message) => (
772
+ <div key={message.id}>
773
+ {message.parts.map((part, index) => {
774
+ if (part.type === "data-network") {
775
+ const networkData = part.data as NetworkData;
776
+
777
+ return (
778
+ <div key={index} className="network-execution">
779
+ <div className="network-header">
780
+ <h3>Agent Network: {networkData.name}</h3>
781
+ <span className={`status status-${networkData.status}`}>
782
+ {networkData.status}
783
+ </span>
784
+ </div>
785
+ <div className="network-steps">
786
+ {networkData.steps.map((step, stepIndex) => (
787
+ <AgentStep key={stepIndex} step={step} />
788
+ ))}
789
+ </div>
790
+ </div>
791
+ );
792
+ }
793
+ return null;
794
+ })}
795
+ </div>
796
+ ))}
797
+ </div>
798
+ );
799
+ }
800
+ ```
801
+
802
+ </TabItem>
803
+
804
+ </Tabs>
805
+
806
+ For more details on agent networks, see [Agent Networks](/docs/v1/agents/networks).
807
+
808
+ ### Custom events
809
+
810
+ Use `writer.custom()` within a tool's `execute()` function to emit custom data parts. This is useful for progress indicators, status updates, or any custom UI updates during tool execution.
811
+
812
+ Custom event types must start with `data-` to be recognized as data parts.
813
+
814
+ :::warning
815
+
816
+ You must `await` the `writer.custom()` call, otherwise you may encounter a `WritableStream is locked` error.
817
+
818
+ :::
819
+
820
+ <Tabs>
821
+
822
+ <TabItem value="backend" label="Backend">
823
+
824
+ Use `writer.custom()` inside the tool's `execute()` function to emit custom `data-` prefixed events at different stages of execution.
825
+
826
+ ```typescript title="src/mastra/tools/task-tool.ts" {18-24,30-36} copy
827
+ import { createTool } from "@mastra/core/tools";
828
+ import { z } from "zod";
829
+
830
+ export const taskTool = createTool({
831
+ id: "process-task",
832
+ description: "Process a task with progress updates",
833
+ inputSchema: z.object({
834
+ task: z.string().describe("The task to process"),
835
+ }),
836
+ outputSchema: z.object({
837
+ result: z.string(),
838
+ status: z.string(),
839
+ }),
840
+ execute: async (inputData, context) => {
841
+ const { task } = inputData;
842
+
843
+ // Emit "in progress" custom event
844
+ await context?.writer?.custom({
845
+ type: "data-tool-progress",
846
+ data: {
847
+ status: "in-progress",
848
+ message: "Gathering information...",
849
+ },
850
+ });
851
+
852
+ // Simulate work
853
+ await new Promise((resolve) => setTimeout(resolve, 3000));
854
+
855
+ // Emit "done" custom event
856
+ await context?.writer?.custom({
857
+ type: "data-tool-progress",
858
+ data: {
859
+ status: "done",
860
+ message: `Successfully processed "${task}"`,
861
+ },
862
+ });
863
+
864
+ return {
865
+ result: `Task "${task}" has been completed successfully!`,
866
+ status: "completed",
867
+ };
868
+ },
869
+ });
870
+ ```
871
+
872
+ </TabItem>
873
+
874
+ <TabItem value="frontend" label="Frontend">
875
+
876
+ Filter message parts for your custom event type and render a progress indicator that updates as new events arrive.
877
+
878
+ ```typescript title="src/components/task-chat.tsx" {31-41,45} copy
879
+ import { useChat } from "@ai-sdk/react";
880
+ import { DefaultChatTransport } from "ai";
881
+ import { useMemo } from "react";
882
+
883
+ type ProgressData = {
884
+ status: "in-progress" | "done";
885
+ message: string;
886
+ };
887
+
888
+ function ProgressIndicator({ progress }: { progress: ProgressData }) {
889
+ return (
890
+ <div className="progress-indicator">
891
+ {progress.status === "in-progress" ? (
892
+ <span className="spinner" />
893
+ ) : (
894
+ <span className="check-icon" />
895
+ )}
896
+ <span className={`status-${progress.status}`}>{progress.message}</span>
897
+ </div>
898
+ );
899
+ }
900
+
901
+ export function TaskChat() {
902
+ const { messages, sendMessage } = useChat({
903
+ transport: new DefaultChatTransport({
904
+ api: "http://localhost:4111/chat/taskAgent",
905
+ }),
906
+ });
907
+
908
+ // Extract the latest progress event from messages
909
+ const latestProgress = useMemo(() => {
910
+ const allProgressParts: ProgressData[] = [];
911
+ messages.forEach((message) => {
912
+ message.parts.forEach((part) => {
913
+ if (part.type === "data-tool-progress") {
914
+ allProgressParts.push(part.data as ProgressData);
915
+ }
916
+ });
917
+ });
918
+ return allProgressParts[allProgressParts.length - 1];
919
+ }, [messages]);
920
+
921
+ return (
922
+ <div>
923
+ {latestProgress && <ProgressIndicator progress={latestProgress} />}
924
+ {messages.map((message) => (
925
+ <div key={message.id}>
926
+ {message.parts.map((part, index) => {
927
+ if (part.type === "text") {
928
+ return <p key={index}>{part.text}</p>;
929
+ }
930
+ return null;
931
+ })}
932
+ </div>
933
+ ))}
934
+ </div>
935
+ );
936
+ }
937
+ ```
938
+
939
+ </TabItem>
940
+
941
+ </Tabs>
942
+
943
+ ### Tool streaming
944
+
945
+ Tools can also stream data using `context.writer.write()` for lower-level control, or pipe an agent's stream directly to the tool's writer. For more details, see [Tool Streaming](/docs/v1/streaming/tool-streaming).
946
+
947
+ ### Examples
948
+
949
+ For live examples of Custom UI patterns, visit [Mastra's UI Dojo](https://ui-dojo.mastra.ai/). The repository includes implementations for:
950
+
951
+ - [Generative UIs](https://github.com/mastra-ai/ui-dojo/blob/main/src/pages/ai-sdk/generative-user-interfaces.tsx) - Custom components for tool outputs
952
+ - [Workflows](https://github.com/mastra-ai/ui-dojo/blob/main/src/pages/ai-sdk/workflow.tsx) - Workflow step visualization
953
+ - [Agent Networks](https://github.com/mastra-ai/ui-dojo/blob/main/src/pages/ai-sdk/network.tsx) - Network execution display
954
+ - [Custom Events](https://github.com/mastra-ai/ui-dojo/blob/main/src/pages/ai-sdk/generative-user-interfaces-with-custom-events.tsx) - Progress indicators with custom events
955
+
393
956
  ## Recipes
394
957
 
395
958
  ### Stream transformations
396
959
 
397
960
  To manually transform Mastra's streams to AI SDK-compatible format, use the [`toAISdkStream()`](/reference/v1/ai-sdk/to-ai-sdk-stream) utility. See the [examples](/reference/v1/ai-sdk/to-ai-sdk-stream#examples) for concrete usage patterns.
398
961
 
962
+ ### Loading historical messages
963
+
964
+ When loading messages from Mastra's memory to display in a chat UI, use [`toAISdkV5Messages()`](/reference/v1/ai-sdk/to-ai-sdk-v5-messages) or [`toAISdkV4Messages()`](/reference/v1/ai-sdk/to-ai-sdk-v4-messages) to convert them to the appropriate AI SDK format for `useChat()`'s `initialMessages`.
965
+
399
966
  ### Passing additional data
400
967
 
401
- [`sendMessage()`](https://ai-sdk.dev/docs/reference/ai-sdk-ui/use-chat#send-message) allows you to pass additional data from the frontend to Mastra. This data can then be used on the server as [`RequestContext`](/docs/v1/server-db/request-context).
968
+ [`sendMessage()`](https://ai-sdk.dev/docs/reference/ai-sdk-ui/use-chat#send-message) allows you to pass additional data from the frontend to Mastra. This data can then be used on the server as [`RequestContext`](/docs/v1/server/request-context).
402
969
 
403
970
  Here's an example of the frontend code:
404
971
 
@@ -477,7 +1044,7 @@ export const mastra = new Mastra({
477
1044
 
478
1045
  :::info
479
1046
 
480
- You can access this data in your tools via the `requestContext` parameter. See the [Request Context documentation](/docs/v1/server-db/request-context) for more details.
1047
+ You can access this data in your tools via the `requestContext` parameter. See the [Request Context documentation](/docs/v1/server/request-context) for more details.
481
1048
 
482
1049
  :::
483
1050
 
@@ -518,49 +1085,305 @@ export async function POST(req: Request) {
518
1085
 
519
1086
  </Tabs>
520
1087
 
521
- ### Custom UI
1088
+ ### Workflow suspend/resume with user approval
522
1089
 
523
- The `@mastra/ai-sdk` package transforms and emits Mastra streams (e.g workflow, network streams) into AI SDK-compatible [uiMessages DataParts](https://ai-sdk.dev/docs/reference/ai-sdk-core/ui-message#datauipart) format.
1090
+ Workflows can suspend execution and wait for user input before continuing. This is useful for approval flows, confirmations, or any human-in-the-loop scenario.
524
1091
 
525
- - **Top-level parts**: These are streamed via direct workflow and network stream transformations (e.g in `workflowRoute()` and `networkRoute()`)
526
- - `data-workflow`: Aggregates a workflow run with step inputs/outputs and final usage.
527
- - `data-network`: Aggregates a routing/network run with ordered steps (agent/workflow/tool executions) and outputs.
1092
+ The workflow uses:
1093
+ - `suspendSchema` / `resumeSchema` - Define the data structure for suspend payload and resume input
1094
+ - `suspend()` - Pauses the workflow and sends the suspend payload to the UI
1095
+ - `resumeData` - Contains the user's response when the workflow resumes
1096
+ - `bail()` - Exits the workflow early (e.g., when user rejects)
528
1097
 
529
- - **Nested parts**: These are streamed via nested and merged streams from within a tool's `execute()` method.
530
- - `data-tool-workflow`: Nested workflow emitted from within a tool stream.
531
- - `data-tool-network`: Nested network emitted from within an tool stream.
532
- - `data-tool-agent`: Nested agent emitted from within an tool stream.
1098
+ <Tabs>
533
1099
 
534
- Here's an example: For a [nested agent stream within a tool](/docs/v1/streaming/tool-streaming#tool-using-an-agent), `data-tool-agent` UI message parts will be emitted and can be leveraged on the client as documented below:
1100
+ <TabItem value="backend" label="Backend">
1101
+
1102
+ Create a workflow step that suspends for approval. The step checks `resumeData` to determine if it's resuming, and calls `suspend()` on first execution.
1103
+
1104
+ ```typescript title="src/mastra/workflows/approval-workflow.ts" copy
1105
+ import { createStep, createWorkflow } from "@mastra/core/workflows";
1106
+ import { z } from "zod";
1107
+
1108
+ const requestApproval = createStep({
1109
+ id: "request-approval",
1110
+ inputSchema: z.object({ requestId: z.string(), summary: z.string() }),
1111
+ outputSchema: z.object({
1112
+ approved: z.boolean(),
1113
+ requestId: z.string(),
1114
+ approvedBy: z.string().optional(),
1115
+ }),
1116
+ resumeSchema: z.object({
1117
+ approved: z.boolean(),
1118
+ approverName: z.string().optional(),
1119
+ }),
1120
+ suspendSchema: z.object({
1121
+ message: z.string(),
1122
+ requestId: z.string(),
1123
+ }),
1124
+ execute: async ({ inputData, resumeData, suspend, bail }) => {
1125
+ // User rejected - bail out
1126
+ if (resumeData?.approved === false) {
1127
+ return bail({ message: "Request rejected" });
1128
+ }
1129
+ // User approved - continue
1130
+ if (resumeData?.approved) {
1131
+ return {
1132
+ approved: true,
1133
+ requestId: inputData.requestId,
1134
+ approvedBy: resumeData.approverName || "User",
1135
+ };
1136
+ }
1137
+ // First execution - suspend and wait
1138
+ return await suspend({
1139
+ message: `Please approve: ${inputData.summary}`,
1140
+ requestId: inputData.requestId,
1141
+ });
1142
+ },
1143
+ });
535
1144
 
536
- ```typescript title="app/page.tsx" copy
537
- "use client";
1145
+ export const approvalWorkflow = createWorkflow({
1146
+ id: "approval-workflow",
1147
+ inputSchema: z.object({ requestId: z.string(), summary: z.string() }),
1148
+ outputSchema: z.object({
1149
+ approved: z.boolean(),
1150
+ requestId: z.string(),
1151
+ approvedBy: z.string().optional(),
1152
+ }),
1153
+ })
1154
+ .then(requestApproval);
1155
+
1156
+ approvalWorkflow.commit();
1157
+ ```
1158
+
1159
+ Register the workflow. Storage is required for suspend/resume to persist state.
1160
+
1161
+ ```typescript title="src/mastra/index.ts" copy
1162
+ import { Mastra } from "@mastra/core";
1163
+ import { workflowRoute } from "@mastra/ai-sdk";
1164
+ import { LibSQLStore } from "@mastra/libsql";
538
1165
 
1166
+ export const mastra = new Mastra({
1167
+ workflows: { approvalWorkflow },
1168
+ storage: new LibSQLStore({
1169
+ url: "file:../mastra.db",
1170
+ }),
1171
+ server: {
1172
+ apiRoutes: [
1173
+ workflowRoute({ path: "/workflow/approvalWorkflow", workflow: "approvalWorkflow" }),
1174
+ ],
1175
+ },
1176
+ });
1177
+ ```
1178
+
1179
+ </TabItem>
1180
+
1181
+ <TabItem value="frontend" label="Frontend">
1182
+
1183
+ Detect when the workflow is suspended and send resume data with `runId`, `step`, and `resumeData`.
1184
+
1185
+ ```typescript title="src/components/approval-workflow.tsx" copy
539
1186
  import { useChat } from "@ai-sdk/react";
540
- import { AgentTool } from '../ui/agent-tool';
541
- import { DefaultChatTransport } from 'ai';
1187
+ import { DefaultChatTransport } from "ai";
1188
+ import { useMemo, useState } from "react";
1189
+ import type { WorkflowDataPart } from "@mastra/ai-sdk";
1190
+
1191
+ type WorkflowData = WorkflowDataPart["data"];
1192
+
1193
+ export function ApprovalWorkflow() {
1194
+ const [requestId, setRequestId] = useState("");
1195
+ const [summary, setSummary] = useState("");
1196
+
1197
+ const { messages, sendMessage, setMessages, status } = useChat({
1198
+ transport: new DefaultChatTransport({
1199
+ api: "http://localhost:4111/workflow/approvalWorkflow",
1200
+ prepareSendMessagesRequest: ({ messages }) => {
1201
+ const lastMessage = messages[messages.length - 1];
1202
+ const text = lastMessage.parts.find((p) => p.type === "text")?.text;
1203
+ const metadata = lastMessage.metadata as Record<string, string>;
1204
+
1205
+ // Resuming: send runId, step, and resumeData
1206
+ if (text === "Approve" || text === "Reject") {
1207
+ return {
1208
+ body: {
1209
+ runId: metadata.runId,
1210
+ step: "request-approval",
1211
+ resumeData: { approved: text === "Approve" },
1212
+ },
1213
+ };
1214
+ }
1215
+ // Starting: send inputData
1216
+ return {
1217
+ body: { inputData: { requestId: metadata.requestId, summary: metadata.summary } },
1218
+ };
1219
+ },
1220
+ }),
1221
+ });
1222
+
1223
+ // Find suspended workflow
1224
+ const suspended = useMemo(() => {
1225
+ for (const m of messages) {
1226
+ for (const p of m.parts) {
1227
+ if (p.type === "data-workflow" && (p.data as WorkflowData).status === "suspended") {
1228
+ return { data: p.data as WorkflowData, runId: p.id };
1229
+ }
1230
+ }
1231
+ }
1232
+ return null;
1233
+ }, [messages]);
1234
+
1235
+ const handleApprove = () => {
1236
+ setMessages([]);
1237
+ sendMessage({ text: "Approve", metadata: { runId: suspended?.runId } });
1238
+ };
1239
+
1240
+ const handleReject = () => {
1241
+ setMessages([]);
1242
+ sendMessage({ text: "Reject", metadata: { runId: suspended?.runId } });
1243
+ };
1244
+
1245
+ return (
1246
+ <div>
1247
+ {!suspended ? (
1248
+ <form onSubmit={(e) => {
1249
+ e.preventDefault();
1250
+ setMessages([]);
1251
+ sendMessage({ text: "Start", metadata: { requestId, summary } });
1252
+ }}>
1253
+ <input value={requestId} onChange={(e) => setRequestId(e.target.value)} placeholder="Request ID" />
1254
+ <input value={summary} onChange={(e) => setSummary(e.target.value)} placeholder="Summary" />
1255
+ <button type="submit" disabled={status !== "ready"}>Submit</button>
1256
+ </form>
1257
+ ) : (
1258
+ <div>
1259
+ <p>{(suspended.data.steps["request-approval"]?.suspendPayload as { message: string })?.message}</p>
1260
+ <button onClick={handleApprove}>Approve</button>
1261
+ <button onClick={handleReject}>Reject</button>
1262
+ </div>
1263
+ )}
1264
+ </div>
1265
+ );
1266
+ }
1267
+ ```
1268
+
1269
+ </TabItem>
1270
+
1271
+ </Tabs>
1272
+
1273
+ Key points:
1274
+ - The suspend payload is accessible via `step.suspendPayload`
1275
+ - To resume, send `runId`, `step` (the step ID), and `resumeData` in the request body
1276
+ - Storage must be configured for suspend/resume to persist workflow state
1277
+
1278
+ For a complete implementation, see the [workflow-suspend-resume example](https://github.com/mastra-ai/ui-dojo/blob/main/src/pages/ai-sdk/workflow-suspend-resume.tsx) in UI Dojo.
1279
+
1280
+ ### Nested agent streams in tools
1281
+
1282
+ Tools can call agents internally and stream the agent's output back to the UI. This creates `data-tool-agent` parts that can be rendered alongside the tool's final output.
1283
+
1284
+ The pattern uses:
1285
+ - `context.mastra.getAgent()` - Get an agent instance from within a tool
1286
+ - `agent.stream()` - Stream the agent's response
1287
+ - `stream.fullStream.pipeTo(context.writer)` - Pipe the agent's stream to the tool's writer
1288
+
1289
+ <Tabs>
1290
+
1291
+ <TabItem value="backend" label="Backend">
1292
+
1293
+ Create a tool that calls an agent and pipes its stream to the tool's writer.
1294
+
1295
+ ```typescript title="src/mastra/tools/nested-agent-tool.ts" copy
1296
+ import { createTool } from "@mastra/core/tools";
1297
+ import { z } from "zod";
1298
+
1299
+ export const nestedAgentTool = createTool({
1300
+ id: "nested-agent-stream",
1301
+ description: "Analyze weather using a nested agent",
1302
+ inputSchema: z.object({
1303
+ city: z.string().describe("The city to analyze"),
1304
+ }),
1305
+ outputSchema: z.object({
1306
+ summary: z.string(),
1307
+ }),
1308
+ execute: async (inputData, context) => {
1309
+ const agent = context?.mastra?.getAgent("weatherAgent");
1310
+ if (!agent) {
1311
+ return { summary: "Weather agent not available" };
1312
+ }
1313
+
1314
+ const stream = await agent.stream(
1315
+ `Analyze the weather in ${inputData.city} and provide a summary.`
1316
+ );
1317
+
1318
+ // Pipe the agent's stream to emit data-tool-agent parts
1319
+ await stream.fullStream.pipeTo(context!.writer!);
1320
+
1321
+ return { summary: (await stream.text) ?? "No summary available" };
1322
+ },
1323
+ });
1324
+ ```
1325
+
1326
+ Create an agent that uses this tool.
1327
+
1328
+ ```typescript title="src/mastra/agents/forecast-agent.ts" copy
1329
+ import { Agent } from "@mastra/core/agent";
1330
+ import { nestedAgentTool } from "../tools/nested-agent-tool";
1331
+
1332
+ export const forecastAgent = new Agent({
1333
+ id: "forecast-agent",
1334
+ instructions: "Use the nested-agent-stream tool when asked about weather.",
1335
+ model: "openai/gpt-4o-mini",
1336
+ tools: { nestedAgentTool },
1337
+ });
1338
+ ```
1339
+
1340
+ </TabItem>
1341
+
1342
+ <TabItem value="frontend" label="Frontend">
1343
+
1344
+ Handle `data-tool-agent` parts to display the nested agent's streamed output.
1345
+
1346
+ ```typescript title="src/components/nested-agent-chat.tsx" copy
1347
+ import { useChat } from "@ai-sdk/react";
1348
+ import { DefaultChatTransport } from "ai";
1349
+ import { useState } from "react";
542
1350
  import type { AgentDataPart } from "@mastra/ai-sdk";
543
1351
 
544
- export default function Page() {
545
- const { messages } = useChat({
1352
+ export function NestedAgentChat() {
1353
+ const [input, setInput] = useState("");
1354
+ const { messages, sendMessage, status } = useChat({
546
1355
  transport: new DefaultChatTransport({
547
- api: 'http://localhost:4111/chat',
1356
+ api: "http://localhost:4111/chat/forecastAgent",
548
1357
  }),
549
1358
  });
550
1359
 
551
1360
  return (
552
1361
  <div>
1362
+ <form onSubmit={(e) => {
1363
+ e.preventDefault();
1364
+ sendMessage({ text: input });
1365
+ setInput("");
1366
+ }}>
1367
+ <input value={input} onChange={(e) => setInput(e.target.value)} placeholder="Enter a city" />
1368
+ <button type="submit" disabled={status !== "ready"}>Get Forecast</button>
1369
+ </form>
1370
+
553
1371
  {messages.map((message) => (
554
1372
  <div key={message.id}>
555
- {message.parts.map((part, i) => {
556
- switch (part.type) {
557
- case 'data-tool-agent':
558
- return (
559
- <AgentTool {...part.data as AgentDataPart} key={`${message.id}-${i}`} />
560
- );
561
- default:
562
- return null;
1373
+ {message.parts.map((part, index) => {
1374
+ if (part.type === "text") {
1375
+ return <p key={index}>{part.text}</p>;
1376
+ }
1377
+ if (part.type === "data-tool-agent") {
1378
+ const { id, data } = part as AgentDataPart;
1379
+ return (
1380
+ <div key={index} className="nested-agent">
1381
+ <strong>Nested Agent: {id}</strong>
1382
+ {data.text && <p>{data.text}</p>}
1383
+ </div>
1384
+ );
563
1385
  }
1386
+ return null;
564
1387
  })}
565
1388
  </div>
566
1389
  ))}
@@ -569,59 +1392,190 @@ export default function Page() {
569
1392
  }
570
1393
  ```
571
1394
 
572
- ```typescript title="ui/agent-tool.ts" copy
573
- import { Tool, ToolContent, ToolHeader, ToolOutput } from "../ai-elements/tool";
574
- import type { AgentDataPart } from "@mastra/ai-sdk";
1395
+ </TabItem>
575
1396
 
576
- export const AgentTool = ({ id, text, status }: AgentDataPart) => {
577
- return (
578
- <Tool>
579
- <ToolHeader
580
- type={`${id}`}
581
- state={status === 'finished' ? 'output-available' : 'input-available'}
582
- />
583
- <ToolContent>
584
- <ToolOutput output={text} />
585
- </ToolContent>
586
- </Tool>
587
- );
588
- };
589
- ```
1397
+ </Tabs>
590
1398
 
591
- ### Custom Tool streaming
1399
+ Key points:
1400
+ - Piping `fullStream` to `context.writer` creates `data-tool-agent` parts
1401
+ - The `AgentDataPart` has `id` (on the part) and `data.text` (the agent's streamed text)
1402
+ - The tool still returns its own output after the stream completes
592
1403
 
593
- To stream custom data parts from within your tool execution function, use the `writer.custom()` method.
1404
+ For a complete implementation, see the [tool-nested-streams example](https://github.com/mastra-ai/ui-dojo/blob/main/src/pages/ai-sdk/tool-nested-streams.tsx) in UI Dojo.
594
1405
 
595
- :::tip
1406
+ ### Streaming agent text from workflow steps
596
1407
 
597
- It is important that you `await` the `writer.custom()` call.
1408
+ Workflow steps can stream an agent's text output in real-time by piping the agent's stream to the step's `writer`. This lets users see the agent "thinking" while the workflow executes, rather than waiting for the step to complete.
598
1409
 
599
- :::
1410
+ The pattern uses:
1411
+ - `writer` in workflow step - Pipe the agent's `fullStream` to the step's writer
1412
+ - `text` and `data-workflow` parts - The frontend receives streaming text alongside step progress
600
1413
 
601
- ```typescript {4,7-10,14-17} copy
602
- import { createTool } from "@mastra/core/tools";
1414
+ <Tabs>
603
1415
 
604
- export const testTool = createTool({
605
- execute: async (inputData, context) => {
606
- const { value } = inputData;
1416
+ <TabItem value="backend" label="Backend">
607
1417
 
608
- await context?.writer?.custom({
609
- type: "data-tool-progress",
610
- status: "pending"
611
- });
1418
+ Create a workflow step that streams an agent's response by piping to the step's `writer`.
612
1419
 
613
- const response = await fetch(...);
1420
+ ```typescript title="src/mastra/workflows/weather-workflow.ts" copy
1421
+ import { createStep, createWorkflow } from "@mastra/core/workflows";
1422
+ import { z } from "zod";
1423
+ import { weatherAgent } from "../agents/weather-agent";
614
1424
 
615
- await context?.writer?.custom({
616
- type: "data-tool-progress",
617
- status: "success"
618
- });
1425
+ const analyzeWeather = createStep({
1426
+ id: "analyze-weather",
1427
+ inputSchema: z.object({ location: z.string() }),
1428
+ outputSchema: z.object({ analysis: z.string(), location: z.string() }),
1429
+ execute: async ({ inputData, writer }) => {
1430
+ const response = await weatherAgent.stream(
1431
+ `Analyze the weather in ${inputData.location} and provide insights.`
1432
+ );
1433
+
1434
+ // Pipe agent stream to step writer for real-time text streaming
1435
+ await response.fullStream.pipeTo(writer);
619
1436
 
620
1437
  return {
621
- value: ""
1438
+ analysis: await response.text,
1439
+ location: inputData.location,
622
1440
  };
623
- }
1441
+ },
624
1442
  });
1443
+
1444
+ const calculateScore = createStep({
1445
+ id: "calculate-score",
1446
+ inputSchema: z.object({ analysis: z.string(), location: z.string() }),
1447
+ outputSchema: z.object({ score: z.number(), summary: z.string() }),
1448
+ execute: async ({ inputData }) => {
1449
+ const score = inputData.analysis.includes("sunny") ? 85 : 50;
1450
+ return { score, summary: `Comfort score for ${inputData.location}: ${score}/100` };
1451
+ },
1452
+ });
1453
+
1454
+ export const weatherWorkflow = createWorkflow({
1455
+ id: "weather-workflow",
1456
+ inputSchema: z.object({ location: z.string() }),
1457
+ outputSchema: z.object({ score: z.number(), summary: z.string() }),
1458
+ })
1459
+ .then(analyzeWeather)
1460
+ .then(calculateScore);
1461
+
1462
+ weatherWorkflow.commit();
625
1463
  ```
626
1464
 
627
- For more information about tool streaming see [Tool streaming documentation](/docs/v1/streaming/tool-streaming).
1465
+ Register the workflow with a `workflowRoute()`. Text streaming is enabled by default.
1466
+
1467
+ ```typescript title="src/mastra/index.ts" copy
1468
+ import { Mastra } from "@mastra/core";
1469
+ import { workflowRoute } from "@mastra/ai-sdk";
1470
+
1471
+ export const mastra = new Mastra({
1472
+ agents: { weatherAgent },
1473
+ workflows: { weatherWorkflow },
1474
+ server: {
1475
+ apiRoutes: [
1476
+ workflowRoute({ path: "/workflow/weather", workflow: "weatherWorkflow" }),
1477
+ ],
1478
+ },
1479
+ });
1480
+ ```
1481
+
1482
+ </TabItem>
1483
+
1484
+ <TabItem value="frontend" label="Frontend">
1485
+
1486
+ Render both `text` parts (streaming agent output) and `data-workflow` parts (step progress).
1487
+
1488
+ ```typescript title="src/components/weather-workflow.tsx" copy
1489
+ import { useChat } from "@ai-sdk/react";
1490
+ import { DefaultChatTransport } from "ai";
1491
+ import { useState } from "react";
1492
+ import type { WorkflowDataPart } from "@mastra/ai-sdk";
1493
+
1494
+ type WorkflowData = WorkflowDataPart["data"];
1495
+
1496
+ export function WeatherWorkflow() {
1497
+ const [location, setLocation] = useState("");
1498
+ const { messages, sendMessage, status } = useChat({
1499
+ transport: new DefaultChatTransport({
1500
+ api: "http://localhost:4111/workflow/weather",
1501
+ prepareSendMessagesRequest: ({ messages }) => ({
1502
+ body: {
1503
+ inputData: {
1504
+ location: messages[messages.length - 1].parts.find((p) => p.type === "text")?.text,
1505
+ },
1506
+ },
1507
+ }),
1508
+ }),
1509
+ });
1510
+
1511
+ return (
1512
+ <div>
1513
+ <form onSubmit={(e) => {
1514
+ e.preventDefault();
1515
+ sendMessage({ text: location });
1516
+ setLocation("");
1517
+ }}>
1518
+ <input value={location} onChange={(e) => setLocation(e.target.value)} placeholder="Enter city" />
1519
+ <button type="submit" disabled={status !== "ready"}>Analyze</button>
1520
+ </form>
1521
+
1522
+ {messages.map((message) => (
1523
+ <div key={message.id}>
1524
+ {message.parts.map((part, index) => {
1525
+ // Streaming agent text
1526
+ if (part.type === "text" && message.role === "assistant") {
1527
+ return (
1528
+ <div key={index}>
1529
+ {status === "streaming" && <p><em>Agent analyzing...</em></p>}
1530
+ <p>{part.text}</p>
1531
+ </div>
1532
+ );
1533
+ }
1534
+ // Workflow step progress
1535
+ if (part.type === "data-workflow") {
1536
+ const workflow = part.data as WorkflowData;
1537
+ return (
1538
+ <div key={index}>
1539
+ {Object.entries(workflow.steps).map(([stepId, step]) => (
1540
+ <div key={stepId}>
1541
+ <strong>{stepId}</strong>: {step.status}
1542
+ </div>
1543
+ ))}
1544
+ </div>
1545
+ );
1546
+ }
1547
+ return null;
1548
+ })}
1549
+ </div>
1550
+ ))}
1551
+ </div>
1552
+ );
1553
+ }
1554
+ ```
1555
+
1556
+ </TabItem>
1557
+
1558
+ </Tabs>
1559
+
1560
+ Key points:
1561
+ - The step's `writer` is available in the `execute` function (not via `context`)
1562
+ - `includeTextStreamParts` defaults to `true` on `workflowRoute()`, so text streams by default
1563
+ - Text parts stream in real-time while `data-workflow` parts update with step status
1564
+
1565
+ For a complete implementation, see the [workflow-agent-text-stream example](https://github.com/mastra-ai/ui-dojo/blob/main/src/pages/ai-sdk/workflow-agent-text-stream.tsx) in UI Dojo.
1566
+
1567
+ ### Multi-stage progress with branching workflows
1568
+
1569
+ For workflows with conditional branching (e.g., express vs standard shipping), you can track progress across different branches by including a identifier in your custom events.
1570
+
1571
+ The UI Dojo example uses a `stage` field in the event data to identify which branch is executing (e.g., `"validation"`, `"standard-processing"`, `"express-processing"`). The frontend groups events by this field to show a pipeline-style progress UI.
1572
+
1573
+ See the [branching-workflow.ts](https://github.com/mastra-ai/ui-dojo/blob/main/src/mastra/workflows/branching-workflow.ts) (backend) and [workflow-custom-events.tsx](https://github.com/mastra-ai/ui-dojo/blob/main/src/pages/ai-sdk/workflow-custom-events.tsx) (frontend) in UI Dojo.
1574
+
1575
+ ### Progress indicators in agent networks
1576
+
1577
+ When using agent networks, you can emit custom progress events from tools used by sub-agents to show which agent is currently active.
1578
+
1579
+ The UI Dojo example includes a `stage` field in the event data to identify which sub-agent is running (e.g., `"report-generation"`, `"report-review"`). The frontend groups events by this field and displays the latest status for each.
1580
+
1581
+ See the [report-generation-tool.ts](https://github.com/mastra-ai/ui-dojo/blob/main/src/mastra/tools/report-generation-tool.ts) (backend) and [agent-network-custom-events.tsx](https://github.com/mastra-ai/ui-dojo/blob/main/src/pages/ai-sdk/agent-network-custom-events.tsx) (frontend) in UI Dojo.