trc-8004-sdk 0.1.0b1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
sdk/cli.py ADDED
@@ -0,0 +1,549 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ TRC-8004 CLI 工具
4
+
5
+ 提供快速创建 Agent 项目的脚手架命令。
6
+
7
+ 使用方式:
8
+ trc8004 init my-agent # 创建新 Agent 项目
9
+ trc8004 init my-agent --port 8200
10
+ trc8004 register # 注册 Agent 到链上
11
+ trc8004 test # 测试 Agent 连通性
12
+ """
13
+
14
+ import argparse
15
+ import os
16
+ import sys
17
+ from pathlib import Path
18
+
19
+
20
+ # ============ 模板定义 ============
21
+
22
+ AGENT_TEMPLATE = '''#!/usr/bin/env python3
23
+ """
24
+ {name} - 基于 TRC-8004 框架的 Agent
25
+
26
+ 启动:
27
+ python app.py
28
+
29
+ 测试:
30
+ curl http://localhost:{port}/.well-known/agent-card.json
31
+ """
32
+
33
+ import json
34
+ import os
35
+ import time
36
+ import uuid
37
+ from typing import Any, Dict
38
+
39
+ from fastapi import FastAPI
40
+ from fastapi.responses import JSONResponse
41
+ from agent_protocol import Agent, Step, Task, router
42
+
43
+ # ============ 配置 ============
44
+
45
+ AGENT_NAME = os.getenv("AGENT_NAME", "{name}")
46
+ AGENT_PORT = int(os.getenv("AGENT_PORT", "{port}"))
47
+ PAYMENT_ADDRESS = os.getenv("PAYMENT_ADDRESS", "TYourPaymentAddress")
48
+
49
+
50
+ # ============ Agent 实例 ============
51
+
52
+ agent = Agent()
53
+
54
+
55
+ def _normalize_input(value: Any) -> Dict[str, Any]:
56
+ """规范化输入"""
57
+ if isinstance(value, dict):
58
+ return value
59
+ if isinstance(value, str):
60
+ try:
61
+ return json.loads(value)
62
+ except Exception:
63
+ return {{"text": value}}
64
+ return {{}}
65
+
66
+
67
+ # ============ Agent Card ============
68
+
69
+ AGENT_CARD = {{
70
+ "type": "https://eips.ethereum.org/EIPS/eip-8004#registration-v1",
71
+ "name": AGENT_NAME,
72
+ "description": "{description}",
73
+ "version": "0.1.0",
74
+ "url": f"http://localhost:{{AGENT_PORT}}",
75
+ "endpoints": [
76
+ {{"name": "A2A", "endpoint": f"http://localhost:{{AGENT_PORT}}", "version": "0.3.0"}},
77
+ {{"name": "agentWallet", "endpoint": f"eip155:1:{{PAYMENT_ADDRESS}}"}}
78
+ ],
79
+ "capabilities": {{"streaming": False, "pushNotifications": False}},
80
+ "defaultInputModes": ["application/json"],
81
+ "defaultOutputModes": ["application/json"],
82
+ "skills": [
83
+ {{
84
+ "id": "hello",
85
+ "name": "Say Hello",
86
+ "description": "返回问候消息",
87
+ "inputSchema": {{
88
+ "type": "object",
89
+ "properties": {{"name": {{"type": "string"}}}},
90
+ }}
91
+ }},
92
+ {{
93
+ "id": "echo",
94
+ "name": "Echo Message",
95
+ "description": "回显输入消息",
96
+ "inputSchema": {{
97
+ "type": "object",
98
+ "properties": {{"message": {{"type": "string"}}}},
99
+ "required": ["message"]
100
+ }}
101
+ }}
102
+ ],
103
+ "tags": {tags}
104
+ }}
105
+
106
+
107
+ # ============ REST Endpoints ============
108
+
109
+ @router.get("/.well-known/agent-card.json")
110
+ def agent_card() -> JSONResponse:
111
+ return JSONResponse(content=AGENT_CARD)
112
+
113
+
114
+ @router.get("/health")
115
+ def health() -> JSONResponse:
116
+ return JSONResponse(content={{"status": "healthy", "agent": AGENT_NAME}})
117
+
118
+
119
+ # ============ A2A Handlers ============
120
+
121
+ async def task_handler(task: Task) -> None:
122
+ print(f"📥 Task created: {{task.task_id}}")
123
+ await Agent.db.create_step(task_id=task.task_id)
124
+
125
+
126
+ async def step_handler(step: Step) -> Step:
127
+ payload = _normalize_input(step.input)
128
+ skill = payload.get("skill") or payload.get("action")
129
+ args = payload.get("args", payload)
130
+
131
+ # ========== 在这里添加你的技能 ==========
132
+
133
+ if skill == "hello":
134
+ name = args.get("name", "World")
135
+ result = {{"message": f"Hello, {{name}}!", "timestamp": int(time.time())}}
136
+ step.output = json.dumps(result, ensure_ascii=False)
137
+ step.is_last = True
138
+ return step
139
+
140
+ if skill == "echo":
141
+ message = args.get("message", "")
142
+ result = {{"echo": message, "length": len(message)}}
143
+ step.output = json.dumps(result, ensure_ascii=False)
144
+ step.is_last = True
145
+ return step
146
+
147
+ # 默认响应
148
+ result = {{
149
+ "error": "UNKNOWN_SKILL" if skill else "NO_SKILL",
150
+ "available": ["hello", "echo"],
151
+ "usage": {{"skill": "hello", "args": {{"name": "Alice"}}}}
152
+ }}
153
+ step.output = json.dumps(result, ensure_ascii=False)
154
+ step.is_last = True
155
+ return step
156
+
157
+
158
+ Agent.setup_agent(task_handler, step_handler)
159
+
160
+
161
+ if __name__ == "__main__":
162
+ print(f"""
163
+ ╔═══════════════════════════════════════════════════════════╗
164
+ ║ {name:^53} ║
165
+ ╠═══════════════════════════════════════════════════════════╣
166
+ ║ Port: {{AGENT_PORT}} ║
167
+ ║ Card: http://localhost:{{AGENT_PORT}}/.well-known/agent-card.json
168
+ ╚═══════════════════════════════════════════════════════════╝
169
+ """)
170
+ Agent.start(port=AGENT_PORT, router=router)
171
+ '''
172
+
173
+ PYPROJECT_TEMPLATE = '''[project]
174
+ name = "{name}"
175
+ version = "0.1.0"
176
+ description = "{description}"
177
+ requires-python = ">=3.11"
178
+
179
+ dependencies = [
180
+ "fastapi>=0.115.0",
181
+ "uvicorn[standard]>=0.30.0",
182
+ "agent-protocol>=1.0.0",
183
+ "httpx>=0.27.0",
184
+ "python-dotenv>=1.0.1",
185
+ ]
186
+
187
+ [project.optional-dependencies]
188
+ sdk = ["trc-8004-sdk"]
189
+ test = ["pytest>=8.0.0"]
190
+
191
+ [tool.uv]
192
+ package = true
193
+
194
+ [build-system]
195
+ requires = ["setuptools>=68"]
196
+ build-backend = "setuptools.build_meta"
197
+ '''
198
+
199
+ ENV_TEMPLATE = '''# Agent 配置
200
+ AGENT_NAME={name}
201
+ AGENT_PORT={port}
202
+ PAYMENT_ADDRESS=TYourPaymentAddress
203
+
204
+ # TRC-8004 SDK 配置 (可选)
205
+ # TRON_RPC_URL=https://nile.trongrid.io
206
+ # TRON_PRIVATE_KEY=your_hex_private_key
207
+ # IDENTITY_REGISTRY=TIdentityRegistryAddress
208
+ # VALIDATION_REGISTRY=TValidationRegistryAddress
209
+ # REPUTATION_REGISTRY=TReputationRegistryAddress
210
+
211
+ # Central Service (可选)
212
+ # CENTRAL_SERVICE_URL=http://localhost:8001
213
+ '''
214
+
215
+ README_TEMPLATE = '''# {name}
216
+
217
+ 基于 TRC-8004 框架的 Agent。
218
+
219
+ ## 快速开始
220
+
221
+ ```bash
222
+ # 安装依赖
223
+ uv sync # 或 pip install -e .
224
+
225
+ # 启动 Agent
226
+ python app.py
227
+
228
+ # 测试
229
+ curl http://localhost:{port}/.well-known/agent-card.json
230
+ ```
231
+
232
+ ## 技能列表
233
+
234
+ | 技能 ID | 名称 | 描述 |
235
+ |---------|------|------|
236
+ | `hello` | Say Hello | 返回问候消息 |
237
+ | `echo` | Echo Message | 回显输入消息 |
238
+
239
+ ## 调用示例
240
+
241
+ ```bash
242
+ # 1. 创建任务
243
+ curl -X POST http://localhost:{port}/ap/v1/agent/tasks \\
244
+ -H "Content-Type: application/json" \\
245
+ -d '{{"input": {{"skill": "hello", "args": {{"name": "Alice"}}}}}}'
246
+
247
+ # 2. 执行步骤 (使用返回的 task_id)
248
+ curl -X POST http://localhost:{port}/ap/v1/agent/tasks/TASK_ID/steps \\
249
+ -H "Content-Type: application/json" \\
250
+ -d '{{}}'
251
+ ```
252
+
253
+ ## 添加新技能
254
+
255
+ 编辑 `app.py` 中的 `step_handler` 函数:
256
+
257
+ ```python
258
+ if skill == "my_new_skill":
259
+ # 你的逻辑
260
+ result = {{"data": "..."}}
261
+ step.output = json.dumps(result)
262
+ step.is_last = True
263
+ return step
264
+ ```
265
+
266
+ 然后在 `AGENT_CARD["skills"]` 中添加技能声明。
267
+
268
+ ## 注册到 Central Service
269
+
270
+ ```bash
271
+ curl -X POST http://localhost:8001/admin/agents \\
272
+ -H "Content-Type: application/json" \\
273
+ -d '{{
274
+ "address": "{name_lower}",
275
+ "name": "{name}",
276
+ "url": "http://localhost:{port}",
277
+ "tags": {tags}
278
+ }}'
279
+ ```
280
+
281
+ ## 链上注册 (可选)
282
+
283
+ ```python
284
+ from sdk import AgentSDK
285
+
286
+ sdk = AgentSDK(private_key="...", identity_registry="...")
287
+ tx_id = sdk.register_agent(token_uri="https://your-domain/{name_lower}.json")
288
+ ```
289
+ '''
290
+
291
+ TEST_TEMPLATE = '''"""
292
+ {name} 单元测试
293
+ """
294
+
295
+ import pytest
296
+ from fastapi.testclient import TestClient
297
+
298
+
299
+ @pytest.fixture
300
+ def client():
301
+ from app import router
302
+ from fastapi import FastAPI
303
+ app = FastAPI()
304
+ app.include_router(router)
305
+ return TestClient(app)
306
+
307
+
308
+ def test_agent_card(client):
309
+ resp = client.get("/.well-known/agent-card.json")
310
+ assert resp.status_code == 200
311
+ data = resp.json()
312
+ assert data["name"] == "{name}"
313
+ assert "skills" in data
314
+
315
+
316
+ def test_health(client):
317
+ resp = client.get("/health")
318
+ assert resp.status_code == 200
319
+ assert resp.json()["status"] == "healthy"
320
+ '''
321
+
322
+ GITIGNORE_TEMPLATE = '''__pycache__/
323
+ *.py[cod]
324
+ .venv/
325
+ .env
326
+ *.db
327
+ *.log
328
+ .pytest_cache/
329
+ '''
330
+
331
+
332
+ # ============ CLI 命令 ============
333
+
334
+ def cmd_init(args):
335
+ """初始化新 Agent 项目"""
336
+ name = args.name
337
+ port = args.port
338
+ tags = args.tags.split(",") if args.tags else ["custom"]
339
+ description = args.description or f"{name} - TRC-8004 Agent"
340
+
341
+ # 创建目录
342
+ project_dir = Path(name.lower().replace(" ", "-").replace("_", "-"))
343
+ if project_dir.exists():
344
+ print(f"❌ 目录已存在: {project_dir}")
345
+ return 1
346
+
347
+ project_dir.mkdir(parents=True)
348
+ tests_dir = project_dir / "tests"
349
+ tests_dir.mkdir()
350
+
351
+ # 生成文件
352
+ files = {
353
+ "app.py": AGENT_TEMPLATE.format(
354
+ name=name, port=port, description=description, tags=tags
355
+ ),
356
+ "pyproject.toml": PYPROJECT_TEMPLATE.format(
357
+ name=name.lower().replace(" ", "-"), description=description
358
+ ),
359
+ ".env.example": ENV_TEMPLATE.format(name=name, port=port),
360
+ "README.md": README_TEMPLATE.format(
361
+ name=name, port=port, tags=tags, name_lower=name.lower().replace(" ", "-")
362
+ ),
363
+ ".gitignore": GITIGNORE_TEMPLATE,
364
+ "tests/__init__.py": "",
365
+ "tests/test_agent.py": TEST_TEMPLATE.format(name=name),
366
+ }
367
+
368
+ for filename, content in files.items():
369
+ filepath = project_dir / filename
370
+ filepath.parent.mkdir(parents=True, exist_ok=True)
371
+ filepath.write_text(content)
372
+
373
+ print(f"""
374
+ ✅ Agent 项目创建成功!
375
+
376
+ 📁 {project_dir}/
377
+ ├── app.py # Agent 主程序
378
+ ├── pyproject.toml # 项目配置
379
+ ├── .env.example # 环境变量模板
380
+ ├── README.md # 文档
381
+ └── tests/ # 测试
382
+
383
+ 🚀 下一步:
384
+ cd {project_dir}
385
+ cp .env.example .env
386
+ uv sync # 或 pip install -e .
387
+ python app.py
388
+
389
+ 📖 文档: {project_dir}/README.md
390
+ """)
391
+ return 0
392
+
393
+
394
+ def cmd_test(args):
395
+ """测试 Agent 连通性"""
396
+ import urllib.request
397
+ import json as json_module
398
+
399
+ url = args.url.rstrip("/")
400
+
401
+ print(f"🔍 测试 Agent: {url}")
402
+
403
+ # 测试 agent-card
404
+ try:
405
+ card_url = f"{url}/.well-known/agent-card.json"
406
+ with urllib.request.urlopen(card_url, timeout=5) as resp:
407
+ card = json_module.loads(resp.read())
408
+ print(f"✅ Agent Card: {card.get('name', 'Unknown')}")
409
+ print(f" Skills: {[s['id'] for s in card.get('skills', [])]}")
410
+ print(f" Tags: {card.get('tags', [])}")
411
+ except Exception as e:
412
+ print(f"❌ Agent Card 获取失败: {e}")
413
+ return 1
414
+
415
+ # 测试 health
416
+ try:
417
+ health_url = f"{url}/health"
418
+ with urllib.request.urlopen(health_url, timeout=5) as resp:
419
+ health = json_module.loads(resp.read())
420
+ print(f"✅ Health: {health.get('status', 'unknown')}")
421
+ except Exception as e:
422
+ print(f"⚠️ Health 端点不可用: {e}")
423
+
424
+ print("\n✅ Agent 连通性测试通过!")
425
+ return 0
426
+
427
+
428
+ def cmd_register(args):
429
+ """注册 Agent 到链上"""
430
+ import json as json_module
431
+
432
+ print("🔗 注册 Agent 到链上...")
433
+
434
+ # 检查环境变量
435
+ required = ["TRON_PRIVATE_KEY", "IDENTITY_REGISTRY"]
436
+ missing = [k for k in required if not os.getenv(k)]
437
+ if missing:
438
+ print(f"❌ 缺少环境变量: {', '.join(missing)}")
439
+ print("\n请设置以下环境变量:")
440
+ print(" export TRON_PRIVATE_KEY=your_hex_private_key")
441
+ print(" export IDENTITY_REGISTRY=TIdentityRegistryAddress")
442
+ return 1
443
+
444
+ try:
445
+ from sdk import AgentSDK
446
+ except ImportError:
447
+ print("❌ 请先安装 SDK: pip install trc-8004-sdk")
448
+ return 1
449
+
450
+ sdk = AgentSDK(
451
+ private_key=os.getenv("TRON_PRIVATE_KEY"),
452
+ rpc_url=os.getenv("TRON_RPC_URL", "https://nile.trongrid.io"),
453
+ network=os.getenv("TRON_NETWORK", "tron:nile"),
454
+ identity_registry=os.getenv("IDENTITY_REGISTRY"),
455
+ )
456
+
457
+ # 加载 metadata
458
+ metadata = None
459
+
460
+ # 优先从 agent-card.json 加载
461
+ if args.card:
462
+ card_path = Path(args.card)
463
+ if not card_path.exists():
464
+ print(f"❌ Agent Card 文件不存在: {card_path}")
465
+ return 1
466
+ try:
467
+ with open(card_path) as f:
468
+ card = json_module.load(f)
469
+ metadata = AgentSDK.extract_metadata_from_card(card)
470
+ print(f"📋 从 Agent Card 提取 metadata:")
471
+ for m in metadata:
472
+ value_preview = m["value"][:50] + "..." if len(m["value"]) > 50 else m["value"]
473
+ print(f" - {m['key']}: {value_preview}")
474
+ except Exception as e:
475
+ print(f"❌ 解析 Agent Card 失败: {e}")
476
+ return 1
477
+ elif args.metadata:
478
+ # 从 JSON 字符串加载
479
+ try:
480
+ raw = json_module.loads(args.metadata)
481
+ if isinstance(raw, dict):
482
+ metadata = [{"key": k, "value": v} for k, v in raw.items()]
483
+ elif isinstance(raw, list):
484
+ metadata = raw
485
+ print(f"📋 使用自定义 metadata: {[m['key'] for m in metadata]}")
486
+ except Exception as e:
487
+ print(f"❌ 解析 metadata JSON 失败: {e}")
488
+ return 1
489
+ elif args.name:
490
+ # 简单模式:只设置 name
491
+ metadata = [{"key": "name", "value": args.name}]
492
+ print(f"📋 使用简单 metadata: name={args.name}")
493
+
494
+ try:
495
+ tx_id = sdk.register_agent(
496
+ token_uri=args.token_uri or "",
497
+ metadata=metadata,
498
+ )
499
+ print(f"\n✅ 注册成功!")
500
+ print(f" 交易 ID: {tx_id}")
501
+ if metadata:
502
+ print(f" Metadata 数量: {len(metadata)}")
503
+ except Exception as e:
504
+ print(f"❌ 注册失败: {e}")
505
+ return 1
506
+
507
+ return 0
508
+
509
+
510
+ def main():
511
+ parser = argparse.ArgumentParser(
512
+ prog="trc8004",
513
+ description="TRC-8004 CLI 工具",
514
+ )
515
+ subparsers = parser.add_subparsers(dest="command", help="可用命令")
516
+
517
+ # init 命令
518
+ init_parser = subparsers.add_parser("init", help="创建新 Agent 项目")
519
+ init_parser.add_argument("name", help="Agent 名称")
520
+ init_parser.add_argument("--port", "-p", type=int, default=8100, help="端口号 (默认 8100)")
521
+ init_parser.add_argument("--tags", "-t", help="标签,逗号分隔 (默认 custom)")
522
+ init_parser.add_argument("--description", "-d", help="Agent 描述")
523
+
524
+ # test 命令
525
+ test_parser = subparsers.add_parser("test", help="测试 Agent 连通性")
526
+ test_parser.add_argument("--url", "-u", default="http://localhost:8100", help="Agent URL")
527
+
528
+ # register 命令
529
+ reg_parser = subparsers.add_parser("register", help="注册 Agent 到链上")
530
+ reg_parser.add_argument("--token-uri", "-t", help="Token URI (可选)")
531
+ reg_parser.add_argument("--card", "-c", help="Agent Card JSON 文件路径 (自动提取 metadata)")
532
+ reg_parser.add_argument("--metadata", "-m", help="Metadata JSON 字符串")
533
+ reg_parser.add_argument("--name", "-n", help="Agent 名称 (简单模式)")
534
+
535
+ args = parser.parse_args()
536
+
537
+ if args.command == "init":
538
+ return cmd_init(args)
539
+ elif args.command == "test":
540
+ return cmd_test(args)
541
+ elif args.command == "register":
542
+ return cmd_register(args)
543
+ else:
544
+ parser.print_help()
545
+ return 0
546
+
547
+
548
+ if __name__ == "__main__":
549
+ sys.exit(main())