Files
agent-bounty-protocol/apps/web/src/lib/a2a-agent-connect.ts
OG T 5a3cf08f8e
All checks were successful
CI and Production Smoke / smoke (push) Successful in 7s
fix: route agent connect through gateway domain
2026-06-12 01:13:13 +08:00

284 lines
9.4 KiB
TypeScript

import { A2A_AGENT_INTEGRATIONS } from "@/lib/a2a-agent-integrations";
import {
AGENT_GATEWAY_URL,
buildAgentDemandCampaignKit,
buildAgentGrowthKit,
isSafeOutboundUrl,
sanitizeAgentId,
} from "@/lib/a2a-growth";
import { prisma } from "@/lib/prisma";
export type ConnectExternalAgentInput = {
agentId?: string | null;
tool?: string | null;
growthWebhook?: string | null;
walletAddress?: string | null;
campaign?: string | null;
source?: string | null;
channel?: string | null;
};
type ConnectExternalAgentError = {
success: false;
error: string;
status: number;
};
type ConnectExternalAgentSuccess = {
success: true;
agent_id: string;
status: "PENDING" | "WHITELISTED" | "BANNED" | "REBEL";
selected_integration: {
id: string;
name: string;
status: string;
monetization_lane: string;
} | null;
outbound_ready: boolean;
webhook_registered: boolean;
wallet_bound: boolean;
referral_url: string;
campaign_kit_url: string;
onboarding_url: string;
referral_touchpoint_url: string;
referral_status_url: string;
open_tasks_url: string;
agent_connect_url: string;
growth_kit: ReturnType<typeof buildAgentGrowthKit>;
demand_campaign_kit: ReturnType<typeof buildAgentDemandCampaignKit>;
next_actions: string[];
};
export type ConnectExternalAgentResult = ConnectExternalAgentSuccess | ConnectExternalAgentError;
function stringValue(value: unknown) {
return typeof value === "string" ? value.trim() : "";
}
function jsonObject(value: unknown): Record<string, unknown> {
if (!value) return {};
if (typeof value === "object" && !Array.isArray(value)) return { ...(value as Record<string, unknown>) };
if (typeof value !== "string") return {};
try {
const parsed = JSON.parse(value);
return parsed && typeof parsed === "object" && !Array.isArray(parsed)
? { ...(parsed as Record<string, unknown>) }
: {};
} catch {
return {};
}
}
function findIntegration(tool: string) {
if (!tool) return null;
return (
A2A_AGENT_INTEGRATIONS.find((integration) => integration.id === tool) ||
A2A_AGENT_INTEGRATIONS.find((integration) => sanitizeAgentId(integration.name) === tool) ||
null
);
}
function cleanWallet(value: string | null | undefined) {
return (value || "").trim().slice(0, 160);
}
function existingWebhook(endpoints: Record<string, unknown>) {
return (
stringValue(endpoints.growth_webhook) ||
stringValue(endpoints.a2a_webhook) ||
stringValue(endpoints.webhook)
);
}
function buildAgentConnectUrl(agentId: string) {
const url = new URL("/agents/connect", AGENT_GATEWAY_URL);
url.searchParams.set("agent_id", agentId);
return url.toString();
}
export async function connectExternalAgent(input: ConnectExternalAgentInput): Promise<ConnectExternalAgentResult> {
const agentId = sanitizeAgentId(input.agentId);
if (!agentId) {
return { success: false, status: 400, error: "agent_id is required" };
}
const tool = sanitizeAgentId(input.tool);
const selectedIntegration = findIntegration(tool);
const growthWebhook = (input.growthWebhook || "").trim();
const walletAddress = cleanWallet(input.walletAddress);
const campaign = sanitizeAgentId(input.campaign) || "a2a-agent-referral";
const source = sanitizeAgentId(input.source) || selectedIntegration?.id || "agent-connect";
const channel = sanitizeAgentId(input.channel) || source;
if (growthWebhook && !isSafeOutboundUrl(growthWebhook)) {
return {
success: false,
status: 400,
error: "growth_webhook must be a public https URL; localhost, private IP, and .local targets are blocked",
};
}
const existingAgent = await prisma.agentProfile.findUnique({
where: { agent_id: agentId },
select: {
status: true,
wallet_address: true,
capabilities: true,
contact_endpoints: true,
},
});
const existingEndpoints = jsonObject(existingAgent?.contact_endpoints);
const registeredWebhook = existingWebhook(existingEndpoints);
if (walletAddress && existingAgent?.wallet_address && existingAgent.wallet_address !== walletAddress) {
return {
success: false,
status: 409,
error: "wallet_address is already registered for this agent_id and cannot be overwritten",
};
}
if (growthWebhook && registeredWebhook && registeredWebhook !== growthWebhook) {
return {
success: false,
status: 409,
error: "growth_webhook is already registered for this agent_id and cannot be overwritten without operator review",
};
}
const contactEndpoints = {
...existingEndpoints,
...(growthWebhook
? {
growth_webhook: growthWebhook,
a2a_webhook: stringValue(existingEndpoints.a2a_webhook) || growthWebhook,
webhook: stringValue(existingEndpoints.webhook) || growthWebhook,
}
: {}),
};
const capabilities = {
...jsonObject(existingAgent?.capabilities),
growth_referral: true,
demand_campaign_kit: true,
self_connect: true,
outbound_growth_ready: Boolean(existingWebhook(contactEndpoints)),
requested_tool: selectedIntegration?.id || tool || null,
campaign,
source,
channel,
};
const agent = await prisma.agentProfile.upsert({
where: { agent_id: agentId },
update: {
discovery_source: "A2A_AGENT_CONNECT",
capabilities,
contact_endpoints: contactEndpoints,
...(walletAddress ? { wallet_address: walletAddress } : {}),
},
create: {
agent_id: agentId,
type: "SCOUT",
status: "PENDING",
discovery_source: "A2A_AGENT_CONNECT",
wallet_address: walletAddress || null,
capabilities,
contact_endpoints: contactEndpoints,
},
select: {
status: true,
wallet_address: true,
},
});
const growthKit = buildAgentGrowthKit({ agentId, campaign, source });
const demandCampaignKit = buildAgentDemandCampaignKit({ agentId, campaign, source, channel });
const campaignKitUrl =
`${AGENT_GATEWAY_URL}/api/a2a/campaigns/demand?agent_id=${encodeURIComponent(agentId)}` +
`&register=true&campaign=${encodeURIComponent(campaign)}&source=${encodeURIComponent(source)}` +
`&channel=${encodeURIComponent(channel)}`;
const onboardingUrl =
`${AGENT_GATEWAY_URL}/api/a2a/onboarding?agent_id=${encodeURIComponent(agentId)}` +
`&register=true&tool=${encodeURIComponent(selectedIntegration?.id || tool || "")}`;
const referralTouchpointUrl =
`${AGENT_GATEWAY_URL}/api/a2a/referrals/touch?agent_id=${encodeURIComponent(agentId)}` +
`&campaign=${encodeURIComponent(campaign)}&source=${encodeURIComponent(source)}&touchpoint=proposal_link_sent`;
const referralStatusUrl = `${AGENT_GATEWAY_URL}/api/a2a/referrals/status?agent_id=${encodeURIComponent(agentId)}`;
await prisma.auditEvent.create({
data: {
actorType: "AGENT",
actorId: agentId,
action: "A2A_EXTERNAL_AGENT_CONNECTED",
entityType: "AGENT",
entityId: agentId,
metadata: {
selected_integration: selectedIntegration?.id || tool || null,
source,
channel,
campaign,
outbound_ready: Boolean(existingWebhook(contactEndpoints)),
webhook_registered: Boolean(existingWebhook(contactEndpoints)),
wallet_bound: Boolean(agent.wallet_address),
referral_url: growthKit.referral_url,
},
},
});
return {
success: true,
agent_id: agentId,
status: agent.status,
selected_integration: selectedIntegration
? {
id: selectedIntegration.id,
name: selectedIntegration.name,
status: selectedIntegration.status,
monetization_lane: selectedIntegration.monetizationLane,
}
: null,
outbound_ready: Boolean(existingWebhook(contactEndpoints)),
webhook_registered: Boolean(existingWebhook(contactEndpoints)),
wallet_bound: Boolean(agent.wallet_address),
referral_url: growthKit.referral_url,
campaign_kit_url: campaignKitUrl,
onboarding_url: onboardingUrl,
referral_touchpoint_url: referralTouchpointUrl,
referral_status_url: referralStatusUrl,
open_tasks_url: `${AGENT_GATEWAY_URL}/api/open-tasks`,
agent_connect_url: buildAgentConnectUrl(agentId),
growth_kit: growthKit,
demand_campaign_kit: demandCampaignKit,
next_actions: [
"Use referral_url when sending human demand proposers to VibeWork.",
"Record non-sensitive outreach with referral_touchpoint_url.",
"Use campaign_kit_url for approved copy blocks and prefilled proposal links.",
"Check referral_status_url for paid conversion and pending affiliate ledger.",
"Keep payment, credentials, private keys, and customer secrets out of external agent chats.",
],
};
}
export function buildAgentConnectDescription() {
return {
success: true,
endpoint: `${AGENT_GATEWAY_URL}/api/a2a/agents/connect`,
method: "POST",
accepts: {
agent_id: "stable external agent id",
tool: "optional integration id such as aider, openclaw, openhands, n8n, dify",
growth_webhook: "optional public https webhook for VibeWork growth kit delivery",
wallet_address: "optional payout wallet or payout account reference",
source: "optional source attribution",
channel: "optional outreach channel",
},
safety: [
"growth_webhook must be public https.",
"localhost, private IP, and .local targets are blocked.",
"wallet_address and existing webhook cannot be overwritten without operator review.",
"Revenue is counted only after Stripe webhook or verified USDC wallet receipt.",
],
};
}