Spaces:
Sleeping
Sleeping
Jeremy commited on
Commit ·
e8daf8a
1
Parent(s): 10937ca
feat: deploy A2A server with Dockerfile, health endpoint, agent card
Browse files- .well-known/agent-card.json +19 -0
- Dockerfile +23 -0
- HF-SPACE-RULE.md +62 -0
- app.py +1 -0
- package.json +14 -0
- server.js +133 -0
.well-known/agent-card.json
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "SIN-Agent",
|
| 3 |
+
"description": "SIN A2A Agent",
|
| 4 |
+
"url": "https://localhost/a2a/v1",
|
| 5 |
+
"protocol": "A2A",
|
| 6 |
+
"version": "1.0.0",
|
| 7 |
+
"capabilities": ["chat", "task", "help"],
|
| 8 |
+
"commands": [
|
| 9 |
+
{"name": "agent.help", "description": "Show available commands"},
|
| 10 |
+
{"name": "agent.health", "description": "Check agent health"},
|
| 11 |
+
{"name": "agent.card", "description": "Show agent card"}
|
| 12 |
+
],
|
| 13 |
+
"endpoints": {
|
| 14 |
+
"a2a": "/a2a/v1",
|
| 15 |
+
"health": "/health",
|
| 16 |
+
"card": "/.well-known/agent-card.json"
|
| 17 |
+
},
|
| 18 |
+
"status": "active"
|
| 19 |
+
}
|
Dockerfile
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM node:22-slim
|
| 2 |
+
|
| 3 |
+
WORKDIR /app
|
| 4 |
+
|
| 5 |
+
# Install dependencies
|
| 6 |
+
COPY package*.json ./
|
| 7 |
+
RUN npm ci --omit=dev 2>/dev/null || npm install
|
| 8 |
+
|
| 9 |
+
# Copy source
|
| 10 |
+
COPY . .
|
| 11 |
+
|
| 12 |
+
# Build if needed
|
| 13 |
+
RUN npm run build 2>/dev/null || true
|
| 14 |
+
|
| 15 |
+
# Expose port
|
| 16 |
+
EXPOSE 7860
|
| 17 |
+
|
| 18 |
+
# Health check
|
| 19 |
+
HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
|
| 20 |
+
CMD curl -f http://localhost:7860/health || exit 1
|
| 21 |
+
|
| 22 |
+
# Start
|
| 23 |
+
CMD ["node", "server.js"]
|
HF-SPACE-RULE.md
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# 🚨 HUGGING FACE SPACE HARDWARE RULE (PRIORITY -7 — ABSOLUT, KEINE AUSNAHMEN) 🚨
|
| 2 |
+
|
| 3 |
+
**ABSOLUTES VERBOT: KEINE GPU/ZeroGPU SPACES FÜR A2A AGENT RUNTIMES!**
|
| 4 |
+
|
| 5 |
+
## WARUM
|
| 6 |
+
|
| 7 |
+
- HF Free Tier erlaubt UNBEGRENZT CPU-Basic Spaces gleichzeitig ($0)
|
| 8 |
+
- GPU/ZeroGPU Spaces haben Quota-Limit (8x) und benötigen Payment
|
| 9 |
+
- Alle Agent Spaces MÜSSEN auf CPU-Basic laufen
|
| 10 |
+
|
| 11 |
+
## PFLICHTEN
|
| 12 |
+
|
| 13 |
+
1. **JEDES HF Space MUSS auf `cpu-basic` Hardware konfiguriert sein**
|
| 14 |
+
2. **NIEMALS** GPU, ZeroGPU, oder paid Hardware für Agent Runtimes
|
| 15 |
+
3. **IMMER** Keep-Alive-Ping alle 5 Minuten (verhindert 48h Sleep)
|
| 16 |
+
|
| 17 |
+
## PFLICHT-DATEIEN FÜR JEDES HF SPACE
|
| 18 |
+
|
| 19 |
+
Jedes HF Space MUSS diese Dateien enthalten:
|
| 20 |
+
|
| 21 |
+
1. **Dockerfile** — Node.js/Python Container Definition
|
| 22 |
+
2. **A2A Server** — Express/FastAPI mit A2A Protocol (`server.js` oder `app.py`)
|
| 23 |
+
3. **Agent Logic** — Die eigentliche Agenten-Funktionalität
|
| 24 |
+
4. **Health Endpoint** — `/health` Endpoint für Monitoring
|
| 25 |
+
5. **A2A Card** — `/.well-known/agent-card.json` für Agent Discovery
|
| 26 |
+
|
| 27 |
+
## KORREKTE SPACE ERSTELLUNG
|
| 28 |
+
|
| 29 |
+
```bash
|
| 30 |
+
# 1. Space erstellen
|
| 31 |
+
curl -X POST https://huggingface.co/api/repos/create \
|
| 32 |
+
-H "Authorization: Bearer $HF_TOKEN" \
|
| 33 |
+
-H "Content-Type: application/json" \
|
| 34 |
+
-d '{"name": "sin-my-agent", "type": "space", "sdk": "docker"}'
|
| 35 |
+
|
| 36 |
+
# 2. Hardware auf CPU-Basic setzen
|
| 37 |
+
curl -X POST https://huggingface.co/api/spaces/{owner}/{space}/hardware \
|
| 38 |
+
-H "Authorization: Bearer $HF_TOKEN" \
|
| 39 |
+
-H "Content-Type: application/json" \
|
| 40 |
+
-d '{"flavor": "cpu-basic"}'
|
| 41 |
+
|
| 42 |
+
# 3. Dockerfile + Code deployen (via git push)
|
| 43 |
+
git clone https://huggingface.co/spaces/{owner}/{space}
|
| 44 |
+
cp Dockerfile server.js package.json .well-known/agent-card.json {space}/
|
| 45 |
+
cd {space} && git add . && git commit -m "deploy agent" && git push
|
| 46 |
+
```
|
| 47 |
+
|
| 48 |
+
## KEEP-ALIVE PFLICHT
|
| 49 |
+
|
| 50 |
+
Jedes Space MUSS Keep-Alive-Ping haben (alle 5min) um 48h Sleep zu verhindern.
|
| 51 |
+
|
| 52 |
+
## KOSTEN
|
| 53 |
+
|
| 54 |
+
| Hardware | Kosten | Max gleichzeitig |
|
| 55 |
+
|----------|--------|-----------------|
|
| 56 |
+
| cpu-basic | **$0** | **UNBEGRENZT** |
|
| 57 |
+
| zerogpu | FREE | 8 pro Account |
|
| 58 |
+
| gpu (T4) | $288/Mo | Unbegrenzt (Payment) |
|
| 59 |
+
|
| 60 |
+
**FAZIT: CPU-Basic = $0, UNBEGRENZT Spaces!**
|
| 61 |
+
|
| 62 |
+
Siehe auch: [Global Dev Docs HF Space Hardware Standard](https://github.com/OpenSIN-AI/Global-Dev-Docs-Standard/blob/main/standards/hf-space-hardware.md)
|
app.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
print("SIN A2A Agent running")
|
package.json
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "a2a-sin-agent-base",
|
| 3 |
+
"version": "1.0.0",
|
| 4 |
+
"description": "SIN A2A Agent Base Server",
|
| 5 |
+
"main": "server.js",
|
| 6 |
+
"scripts": {
|
| 7 |
+
"start": "node server.js",
|
| 8 |
+
"dev": "node --watch server.js",
|
| 9 |
+
"build": "echo 'No build step needed'"
|
| 10 |
+
},
|
| 11 |
+
"dependencies": {
|
| 12 |
+
"express": "^4.21.0"
|
| 13 |
+
}
|
| 14 |
+
}
|
server.js
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
const express = require('express');
|
| 2 |
+
const path = require('path');
|
| 3 |
+
const fs = require('fs');
|
| 4 |
+
|
| 5 |
+
const app = express();
|
| 6 |
+
const PORT = process.env.PORT || 7860;
|
| 7 |
+
|
| 8 |
+
// Agent name from env or default
|
| 9 |
+
const AGENT_NAME = process.env.AGENT_NAME || 'SIN-Agent';
|
| 10 |
+
const AGENT_DESCRIPTION = process.env.AGENT_DESCRIPTION || 'SIN A2A Agent';
|
| 11 |
+
|
| 12 |
+
// Middleware
|
| 13 |
+
app.use(express.json());
|
| 14 |
+
app.use(express.static(path.join(__dirname, 'public')));
|
| 15 |
+
|
| 16 |
+
// Health endpoint
|
| 17 |
+
app.get('/health', (req, res) => {
|
| 18 |
+
res.json({
|
| 19 |
+
status: 'ok',
|
| 20 |
+
agent: AGENT_NAME,
|
| 21 |
+
timestamp: new Date().toISOString(),
|
| 22 |
+
uptime: process.uptime(),
|
| 23 |
+
version: '1.0.0'
|
| 24 |
+
});
|
| 25 |
+
});
|
| 26 |
+
|
| 27 |
+
// A2A Card endpoint
|
| 28 |
+
app.get('/.well-known/agent-card.json', (req, res) => {
|
| 29 |
+
const cardPath = path.join(__dirname, '.well-known', 'agent-card.json');
|
| 30 |
+
if (fs.existsSync(cardPath)) {
|
| 31 |
+
res.json(JSON.parse(fs.readFileSync(cardPath, 'utf8')));
|
| 32 |
+
} else {
|
| 33 |
+
res.json({
|
| 34 |
+
name: AGENT_NAME,
|
| 35 |
+
description: AGENT_DESCRIPTION,
|
| 36 |
+
url: `https://${process.env.SPACE_HOST || 'localhost'}/a2a/v1`,
|
| 37 |
+
protocol: 'A2A',
|
| 38 |
+
version: '1.0.0',
|
| 39 |
+
capabilities: ['chat', 'task'],
|
| 40 |
+
status: 'active'
|
| 41 |
+
});
|
| 42 |
+
}
|
| 43 |
+
});
|
| 44 |
+
|
| 45 |
+
// A2A v1 endpoint
|
| 46 |
+
app.post('/a2a/v1', async (req, res) => {
|
| 47 |
+
try {
|
| 48 |
+
const { action, params } = req.body;
|
| 49 |
+
|
| 50 |
+
switch (action) {
|
| 51 |
+
case 'agent.help':
|
| 52 |
+
res.json({
|
| 53 |
+
result: {
|
| 54 |
+
name: AGENT_NAME,
|
| 55 |
+
description: AGENT_DESCRIPTION,
|
| 56 |
+
commands: ['help', 'health', 'status'],
|
| 57 |
+
version: '1.0.0'
|
| 58 |
+
}
|
| 59 |
+
});
|
| 60 |
+
break;
|
| 61 |
+
|
| 62 |
+
case 'agent.health':
|
| 63 |
+
res.json({
|
| 64 |
+
result: {
|
| 65 |
+
status: 'ok',
|
| 66 |
+
agent: AGENT_NAME,
|
| 67 |
+
uptime: process.uptime()
|
| 68 |
+
}
|
| 69 |
+
});
|
| 70 |
+
break;
|
| 71 |
+
|
| 72 |
+
case 'agent.card':
|
| 73 |
+
res.json({
|
| 74 |
+
result: {
|
| 75 |
+
name: AGENT_NAME,
|
| 76 |
+
description: AGENT_DESCRIPTION,
|
| 77 |
+
protocol: 'A2A',
|
| 78 |
+
version: '1.0.0'
|
| 79 |
+
}
|
| 80 |
+
});
|
| 81 |
+
break;
|
| 82 |
+
|
| 83 |
+
default:
|
| 84 |
+
res.json({
|
| 85 |
+
result: {
|
| 86 |
+
message: `Unknown action: ${action}. Use 'agent.help' for available commands.`,
|
| 87 |
+
agent: AGENT_NAME
|
| 88 |
+
}
|
| 89 |
+
});
|
| 90 |
+
}
|
| 91 |
+
} catch (error) {
|
| 92 |
+
res.status(500).json({
|
| 93 |
+
error: error.message,
|
| 94 |
+
agent: AGENT_NAME
|
| 95 |
+
});
|
| 96 |
+
}
|
| 97 |
+
});
|
| 98 |
+
|
| 99 |
+
// A2A GET endpoint
|
| 100 |
+
app.get('/a2a/v1', (req, res) => {
|
| 101 |
+
res.json({
|
| 102 |
+
name: AGENT_NAME,
|
| 103 |
+
description: AGENT_DESCRIPTION,
|
| 104 |
+
protocol: 'A2A',
|
| 105 |
+
version: '1.0.0',
|
| 106 |
+
endpoints: {
|
| 107 |
+
post: '/a2a/v1',
|
| 108 |
+
card: '/.well-known/agent-card.json',
|
| 109 |
+
health: '/health'
|
| 110 |
+
}
|
| 111 |
+
});
|
| 112 |
+
});
|
| 113 |
+
|
| 114 |
+
// Root endpoint
|
| 115 |
+
app.get('/', (req, res) => {
|
| 116 |
+
res.json({
|
| 117 |
+
name: AGENT_NAME,
|
| 118 |
+
description: AGENT_DESCRIPTION,
|
| 119 |
+
status: 'active',
|
| 120 |
+
endpoints: {
|
| 121 |
+
health: '/health',
|
| 122 |
+
a2a: '/a2a/v1',
|
| 123 |
+
card: '/.well-known/agent-card.json'
|
| 124 |
+
}
|
| 125 |
+
});
|
| 126 |
+
});
|
| 127 |
+
|
| 128 |
+
// Start server
|
| 129 |
+
app.listen(PORT, '0.0.0.0', () => {
|
| 130 |
+
console.log(`${AGENT_NAME} listening on port ${PORT}`);
|
| 131 |
+
console.log(`Health: http://localhost:${PORT}/health`);
|
| 132 |
+
console.log(`A2A: http://localhost:${PORT}/a2a/v1`);
|
| 133 |
+
});
|