Skip to content

Commit da4afe0

Browse files
committed
feat: add AG-UI agent patterns and frontend support
1 parent f1170fb commit da4afe0

File tree

18 files changed

+755
-16
lines changed

18 files changed

+755
-16
lines changed

docs/AGUI_INTEGRATION.md

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
# AG-UI Protocol Integration
2+
3+
Guide for using the AG-UI (Agent-User Interaction) protocol patterns in FAST.
4+
5+
---
6+
7+
## Overview
8+
9+
[AG-UI](https://docs.ag-ui.com/concepts/overview) is an open protocol that defines a standard SSE event format for agent-to-frontend communication. Instead of each framework emitting its own event schema (Strands events, LangChain message chunks, etc.), AG-UI provides a unified event vocabulary: `TEXT_MESSAGE_CONTENT`, `TOOL_CALL_START`, `TOOL_CALL_ARGS`, `TOOL_CALL_RESULT`, `RUN_FINISHED`, and so on.
10+
11+
FAST includes two AG-UI agent patterns:
12+
13+
| Pattern | Framework | Location |
14+
|---------|-----------|----------|
15+
| `agui-strands-agent` | Strands + `ag-ui-strands` | `agent_patterns/agui-strands-agent/` |
16+
| `agui-langgraph-agent` | LangGraph + `copilotkit` | `agent_patterns/agui-langgraph-agent/` |
17+
18+
Both patterns use `BedrockAgentCoreApp` as the entrypoint (same as the HTTP patterns), which means AgentCore Runtime headers (WorkloadAccessToken, Authorization, Session-Id) are available for Gateway auth, Memory, and secure user identity extraction.
19+
20+
---
21+
22+
## How It Works
23+
24+
### Architecture
25+
26+
```
27+
Frontend (Amplify)
28+
29+
│ POST /invocations (AG-UI RunAgentInput payload)
30+
31+
AgentCore Runtime
32+
33+
│ Proxies request to container port 8080
34+
│ Injects: WorkloadAccessToken, Authorization, Session-Id headers
35+
36+
Agent Container
37+
38+
│ BedrockAgentCoreApp reads headers → sets ContextVars
39+
│ @entrypoint handler creates agent, runs it
40+
41+
AG-UI Wrapper (StrandsAgent / LangGraphAGUIAgent)
42+
43+
│ Translates framework events → AG-UI SSE events
44+
45+
Frontend Parser (parsers/agui.ts)
46+
47+
│ Maps AG-UI events → StreamEvent types
48+
49+
ChatInterface.tsx renders messages
50+
```
51+
52+
### Request Flow
53+
54+
1. The frontend sends an AG-UI `RunAgentInput` payload (with `threadId`, `messages`, `runId`)
55+
2. AgentCore Runtime proxies the request, injecting auth headers
56+
3. `BedrockAgentCoreApp` reads headers and populates `BedrockAgentCoreContext` (ContextVars)
57+
4. The `@entrypoint` handler extracts user identity from the JWT, creates the agent with Memory and Gateway tools
58+
5. The AG-UI wrapper translates framework streaming events into AG-UI SSE events
59+
6. The frontend `parseAguiChunk` parser maps AG-UI events to the shared `StreamEvent` types
60+
61+
### AG-UI vs HTTP Protocol on AgentCore Runtime
62+
63+
AgentCore Runtime supports both `HTTP` and `AGUI` server protocols. The difference is minimal: with `AGUI`, platform-level errors are returned as AG-UI-compliant `RUN_ERROR` events in the SSE stream (HTTP 200) instead of HTTP error codes. Everything else — auth, session headers, payload passthrough — is identical.
64+
65+
The AG-UI patterns in FAST deploy with `HTTP` protocol, which works correctly because the agent container handles AG-UI event formatting internally.
66+
67+
---
68+
69+
## Agent Patterns
70+
71+
### AG-UI Strands (`agui-strands-agent`)
72+
73+
**Location**: `agent_patterns/agui-strands-agent/`
74+
75+
Uses `ag-ui-strands` (`StrandsAgent`) to wrap a Strands `Agent`. The agent is created per-request inside the `@entrypoint` handler, ensuring each request gets a fresh `Agent` with the correct `session_manager` and fresh MCP client connections.
76+
77+
**Includes**: AgentCore Memory, Gateway MCP tools, Code Interpreter, AG-UI SSE streaming.
78+
79+
### AG-UI LangGraph (`agui-langgraph-agent`)
80+
81+
**Location**: `agent_patterns/agui-langgraph-agent/`
82+
83+
Uses `copilotkit` (`LangGraphAGUIAgent`) to wrap a LangGraph compiled graph. Uses `ActorAwareLangGraphAgent`, a subclass that rebuilds the graph per-request to ensure fresh Gateway MCP tool connections with valid tokens.
84+
85+
**Includes**: AgentCore Memory (checkpointer), Gateway MCP tools, Code Interpreter, CopilotKit middleware, AG-UI SSE streaming.
86+
87+
---
88+
89+
## Frontend
90+
91+
### Parser Auto-Selection
92+
93+
The AG-UI parser is automatically selected based on the pattern name prefix. Any pattern starting with `agui-` uses the AG-UI parser (`parsers/agui.ts`). Unlike the HTTP patterns — which each require a framework-specific parser (Strands, LangGraph, Claude) to handle their different streaming formats — all AG-UI patterns share a single parser. This is one of the key benefits of the AG-UI protocol: the backend framework is abstracted away behind a standard event vocabulary, so the frontend doesn't need to know whether the agent uses Strands or LangGraph.
94+
95+
See `frontend/src/lib/agentcore-client/client.ts` for the parser selection logic and `infra-cdk/config.yaml` comments for the full prefix-to-parser mapping.
96+
97+
### AG-UI Payload Format
98+
99+
The frontend automatically sends the correct payload format based on the pattern prefix. AG-UI patterns receive a `RunAgentInput` payload (with `threadId`, `messages`, `runId`), while HTTP patterns receive the standard `{ prompt, runtimeSessionId }` format. This is handled by `AgentCoreClient.invoke()`.
100+
101+
---
102+
103+
## Deployment
104+
105+
Set the pattern in `infra-cdk/config.yaml`:
106+
107+
```yaml
108+
backend:
109+
pattern: agui-strands-agent # or agui-langgraph-agent
110+
deployment_type: docker
111+
```
112+
113+
No CDK changes are required. The AG-UI patterns deploy as standard HTTP containers on AgentCore Runtime.
114+
115+
---
116+
117+
## CopilotKit Integration
118+
119+
[CopilotKit](https://www.copilotkit.ai/) is a React UI library that natively understands the AG-UI protocol. While FAST's built-in frontend includes a lightweight AG-UI parser for basic chat streaming, CopilotKit provides a much richer set of capabilities for building agent-powered applications:
120+
121+
- **Chat UI components**: Pre-built `<CopilotChat />` and `<CopilotPopup />` components with streaming, markdown rendering, and tool call visualization out of the box
122+
- **Generative UI**: Agents can render custom React components in the chat via `TOOL_CALL_RESULT` events — tables, charts, forms, or any UI the agent decides to show
123+
- **Frontend tool calls**: Define tools that execute on the client side (e.g., updating a canvas, modifying app state), which the agent can invoke through the AG-UI protocol
124+
- **Shared state**: Bidirectional state sync between the agent and the frontend via `STATE_SNAPSHOT` events — the agent can read and write to frontend state (e.g., a todo list, a document editor)
125+
- **Human-in-the-loop**: Built-in support for agent interrupts where the agent pauses execution and asks the user for confirmation or input before proceeding
126+
- **Textarea AI suggestions**: `<CopilotTextarea />` provides inline AI-powered autocompletions in any text input
127+
128+
CopilotKit is a separate frontend that can replace the built-in FAST frontend when deeper AG-UI integration is needed. The AG-UI agent patterns in FAST (`agui-strands-agent`, `agui-langgraph-agent`) work as the backend for CopilotKit without any changes — CopilotKit connects to the same `/invocations` endpoint and speaks the same AG-UI protocol.
129+
130+
For a full working example, see [PR #63](https://github.com/awslabs/fullstack-solution-template-for-agentcore/pull/63) which demonstrates CopilotKit integrated with the AG-UI LangGraph pattern, including generative UI, frontend tools, and shared state.
131+
132+
---
133+
134+
## Additional Resources
135+
136+
- [AG-UI Protocol Documentation](https://docs.ag-ui.com/concepts/overview)
137+
- [ag-ui-strands on PyPI](https://pypi.org/project/ag-ui-strands/)
138+
- [CopilotKit Documentation](https://docs.copilotkit.ai/)
139+
- [Strands AG-UI Integration Guide](https://strandsagents.com/docs/community/integrations/ag-ui/)

frontend/src/lib/agentcore-client/client.ts

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,24 +5,29 @@ import type { AgentCoreConfig, AgentPattern, ChunkParser, StreamCallback } from
55
import { parseStrandsChunk } from "./parsers/strands"
66
import { parseLanggraphChunk } from "./parsers/langgraph"
77
import { parseClaudeAgentSdkChunk } from "./parsers/claude-agent-sdk"
8+
import { parseAguiChunk } from "./parsers/agui"
89
import { readSSEStream } from "./utils/sse"
910

10-
const PARSERS: Record<AgentPattern, ChunkParser> = {
11-
"strands-single-agent": parseStrandsChunk,
12-
"langgraph-single-agent": parseLanggraphChunk,
13-
"claude-agent-sdk-single-agent": parseClaudeAgentSdkChunk,
14-
"claude-agent-sdk-multi-agent": parseClaudeAgentSdkChunk,
11+
/** Resolve parser from pattern prefix. Defaults to strands parser. */
12+
function getParser(pattern: AgentPattern): ChunkParser {
13+
if (pattern.startsWith("agui-")) return parseAguiChunk
14+
if (pattern.startsWith("langgraph-")) return parseLanggraphChunk
15+
if (pattern.startsWith("claude-")) return parseClaudeAgentSdkChunk
16+
if (pattern.startsWith("strands-")) return parseStrandsChunk
17+
return parseStrandsChunk
1518
}
1619

1720
export class AgentCoreClient {
1821
private runtimeArn: string
1922
private region: string
23+
private pattern: AgentPattern
2024
private parser: ChunkParser
2125

2226
constructor(config: AgentCoreConfig) {
2327
this.runtimeArn = config.runtimeArn
2428
this.region = config.region ?? "us-east-1"
25-
this.parser = PARSERS[config.pattern]
29+
this.pattern = config.pattern
30+
this.parser = getParser(config.pattern)
2631
}
2732

2833
generateSessionId(): string {
@@ -44,6 +49,22 @@ export class AgentCoreClient {
4449

4550
const traceId = `1-${Math.floor(Date.now() / 1000).toString(16)}-${crypto.randomUUID()}`
4651

52+
// Build payload based on pattern — AG-UI protocol expects a different format
53+
const body = this.pattern.startsWith("agui-")
54+
? {
55+
threadId: sessionId,
56+
runId: crypto.randomUUID(),
57+
messages: [{ id: crypto.randomUUID(), role: "user", content: query }],
58+
state: {},
59+
tools: [],
60+
context: [],
61+
forwardedProps: {},
62+
}
63+
: {
64+
prompt: query,
65+
runtimeSessionId: sessionId,
66+
}
67+
4768
// User identity is extracted server-side from the validated JWT token
4869
// (Authorization header), not sent in the payload body. This prevents
4970
// impersonation via prompt injection.
@@ -55,10 +76,7 @@ export class AgentCoreClient {
5576
"Content-Type": "application/json",
5677
"X-Amzn-Bedrock-AgentCore-Runtime-Session-Id": sessionId,
5778
},
58-
body: JSON.stringify({
59-
prompt: query,
60-
runtimeSessionId: sessionId,
61-
}),
79+
body: JSON.stringify(body),
6280
})
6381

6482
if (!response.ok) {
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
import type { ChunkParser } from "../types"
5+
6+
/**
7+
* Parses SSE chunks from AG-UI (ag-ui-strands) agents.
8+
*
9+
* AG-UI events arrive as `data: <JSON>` where each JSON object has a `type` field:
10+
* RUN_STARTED, STATE_SNAPSHOT, TEXT_MESSAGE_START, TEXT_MESSAGE_CONTENT,
11+
* TEXT_MESSAGE_END, TOOL_CALL_START, TOOL_CALL_ARGS, TOOL_CALL_END,
12+
* TOOL_CALL_RESULT, RUN_FINISHED
13+
*/
14+
export const parseAguiChunk: ChunkParser = (line, callback) => {
15+
if (!line.startsWith("data: ")) return
16+
17+
const data = line.substring(6).trim()
18+
if (!data) return
19+
20+
try {
21+
const json = JSON.parse(data)
22+
const eventType: string = json.type
23+
24+
switch (eventType) {
25+
case "TEXT_MESSAGE_CONTENT":
26+
callback({ type: "text", content: json.delta ?? "" })
27+
break
28+
29+
case "TOOL_CALL_START":
30+
callback({
31+
type: "tool_use_start",
32+
toolUseId: json.toolCallId,
33+
name: json.toolCallName,
34+
})
35+
break
36+
37+
case "TOOL_CALL_ARGS":
38+
callback({
39+
type: "tool_use_delta",
40+
toolUseId: json.toolCallId,
41+
input: json.delta ?? "",
42+
})
43+
break
44+
45+
case "TOOL_CALL_RESULT":
46+
callback({
47+
type: "tool_result",
48+
toolUseId: json.toolCallId,
49+
result: json.content ?? "",
50+
})
51+
break
52+
53+
case "RUN_FINISHED":
54+
callback({ type: "result", stopReason: "end_turn" })
55+
callback({ type: "lifecycle", event: "run_finished" })
56+
break
57+
58+
case "RUN_STARTED":
59+
callback({ type: "lifecycle", event: "run_started" })
60+
break
61+
62+
case "TEXT_MESSAGE_START":
63+
callback({ type: "lifecycle", event: "message_start" })
64+
break
65+
66+
case "TEXT_MESSAGE_END":
67+
callback({ type: "lifecycle", event: "message_end" })
68+
break
69+
70+
case "STATE_SNAPSHOT":
71+
case "TOOL_CALL_END":
72+
// Informational — no action needed
73+
break
74+
75+
default:
76+
console.debug("Unhandled AG-UI event type:", eventType)
77+
}
78+
} catch {
79+
console.debug("Failed to parse AG-UI event:", data)
80+
}
81+
}

frontend/src/lib/agentcore-client/types.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
22
// SPDX-License-Identifier: Apache-2.0
33

4-
/** Supported agent framework patterns */
4+
/** Supported agent pattern prefixes — determines the frontend parser */
55
export type AgentPattern =
6-
| "strands-single-agent"
7-
| "langgraph-single-agent"
8-
| "claude-agent-sdk-single-agent"
9-
| "claude-agent-sdk-multi-agent"
6+
| `agui-${string}`
7+
| `strands-${string}`
8+
| `langgraph-${string}`
9+
| `claude-${string}`
1010

1111
/** Configuration for AgentCoreClient */
1212
export interface AgentCoreConfig {

infra-cdk/config.yaml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,13 @@ stack_name_base: FAST-stack
55
admin_user_email: # Example: admin@example.com
66

77
backend:
8-
pattern: strands-single-agent # Available patterns: strands-single-agent, langgraph-single-agent, claude-agent-sdk
8+
pattern: strands-single-agent # See patterns/ for available options
9+
# Pattern prefix determines the frontend parser:
10+
# agui-* → AG-UI parser (e.g. agui-strands-agent)
11+
# strands-* → Strands parser (e.g. strands-single-agent)
12+
# langgraph-* → LangGraph parser (e.g. langgraph-single-agent)
13+
# claude-* → Claude parser (e.g. claude-agent-sdk-single-agent)
14+
# The folder name must match exactly and start with the desired parser prefix.
915
deployment_type: docker # Available deployment types: docker (default), zip (not supported for claude-agent-sdk)
1016
network_mode: PUBLIC # Available network modes: PUBLIC (default), VPC
1117

infra-cdk/lib/backend-stack.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,13 @@ export class BackendStack extends cdk.NestedStack {
364364
description: `${pattern} agent runtime for ${config.stack_name_base}`,
365365
})
366366

367+
// AGUI protocol override — CloudFormation doesn't support AGUI enum yet
368+
// (only MCP | HTTP | A2A). Runtime deploys as HTTP, which also works properly.
369+
// if (pattern.startsWith("agui-")) {
370+
// const cfnRuntime = this.agentRuntime.node.defaultChild as cdk.CfnResource
371+
// cfnRuntime.addPropertyOverride("ProtocolConfiguration", "AGUI")
372+
// }
373+
367374
// Make sure that ZIP is uploaded before Runtime is created
368375
if (zipPackagerResource) {
369376
this.agentRuntime.node.addDependency(zipPackagerResource)
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
FROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim
5+
6+
WORKDIR /app
7+
8+
# Configure UV for container environment
9+
ENV UV_SYSTEM_PYTHON=1 \
10+
UV_COMPILE_BYTECODE=1 \
11+
DOCKER_CONTAINER=1 \
12+
OTEL_PYTHON_LOG_CORRELATION=true \
13+
PYTHONUNBUFFERED=1
14+
15+
# Copy pyproject.toml and shared packages for installation
16+
COPY pyproject.toml .
17+
COPY gateway/ gateway/
18+
COPY tools/ tools/
19+
20+
# Copy and install agent-specific requirements first
21+
COPY patterns/agui-langgraph-agent/requirements.txt requirements.txt
22+
RUN uv pip install --no-cache -r requirements.txt && \
23+
uv pip install --no-cache aws-opentelemetry-distro==0.10.1
24+
25+
# Install FAST package with only core dependencies (no dev/agent optional deps)
26+
RUN uv pip install --no-cache -e . --no-deps && \
27+
uv pip install --no-cache requests>=2.31.0
28+
29+
# Create non-root user
30+
RUN useradd -m -u 1000 bedrock_agentcore
31+
USER bedrock_agentcore
32+
33+
EXPOSE 8080
34+
35+
# Copy agent code and tools
36+
COPY patterns/agui-langgraph-agent/agent.py .
37+
COPY patterns/agui-langgraph-agent/tools/ tools/
38+
COPY patterns/utils/ utils/
39+
40+
# Healthcheck
41+
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
42+
CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8080/ping', timeout=2)" || exit 1
43+
44+
# Start agent with OpenTelemetry instrumentation
45+
CMD ["opentelemetry-instrument", "python", "-m", "agent"]

0 commit comments

Comments
 (0)