Skip to content

Commit 6d7638f

Browse files
committed
feat(a2a): a2a added
1 parent fdac431 commit 6d7638f

File tree

19 files changed

+3005
-0
lines changed

19 files changed

+3005
-0
lines changed
Lines changed: 271 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,271 @@
1+
/**
2+
* A2A Agent Card Endpoint
3+
*
4+
* Returns the Agent Card (discovery document) for an A2A agent.
5+
* Also supports CRUD operations for managing agents.
6+
*/
7+
8+
import { db } from '@sim/db'
9+
import { a2aAgent, workflow } from '@sim/db/schema'
10+
import { createLogger } from '@sim/logger'
11+
import { eq } from 'drizzle-orm'
12+
import { type NextRequest, NextResponse } from 'next/server'
13+
import { generateAgentCard, generateSkillsFromWorkflow } from '@/lib/a2a/agent-card'
14+
import type { AgentAuthentication, AgentCapabilities, AgentSkill } from '@/lib/a2a/types'
15+
import { checkHybridAuth } from '@/lib/auth/hybrid'
16+
import { loadWorkflowFromNormalizedTables } from '@/lib/workflows/persistence/utils'
17+
18+
const logger = createLogger('A2AAgentCardAPI')
19+
20+
export const dynamic = 'force-dynamic'
21+
22+
interface RouteParams {
23+
agentId: string
24+
}
25+
26+
/**
27+
* GET - Returns the Agent Card for discovery
28+
*/
29+
export async function GET(request: NextRequest, { params }: { params: Promise<RouteParams> }) {
30+
const { agentId } = await params
31+
32+
try {
33+
const [agent] = await db
34+
.select({
35+
agent: a2aAgent,
36+
workflow: workflow,
37+
})
38+
.from(a2aAgent)
39+
.innerJoin(workflow, eq(a2aAgent.workflowId, workflow.id))
40+
.where(eq(a2aAgent.id, agentId))
41+
.limit(1)
42+
43+
if (!agent) {
44+
return NextResponse.json({ error: 'Agent not found' }, { status: 404 })
45+
}
46+
47+
if (!agent.agent.isPublished) {
48+
// Check if requester has access (for preview)
49+
const auth = await checkHybridAuth(request, { requireWorkflowId: false })
50+
if (!auth.success) {
51+
return NextResponse.json({ error: 'Agent not published' }, { status: 404 })
52+
}
53+
}
54+
55+
const agentCard = generateAgentCard(
56+
{
57+
id: agent.agent.id,
58+
name: agent.agent.name,
59+
description: agent.agent.description,
60+
version: agent.agent.version,
61+
capabilities: agent.agent.capabilities as AgentCapabilities,
62+
skills: agent.agent.skills as AgentSkill[],
63+
authentication: agent.agent.authentication as AgentAuthentication,
64+
},
65+
{
66+
id: agent.workflow.id,
67+
name: agent.workflow.name,
68+
description: agent.workflow.description,
69+
}
70+
)
71+
72+
return NextResponse.json(agentCard, {
73+
headers: {
74+
'Content-Type': 'application/json',
75+
'Cache-Control': agent.agent.isPublished ? 'public, max-age=3600' : 'private, no-cache',
76+
},
77+
})
78+
} catch (error) {
79+
logger.error('Error getting Agent Card:', error)
80+
return NextResponse.json({ error: 'Internal server error' }, { status: 500 })
81+
}
82+
}
83+
84+
/**
85+
* PUT - Update an agent
86+
*/
87+
export async function PUT(request: NextRequest, { params }: { params: Promise<RouteParams> }) {
88+
const { agentId } = await params
89+
90+
try {
91+
const auth = await checkHybridAuth(request, { requireWorkflowId: false })
92+
if (!auth.success || !auth.userId) {
93+
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
94+
}
95+
96+
const [existingAgent] = await db
97+
.select()
98+
.from(a2aAgent)
99+
.where(eq(a2aAgent.id, agentId))
100+
.limit(1)
101+
102+
if (!existingAgent) {
103+
return NextResponse.json({ error: 'Agent not found' }, { status: 404 })
104+
}
105+
106+
const body = await request.json()
107+
108+
// Update agent
109+
const [updatedAgent] = await db
110+
.update(a2aAgent)
111+
.set({
112+
name: body.name ?? existingAgent.name,
113+
description: body.description ?? existingAgent.description,
114+
version: body.version ?? existingAgent.version,
115+
capabilities: body.capabilities ?? existingAgent.capabilities,
116+
skills: body.skills ?? existingAgent.skills,
117+
authentication: body.authentication ?? existingAgent.authentication,
118+
isPublished: body.isPublished ?? existingAgent.isPublished,
119+
publishedAt:
120+
body.isPublished && !existingAgent.isPublished ? new Date() : existingAgent.publishedAt,
121+
updatedAt: new Date(),
122+
})
123+
.where(eq(a2aAgent.id, agentId))
124+
.returning()
125+
126+
logger.info(`Updated A2A agent: ${agentId}`)
127+
128+
return NextResponse.json({ success: true, agent: updatedAgent })
129+
} catch (error) {
130+
logger.error('Error updating agent:', error)
131+
return NextResponse.json({ error: 'Internal server error' }, { status: 500 })
132+
}
133+
}
134+
135+
/**
136+
* DELETE - Delete an agent
137+
*/
138+
export async function DELETE(request: NextRequest, { params }: { params: Promise<RouteParams> }) {
139+
const { agentId } = await params
140+
141+
try {
142+
const auth = await checkHybridAuth(request, { requireWorkflowId: false })
143+
if (!auth.success || !auth.userId) {
144+
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
145+
}
146+
147+
const [existingAgent] = await db
148+
.select()
149+
.from(a2aAgent)
150+
.where(eq(a2aAgent.id, agentId))
151+
.limit(1)
152+
153+
if (!existingAgent) {
154+
return NextResponse.json({ error: 'Agent not found' }, { status: 404 })
155+
}
156+
157+
await db.delete(a2aAgent).where(eq(a2aAgent.id, agentId))
158+
159+
logger.info(`Deleted A2A agent: ${agentId}`)
160+
161+
return NextResponse.json({ success: true })
162+
} catch (error) {
163+
logger.error('Error deleting agent:', error)
164+
return NextResponse.json({ error: 'Internal server error' }, { status: 500 })
165+
}
166+
}
167+
168+
/**
169+
* POST - Publish/unpublish an agent
170+
*/
171+
export async function POST(request: NextRequest, { params }: { params: Promise<RouteParams> }) {
172+
const { agentId } = await params
173+
174+
try {
175+
const auth = await checkHybridAuth(request, { requireWorkflowId: false })
176+
if (!auth.success || !auth.userId) {
177+
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
178+
}
179+
180+
const [existingAgent] = await db
181+
.select()
182+
.from(a2aAgent)
183+
.where(eq(a2aAgent.id, agentId))
184+
.limit(1)
185+
186+
if (!existingAgent) {
187+
return NextResponse.json({ error: 'Agent not found' }, { status: 404 })
188+
}
189+
190+
const body = await request.json()
191+
const action = body.action as 'publish' | 'unpublish' | 'refresh'
192+
193+
if (action === 'publish') {
194+
// Verify workflow is deployed
195+
const [wf] = await db
196+
.select({ isDeployed: workflow.isDeployed })
197+
.from(workflow)
198+
.where(eq(workflow.id, existingAgent.workflowId))
199+
.limit(1)
200+
201+
if (!wf?.isDeployed) {
202+
return NextResponse.json(
203+
{ error: 'Workflow must be deployed before publishing agent' },
204+
{ status: 400 }
205+
)
206+
}
207+
208+
await db
209+
.update(a2aAgent)
210+
.set({
211+
isPublished: true,
212+
publishedAt: new Date(),
213+
updatedAt: new Date(),
214+
})
215+
.where(eq(a2aAgent.id, agentId))
216+
217+
logger.info(`Published A2A agent: ${agentId}`)
218+
return NextResponse.json({ success: true, isPublished: true })
219+
}
220+
221+
if (action === 'unpublish') {
222+
await db
223+
.update(a2aAgent)
224+
.set({
225+
isPublished: false,
226+
updatedAt: new Date(),
227+
})
228+
.where(eq(a2aAgent.id, agentId))
229+
230+
logger.info(`Unpublished A2A agent: ${agentId}`)
231+
return NextResponse.json({ success: true, isPublished: false })
232+
}
233+
234+
if (action === 'refresh') {
235+
// Refresh skills from workflow
236+
const workflowData = await loadWorkflowFromNormalizedTables(existingAgent.workflowId)
237+
if (!workflowData) {
238+
return NextResponse.json({ error: 'Failed to load workflow' }, { status: 500 })
239+
}
240+
241+
const [wf] = await db
242+
.select({ name: workflow.name, description: workflow.description })
243+
.from(workflow)
244+
.where(eq(workflow.id, existingAgent.workflowId))
245+
.limit(1)
246+
247+
const skills = generateSkillsFromWorkflow(
248+
existingAgent.workflowId,
249+
wf?.name || existingAgent.name,
250+
wf?.description,
251+
workflowData.blocks
252+
)
253+
254+
await db
255+
.update(a2aAgent)
256+
.set({
257+
skills,
258+
updatedAt: new Date(),
259+
})
260+
.where(eq(a2aAgent.id, agentId))
261+
262+
logger.info(`Refreshed skills for A2A agent: ${agentId}`)
263+
return NextResponse.json({ success: true, skills })
264+
}
265+
266+
return NextResponse.json({ error: 'Invalid action' }, { status: 400 })
267+
} catch (error) {
268+
logger.error('Error with agent action:', error)
269+
return NextResponse.json({ error: 'Internal server error' }, { status: 500 })
270+
}
271+
}

0 commit comments

Comments
 (0)