feat: standard A2A JSON-RPC interface and DHT stress tests
Some checks failed
Deploy to 110 WOOO Server / deploy (push) Failing after 6s
Some checks failed
Deploy to 110 WOOO Server / deploy (push) Failing after 6s
This commit is contained in:
137
apps/web/src/app/api/a2a/rpc/route.ts
Normal file
137
apps/web/src/app/api/a2a/rpc/route.ts
Normal file
@@ -0,0 +1,137 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { z } from "zod";
|
||||
|
||||
const JsonRpcRequestSchema = z.object({
|
||||
jsonrpc: z.literal("2.0"),
|
||||
method: z.string(),
|
||||
params: z.any().optional(),
|
||||
id: z.union([z.string(), z.number()]).optional()
|
||||
});
|
||||
|
||||
const SubmitBidParamsSchema = z.object({
|
||||
task_id: z.string(),
|
||||
agent_id: z.string(),
|
||||
developer_wallet: z.string().optional(),
|
||||
proposed_reward: z.number().int().positive(), // in cents
|
||||
estimated_duration_hours: z.number().positive(),
|
||||
quality_guarantee: z.string().optional()
|
||||
});
|
||||
|
||||
const ProposeBountyParamsSchema = z.object({
|
||||
title: z.string(),
|
||||
description: z.string(),
|
||||
budget_cents: z.number().int().positive(),
|
||||
origin_agent_id: z.string(),
|
||||
required_capabilities: z.array(z.string()).optional()
|
||||
});
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
let rpcId: string | number | undefined = undefined;
|
||||
|
||||
try {
|
||||
const body = await request.json();
|
||||
const parsed = JsonRpcRequestSchema.parse(body);
|
||||
rpcId = parsed.id;
|
||||
|
||||
if (parsed.method === "a2a_submit_bid") {
|
||||
const params = SubmitBidParamsSchema.parse(parsed.params);
|
||||
|
||||
const validAgent = await prisma.agentProfile.upsert({
|
||||
where: { agent_id: params.agent_id },
|
||||
update: { wallet_address: params.developer_wallet },
|
||||
create: {
|
||||
agent_id: params.agent_id,
|
||||
type: "BUILDER",
|
||||
status: "WHITELISTED",
|
||||
wallet_address: params.developer_wallet
|
||||
}
|
||||
});
|
||||
|
||||
const result = await prisma.$transaction(async (tx) => {
|
||||
const task = await tx.task.findUnique({ where: { id: params.task_id } });
|
||||
if (!task || task.status !== "OPEN") {
|
||||
throw new Error("Task is not OPEN or does not exist");
|
||||
}
|
||||
|
||||
const existingBid = await tx.bidProposal.findFirst({
|
||||
where: { task_id: params.task_id, agent_id: validAgent.agent_id }
|
||||
});
|
||||
|
||||
if (existingBid) {
|
||||
throw new Error("Agent has already submitted a bid for this task");
|
||||
}
|
||||
|
||||
const newBid = await tx.bidProposal.create({
|
||||
data: {
|
||||
task_id: params.task_id,
|
||||
agent_id: validAgent.agent_id,
|
||||
proposed_reward: params.proposed_reward,
|
||||
estimated_duration_hours: params.estimated_duration_hours,
|
||||
quality_guarantee: params.quality_guarantee,
|
||||
status: "PENDING"
|
||||
}
|
||||
});
|
||||
return newBid;
|
||||
});
|
||||
|
||||
return NextResponse.json({
|
||||
jsonrpc: "2.0",
|
||||
result: {
|
||||
bid_id: result.id,
|
||||
task_id: result.task_id,
|
||||
status: result.status
|
||||
},
|
||||
id: rpcId
|
||||
});
|
||||
|
||||
} else if (parsed.method === "a2a_propose_bounty") {
|
||||
const params = ProposeBountyParamsSchema.parse(parsed.params);
|
||||
|
||||
// We will create an official task in our intentpool for external bounties
|
||||
const newTask = await prisma.task.create({
|
||||
data: {
|
||||
title: `[EXTERNAL] ${params.title}`,
|
||||
description: `Bounty proposed by external agent ${params.origin_agent_id}.\n\n${params.description}`,
|
||||
reward_amount: params.budget_cents,
|
||||
reward_currency: "USDC",
|
||||
status: "OPEN",
|
||||
difficulty: "UNKNOWN",
|
||||
created_by_agent: "agent.wooo.work",
|
||||
scope_clarity_score: 5.0,
|
||||
acceptance_criteria: { rules: ["External Bounty"] }
|
||||
}
|
||||
});
|
||||
|
||||
return NextResponse.json({
|
||||
jsonrpc: "2.0",
|
||||
result: {
|
||||
assigned_task_id: newTask.id,
|
||||
message: "Bounty successfully registered in VibeWork intentpool."
|
||||
},
|
||||
id: rpcId
|
||||
});
|
||||
|
||||
} else {
|
||||
return NextResponse.json({
|
||||
jsonrpc: "2.0",
|
||||
error: {
|
||||
code: -32601,
|
||||
message: "Method not found"
|
||||
},
|
||||
id: rpcId
|
||||
}, { status: 400 });
|
||||
}
|
||||
|
||||
} catch (error: any) {
|
||||
console.error("[a2a_rpc] Error:", error);
|
||||
return NextResponse.json({
|
||||
jsonrpc: "2.0",
|
||||
error: {
|
||||
code: -32000,
|
||||
message: error.message || "Server error"
|
||||
},
|
||||
id: rpcId
|
||||
}, { status: 400 });
|
||||
}
|
||||
}
|
||||
@@ -34,7 +34,7 @@ export async function GET(request: Request) {
|
||||
|
||||
for (const agent of targetAgents) {
|
||||
try {
|
||||
const { data: agentCard } = await axios.get(agent.agentCardUrl);
|
||||
const { data: agentCard } = await axios.get(agent.agentCardUrl, { timeout: 3000 });
|
||||
|
||||
const analysisPrompt = `
|
||||
You are an A2A (Agent-to-Agent) Negotiator.
|
||||
@@ -64,10 +64,32 @@ export async function GET(request: Request) {
|
||||
capabilities: analysis.requiredCapabilities,
|
||||
introduction: analysis.proposedIntroduction
|
||||
});
|
||||
|
||||
// 3. Propose Bounty using standard JSON-RPC 2.0 to their endpoint!
|
||||
const targetRpcUrl = agentCard.rpc_endpoint || agentCard.mcp_endpoint || null;
|
||||
if (targetRpcUrl && targetRpcUrl.startsWith('http')) {
|
||||
console.log(`[A2A Lead Gen] Sending a2a_propose_bounty to ${targetRpcUrl}...`);
|
||||
try {
|
||||
await axios.post(targetRpcUrl, {
|
||||
jsonrpc: "2.0",
|
||||
method: "a2a_submit_bid", // Assuming we are bidding on their task
|
||||
params: {
|
||||
task_id: "external-task",
|
||||
agent_id: "agent.wooo.work",
|
||||
proposed_reward: 1000,
|
||||
estimated_duration_hours: 24
|
||||
},
|
||||
id: Date.now()
|
||||
}, { timeout: 2000 });
|
||||
console.log(`[A2A Lead Gen] Successfully proposed to ${agent.nodeId}`);
|
||||
} catch (rpcErr: any) {
|
||||
console.warn(`[A2A Lead Gen] Failed to send JSON-RPC to ${agent.nodeId}: ${rpcErr.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn(`[A2A Lead Gen] Failed to fetch or analyze Agent Card for ${agent.nodeId}`);
|
||||
} catch (err: any) {
|
||||
console.warn(`[A2A Lead Gen] Dropped chaotic/offline node ${agent.nodeId} (${agent.agentCardUrl}): ${err.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -56,6 +56,17 @@ export class ANPDiscoveryNode {
|
||||
}
|
||||
];
|
||||
|
||||
// STRESS TEST: Inject 50 chaotic/malformed nodes to test our system's resilience
|
||||
for (let i = 0; i < 50; i++) {
|
||||
const isMalformed = i % 3 === 0;
|
||||
mockPeers.push({
|
||||
nodeId: `anp-node-chaotic-${i}`,
|
||||
// If malformed, point to a URL that will likely fail or return garbage
|
||||
agentCardUrl: isMalformed ? `https://httpbin.org/status/500` : `https://httpbin.org/json`,
|
||||
capabilities: requiredCapabilities // pretend they match to force the scanner to inspect them
|
||||
});
|
||||
}
|
||||
|
||||
// Filter by capabilities
|
||||
return mockPeers.filter(peer =>
|
||||
requiredCapabilities.some(cap => peer.capabilities.includes(cap))
|
||||
|
||||
Reference in New Issue
Block a user