Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions DONATE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Support AgentMesh

**AgentMesh is completely free — personal use and commercial use, no license file, no license key, no subscription. Use it, ship it, put it inside your company.**

One person maintains this, and every donation goes to the one thing an infrastructure project cannot fake: **real servers, real measurement, real production traffic.**

### Why donate?

AgentMesh is not a toy proxy or a dashboard over an API. It is a full platform:

- API Gateway + Service Mesh + Service Discovery + Message Broker + Observability — built for AI Agents
- Control Plane / Data Plane separation, Gateway Cluster with horizontal scaling, NATS JetStream, Redis coordination, Postgres source of truth, S3 artifacts
- Reliability: Circuit Breakers, Bulkheads, Retries with jitter, Timeouts, Fan-out limits, Idempotency
- Security: ApiKey, JWT, OIDC, mTLS, Workload Identity, Policy Engine with delegation and privilege escalation prevention
- Versioning: semver resolution with latest/stable/canary/minimum/range
- Multi-tenancy: strict org/project isolation
- Messaging: sync/async/streaming with trace propagation
- Artifacts: S3-compatible with lifecycle and access control
- MCP Bridge: A2A ↔ MCP translation with clear protocol separation
- Webhooks: signed delivery, retries, SSRF protection, dead-letter
- Observability: OpenTelemetry tracing User → Agent A → Agent B → Tool, metrics for latency, errors, retries, queue time
- Web Console: Next.js, Tailwind, shadcn/ui, TanStack Query/Table, React Flow live task graph

Running this in production costs real money: Postgres, Redis, NATS, S3, multiple gateway nodes, telemetry storage, CI, and continuous testing against real agent workloads. Donations keep the infrastructure honest.

### Addresses

**Bitcoin · BTC mainnet**

```
bc1q36uzqlkaav3lkscknhemcem0lcjtkhepdqckul
```

**BNB Smart Chain · BEP-20**

```
0x57902d3955D5F1C0fbCaEA0a12A7D691c792487E
```

**Solana · SOL mainnet**

```
4hCYetZjvK8mkuobRvPYXyRnM84aTj3q8LZ1GpiTK8HR
```

**Tron · TRC-20**

```
TVFZKSwMYNw1jiCyKKtKoVG3HbpB4DhsA5
```

Scan a card with your wallet app, or use the copy button under it. Check the address in your wallet before sending — network fees are lowest on Solana and Tron.

No platform takes a cut. Funds go directly to the maintainer.

Made ❤️ by Mohammad @llllxyz — https://t.me/llllxyz
513 changes: 443 additions & 70 deletions README.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion adapters/nats/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@agentmesh/nats-adapter",
"version": "0.1.0",
"version": "1.0.0",
"private": true,
"description": "NATS JetStream adapter for event bus",
"type": "module",
Expand Down
2 changes: 1 addition & 1 deletion adapters/oidc/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@agentmesh/oidc-adapter",
"version": "0.1.0",
"version": "1.0.0",
"private": true,
"description": "OIDC adapter for authentication",
"type": "module",
Expand Down
2 changes: 1 addition & 1 deletion adapters/postgres/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@agentmesh/postgres-adapter",
"version": "0.1.0",
"version": "1.0.0",
"private": true,
"description": "Postgres adapter with Drizzle ORM",
"type": "module",
Expand Down
2 changes: 1 addition & 1 deletion adapters/redis/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@agentmesh/redis-adapter",
"version": "0.1.0",
"version": "1.0.0",
"private": true,
"description": "Redis adapter for cache, rate limiting, coordination",
"type": "module",
Expand Down
2 changes: 1 addition & 1 deletion adapters/s3/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@agentmesh/s3-adapter",
"version": "0.1.0",
"version": "1.0.0",
"private": true,
"description": "S3-compatible storage adapter",
"type": "module",
Expand Down
31 changes: 31 additions & 0 deletions apps/console/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
FROM node:20-alpine AS base
RUN corepack enable && corepack prepare pnpm@9.12.3 --activate
WORKDIR /app

FROM base AS deps
COPY package.json pnpm-workspace.yaml pnpm-lock.yaml turbo.json tsconfig.base.json tsconfig.json ./
COPY packages/a2a-protocol/package.json ./packages/a2a-protocol/
COPY packages/sdk/package.json ./packages/sdk/
COPY apps/console/package.json ./apps/console/
RUN pnpm install --frozen-lockfile || pnpm install

FROM base AS builder
COPY --from=deps /app/node_modules ./node_modules
COPY --from=deps /app/packages ./packages
COPY . .
RUN pnpm turbo run build --filter=@agentmesh/console... --concurrency=1

FROM base AS runner
WORKDIR /app
ENV NODE_ENV=production
COPY --from=builder /app/apps/console/.next ./apps/console/.next
COPY --from=builder /app/apps/console/package.json ./apps/console/
COPY --from=builder /app/apps/console/next.config.js ./apps/console/
COPY --from=builder /app/apps/console/public ./apps/console/public
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/packages ./packages
COPY --from=builder /app/pnpm-workspace.yaml ./pnpm-workspace.yaml
EXPOSE 3000
HEALTHCHECK --interval=10s --timeout=5s --retries=3 CMD wget --no-verbose --tries=1 --spider http://localhost:3000/ || exit 1
CMD ["node", "node_modules/next/dist/bin/next", "start", "-p", "3000", "-H", "0.0.0.0"]
WORKDIR /app/apps/console
5 changes: 5 additions & 0 deletions apps/console/next-env.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />

// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information.
14 changes: 14 additions & 0 deletions apps/console/next.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
transpilePackages: ["@agentmesh/sdk"],
experimental: { typedRoutes: false },
async rewrites() {
return [
{ source: "/api/control-plane/:path*", destination: `${process.env.CONTROL_PLANE_URL || "http://localhost:3002"}/:path*` },
{ source: "/api/gateway/:path*", destination: `${process.env.GATEWAY_URL || "http://localhost:3001"}/:path*` },
];
},
};

export default nextConfig;
39 changes: 31 additions & 8 deletions apps/console/package.json
Original file line number Diff line number Diff line change
@@ -1,14 +1,37 @@
{
"name": "@agentmesh/console",
"version": "0.1.0",
"version": "1.0.0",
"private": true,
"description": "AgentMesh Web Console - Phase 4",
"description": "AgentMesh Web Console - Infrastructure-grade UI for AI Agent Gateway and Control Plane",
"type": "module",
"scripts": {
"build": "echo 'Console - Phase 4: Next.js + shadcn + React Flow will be here'",
"dev": "echo 'Console Phase 4 not yet implemented' && sleep 999999",
"typecheck": "echo ok",
"lint": "echo ok",
"clean": "echo ok",
"test": "echo ok"
"build": "next build",
"dev": "next dev -p 3000 -H 0.0.0.0",
"start": "next start -p 3000 -H 0.0.0.0",
"typecheck": "tsc -p tsconfig.json --noEmit",
"lint": "next lint",
"clean": "rm -rf .next dist out"
},
"dependencies": {
"@agentmesh/sdk": "workspace:*",
"@tanstack/react-query": "^5.56.2",
"@tanstack/react-table": "^8.20.5",
"next": "^14.2.13",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"reactflow": "^11.11.3",
"tailwind-merge": "^2.5.2",
"clsx": "^2.1.1",
"lucide-react": "^0.441.0",
"zod": "^3.23.8"
},
"devDependencies": {
"@types/node": "^20.14.0",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"autoprefixer": "^10.4.20",
"postcss": "^8.4.47",
"tailwindcss": "^3.4.10",
"typescript": "^5.6.3"
}
}
3 changes: 3 additions & 0 deletions apps/console/postcss.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default {
plugins: { tailwindcss: {}, autoprefixer: {} },
};
68 changes: 68 additions & 0 deletions apps/console/src/app/agents/[id]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { api } from "@/lib/api";

export default async function AgentDetailPage({ params }: { params: { id: string } }) {
let agent: any = null;
let card: any = null;
try {
const res = await api.controlPlane.agents.get(params.id);
agent = res.agent;
} catch {}
try {
const res = await api.controlPlane.agents.getCard(params.id);
card = res.card;
} catch {}

if (!agent) return <div>Agent not found: {params.id}</div>;

return (
<div className="space-y-4">
<h1 className="text-2xl font-bold">{agent.name}</h1>
<div className="flex gap-2">
<Badge>{agent.version}</Badge>
<Badge variant={agent.health === "HEALTHY" ? "default" : "destructive"}>{agent.health}</Badge>
<Badge variant="outline">{agent.trustLevel}</Badge>
</div>

<div className="grid gap-4 md:grid-cols-2">
<Card>
<CardHeader><CardTitle>Details</CardTitle></CardHeader>
<CardContent className="text-sm space-y-2">
<div>ID: {agent.id}</div>
<div>URL: {agent.url}</div>
<div>Org: {agent.organizationId ?? "none"}</div>
<div>Project: {agent.projectId ?? "none"}</div>
<div>Region: {agent.region ?? "none"}</div>
<div>Environment: {agent.environment}</div>
<div>Created: {agent.createdAt}</div>
<div>Last Seen: {agent.lastSeenAt}</div>
<div>Description: {agent.description}</div>
</CardContent>
</Card>

<Card>
<CardHeader><CardTitle>Skills</CardTitle><CardDescription>{agent.skills?.length ?? 0} skills</CardDescription></CardHeader>
<CardContent className="space-y-2">
{agent.skills?.map((s: any) => (
<div key={s.skillId} className="border rounded p-2">
<div className="font-medium">{s.name} <Badge variant="outline" className="ml-2 text-[10px]">{s.skillId}</Badge></div>
<div className="text-xs text-muted-foreground">{s.description}</div>
<div className="flex gap-1 mt-1">{s.tags?.map((t: string) => <Badge key={t} variant="outline" className="text-[10px]">{t}</Badge>)}</div>
</div>
))}
</CardContent>
</Card>
</div>

{card && (
<Card>
<CardHeader><CardTitle>Agent Card</CardTitle></CardHeader>
<CardContent>
<pre className="text-xs bg-secondary p-4 rounded overflow-auto max-h-96">{JSON.stringify(card, null, 2)}</pre>
</CardContent>
</Card>
)}
</div>
);
}
52 changes: 52 additions & 0 deletions apps/console/src/app/agents/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { api } from "@/lib/api";
import Link from "next/link";

export default async function AgentsPage({ searchParams }: { searchParams: { skill?: string; search?: string } }) {
let data: any = { agents: [], total: 0 };
try {
data = await api.controlPlane.agents.list({ skill: searchParams.skill, search: searchParams.search, limit: "100" } as any);
} catch (e) {
console.warn(e);
}

return (
<div className="space-y-4">
<h1 className="text-2xl font-bold">Agent Registry</h1>
<p className="text-sm text-muted-foreground">Total: {data.total ?? data.agents?.length ?? 0} agents • Discovery by skill, capability, version, region</p>

<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
{data.agents?.map((agent: any) => (
<Card key={agent.id}>
<CardHeader>
<CardTitle className="text-base">
<Link href={`/agents/${agent.id}`} className="hover:underline">{agent.name}</Link>
</CardTitle>
<div className="flex gap-2 flex-wrap">
<Badge variant="secondary">{agent.version}</Badge>
<Badge variant={agent.health === "HEALTHY" ? "default" : agent.health === "UNHEALTHY" ? "destructive" : "outline"}>{agent.health}</Badge>
<Badge variant="outline">{agent.trustLevel}</Badge>
</div>
</CardHeader>
<CardContent className="text-sm space-y-2">
<div className="text-muted-foreground truncate">{agent.description ?? agent.url}</div>
<div className="text-xs">ID: {agent.id.slice(0, 20)}...</div>
<div className="text-xs">URL: {agent.url}</div>
{agent.skills?.length > 0 && (
<div className="flex flex-wrap gap-1">
{agent.skills.slice(0, 5).map((s: any) => (
<Badge key={s.skillId} variant="outline" className="text-[10px]">{s.name}</Badge>
))}
</div>
)}
<div className="text-xs text-muted-foreground">Org: {agent.organizationId ?? "default"} • Project: {agent.projectId ?? "default"}</div>
</CardContent>
</Card>
))}
</div>

{data.agents?.length === 0 && <div className="text-muted-foreground text-sm">No agents registered yet. Use API POST /v1/agents to register.</div>}
</div>
);
}
43 changes: 43 additions & 0 deletions apps/console/src/app/artifacts/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { api } from "@/lib/api";

export default async function ArtifactsPage() {
let data: any = { artifacts: [], total: 0 };
try {
data = await api.controlPlane.artifacts.list({ limit: "100" });
} catch {}

return (
<div className="space-y-4">
<h1 className="text-2xl font-bold">Artifacts</h1>
<p className="text-sm text-muted-foreground">S3-Compatible Storage • Checksum, Retention, Access Control</p>

<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
{data.artifacts?.map((art: any) => (
<Card key={art.id}>
<CardHeader>
<CardTitle className="text-sm">{art.name ?? art.id.slice(0, 16)}</CardTitle>
<div className="flex gap-2">
<Badge variant="outline">{art.contentType}</Badge>
<Badge variant="secondary">{(art.size / 1024).toFixed(1)} KB</Badge>
</div>
</CardHeader>
<CardContent className="text-xs space-y-1">
<div>ID: {art.id.slice(0, 20)}...</div>
<div>Checksum: {art.checksum?.slice(0, 16)}...</div>
<div>Bucket: {art.storageBucket} • Key: {art.storageKey.slice(0, 40)}...</div>
<div>Task: {art.taskId?.slice(0, 12) ?? "none"} • Agent: {art.agentId?.slice(0, 12) ?? "none"}</div>
<div>Access: {art.accessControl} • Retention: {art.retentionDays ?? "none"} days</div>
<div>Created: {art.createdAt}</div>
{art.expiresAt && <div>Expires: {art.expiresAt}</div>}
<div className="flex gap-1 flex-wrap">{art.tags?.map((t: string) => <Badge key={t} variant="outline" className="text-[10px]">{t}</Badge>)}</div>
</CardContent>
</Card>
))}
</div>

{data.artifacts?.length === 0 && <div className="text-sm text-muted-foreground">No artifacts yet. Create via POST /v1/artifacts</div>}
</div>
);
}
Loading
Loading