DEV Community

Cover image for Easily Build a Frontend for your AWS Strands Agents using AG-UI in 30 minutes
Bonnie for CopilotKit

Posted on

Easily Build a Frontend for your AWS Strands Agents using AG-UI in 30 minutes

TL;DR

In this guide, you will learn how to build a frontend for your AWS Strands Agents using AG-UI Protocol and CopilotKit. AWS Strands will power the AI agents backend, while CopilotKit powers the frontend, and then AG-UI creates a bridge that enables the frontend to communicate with the backend.

Check out AG-UI's GitHub ⭐️

Before we jump in, here is what we will cover:

  • What is AWS Strands?

  • Setting up a AWS Strands + AG-UI + CopilotKit agent using CLI

  • Integrating your AWS Strands agent with AG-UI protocol in the backend

  • Building a frontend for your AWS Strands + AG-UI agent using CopilotKit

Here is a preview of what you can build using AWS Strands + AG-UI + CopilotKit.

What is AWS Strands?

AWS Strands, also known as Strands Agents, is an open-source agent development framework developed by the AWS team for building and deploying AI agents.

The framework adopts a model-driven approach where it leverages model reasoning to plan, orchestrate tasks, and reflect on goals.

Moreover, Strands is model-agnostic and supports various LLM providers, including Amazon Bedrock, Anthropic, OpenAI, Meta Llama, and local models via Ollama or LiteLLM.

Below are the key features and capabilities of AWS Strands:

  • Tool Integration: It provides straightforward mechanisms for integrating custom tools and comes with native support for the Model Context Protocol (MCP), which allows agents to access a vast ecosystem of third-party tools.

  • Multi-Agent Systems: Strands simplifies the creation of complex systems where multiple specialized agents collaborate on complex tasks.

  • Observability: It includes native features for observability, such as traces, metrics, and logs, which are essential for debugging and monitoring agent performance.

You can learn more about AWS Strands if you visit the Strands Agents docs.

Image from Notion

Prerequisites

  • Node.js 20+

  • Python 3.12+

  • OpenAI API Key (for the strands agent)

  • Any of the following package managers:

    • pnpm (recommended)
    • npm
    • yarn
    • bun

Setting up a AWS Strands + AG-UI + CopilotKit agent using CLI

In this section, you will learn how to set up a full-stack AWS Strands agent using a CLI command that sets up the backend using AG-UI protocol and the frontend using CopilotKit.

Let’s get started.

Step 1: Run CLI command

If you don’t already have an AWS Strands agent, you can set up one quickly by running the CLI command below in your terminal.

npx copilotkit@latest create -f aws-strands-py
Enter fullscreen mode Exit fullscreen mode

Then give your project a name as shown below.

Image from Notion

Step 2: Install dependencies

Once your project has been created successfully, install dependencies using your preferred package manager:

# Using pnpm (recommended)
pnpm install

# Using npm
npm install

# Using yarn
yarn install

# Using bun
bun install
Enter fullscreen mode Exit fullscreen mode

Step 3: Set up your OpenAI key

After installing the dependencies, create a .env file in the root folder and add your OpenAI API key.

OPENAI_API_KEY="your-open-api-key-here”
Enter fullscreen mode Exit fullscreen mode

Step 4: Run development server

Then start the development server using your preferred package manager:

# Using pnpm
pnpm dev

# Using npm
npm run dev

# Using yarn
yarn dev

# Using bun
bun run dev
Enter fullscreen mode Exit fullscreen mode

Once the development server is running, navigate to http://localhost:3000/, and you should see your AWS Strands + AG-UI + CopilotKit agent up and running.

Image from Notion

Congrats! You've successfully set up a full-stack AWS Strands agent. Your AI agent is now ready to use! Try playing around with the suggestions in the chat to test the integration.

Integrating your AWS Strands agent with AG-UI protocol in the backend

In this section, you will learn how to integrate your AWS Strands agent with AG-UI protocol, to expose it to the frontend.

Let’s jump in.

Step 1: Install AWS Strands + AG-UI packages

To get started, install the AWS Strands + AG-UI packages, along with other necessary dependencies, using the commands below.

uv pip install ag-ui-protocol strands-agents[OpenAI] strands-agents-tools ag_ui_strands
Enter fullscreen mode Exit fullscreen mode

Step 2: Import required packages

Once you have installed the required packages, import them as shown below in a main.py file.

# AG-UI / Strands integration helpers:
# - StrandsAgent: Wrapper that makes a Strands Agent compatible with AG-UI protocol
# - StrandsAgentConfig: Configuration for shared state and tool behaviors
# - ToolBehavior: Defines how specific tools interact with the AG-UI state system
# - create_strands_app: Factory function that creates a FastAPI application
from ag_ui_strands import (
    StrandsAgent,
    StrandsAgentConfig,
    ToolBehavior,
    create_strands_app,
)

# dotenv: Loads environment variables from a .env file for local development
from dotenv import load_dotenv

# Pydantic: Used for data validation and schema definition
from pydantic import BaseModel, Field

# Strands SDK:
# - Agent: The core agent class that orchestrates LLM interactions
# - tool: Decorator to mark functions as tools the agent can call
from strands import Agent, tool

# OpenAI model wrapper from Strands for using GPT models
from strands.models.openai import OpenAIModel

// ...
Enter fullscreen mode Exit fullscreen mode

Step 3: Define your AWS Strands Agent Data Models

After importing required packages, define Pydantic models for structured data validation to ensure that tool inputs are validated adequately before processing, as shown below.

# Pydantic: Used for data validation and schema definition
from pydantic import BaseModel, Field

// ...

class ProverbsList(BaseModel):
    """Pydantic model representing the entire proverbs list.

    We use a dedicated model to validate tool inputs for `update_proverbs`.
    The `proverbs` field is a list of strings describing the stored proverbs.
    """

    # The proverbs field is a list of strings with a description for the LLM
    proverbs: List[str] = Field(description="The complete list of proverbs")

 // ...
Enter fullscreen mode Exit fullscreen mode

Step 4: Define your AWS Strands Agent Tools

Once you have defined your agent data models, define your agent tools decorated with @tool to register them with the Strands framework, as shown below.

// ...

@tool
def get_weather(location: str):
    """Backend tool example: returns weather information for a location.

    This is a simple synchronous tool demonstrating a backend operation. In a
    real app this would call an external weather API. Here it returns a JSON
    string for simplicity so agents and UI can display structured data.

    Args:
        location: The location to get weather for (e.g., "Seattle, WA")

    Returns:
        A JSON string containing weather information.
    """
    # Return a JSON-encoded placeholder response
    # In production, this would call an actual weather API like OpenWeatherMap
    return json.dumps({"location": "70 degrees"})

@tool
def set_theme_color(theme_color: str):
    """Frontend-only tool example: request a UI theme color change.

    Frontend tools are declared on the backend so the agent can request UI
    actions, but actual execution happens in the browser via a frontend
    integration (e.g., `useFrontendTool`). Because the frontend performs the
    action, this backend implementation returns `None`.

    Args:
        theme_color: CSS color (hex, name, or rgb) that the UI should apply
    """
    # Return None because the actual implementation is in the frontend
    # The frontend will intercept this tool call and apply the theme change
    return None

@tool
def update_proverbs(proverbs_list: ProverbsList):
    """Backend tool: replace the entire proverbs list.

    IMPORTANT: This tool expects the complete list of proverbs, not just
    additions. When the UI or user wants to modify the list, always send the
    full array so the backend can store a canonical snapshot.

    Args:
        proverbs_list: A validated `ProverbsList` instance containing the
                        complete array of proverbs.

    Returns:
        A success message string on completion.
    """
    # In a real app, this would persist to a DB. Here we return success.
    # The state synchronization happens via the ToolBehavior config below.
    return "Proverbs updated successfully."

 // ...
Enter fullscreen mode Exit fullscreen mode

Step 5: Define State Management Functions

After defining your agent tools, configure state management functions to handle state synchronization between the agent and the UI, as shown below.

// ...

def build_proverbs_prompt(input_data, user_message: str) -> str:
    """Build a model prompt that includes the current proverbs state.

    When the agent composes messages for the model, we inject the current
    `proverbs` array into the prompt so the model can reference or modify it.

    This function acts as a "state context builder" - it takes the current
    shared state and incorporates it into the prompt sent to the LLM.

    Args:
        input_data: The shared state container (may include a `state` key)
        user_message: The user's raw request/message

    Returns:
        The combined prompt string includes proverbs when available.
    """
    # Step 5a: Try to extract the state dictionary from input_data
    state_dict = getattr(input_data, "state", None)

    # Step 5b: Check if we have a valid state with proverbs
    if isinstance(state_dict, dict) and "proverbs" in state_dict:
        # Step 5c: Pretty-print the proverbs array for the model to read easily
        # Using indent=2 makes the JSON human-readable in the prompt
        proverbs_json = json.dumps(state_dict["proverbs"], indent=2)

        # Step 5d: Combine the proverbs context with the user message
        return (
            f"Current proverbs list:\n{proverbs_json}\n\nUser request: {user_message}"
        )

    # Step 5e: Fall back to the plain user message if no proverbs state is available
    return user_message

async def proverbs_state_from_args(context):
    """Extract a `{'proverbs': [...]}` snapshot from tool arguments.

    This function is used by the shared state system when the `update_proverbs`
    tool runs. It inspects the tool input and returns a small dictionary
    containing only the `proverbs` array so AG-UI can update its local state.

    This is an async function because AG-UI's state handling may involve
    async operations in more complex scenarios.

    Args:
        context: ToolResultContext containing `tool_input` (string or dict)

    Returns:
        A dictionary like `{"proverbs": [...]}` or `None` on error.
    """
    try:
        # Step 5f: Get the raw tool input from the context
        tool_input = context.tool_input

        # Step 5g: If the tool input was serialized to a string, parse it back
        # This handles cases where the LLM sends the input as a JSON string
        if isinstance(tool_input, str):
            tool_input = json.loads(tool_input)

        # Step 5h: Some call sites may package the list under `proverbs_list` key
        # This provides flexibility in how the data is structured
        proverbs_data = tool_input.get("proverbs_list", tool_input)

        # Step 5i: Ensure we extract the array safely with proper type checking
        if isinstance(proverbs_data, dict):
            proverbs_array = proverbs_data.get("proverbs", [])
        else:
            proverbs_array = []

        # Step 5j: Return the state snapshot in the expected format
        return {"proverbs": proverbs_array}
    except Exception:
        # Step 5k: Return None to indicate we couldn't build a valid state snapshot
        # AG-UI will handle this gracefully and not update the state
        return None

    // ...
Enter fullscreen mode Exit fullscreen mode

Step 6: Configure Shared State between your Agent and UI

Once you have defined state management functions, configure how your AWS Strands agent and the UI share state and behave for specific tools using AG-UI’s StrandsAgentConfig class and state management functions you defined earlier, as shown below.

// ...

shared_state_config = StrandsAgentConfig(
    # Step 6a: Inject proverbs into the prompt using the builder function above
    # This ensures the LLM always has context about the current proverbs list
    state_context_builder=build_proverbs_prompt,

    # Step 6b: Define tool-specific behaviors for AG-UI integration
    # This dictionary maps tool names to their ToolBehavior configurations
    tool_behaviors={
        "update_proverbs": ToolBehavior(
            # Step 6c: Skip messages snapshot to avoid redundant state updates
            # When this is True, the tool result won't trigger a full message sync
            skip_messages_snapshot=True,

            # Step 6d: Use our custom state extractor function
            # This tells AG-UI how to extract state changes from tool arguments
            state_from_args=proverbs_state_from_args,
        )
    },
)

// ...
Enter fullscreen mode Exit fullscreen mode

Step 7: Configure your AWS Strands Agent

After configuring shared state, configure your AWS Strands agent by first initializing the OpenAI model using your OpenAI API key, as shown below.

// ...

# Step 7a: Retrieve the OpenAI API key from environment variables
# Default to empty string if not set (will cause authentication errors)
api_key = os.getenv("OPENAI_API_KEY", "")

# Step 7b: Create the OpenAI model instance with configuration
model = OpenAIModel(
    # Pass API key through client_args for the underlying OpenAI client
    client_args={"api_key": api_key},
    # Use GPT-4o (the optimized version); change as appropriate for your account
    model_id="gpt-4o",
)

// ...
Enter fullscreen mode Exit fullscreen mode

Then define the system prompt that sets the agent's persona and behavior, as shown below.

// ...

system_prompt = (
    "You are a helpful and wise assistant who helps manage a collection of proverbs."
)

// ...
Enter fullscreen mode Exit fullscreen mode

Finally, create a Strands Agent with the model and declared tools, as shown below.

// ...

strands_agent = Agent(
    model=model,                                          # The LLM to use
    system_prompt=system_prompt,                          # Agent's persona
    tools=[update_proverbs, get_weather, set_theme_color],  # Available tools
)

// ...
Enter fullscreen mode Exit fullscreen mode

Step 8: Integrate your AWS Strands Agent with AG-UI Protocol

Once you have configured your AWS Strands agent, integrate it with AG-UI protocol using AG-UI’s StrandsAgent wrapper, as shown below.

// ...

# Step 9a: Create a StrandsAgent wrapper that adds AG-UI capabilities
agui_agent = StrandsAgent(
    agent=strands_agent,       # The underlying Strands agent
    name="proverbs_agent",     # Unique identifier for this agent
    description="A proverbs assistant that collaborates with you to manage proverbs",
    config=shared_state_config,  # Shared state and tool behavior configuration
)

// ...
Enter fullscreen mode Exit fullscreen mode

Step 9: Create a FastAPI Server

After integrating your AWS Strands agent with AG-UI protocol, create a FastAPI server using the AG-UI factory function to expose your Strands + AG-UI agent to the frontend, as shown below.

// ...

app = create_strands_app(agui_agent, "/")

if __name__ == "__main__":
    # Step 11a: Import uvicorn ASGI server (only needed when running directly)
    import uvicorn

    # Step 11b: Start the development server with auto-reload enabled
    # - "main:app": Module and app variable reference for uvicorn
    # - host="0.0.0.0": Listen on all network interfaces
    # - port=8000: Default HTTP port for development
    # - reload=True: Auto-restart on code changes (development mode)
    uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=True)
Enter fullscreen mode Exit fullscreen mode

Congrats! You've successfully integrated your Python AWS Strands Agent with AG-UI protocol and it is available at http://localhost:8000 (or specified port) endpoint.

Let’s now see how to add a frontend to your AG-UI wrapped AWS Strands agent.

Building a frontend for your AWS Strands + AG-UI agent using CopilotKit

In this section, you will learn how to add a frontend to your AWS Strands + AG-UI agent using CopilotKit, which runs anywhere that React runs.

Let’s get started.

Step 1: Install CopilotKit packages

To get started, install the latest CopilotKit packages in your frontend.

npm install @copilotkit/react-ui @copilotkit/react-core @copilotkit/runtime @ag-ui/client
Enter fullscreen mode Exit fullscreen mode

Step 2: Set up the Copilot Runtime instance

Once you have installed the CopilotKit packages, set up the Copilot runtime instance with an HttpAgent example in the /api/copilotkit API route to enable your frontend to make HTTP requests to the backend.

import {
  CopilotRuntime,
  ExperimentalEmptyAdapter,
  copilotRuntimeNextJSAppRouterEndpoint,
} from "@copilotkit/runtime";

import { HttpAgent } from "@ag-ui/client";
import { NextRequest } from "next/server";

// 1. You can use any service adapter here for multi-agent support. We use
//    the empty adapter since we're only using one agent.
const serviceAdapter = new ExperimentalEmptyAdapter();

// 2. Create the CopilotRuntime instance and utilize the Strands AG-UI
//    integration to set up the connection.
const runtime = new CopilotRuntime({
  agents: {
    // Our FastAPI endpoint URL
    strands_agent: new HttpAgent({ url: "http://localhost:8000" }),
  },
});

// 3. Build a Next.js API route to handle CopilotKit runtime requests.
export const POST = async (req: NextRequest) => {
  const { handleRequest } = copilotRuntimeNextJSAppRouterEndpoint({
    runtime,
    serviceAdapter,
    endpoint: "/api/copilotkit",
  });

  return handleRequest(req);
};
Enter fullscreen mode Exit fullscreen mode

Step 3: Set up CopilotKit provider

After setting up a Copilot Runtime instance, set up the CopilotKit provider component that manages your ADK agent sessions.

To set up the CopilotKit Provider, the  component must wrap the Copilot-aware parts of your application.

For most use cases, it's appropriate to wrap the CopilotKit provider around the entire app, e.g., in your layout.tsx file.

// Step 1: Import necessary types and components from Next.js and CopilotKit
import type { Metadata } from "next";

import { CopilotKit } from "@copilotkit/react-core";
import "./globals.css";
import "@copilotkit/react-ui/styles.css";

// Step 2: Define metadata for the application, used by Next.js for SEO and page headers
export const metadata: Metadata = {
  title: "Create Next App",
  description: "Generated by create next app",
};

// Step 3: Define the RootLayout component, which wraps the entire application
export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  // Step 4: Return the JSX structure for the layout
  return (
    <html lang="en">
      <body className={"antialiased"}>
        {/* Step 5: Wrap the children components with CopilotKit provider to enable CopilotKit functionality */}
        <CopilotKit runtimeUrl="/api/copilotkit" agent="strands_agent">
          {children}
        </CopilotKit>
      </body>
    </html>
  );
}
Enter fullscreen mode Exit fullscreen mode

Step 4: Set up a Copilot UI component

Once you have set up the CopilotKit Provider, add a Copilot UI component to enable interaction with your ADK agent. CopilotKit ships with several built-in chat components, which include CopilotPopupCopilotSidebar, and CopilotChat.

To set up a Copilot UI component, define it alongside your core page components, e.g., in your page.tsx file.

"use client";

import { CopilotKitCSSProperties, CopilotSidebar } from "@copilotkit/react-ui";
import { useState } from "react";

export default function CopilotKitPage() {

  // ...

  return (
    <main
      // Step 2d-i: Apply theme color as a CSS custom property
      // CopilotKit components read this variable to style their elements
      style={
        { "--copilot-kit-primary-color": themeColor } as CopilotKitCSSProperties
      }
    >
      {/* Step 2d-ii: CopilotSidebar provides the chat interface */}
      <CopilotSidebar
        // Step 2d-iii: Configuration options for the sidebar behavior
        clickOutsideToClose={false}  // Keep sidebar open when clicking outside
        defaultOpen={true}            // Start with sidebar visible

        // Step 2d-iv: Labels customize the sidebar header and welcome message
        labels={{
          title: "Popup Assistant",
          initial: "👋 Hi, there! You're chatting with a Strands agent.",
        }}

        // Step 2d-v: Suggestions provide quick-action buttons for users
        // Each suggestion sends a predefined message when clicked
        suggestions={[
          {
            title: "Generative UI",
            message: "What's the weather in San Francisco?",
          },
          {
            title: "Frontend Tools",
            message: "Set the theme to green.",
          },
          {
            title: "Writing Agent State",
            message: "Add a proverb about AI.",
          },
        ]}
      >
        {/* Step 2d-vi: Child content is pushed to the side by the sidebar */}
        <YourMainContent themeColor={themeColor} />
      </CopilotSidebar>
    </main>
  );
}
Enter fullscreen mode Exit fullscreen mode

Step 5: Sync your AWS Strands + AG-UI agent state with the Frontend

After setting up a Copilot UI component, sync your AWS Strands + AG-UI agent state with the frontend using CopilotKit hooks.

To sync your AWS Strands + AG-UI agent state with the frontend, use the CopilotKit useCoAgent hook that allows you to share state bidirectionally between your application and the agent.

"use client";

import { useCoAgent } from "@copilotkit/react-core";

function YourMainContent({ themeColor }: { themeColor: string }) {

  // 🪁 Shared State: https://docs.copilotkit.ai/coagents/shared-state
  const { state, setState } = useCoAgent({

      // Agent name must match the CopilotKit Provider StrandsAgent name
    name: "strands_agent",
    initialState: {
      proverbs: [
        "CopilotKit may be new, but it's the best thing since sliced bread.",
      ],
    },
  })

     // ...
  return (

  // ...
  )
Enter fullscreen mode Exit fullscreen mode

Step 6: Rendering your AWS Strands + AG-UI Agent Tool Calls

Once you have synced your agent state with the frontend, render your agent's tool calls with custom UI components using the CopilotKit’s [useRenderToolCall](https://docs.copilotkit.ai/reference/hooks/useRenderToolCall) hook to provide the user with feedback about what your agent is doing, specifically when your agent is calling tools, as shown below.

"use client";

import {
  useDefaultTool,
  useRenderToolCall,
} from "@copilotkit/react-core";

// ...

function YourMainContent({ themeColor }: { themeColor: string }) {

  // -------------------------------------------------------------------------
  // Step 3c: Generative UI for Weather Tool
  // -------------------------------------------------------------------------
  // 🪁 Generative UI: https://docs.copilotkit.ai/strands/generative-ui/backend-tools
  //
  // This hook tells CopilotKit how to render the "get_weather" tool call.
  // Instead of showing raw JSON, it displays a custom WeatherCard component.
  useRenderToolCall(
    {
      // Step 3c-i: Tool name to intercept for custom rendering
      name: "get_weather",

      // Step 3c-ii: Parameter schema (for documentation/type checking)
      parameters: [
        {
          name: "location",
          description: "The location to get the weather for.",
          required: true,
        },
      ],

      // Step 3c-iii: Render function called when this tool is executed
      // Returns a React component that displays the tool result visually
      render: (props) => (
        <WeatherCard themeColor={themeColor} location={props.args.location} />
      ),
    },
    // Step 3c-iv: Dependency array - re-creates the render when themeColor changes
    [themeColor],
  );

  // -------------------------------------------------------------------------
  // Step 3d: Default Generative UI (Fallback Renderer)
  // -------------------------------------------------------------------------
  // 🪁 Default Generative UI: https://docs.copilotkit.ai/strands/generative-ui/backend-tools
  //
  // This hook provides a fallback UI for any tool that doesn't have a
  // specific useRenderToolCall defined. It ensures all tools have some
  // visual representation in the chat.
  useDefaultTool(
    {
      // Step 3d-i: Default render function receives all tool props
      render: (props) => (
        <DefaultToolComponent themeColor={themeColor} {...props} />
      ),
    },
    // Step 3d-ii: Dependency array for re-render on theme changes
    [themeColor],
  );

  return (

    // ...

  );
}
Enter fullscreen mode Exit fullscreen mode

Then try asking the agent for the weather in a location. You should see the custom UI component that we added, which renders the get weather tool call and displays the arguments that were passed to the tool.

Image from Notion

Step 7: Declaring Frontend Tools

After rendering your agent's tool calls, you can declare frontend tools using the CopilotKit’s [useFrontendTool](https://docs.copilotkit.ai/reference/hooks/useFrontendTool) hook that your agent can call to interact with client-side primitives such as:

  • Reading or modifying React component state

  • Accessing browser APIs like localStorage, sessionStorage, or cookies

  • Triggering UI updates or animations

  • Interacting with third-party frontend libraries

  • Performing actions that require the user's immediate browser context

You can declare frontend tools and use them within your AWS Strands agent, as shown below.

"use client";

import {

  useFrontendTool,

  } from "@copilotkit/react-core";

export default function CopilotKitPage() {
  // -------------------------------------------------------------------------
  // Step 2a: Local State for Theme Color
  // -------------------------------------------------------------------------
  // Initialize with a default indigo color. This state will be updated
  // when the agent calls the "set_theme_color" frontend tool.
  const [themeColor, setThemeColor] = useState("#6366f1");

  // Step 2b: Debug Effect - logs theme color changes to console
  useEffect(() => {
    console.log(themeColor);
  }, [themeColor]);

  // -------------------------------------------------------------------------
  // Step 2c: Frontend Tool Declaration
  // -------------------------------------------------------------------------
  // 🪁 Frontend Actions: https://docs.copilotkit.ai/guides/frontend-actions
  //
  // This hook declares a tool that the agent can call, but the execution
  // happens here in the browser (not on the backend server).
  //
  // When the backend agent calls "set_theme_color", CopilotKit intercepts
  // the call and runs the handler function defined here instead.
  useFrontendTool({
    // Step 2c-i: Tool name must match the backend tool declaration
    name: "set_theme_color",

    // Step 2c-ii: Parameter schema for the LLM to understand the tool
    parameters: [
      {
        name: "theme_color",
        description: "The theme color to set. Make sure to pick nice colors.",
        required: true,
      },
    ],

    // Step 2c-iii: Handler function executed when the tool is called
    // Destructures the theme_color from the tool arguments and updates the state
    handler({ theme_color }) {
      setThemeColor(theme_color);
    },
  });

  // -------------------------------------------------------------------------
  // Step 2d: Render the Page Layout
  // -------------------------------------------------------------------------
  return (
    <main
      // Step 2d-i: Apply theme color as a CSS custom property
      // CopilotKit components read this variable to style their elements
      style={
        { "--copilot-kit-primary-color": themeColor } as CopilotKitCSSProperties
      }
    >
      {/* Step 2d-ii: CopilotSidebar provides the chat interface */}
      <CopilotSidebar
        // Step 2d-iii: Configuration options for the sidebar behavior
        clickOutsideToClose={false}  // Keep sidebar open when clicking outside
        defaultOpen={true}            // Start with sidebar visible

        // Step 2d-iv: Labels customize the sidebar header and welcome message
        labels={{
          title: "Popup Assistant",
          initial: "👋 Hi, there! You're chatting with a Strands agent.",
        }}

        // Step 2d-v: Suggestions provide quick-action buttons for users
        // Each suggestion sends a predefined message when clicked
        suggestions={[
          {
            title: "Generative UI",
            message: "What's the weather in San Francisco?",
          },
          {
            title: "Frontend Tools",
            message: "Set the theme to green.",
          },
          {
            title: "Writing Agent State",
            message: "Add a proverb about AI.",
          },
        ]}
      >
        {/* Step 2d-vi: Child content is pushed to the side by the sidebar */}
        <YourMainContent themeColor={themeColor} />
      </CopilotSidebar>
    </main>
  );
}
Enter fullscreen mode Exit fullscreen mode

Then try asking the agent to set the UI theme to green. You should see the theme change to green, as shown below.

Image from Notion

Step 8: Stream your AWS Strands + AG-UI agent responses in the frontend

To stream your AWS Strands + AG-UI agent responses or results in the frontend, pass the agent’s state field values to the frontend components, as shown below.

"use client";

import { useCoAgent } from "@copilotkit/react-core";

function YourMainContent({ themeColor }: { themeColor: string }) {
  // 🪁 Shared State: https://docs.copilotkit.ai/coagents/shared-state
  const { state, setState } = useCoAgent<AgentState>({
    name: "strands_agent",
    initialState: {
      proverbs: [
        "CopilotKit may be new, but it's the best thing since sliced bread.",
      ],
    },
  })

  // ...

  return (
    <div
      // Step 3e-i: Dynamic background color from theme state
      style={{ backgroundColor: themeColor }}
      className="h-screen flex justify-center items-center flex-col transition-colors duration-300"
    >
      {/* Step 3e-ii: Glass-morphism card container for proverbs */}
      <div className="bg-white/20 backdrop-blur-md p-8 rounded-2xl shadow-xl max-w-2xl w-full">
        {/* Step 3e-iii: Page header */}
        <h1 className="text-4xl font-bold text-white mb-2 text-center">
          Proverbs
        </h1>
        <p className="text-gray-200 text-center italic mb-6">
          This is a demonstrative page, but it could be anything you want! 🪁
        </p>
        <hr className="border-white/20 my-6" />

        {/* Step 3e-iv: Proverbs list - maps over shared state array */}
        <div className="flex flex-col gap-3">
          {state.proverbs?.map((proverb, index) => (
            <div
              key={index}
              className="bg-white/15 p-4 rounded-xl text-white relative group hover:bg-white/20 transition-all"
            >
              {/* Step 3e-v: Proverb text content */}
              <p className="pr-8">{proverb}</p>

              {/* Step 3e-vi: Delete button - appears on hover */}
              <button
                onClick={() =>
                  // Step 3e-vii: Update shared state by filtering out this proverb
                  // This change will sync back to the backend agent
                  setState({
                    ...state,
                    proverbs: state.proverbs?.filter((_, i) => i !== index),
                  })
                }
                className="absolute right-3 top-3 opacity-0 group-hover:opacity-100 transition-opacity
                  bg-red-500 hover:bg-red-600 text-white rounded-full h-6 w-6 flex items-center justify-center"
              >
                
              </button>
            </div>
          ))}
        </div>

        {/* Step 3e-viii: Empty state message when no proverbs exist */}
        {state.proverbs?.length === 0 && (
          <p className="text-center text-white/80 italic my-8">
            No proverbs yet. Ask the assistant to add some!
          </p>
        )}
      </div>
    </div>
    );
}
Enter fullscreen mode Exit fullscreen mode

If you query your AWS Strands + AG-UI agent, you should see the agent’s response or results streaming in the UI, as shown below.

Image from Notion

Conclusion

In this guide, we have walked through the steps to build a frontend for your AWS Strands agent using the AG-UI protocol and CopilotKit.

While we’ve explored a couple of features, we have barely scratched the surface of the countless use cases for CopilotKit, ranging from building interactive AI chatbots to building agentic solutions—in essence, CopilotKit lets you add a ton of functional AI capabilities to your products in minutes.

Hopefully, this guide makes it easier for you to integrate AI-powered Copilots into your existing application.

Follow CopilotKit on Twitter and say hi. If you'd like to build something cool, join the Discord community.

Top comments (4)

Collapse
 
fliin_0788 profile image
Fliin

Comprehensive tutorial. We are an AWS shop and would love to go through this starter build to see if it's a good fit for what we are trying to accomplish.

Collapse
 
luis_prott profile image
Luis Prott

Nice, we are building with AWS Strands. Where do I go if I get hung up?

Collapse
 
nathan_tarbert profile image
Nathan Tarbert CopilotKit

Amazing to see a tutorial using AWS Strands agents!
Nicely done, Bonnie!

Collapse
 
eli_discovers profile image
Eli Berman CopilotKit

Great stuff and was super easy to follow!