Skip to content

Commit ccf517f

Browse files
authored
A2A Middlware initial implementation (#460)
1 parent d0ff5f4 commit ccf517f

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+4670
-72
lines changed

.github/workflows/dojo-e2e.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ jobs:
1414
fail-fast: false
1515
matrix:
1616
include:
17+
- suite: a2a-middleware
18+
test_path: tests/a2aMiddlewareTests
19+
services: ["dojo","a2a-middleware"]
20+
wait_on: http://localhost:9999,tcp:localhost:8011,tcp:localhost:8012,tcp:localhost:8013,tcp:localhost:8014
1721
- suite: adk-middleware
1822
test_path: tests/adkMiddlewareTests
1923
services: ["dojo","adk-middleware"]

docs/concepts/agents.mdx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ foundation for:
4040
import { AbstractAgent } from "@ag-ui/client"
4141

4242
class MyAgent extends AbstractAgent {
43-
protected run(input: RunAgentInput): RunAgent {
43+
run(input: RunAgentInput): RunAgent {
4444
// Implementation details
4545
}
4646
}
@@ -89,7 +89,7 @@ You can create custom agents to integrate with any AI service by extending
8989
class CustomAgent extends AbstractAgent {
9090
// Custom properties and methods
9191

92-
protected run(input: RunAgentInput): RunAgent {
92+
run(input: RunAgentInput): RunAgent {
9393
// Implement the agent's logic
9494
}
9595
}
@@ -113,7 +113,7 @@ import {
113113
import { Observable } from "rxjs"
114114

115115
class SimpleAgent extends AbstractAgent {
116-
protected run(input: RunAgentInput): RunAgent {
116+
run(input: RunAgentInput): RunAgent {
117117
const { threadId, runId } = input
118118

119119
return () =>

docs/quickstart/middleware.mdx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@ import {
201201
import { Observable } from "rxjs"
202202

203203
export class OpenAIAgent extends AbstractAgent {
204-
protected run(input: RunAgentInput): Observable<BaseEvent> {
204+
run(input: RunAgentInput): Observable<BaseEvent> {
205205
const messageId = Date.now().toString()
206206
return new Observable<BaseEvent>((observer) => {
207207
observer.next({
@@ -290,7 +290,7 @@ export class OpenAIAgent extends AbstractAgent {
290290
this.openai = openai ?? new OpenAI()
291291
}
292292

293-
protected run(input: RunAgentInput): Observable<BaseEvent> {
293+
run(input: RunAgentInput): Observable<BaseEvent> {
294294
return new Observable<BaseEvent>((observer) => {
295295
// Same as before - emit RUN_STARTED to begin
296296
observer.next({

docs/sdk/js/client/http-agent.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ Default implementation:
112112
Implements the abstract `run()` method from `AbstractAgent` using HTTP requests.
113113

114114
```typescript
115-
protected run(input: RunAgentInput): RunAgent
115+
run(input: RunAgentInput): RunAgent
116116
```
117117

118118
## Properties
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { Page, Locator, expect } from '@playwright/test';
2+
3+
export class A2AChatPage {
4+
readonly page: Page;
5+
readonly mainChatTab: Locator;
6+
7+
constructor(page: Page) {
8+
this.page = page;
9+
this.mainChatTab = page.getByRole('tab', {name: 'Main Chat' });
10+
}
11+
12+
async openChat() {
13+
await this.mainChatTab.isVisible();
14+
}
15+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import {
2+
test,
3+
expect,
4+
waitForAIResponse,
5+
retryOnAIFailure,
6+
} from "../../test-isolation-helper";
7+
import { A2AChatPage } from "../../pages/a2aMiddlewarePages/A2AChatPage";
8+
9+
test.describe("A2A Chat Feature", () => {
10+
test("[A2A Middleware] Tab bar exists", async ({
11+
page,
12+
}) => {
13+
await retryOnAIFailure(async () => {
14+
await page.goto(
15+
"/a2a-middleware/feature/a2a_chat"
16+
);
17+
18+
const chat = new A2AChatPage(page);
19+
20+
await chat.openChat();
21+
// This should already be handled previously but we just need a base case
22+
await chat.mainChatTab.waitFor({ state: "visible" });
23+
});
24+
});
25+
});

typescript-sdk/apps/dojo/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
"generate-content-json": "npx tsx scripts/generate-content-json.ts"
1212
},
1313
"dependencies": {
14+
"@ag-ui/a2a-middleware": "workspace:*",
1415
"@ag-ui/adk": "workspace:*",
1516
"@ag-ui/agno": "workspace:*",
1617
"@ag-ui/crewai": "workspace:*",
@@ -51,6 +52,7 @@
5152
"@types/react-syntax-highlighter": "^15.5.13",
5253
"class-variance-authority": "^0.7.1",
5354
"clsx": "^2.1.1",
55+
"dedent": "^1.7.0",
5456
"diff": "^7.0.0",
5557
"fast-json-patch": "^3.1.1",
5658
"lucide-react": "^0.477.0",
@@ -66,6 +68,7 @@
6668
"rxjs": "7.8.1",
6769
"tailwind-merge": "^3.0.2",
6870
"tailwindcss-animate": "^1.0.7",
71+
"untruncate-json": "^0.0.1",
6972
"uuid": "^11.1.0",
7073
"zod": "^3.25.67"
7174
},

typescript-sdk/apps/dojo/scripts/prep-dojo-everything.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,11 @@ const ALL_TARGETS = {
103103
name: 'ADK Middleware',
104104
cwd: path.join(integrationsRoot, 'adk-middleware/python/examples'),
105105
},
106+
'a2a-middleware': {
107+
command: 'uv sync',
108+
name: 'A2A Middleware',
109+
cwd: path.join(integrationsRoot, 'a2a-middleware/examples'),
110+
},
106111
'dojo': {
107112
command: 'pnpm install --no-frozen-lockfile && pnpm build --filter=demo-viewer...',
108113
name: 'Dojo',

typescript-sdk/apps/dojo/scripts/run-dojo-everything.js

Lines changed: 54 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -47,76 +47,100 @@ const integrationsRoot = path.join(gitRoot, 'typescript-sdk', 'integrations');
4747

4848
// Define all runnable services keyed by a stable id
4949
const ALL_SERVICES = {
50-
'server-starter': {
50+
'server-starter': [{
5151
command: 'poetry run dev',
5252
name: 'Server Starter',
5353
cwd: path.join(integrationsRoot, 'server-starter/server/python'),
5454
env: { PORT: 8000 },
55-
},
56-
'server-starter-all': {
55+
}],
56+
'server-starter-all': [{
5757
command: 'poetry run dev',
5858
name: 'Server AF',
5959
cwd: path.join(integrationsRoot, 'server-starter-all-features/server/python'),
6060
env: { PORT: 8001 },
61-
},
62-
'agno': {
61+
}],
62+
'agno': [{
6363
command: 'uv run dev',
6464
name: 'Agno',
6565
cwd: path.join(integrationsRoot, 'agno/examples'),
6666
env: { PORT: 8002 },
67-
},
68-
'crew-ai': {
67+
}],
68+
'crew-ai': [{
6969
command: 'poetry run dev',
7070
name: 'CrewAI',
7171
cwd: path.join(integrationsRoot, 'crewai/python'),
7272
env: { PORT: 8003 },
73-
},
74-
'langgraph-fastapi': {
73+
}],
74+
'langgraph-fastapi': [{
7575
command: 'poetry run dev',
7676
name: 'LG FastAPI',
7777
cwd: path.join(integrationsRoot, 'langgraph/examples/python'),
7878
env: {
7979
PORT: 8004,
8080
POETRY_VIRTUALENVS_IN_PROJECT: 'false',
8181
},
82-
},
83-
'langgraph-platform-python': {
82+
}],
83+
'langgraph-platform-python': [{
8484
command: 'pnpx @langchain/langgraph-cli@latest dev --no-browser --host 127.0.0.1 --port 8005',
8585
name: 'LG Platform Py',
8686
cwd: path.join(integrationsRoot, 'langgraph/examples/python'),
8787
env: { PORT: 8005 },
88-
},
89-
'langgraph-platform-typescript': {
88+
}],
89+
'langgraph-platform-typescript': [{
9090
command: 'pnpx @langchain/langgraph-cli@latest dev --no-browser --host 127.0.0.1 --port 8006',
9191
name: 'LG Platform TS',
9292
cwd: path.join(integrationsRoot, 'langgraph/examples/typescript/'),
9393
env: { PORT: 8006 },
94-
},
95-
'llama-index': {
94+
}],
95+
'llama-index': [{
9696
command: 'uv run dev',
9797
name: 'Llama Index',
9898
cwd: path.join(integrationsRoot, 'llamaindex/server-py'),
9999
env: { PORT: 8007 },
100-
},
101-
'mastra': {
100+
}],
101+
'mastra': [{
102102
command: 'npm run dev',
103103
name: 'Mastra',
104104
cwd: path.join(integrationsRoot, 'mastra/example'),
105105
env: { PORT: 8008 },
106-
},
107-
'pydantic-ai': {
106+
}],
107+
'pydantic-ai': [{
108108
command: 'uv run dev',
109109
name: 'Pydantic AI',
110110
cwd: path.join(integrationsRoot, 'pydantic-ai/examples'),
111111
env: { PORT: 8009 },
112-
},
113-
'adk-middleware': {
112+
}],
113+
'adk-middleware': [{
114114
command: 'uv run dev',
115115
name: 'ADK Middleware',
116116
cwd: path.join(integrationsRoot, 'adk-middleware/python/examples'),
117117
env: { PORT: 8010 },
118+
}],
119+
'a2a-middleware': [{
120+
command: 'uv run buildings_management.py',
121+
name: 'A2A Middleware: Buildings Management',
122+
cwd: path.join(integrationsRoot, 'a2a-middleware/examples'),
123+
env: { PORT: 8011 },
118124
},
119-
'dojo': {
125+
{
126+
command: 'uv run finance.py',
127+
name: 'A2A Middleware: Finance',
128+
cwd: path.join(integrationsRoot, 'a2a-middleware/examples'),
129+
env: { PORT: 8012 },
130+
},
131+
{
132+
command: 'uv run it.py',
133+
name: 'A2A Middleware: IT',
134+
cwd: path.join(integrationsRoot, 'a2a-middleware/examples'),
135+
env: { PORT: 8013 },
136+
},
137+
{
138+
command: 'uv run orchestrator.py',
139+
name: 'A2A Middleware: Orchestrator',
140+
cwd: path.join(integrationsRoot, 'a2a-middleware/examples'),
141+
env: { PORT: 8014 },
142+
}],
143+
'dojo': [{
120144
command: 'pnpm run start',
121145
name: 'Dojo',
122146
cwd: path.join(gitRoot, 'typescript-sdk/apps/dojo'),
@@ -133,9 +157,13 @@ const ALL_SERVICES = {
133157
MASTRA_URL: 'http://localhost:8008',
134158
PYDANTIC_AI_URL: 'http://localhost:8009',
135159
ADK_MIDDLEWARE_URL: 'http://localhost:8010',
160+
A2A_MIDDLEWARE_BUILDINGS_MANAGEMENT_URL: 'http://localhost:8011',
161+
A2A_MIDDLEWARE_FINANCE_URL: 'http://localhost:8012',
162+
A2A_MIDDLEWARE_IT_URL: 'http://localhost:8013',
163+
A2A_MIDDLEWARE_ORCHESTRATOR_URL: 'http://localhost:8014',
136164
NEXT_PUBLIC_CUSTOM_DOMAIN_TITLE: 'cpkdojo.local___CopilotKit Feature Viewer',
137165
},
138-
},
166+
}],
139167
};
140168

141169
function printDryRunServices(procs) {
@@ -169,12 +197,12 @@ async function main() {
169197
// Build processes, warn for unknown keys
170198
const procs = [];
171199
for (const key of selectedKeys) {
172-
const svc = ALL_SERVICES[key];
173-
if (!svc) {
200+
const svcs = ALL_SERVICES[key];
201+
if (!svcs || svcs.length === 0) {
174202
console.warn(`Skipping unknown service: ${key}`);
175203
continue;
176204
}
177-
procs.push(svc);
205+
procs.push(...svcs);
178206
}
179207

180208
if (dryRun) {

typescript-sdk/apps/dojo/src/agents.ts

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ import getEnvVars from "./env";
1616
import { mastra } from "./mastra";
1717
import { PydanticAIAgent } from "@ag-ui/pydantic-ai";
1818
import { ADKAgent } from "@ag-ui/adk";
19+
import { HttpAgent } from "@ag-ui/client";
20+
import { A2AMiddlewareAgent } from "@ag-ui/a2a-middleware";
1921

2022
const envVars = getEnvVars();
2123
export const agentsIntegrations: AgentIntegrationConfig[] = [
@@ -225,7 +227,7 @@ export const agentsIntegrations: AgentIntegrationConfig[] = [
225227
subgraphs: new LangGraphAgent({
226228
deploymentUrl: envVars.langgraphTypescriptUrl,
227229
graphId: "subgraphs",
228-
})
230+
}),
229231
};
230232
},
231233
},
@@ -286,4 +288,34 @@ export const agentsIntegrations: AgentIntegrationConfig[] = [
286288
};
287289
},
288290
},
291+
{
292+
id: "a2a",
293+
agents: async () => {
294+
// A2A agents: building management, finance, it agents
295+
const agentUrls = [envVars.a2aMiddlewareBuildingsManagementUrl, envVars.a2aMiddlewareFinanceUrl, envVars.a2aMiddlewareItUrl];
296+
// AGUI orchestration/routing agent
297+
const orchestrationAgent = new HttpAgent({
298+
url: envVars.a2aMiddlewareOrchestratorUrl,
299+
});
300+
return {
301+
a2a_chat: new A2AMiddlewareAgent({
302+
description: "Middleware that connects to remote A2A agents",
303+
agentUrls,
304+
orchestrationAgent,
305+
instructions: `
306+
You are an HR agent. You are responsible for hiring employees and other typical HR tasks.
307+
308+
It's very important to contact all the departments necessary to complete the task.
309+
For example, to hire an employee, you must contact all 3 departments: Finance, IT and Buildings Management. Help the Buildings Management department to find a table.
310+
311+
You can make tool calls on behalf of other agents.
312+
DO NOT FORGET TO COMMUNICATE BACK TO THE RELEVANT AGENT IF MAKING A TOOL CALL ON BEHALF OF ANOTHER AGENT!!!
313+
314+
When choosing a seat with the buildings management agent, You MUST use the \`pickTable\` tool to have the user pick a seat.
315+
The buildings management agent will then use the \`pickSeat\` tool to pick a seat.
316+
`,
317+
}),
318+
};
319+
},
320+
},
289321
];

0 commit comments

Comments
 (0)