React to AG-UI Agent
Nx Plugin for AWS provides a generator to connect a React website to an Agent that exposes the AG-UI protocol. It wires up CopilotKit with an @ag-ui/client HttpAgent on your website, with AWS IAM and Cognito authentication support.
Prerequisites
Section titled “Prerequisites”Before using this generator, ensure you have:
- A React website (generated using the
ts#websitegenerator) - A TypeScript or Python Agent with
protocol=ag-ui(generated using thets#agentorpy#agentgenerator) - For deployed agents, Cognito Auth added via the
ts#website#authgenerator
Run the Generator
Section titled “Run the Generator”Run this generator@aws/nx-plugin:connection
pnpm nx g @aws/nx-plugin:connection yarn nx g @aws/nx-plugin:connection npx nx g @aws/nx-plugin:connection bunx nx g @aws/nx-plugin:connection- Install the Nx Console VSCode Plugin if you haven't already
- Open the Nx Console in VSCode
- Click
Generate (UI)in the "Common Nx Commands" section - Search for
@aws/nx-plugin - connection - Fill in the required parameters
- Click
Generate
Build your command5
Required
Required
You will be prompted to select your React website as the source project and the project containing your AG-UI Agent as the target project. If your target project contains multiple components (such as multiple agents or other component types), you will be prompted to specify a targetComponent to disambiguate.
Options
Section titled “Options”sourceProjectRequiredstringThe source project
targetProjectRequiredstringThe target project to connect to
sourceComponentstringThe source component to connect from (component name, path relative to source project root, or generator id). Use '.' to explicitly select the project as the source.
targetComponentstringThe target component to connect to (component name, path relative to target project root, or generator id). Use '.' to explicitly select the project as the target.
preferInstallDependenciesbooleanDefault:trueWhether to prefer installing dependencies after the generator runs. Set to false to defer installing when batching multiple generators (an install still runs if needed so subsequent generators can compute the Nx project graph); install once at the end.
Generator Output
Section titled “Generator Output”The generator creates a single shared AguiProvider component, one hook per connected agent, and a themed wrapper for the CopilotKit chat components:
Directorysrc
Directorycomponents
- AguiProvider.tsx Single
CopilotKitProviderfor every AG-UI agent. Created on the firstconnectionrun and updated on subsequent runs to register each new agent. - <agent-name>-chat.tsx A themed
<AgentName>Chatbound to this agent’s id. One file perconnectionrun. Directorycopilot
- index.tsx Re-exports
CopilotChat,CopilotSidebarandCopilotPopupwith slot defaults that match your website’sux(Cloudscape, Shadcn, or no theme at all). - ThemeComponents .tsx Per-slot theme components (e.g.
CloudscapeAssistantMessage.tsx,ShadcnChatInput.tsx). Only vended whenuxiscloudscapeorshadcn.
- index.tsx Re-exports
- AguiProvider.tsx Single
Directoryhooks
- useAgui<AgentName>.tsx Registers one AG-UI agent and exports its id as
<AGENT_NAME>_ID. One file perconnectionrun. - useSigV4.tsx SigV4 signing (IAM only)
- useAgui<AgentName>.tsx Registers one AG-UI agent and exports its id as
Running connection a second time for a different agent adds a new useAgui<AgentName>.tsx hook and <agent-name>-chat.tsx component and updates AguiProvider.tsx to register both hooks — any custom edits you’ve made to the provider are preserved. main.tsx keeps its single <AguiProvider> wrapper — you never end up with nested providers.
The following dependencies are added to the root package.json:
@copilotkit/react-core— shipsCopilotKitProviderand chat components (CopilotChat,CopilotSidebar,CopilotPopup)@ag-ui/client—HttpAgentused by the generated hooksaws4fetch,oidc-client-ts,react-oidc-context,@aws-sdk/credential-providers— IAM auth onlyreact-oidc-context— Cognito auth
How It Works
Section titled “How It Works”AG-UI Connection
Section titled “AG-UI Connection”Each useAgui<AgentName> hook reads its agent’s runtime value from Runtime Configuration and instantiates an @ag-ui/client HttpAgent:
- Deployed: the runtime value is a Bedrock AgentCore Runtime ARN, which is converted to the AgentCore HTTPS endpoint:
https://bedrock-agentcore.<region>.amazonaws.com/runtimes/<encoded-arn>/invocations?qualifier=DEFAULT - Local development:
devoverrides the value to the agent’s local URL (e.g.http://localhost:8081)
The shared AguiProvider calls every generated hook and spreads each one into selfManagedAgents on a single CopilotKitProvider, which exposes them all to CopilotKit components.
CopilotKit Integration
Section titled “CopilotKit Integration”CopilotKit is the 1st-party reference React client for the AG-UI protocol and ships ready-made chat components:
<CopilotChat />— full chat interface<CopilotSidebar />— fixed side panel chat<CopilotPopup />— floating chat popup
Place any of these anywhere inside the <AguiProvider> wrapper (already wired into main.tsx for you).
Authentication
Section titled “Authentication”The generated code handles authentication depending on your agent’s configuration:
- IAM (default): uses AWS SigV4-signed HTTP requests. Credentials are obtained from the Cognito Identity Pool configured with your website’s auth.
- Cognito: embeds the JWT access token in the
Authorizationheader as a Bearer token.
Sessions and Threads
Section titled “Sessions and Threads”AG-UI and AgentCore Runtime each identify a conversation differently, and the generated hook ties them together:
threadId— the AG-UI conversation identifier, sent in the request body. CopilotKit generates a random UUID per chat unless you pass an explicitthreadId.- Session ID — the AgentCore Runtime session, sent in the
X-Amzn-Bedrock-AgentCore-Runtime-Session-Idheader. It selects the microVM serving the request, and is what your agent’ssession.ts/session.pykeys conversation state on.
The hook derives the session ID from the thread ID, right-padding it to the 33 characters AgentCore Runtime requires:
function agentCoreSessionId(input: RunAgentInput): string { return (input.threadId ?? '').padEnd(33, '0');}Leaving threadId unset is simplest — CopilotKit’s generated UUID is already 36 characters. If you pass one explicitly, make it at least 33 characters, since padding maps thread IDs that differ only in trailing characters onto the same session.
Both Session ID and Thread ID are provided by the browser. To restrict each user to their own conversations, refer to the py#agent or ts#agent guide.
Infrastructure
Section titled “Infrastructure”If your agent uses IAM auth, the Cognito Identity Pool’s authenticated role must be granted permission to invoke the agent.
const identity = new UserIdentity(this, 'Identity');const myAgent = new MyAgent(this, 'MyAgent');
// Grant the authenticated Cognito role permission to invoke the agentmyAgent.grantInvokeAccess(identity.identityPool.authenticatedRole);grantInvokeAccess wires up all AgentCore invoke actions (InvokeAgentRuntime, InvokeAgentRuntimeWithWebSocketStream) on the agent’s runtime ARN.
module "identity" { source = "../../common/terraform/src/core/user-identity"}
module "my_agent" { source = "../../common/terraform/src/app/agents/my-agent"
appconfig_application_id = module.runtime_config_appconfig.application_id appconfig_application_arn = module.runtime_config_appconfig.application_arn}
# Grant the authenticated Cognito role permission to invoke the agentresource "aws_iam_policy" "invoke_my_agent" { name = "InvokeMyAgentPolicy" policy = jsonencode({ Version = "2012-10-17" Statement = [{ Effect = "Allow" Action = [ "bedrock-agentcore:InvokeAgentRuntime", "bedrock-agentcore:InvokeAgentRuntimeWithWebSocketStream", ] Resource = [ module.my_agent.agent_core_runtime_arn, "${module.my_agent.agent_core_runtime_arn}/*", ] }] })}
resource "aws_iam_role_policy_attachment" "invoke_my_agent" { role = module.identity.authenticated_role_name policy_arn = aws_iam_policy.invoke_my_agent.arn}If your agent uses Cognito auth, you do not need to define any additional infrastructure to connect your website to your agent.
Using the Generated Code
Section titled “Using the Generated Code”Adding a Chat Interface
Section titled “Adding a Chat Interface”The generator vends a <AgentName>Chat component per connected agent, already bound to that agent’s id and themed to match your website’s ux. Drop it anywhere inside the <AguiProvider> wrapper:
import { StoryAgentChat } from './components/story-agent-chat';
function ChatPage() { return ( <StoryAgentChat labels={{ welcomeMessageText: 'How can I help you today?', chatInputPlaceholder: 'Ask me anything...', }} /> );}It forwards every CopilotChat prop except agentId, so anything you can pass to <CopilotChat /> works here too.
Connecting Multiple AG-UI Agents
Section titled “Connecting Multiple AG-UI Agents”Run the connection generator once per agent. Each run vends that agent’s own chat component, so routing a chat to a particular agent is a matter of which component you render:
import { StoryAgentChat } from './components/story-agent-chat';import { ResearchAgentChat } from './components/research-agent-chat';
<StoryAgentChat /> {/* talks to StoryAgent */}<ResearchAgentChat /> {/* talks to ResearchAgent */}If you need the raw id — to call CopilotKit’s own hooks, say — each generated hook exports it:
import { STORY_AGENT_ID } from './hooks/useAguiStoryAgent';Customising the Look and Feel
Section titled “Customising the Look and Feel”<CopilotChat /> (and <CopilotSidebar />, <CopilotPopup />) use a recursive slot system — you can override any sub-component with either a Tailwind class string, a prop object, or a custom React component. See the CopilotKit slots guide for the full slot tree.
Built-in Themes
Section titled “Built-in Themes”The generator reads metadata.ux from your React website project and vends a themed wrapper module at src/components/copilot/index.tsx so the chat components match the rest of your UI without any extra configuration:
ux | Styling applied to CopilotChat / CopilotSidebar / CopilotPopup |
|---|---|
cloudscape | Messages render inside Cloudscape ChatBubbles with gen-AI Avatars (matching the Cloudscape generative AI chat pattern); the typing indicator becomes a LoadingBar and the input is a PromptInput. Built from @cloudscape-design/components and @cloudscape-design/chat-components. |
shadcn | Assistant messages render in a bg-muted bubble with a Sparkles avatar; user messages render right-aligned in a bg-primary bubble with a User avatar. The input is a rounded Textarea + pill-shaped send/stop Button (Enter submits, Shift+Enter newlines). Uses shadcn primitives from the shared common-shadcn package. |
none (or anything else) | No theme — the module just re-exports the default CopilotKit components. |
The vended <AgentName>Chat components are already themed. For a chat you wire up yourself, import from the local theme module (not @copilotkit/react-core/v2 directly) so the theme is applied automatically:
import { CopilotChat } from './components/copilot';import { STORY_AGENT_ID } from './hooks/useAguiStoryAgent';
<CopilotChat agentId={STORY_AGENT_ID} />The theme is applied as slot defaults, so any slot you explicitly pass still wins — you keep full control whenever you need a one-off override.
Customising the Theme
Section titled “Customising the Theme”The generated theme lives entirely inside your project:
src/components/copilot/index.tsx— exports the themedCopilotChat/CopilotSidebar/CopilotPopupand thecloudscapeCopilotTheme/shadcnCopilotThemeobjects. Edit this file to change the default slot wiring for every chat in your app.src/components/copilot/<ThemeComponent>.tsx— per-slot theme components (e.g.CloudscapeAssistantMessage,ShadcnChatInput). Edit these to tweak the look of a single slot without re-wiring the theme.
For example, to drop in your own user-message renderer while keeping the rest of the theme, edit the relevant file in src/components/copilot/ and re-export it from index.tsx.
Tailwind styling via slots
Section titled “Tailwind styling via slots”Per-chat overrides still work alongside the theme — anything you pass as a slot prop overrides the themed default:
<StoryAgentChat // style the input and its children input={{ textArea: 'text-blue-600', sendButton: 'bg-blue-600 hover:bg-blue-700', }} // style nested message slots messageView={{ assistantMessage: 'bg-blue-50 rounded-xl p-2', userMessage: 'bg-blue-100 rounded-xl', }}/>Replacing a slot with a custom component
Section titled “Replacing a slot with a custom component”Any slot can take a React component instead of a className, so you can replace the default entirely. Type your component against the props the slot declares — sendButton renders a <button>, so it receives ButtonHTMLAttributes:
import { StoryAgentChat } from './components/story-agent-chat';
const MySendButton: React.FC<React.ButtonHTMLAttributes<HTMLButtonElement>> = ({ onClick,}) => ( <button onClick={onClick} className="my-send-btn"> Send </button>);
<StoryAgentChat input={{ sendButton: MySendButton }} />;Deeper overrides follow the same shape — e.g. replace just the copy button on assistant messages:
<StoryAgentChat messageView={{ assistantMessage: { copyButton: ({ onClick }) => <button onClick={onClick}>Copy</button>, }, }}/>Local Development
Section titled “Local Development”The connection generator automatically configures dev integration:
- Running
nx dev <website>will also start the agent’s local server - The runtime config is overridden to point to the local AG-UI URL (e.g.
http://localhost:8081) - Both the website and the agent hot-reload together
pnpm nx dev <WebsiteProject>yarn nx dev <WebsiteProject>npx nx dev <WebsiteProject>bunx nx dev <WebsiteProject>