> ## Documentation Index
> Fetch the complete documentation index at: https://docs.thesys.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Integrating Mastra with Thesys

> Give your Mastra agents interactive UI components and dynamic workflows powered by Thesys C1

Combine **Mastra's** orchestration with Generative UI from Thesys to move beyond text-only agents.
Render agentic UIs that include real-time dashboards, dynamic forms, and interactive
workflows - all generated from your agent logic.

With lightweight React or TypeScript integration, you can turn multi-agent workflows into
production-ready applications that feel natural, adaptive, and user-friendly.

<Note>
  This guide assumes you have basic knowledge of Mastra and NextJS.
  You'll also need a Thesys API key from the [C1 Console](https://console.thesys.dev/keys).
</Note>

<Steps>
  <Step title="Create a new NextJS project">
    ```bash theme={null}
    npx create-next-app@latest thesys-mastra-app
    ```

    Install the dependencies:

    ```json package.json theme={null}
    {
      "dependencies": {
        "@ai-sdk/openai": "^2.0.23",
        "@crayonai/react-ui": "^0.8.24",
        "@crayonai/react-core": "^0.7.6",
        "@crayonai/stream": "^0.6.4",
        "@mastra/core": "^0.15.2",
        "@mastra/libsql": "^0.13.7",
        "@mastra/loggers": "^0.10.9",
        "@thesysai/genui-sdk": "^0.6.31",
        ...
      }
    }
    ```

    ```bash theme={null}
    npm install
    ```
  </Step>

  <Step title="Create a new Mastra agent">
    You can follow the [Mastra Quickstart](https://mastra.ai/en/docs/getting-started/installation) to create a new Mastra agent
    or just follow along this guide. We'll set up the example weather agent from the Quickstart but change
    the LLM provider to Thesys.

    <Tabs>
      <Tab title="Setting up the Mastra server">
        ```ts src/server/index.ts theme={null}
        import { Mastra } from "@mastra/core/mastra";

        import { weatherAgent } from "./agents/weather-agent";

        export const mastra = new Mastra({
          agents: { weatherAgent },
        });

        ```
      </Tab>

      <Tab title="Building the agent">
        ```ts src/server/agents/weather-agent.ts [expandable] theme={null}
        import { createOpenAI } from "@ai-sdk/openai";
        import { Agent } from "@mastra/core/agent";
        import { weatherTool } from "../tools/weather-tool";

        export const weatherAgent = new Agent({
          name: "Weather Agent",
          instructions: `
              You are a helpful weather assistant that provides accurate weather information and can help planning activities based on the weather.

              Your primary function is to help users get weather details for specific locations. When responding:
              - Always ask for a location if none is provided
              - If the location name isn't in English, please translate it
              - If giving a location with multiple parts (e.g. "New York, NY"), use the most relevant part (e.g. "New York")
              - Include relevant detailsa like humidity, wind conditions, and precipitation
              - Keep responses concise but informative
              - If the user asks for activities and provides the weather forecast, suggest activities based on the weather forecast.
              - If the user asks for activities, respond in the format they request.

              Use the weatherTool to fetch current weather data.
        `,
          model: createOpenAI({
            baseURL: "https://api.thesys.dev/v1/embed",
            apiKey: process.env.THESYS_API_KEY,
          }).chat("c1/anthropic/claude-sonnet-4/v-20250815"),
          tools: { weatherTool },
        });
        ```
      </Tab>

      <Tab title="Building the tool">
        ```ts src/server/tools/weather-tool.ts [expandable] theme={null}
        import { createTool } from '@mastra/core/tools';
        import { z } from 'zod';

        interface GeocodingResponse {
          results: {
            latitude: number;
            longitude: number;
            name: string;
          }[];
        }
        interface WeatherResponse {
          current: {
            time: string;
            temperature_2m: number;
            apparent_temperature: number;
            relative_humidity_2m: number;
            wind_speed_10m: number;
            wind_gusts_10m: number;
            weather_code: number;
          };
        }

        export const weatherTool = createTool({
          id: 'get-weather',
          description: 'Get current weather for a location',
          inputSchema: z.object({
            location: z.string().describe('City name'),
          }),
          outputSchema: z.object({
            temperature: z.number(),
            feelsLike: z.number(),
            humidity: z.number(),
            windSpeed: z.number(),
            windGust: z.number(),
            conditions: z.string(),
            location: z.string(),
          }),
          execute: async ({ context }) => {
            return await getWeather(context.location);
          },
        });

        const getWeather = async (location: string) => {
          const geocodingUrl = `https://geocoding-api.open-meteo.com/v1/search?name=${encodeURIComponent(location)}&count=1`;
          const geocodingResponse = await fetch(geocodingUrl);
          const geocodingData = (await geocodingResponse.json()) as GeocodingResponse;

          if (!geocodingData.results?.[0]) {
            throw new Error(`Location '${location}' not found`);
          }

          const { latitude, longitude, name } = geocodingData.results[0];

          const weatherUrl = `https://api.open-meteo.com/v1/forecast?latitude=${latitude}&longitude=${longitude}&current=temperature_2m,apparent_temperature,relative_humidity_2m,wind_speed_10m,wind_gusts_10m,weather_code`;

          const response = await fetch(weatherUrl);
          const data = (await response.json()) as WeatherResponse;

          return {
            temperature: data.current.temperature_2m,
            feelsLike: data.current.apparent_temperature,
            humidity: data.current.relative_humidity_2m,
            windSpeed: data.current.wind_speed_10m,
            windGust: data.current.wind_gusts_10m,
            conditions: getWeatherCondition(data.current.weather_code),
            location: name,
          };
        };

        function getWeatherCondition(code: number): string {
          const conditions: Record<number, string> = {
            0: 'Clear sky',
            1: 'Mainly clear',
            2: 'Partly cloudy',
            3: 'Overcast',
            45: 'Foggy',
            48: 'Depositing rime fog',
            51: 'Light drizzle',
            53: 'Moderate drizzle',
            55: 'Dense drizzle',
            56: 'Light freezing drizzle',
            57: 'Dense freezing drizzle',
            61: 'Slight rain',
            63: 'Moderate rain',
            65: 'Heavy rain',
            66: 'Light freezing rain',
            67: 'Heavy freezing rain',
            71: 'Slight snow fall',
            73: 'Moderate snow fall',
            75: 'Heavy snow fall',
            77: 'Snow grains',
            80: 'Slight rain showers',
            81: 'Moderate rain showers',
            82: 'Violent rain showers',
            85: 'Slight snow showers',
            86: 'Heavy snow showers',
            95: 'Thunderstorm',
            96: 'Thunderstorm with slight hail',
            99: 'Thunderstorm with heavy hail',
          };
          return conditions[code] || 'Unknown';
        }
        ```
      </Tab>
    </Tabs>
  </Step>

  <Step title="Change the render function to use C1Chat">
    ```tsx src/app/page.tsx theme={null}
    "use client";

    import { C1Chat } from "@thesysai/genui-sdk";
    import "@crayonai/react-ui/styles/index.css";

    export default function Home() {
      return <C1Chat apiUrl="/api/chat" />;
    }
    ```
  </Step>

  <Step title="Implement the chat endpoint">
    We will modify the chat endpoint from the [template app](https://github.com/thesysdev/template-c1-next/blob/main/src/app/api/chat/route.ts)
    to use our newly created Mastra agent and return the response as a SSE stream.

    <Tabs>
      <Tab title="Modifying the chat endpoint">
        ```ts src/app/api/chat/route.ts [expandable] theme={null}
        import { NextRequest, NextResponse } from "next/server";
        import { getMessageStore, MastraMessage } from "./messageStore";
        import { mastra } from "../../../server";

        export async function POST(req: NextRequest) {
          const { prompt, threadId } = (await req.json()) as {
            prompt: { content: string };
            threadId: string;
          };
          const messageStore = getMessageStore(threadId);
          const agent = mastra.getAgent("weatherAgent");

          // Prepare messages including history and current user message
          const userMessage = {
            id: crypto.randomUUID(),
            role: "user" as const,
            content: prompt.content,
            createdAt: new Date(),
          };
          const messages = [
            ...messageStore.getMastraCompatibleMessageList(),
            userMessage,
          ];

          // Add the user message to the store first
          messageStore.addMessage(userMessage);

          // Use streaming for the response
          const stream = await agent.streamVNext(messages);

          // Create a readable stream that processes the agent stream
          const readableStream = new ReadableStream({
            async start(controller) {
              try {
                let fullContent = "";

                for await (const chunk of stream.fullStream) {
                  if (chunk.type === "text-delta") {
                    const text = chunk.payload.text;
                    if (text) {
                      fullContent += text;
                      // Send the text chunk to the client
                      controller.enqueue(new TextEncoder().encode(text));
                    }
                  }
                }

                // After streaming is complete, save the assistant message
                const assistantMessage: MastraMessage = {
                  id: crypto.randomUUID(),
                  role: "assistant",
                  content: fullContent,
                  createdAt: new Date(),
                };
                messageStore.addMessage(assistantMessage);

                controller.close();
              } catch (error) {
                controller.error(error);
              }
            },
          });

          return new NextResponse(readableStream, {
            headers: {
              "Content-Type": "text/plain",
              "Cache-Control": "no-cache",
              Connection: "keep-alive",
            },
          });
        }
        ```
      </Tab>

      <Tab title="Modifying the message store">
        ```ts src/app/api/chat/messageStore.ts [expandable] theme={null}
        // Mastra-compatible message type
        export type MastraMessage = {
          id: string;
          role: "user" | "assistant" | "system";
          content: string;
          createdAt: Date;
          threadId?: string;
        };

        const messagesStore: {
          [threadId: string]: MastraMessage[];
        } = {};

        export const getMessageStore = (id: string) => {
          if (!messagesStore[id]) {
            messagesStore[id] = [];
          }
          const messageList = messagesStore[id];
          return {
            addMessage: (message: MastraMessage) => {
              messageList.push({
                ...message,
                threadId: id,
              });
            },
            messageList,
            getMastraCompatibleMessageList: () => {
              return messageList;
            },
          };
        };
        ```
      </Tab>
    </Tabs>
  </Step>

  <Step title="Run the app">
    We need to modify the `.env.local` file to include the Thesys API key and then add
    the Mastra server to the `next.config.ts` file and start the app.

    ```ts next.config.ts theme={null}
    import type { NextConfig } from "next";

    const nextConfig: NextConfig = {
      /* config options here */
      serverExternalPackages: ["@server/*"],
    };

    export default nextConfig;
    ```

    Now simply start the app:

    ```bash theme={null}
    export THESYS_API_KEY=<your-api-key>
    npm run dev
    ```
  </Step>
</Steps>

<Card title="View the code" icon="github" href="https://github.com/thesysdev/examples/tree/main/mastra">
  Find more examples and complete code on our GitHub repository.
</Card>
