Jeremy commited on
Commit
5c57fd7
·
1 Parent(s): aaa0d54

feat: deploy A2A server with Dockerfile, health endpoint, agent card

Browse files
Files changed (6) hide show
  1. .well-known/agent-card.json +19 -0
  2. Dockerfile +23 -0
  3. HF-SPACE-RULE.md +62 -0
  4. app.py +1 -0
  5. package.json +14 -0
  6. 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
+ });