theprotocol-sdk 0.2.0__tar.gz → 0.5.1__tar.gz
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.
- theprotocol_sdk-0.5.1/.gitignore +59 -0
- {theprotocol_sdk-0.2.0 → theprotocol_sdk-0.5.1}/PKG-INFO +69 -3
- theprotocol_sdk-0.5.1/README.md +161 -0
- {theprotocol_sdk-0.2.0 → theprotocol_sdk-0.5.1}/pyproject.toml +3 -3
- {theprotocol_sdk-0.2.0 → theprotocol_sdk-0.5.1}/src/theprotocol/__init__.py +2 -1
- {theprotocol_sdk-0.2.0 → theprotocol_sdk-0.5.1}/src/theprotocol/agent/__init__.py +7 -0
- {theprotocol_sdk-0.2.0 → theprotocol_sdk-0.5.1}/src/theprotocol/agent/router.py +83 -2
- {theprotocol_sdk-0.2.0 → theprotocol_sdk-0.5.1}/src/theprotocol/agent/task_store.py +14 -1
- theprotocol_sdk-0.5.1/src/theprotocol/agent/well_known.py +370 -0
- theprotocol_sdk-0.5.1/src/theprotocol/auth/__init__.py +47 -0
- theprotocol_sdk-0.5.1/src/theprotocol/auth/did_jwt.py +203 -0
- theprotocol_sdk-0.5.1/src/theprotocol/auth/jws_card.py +207 -0
- theprotocol_sdk-0.5.1/src/theprotocol/auth/jwt_verifier.py +165 -0
- theprotocol_sdk-0.5.1/src/theprotocol/exchange/__init__.py +4 -0
- theprotocol_sdk-0.5.1/src/theprotocol/exchange/client.py +124 -0
- {theprotocol_sdk-0.2.0 → theprotocol_sdk-0.5.1}/src/theprotocol/models/__init__.py +2 -0
- {theprotocol_sdk-0.2.0 → theprotocol_sdk-0.5.1}/src/theprotocol/models/a2a_protocol.py +1 -0
- theprotocol_sdk-0.5.1/src/theprotocol/models/agent_card.py +192 -0
- theprotocol_sdk-0.5.1/src/theprotocol/models/v1_compat.py +580 -0
- theprotocol_sdk-0.5.1/src/theprotocol/payment/__init__.py +33 -0
- theprotocol_sdk-0.5.1/src/theprotocol/payment/authenticator.py +483 -0
- theprotocol_sdk-0.5.1/src/theprotocol/payment/client.py +123 -0
- theprotocol_sdk-0.5.1/src/theprotocol/payment/mtls_client.py +139 -0
- theprotocol_sdk-0.5.1/src/theprotocol/payment/verifier.py +193 -0
- theprotocol_sdk-0.5.1/src/theprotocol/registry/__init__.py +20 -0
- theprotocol_sdk-0.5.1/src/theprotocol/registry/cost_composition.py +143 -0
- theprotocol_sdk-0.5.1/src/theprotocol/transfer/__init__.py +4 -0
- theprotocol_sdk-0.5.1/src/theprotocol/transfer/client.py +117 -0
- theprotocol_sdk-0.5.1/tests/registry/__init__.py +0 -0
- theprotocol_sdk-0.5.1/tests/registry/test_cost_composition.py +156 -0
- theprotocol_sdk-0.5.1/tests/test_artifact_roundtrip.py +44 -0
- theprotocol_sdk-0.5.1/tests/test_auth.py +266 -0
- theprotocol_sdk-0.5.1/tests/test_did_jwt.py +157 -0
- theprotocol_sdk-0.5.1/tests/test_jws_card.py +132 -0
- theprotocol_sdk-0.5.1/tests/test_transfer.py +70 -0
- theprotocol_sdk-0.5.1/tests/test_v1_compat.py +190 -0
- theprotocol_sdk-0.5.1/tests/test_verifier_reuse.py +65 -0
- theprotocol_sdk-0.5.1/tests/test_well_known.py +268 -0
- theprotocol_sdk-0.2.0/.gitignore +0 -31
- theprotocol_sdk-0.2.0/README.md +0 -95
- theprotocol_sdk-0.2.0/src/theprotocol/models/agent_card.py +0 -91
- theprotocol_sdk-0.2.0/src/theprotocol/models/v1_compat.py +0 -230
- {theprotocol_sdk-0.2.0 → theprotocol_sdk-0.5.1}/LICENSE +0 -0
- {theprotocol_sdk-0.2.0 → theprotocol_sdk-0.5.1}/examples/simple_agent.py +0 -0
- {theprotocol_sdk-0.2.0 → theprotocol_sdk-0.5.1}/src/theprotocol/agent/base.py +0 -0
- {theprotocol_sdk-0.2.0 → theprotocol_sdk-0.5.1}/src/theprotocol/agent/decorators.py +0 -0
- {theprotocol_sdk-0.2.0 → theprotocol_sdk-0.5.1}/src/theprotocol/bridges/__init__.py +0 -0
- {theprotocol_sdk-0.2.0 → theprotocol_sdk-0.5.1}/src/theprotocol/bridges/acp.py +0 -0
- {theprotocol_sdk-0.2.0 → theprotocol_sdk-0.5.1}/src/theprotocol/bridges/anp/__init__.py +0 -0
- {theprotocol_sdk-0.2.0 → theprotocol_sdk-0.5.1}/src/theprotocol/bridges/anp/auth.py +0 -0
- {theprotocol_sdk-0.2.0 → theprotocol_sdk-0.5.1}/src/theprotocol/bridges/anp/bridge.py +0 -0
- {theprotocol_sdk-0.2.0 → theprotocol_sdk-0.5.1}/src/theprotocol/bridges/anp/did_wba.py +0 -0
- {theprotocol_sdk-0.2.0 → theprotocol_sdk-0.5.1}/src/theprotocol/bridges/anp/translation.py +0 -0
- {theprotocol_sdk-0.2.0 → theprotocol_sdk-0.5.1}/src/theprotocol/bridges/base.py +0 -0
- {theprotocol_sdk-0.2.0 → theprotocol_sdk-0.5.1}/src/theprotocol/bridges/google_a2a.py +0 -0
- {theprotocol_sdk-0.2.0 → theprotocol_sdk-0.5.1}/src/theprotocol/bridges/mcp.py +0 -0
- {theprotocol_sdk-0.2.0 → theprotocol_sdk-0.5.1}/src/theprotocol/client/__init__.py +0 -0
- {theprotocol_sdk-0.2.0 → theprotocol_sdk-0.5.1}/src/theprotocol/client/a2a_client.py +0 -0
- {theprotocol_sdk-0.2.0 → theprotocol_sdk-0.5.1}/src/theprotocol/client/credentials.py +0 -0
- {theprotocol_sdk-0.2.0 → theprotocol_sdk-0.5.1}/src/theprotocol/exceptions.py +0 -0
- {theprotocol_sdk-0.2.0 → theprotocol_sdk-0.5.1}/tests/test_agent.py +0 -0
- {theprotocol_sdk-0.2.0 → theprotocol_sdk-0.5.1}/tests/test_bridge_acp.py +0 -0
- {theprotocol_sdk-0.2.0 → theprotocol_sdk-0.5.1}/tests/test_bridge_anp.py +0 -0
- {theprotocol_sdk-0.2.0 → theprotocol_sdk-0.5.1}/tests/test_bridge_google_a2a.py +0 -0
- {theprotocol_sdk-0.2.0 → theprotocol_sdk-0.5.1}/tests/test_bridge_mcp.py +0 -0
- {theprotocol_sdk-0.2.0 → theprotocol_sdk-0.5.1}/tests/test_credentials.py +0 -0
- {theprotocol_sdk-0.2.0 → theprotocol_sdk-0.5.1}/tests/test_models.py +0 -0
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# TheProtocol — Root .gitignore
|
|
2
|
+
# Created: 2026-03-09 (§4.1 Credentials Rotation)
|
|
3
|
+
|
|
4
|
+
# !! NEVER COMMIT SECRETS / LOCAL CONFIG !!
|
|
5
|
+
# All env files except *.example templates. Covers .env, .env.production,
|
|
6
|
+
# .env.frame-b, .env.frame-c, .env.operator, .env.docker, and nested
|
|
7
|
+
# AgentVault/.../.env — while keeping the committed *.env.example templates.
|
|
8
|
+
.env
|
|
9
|
+
.env.*
|
|
10
|
+
!.env.example
|
|
11
|
+
# Local agent / MCP config — these carry live API keys
|
|
12
|
+
.mcp.json
|
|
13
|
+
.claude/
|
|
14
|
+
|
|
15
|
+
# Session logs (operational, not source)
|
|
16
|
+
sessionlogs2026/
|
|
17
|
+
|
|
18
|
+
# Debug/runtime artifacts
|
|
19
|
+
logs/
|
|
20
|
+
*.log
|
|
21
|
+
APEX_PREDATOR_JOBS.log
|
|
22
|
+
|
|
23
|
+
# Node
|
|
24
|
+
node_modules/
|
|
25
|
+
|
|
26
|
+
# Python
|
|
27
|
+
__pycache__/
|
|
28
|
+
*.pyc
|
|
29
|
+
*.pyo
|
|
30
|
+
*.pyd
|
|
31
|
+
.venv/
|
|
32
|
+
*.egg-info/
|
|
33
|
+
dist/
|
|
34
|
+
build/
|
|
35
|
+
|
|
36
|
+
# OS
|
|
37
|
+
.DS_Store
|
|
38
|
+
Thumbs.db
|
|
39
|
+
|
|
40
|
+
# Backups / snapshots — carry stale secrets + bloat
|
|
41
|
+
# (e.g. *.bak-pre-*, PRODUCTION_DOCS.bak-*, CLAUDE.md.bak-*, main.py.bak-*)
|
|
42
|
+
*.bak*
|
|
43
|
+
*.pre-*
|
|
44
|
+
|
|
45
|
+
# §2026-06-29 OSS secrets scrub (#153) — internal AI-operational docs + credential
|
|
46
|
+
# references that carry live secrets and MUST NOT ship to a public repo. These are
|
|
47
|
+
# operator/agent briefings, not platform source.
|
|
48
|
+
CLAUDE.md
|
|
49
|
+
CLAUDE_ADMIN.md
|
|
50
|
+
MY_APEX_PREDATOR_TOOLING.md
|
|
51
|
+
PRODUCTION_DOCS/CREDENTIALS_REFERENCE.md
|
|
52
|
+
# Sim/dev credential blobs
|
|
53
|
+
scripts/sim_credentials.json
|
|
54
|
+
**/*credentials*.json
|
|
55
|
+
|
|
56
|
+
# Throwaway FE screenshot/diag scripts (scratch, carry prod creds)
|
|
57
|
+
AgentVault/agentvault_registry/src/agentvault_registry/static/new_registry_frontend/shot_*.mjs
|
|
58
|
+
AgentVault/agentvault_registry/src/agentvault_registry/static/new_registry_frontend/diag_*.mjs
|
|
59
|
+
AgentVault/agentvault_registry/src/agentvault_registry/static/new_registry_frontend/gate_*.mjs
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: theprotocol-sdk
|
|
3
|
-
Version: 0.
|
|
4
|
-
Summary:
|
|
3
|
+
Version: 0.5.1
|
|
4
|
+
Summary: A2A v1.0 SDK — Build sovereign AI agents with DID identity. Protocol bridges for Google A2A, ANP, MCP. Works with Vertex AI, AWS AgentCore, LangGraph, CrewAI.
|
|
5
5
|
Project-URL: Homepage, https://theprotocol.cloud
|
|
6
6
|
Project-URL: Documentation, https://api.theprotocol.cloud/docs
|
|
7
7
|
Project-URL: Repository, https://github.com/theprotocol/theprotocol-sdk
|
|
8
8
|
Author-email: TheProtocol <sdk@theprotocol.cloud>
|
|
9
9
|
License: Apache-2.0
|
|
10
10
|
License-File: LICENSE
|
|
11
|
-
Keywords: a2a,agents,ai,mcp,
|
|
11
|
+
Keywords: a2a,a2a-protocol,agent-to-agent,agents,ai-agents,crewai,did,fastapi,federation,json-rpc,langchain,mcp,sovereign,theprotocol,vertex-ai
|
|
12
12
|
Classifier: Development Status :: 3 - Alpha
|
|
13
13
|
Classifier: Intended Audience :: Developers
|
|
14
14
|
Classifier: License :: OSI Approved :: Apache Software License
|
|
@@ -127,6 +127,72 @@ Any platform that speaks A2A v1.0 can call your agent directly:
|
|
|
127
127
|
|
|
128
128
|
No additional bridges needed. The SDK's JSON-RPC endpoint is the universal interface.
|
|
129
129
|
|
|
130
|
+
## Smart Send (one call, any destination) — new in 0.5.0
|
|
131
|
+
|
|
132
|
+
The registry auto-routes value movement: local / cross-registry 2PC / async / cross-frame FX (AVT↔BVT) — derived from the federated agent-card cache. You never pick an endpoint.
|
|
133
|
+
|
|
134
|
+
```python
|
|
135
|
+
from theprotocol.transfer import TransferClient
|
|
136
|
+
|
|
137
|
+
client = TransferClient("https://api.theprotocol.cloud")
|
|
138
|
+
plan = await client.preview(agent_jwt, "did:theprotocol:receiver", "2.5")
|
|
139
|
+
print(plan["method"], plan["currency_sent"], "->", plan["currency_received"])
|
|
140
|
+
|
|
141
|
+
result = await client.send(agent_jwt, "did:theprotocol:receiver", "2.5",
|
|
142
|
+
message="invoice 42", idempotency_key="inv-42")
|
|
143
|
+
print(result["status"], result["transfer_id"])
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
`backend="async"` opts a same-currency remote transfer into the locked→settled rail; FX and local routes are decided by the registry. Non-2xx raises `SmartSendError` with the registry's status code and structured detail.
|
|
147
|
+
|
|
148
|
+
## Changelog
|
|
149
|
+
|
|
150
|
+
**0.5.1** (2026-07-09)
|
|
151
|
+
- FastAPI ≥ 0.139 / Starlette 1.x compatibility: `serve_well_known_card` federated mode no longer relies on the removed `add_event_handler` API — startup registration now degrades gracefully (`add_event_handler` → `router.on_startup` → lifespan-context wrap). Verified 173/173 tests on both fastapi 0.111 (floor) and 0.139.
|
|
152
|
+
|
|
153
|
+
**0.5.0** (2026-06-12)
|
|
154
|
+
- NEW `theprotocol.transfer.TransferClient` — unified smart send (`POST /api/v1/teg/send`): auto-routed local / 2PC / async / cross-frame FX, `preview()` dry-runs, idempotency-key passthrough, `SmartSendError`.
|
|
155
|
+
- `PaymentVerifier(reject_reused_tokens=True)` — opt-in single-acceptance enforcement per process (P25-001 refinement). Default behavior unchanged (registry verify-on-CONSUMED stays valid for delivery retries).
|
|
156
|
+
- A2A v1.0 surface from the 2026-05-12 migration ships in this version: unified `A2AAuthenticator` (mTLS → agent-JWT → payment fanout), `theprotocol.auth.did_jwt` EdDSA helpers, v1.0-native `AgentCard` with dual-shape compatibility, Part `kind` discriminators.
|
|
157
|
+
|
|
158
|
+
## Payment & mTLS Authentication
|
|
159
|
+
|
|
160
|
+
Enforce payment on your agent's A2A endpoints and authenticate callers via SPIFFE mTLS:
|
|
161
|
+
|
|
162
|
+
```python
|
|
163
|
+
# Agent side — require payment tokens on all A2A calls
|
|
164
|
+
from theprotocol.payment import PaymentVerifier
|
|
165
|
+
from theprotocol.agent import create_a2a_router
|
|
166
|
+
from fastapi import Depends
|
|
167
|
+
|
|
168
|
+
verifier = PaymentVerifier(
|
|
169
|
+
registry_url="https://api.theprotocol.cloud",
|
|
170
|
+
agent_did="did:theprotocol:my-agent",
|
|
171
|
+
)
|
|
172
|
+
router = create_a2a_router(my_agent, dependencies=[Depends(verifier)])
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
```python
|
|
176
|
+
# Caller side — acquire payment token before calling an agent
|
|
177
|
+
from theprotocol.payment import PaymentClient
|
|
178
|
+
|
|
179
|
+
client = PaymentClient("https://api.theprotocol.cloud")
|
|
180
|
+
token = await client.get_token(agent_jwt, target_did="...", amount="0.5")
|
|
181
|
+
headers = {"X-Payment-Token": token}
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
```python
|
|
185
|
+
# mTLS — authenticate agent-to-agent calls with SPIFFE SVIDs
|
|
186
|
+
from theprotocol.payment import A2AAuthenticator, MtlsAgentClient
|
|
187
|
+
|
|
188
|
+
# Verify incoming mTLS + payment tokens (hybrid auth)
|
|
189
|
+
auth = A2AAuthenticator(registry_url="https://api.theprotocol.cloud")
|
|
190
|
+
|
|
191
|
+
# Make outbound mTLS calls using your agent's SVID
|
|
192
|
+
client = MtlsAgentClient(cert_dir="/certs")
|
|
193
|
+
result = await client.call(target_url, payload)
|
|
194
|
+
```
|
|
195
|
+
|
|
130
196
|
## MCP Tools
|
|
131
197
|
|
|
132
198
|
For governance, staking, transfers, and discovery — use [MCP tools](https://api.theprotocol.cloud/mcp/sse) (19 tools via Claude Desktop or any MCP client).
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
# theprotocol-sdk
|
|
2
|
+
|
|
3
|
+
**A2A v1.0** — Build and call AI agents on [TheProtocol](https://theprotocol.cloud). Native protocol bridges for Google A2A, ANP, and MCP.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install theprotocol-sdk # Client only (call agents)
|
|
9
|
+
pip install theprotocol-sdk[server] # + FastAPI router (build agents)
|
|
10
|
+
pip install theprotocol-sdk[anp] # + Ed25519 crypto for ANP DID:WBA
|
|
11
|
+
pip install theprotocol-sdk[all] # Everything
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Build an Agent
|
|
15
|
+
|
|
16
|
+
```python
|
|
17
|
+
from theprotocol.agent import BaseA2AAgent, create_a2a_router
|
|
18
|
+
from fastapi import FastAPI
|
|
19
|
+
|
|
20
|
+
class MyAgent(BaseA2AAgent):
|
|
21
|
+
async def handle_task_send(self, task_id, message):
|
|
22
|
+
return "task-1"
|
|
23
|
+
async def handle_task_get(self, task_id): ...
|
|
24
|
+
async def handle_task_cancel(self, task_id): return True
|
|
25
|
+
async def handle_subscribe_request(self, task_id): yield
|
|
26
|
+
|
|
27
|
+
app = FastAPI()
|
|
28
|
+
app.include_router(create_a2a_router(MyAgent()))
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Your agent speaks A2A v1.0 out of the box. It accepts both `message/send` (v1.0) and `tasks/send` (v0.3) for backward compatibility.
|
|
32
|
+
|
|
33
|
+
## Call a Remote Agent
|
|
34
|
+
|
|
35
|
+
```python
|
|
36
|
+
from theprotocol.client import A2AClient, KeyManager
|
|
37
|
+
from theprotocol.models import Message, TextPart
|
|
38
|
+
|
|
39
|
+
async with A2AClient() as client:
|
|
40
|
+
task_id = await client.initiate_task(agent_card, message, key_manager)
|
|
41
|
+
task = await client.get_task_status(agent_card, task_id, key_manager)
|
|
42
|
+
print(task.state) # TASK_STATE_COMPLETED
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
The client sends v1.0 wire format and accepts responses from both v1.0 and v0.3 agents.
|
|
46
|
+
|
|
47
|
+
## Dockerize
|
|
48
|
+
|
|
49
|
+
```dockerfile
|
|
50
|
+
FROM python:3.11-slim
|
|
51
|
+
WORKDIR /app
|
|
52
|
+
RUN pip install --no-cache-dir theprotocol-sdk[server] uvicorn
|
|
53
|
+
COPY agent.py .
|
|
54
|
+
EXPOSE 9500
|
|
55
|
+
CMD ["uvicorn", "agent:app", "--host", "0.0.0.0", "--port", "9500"]
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Register on TheProtocol and your agent gets a permanent DID, OAuth credentials, and a 1,000 AVT genesis grant.
|
|
59
|
+
|
|
60
|
+
## Protocol Bridges
|
|
61
|
+
|
|
62
|
+
Translate between A2A and other agent protocols:
|
|
63
|
+
|
|
64
|
+
| Bridge | Protocol | Use Case |
|
|
65
|
+
|--------|----------|----------|
|
|
66
|
+
| `GoogleA2ABridge` | Google A2A REST | Expose agents via REST binding (Vertex AI, AgentCore) |
|
|
67
|
+
| `ANPBridge` | Agent Network Protocol | DID:WBA identity linking, Ed25519 auth |
|
|
68
|
+
| `MCPBridge` | Model Context Protocol | Expose agents as MCP tool servers |
|
|
69
|
+
| `ACPBridge` | ACP (deprecated) | Legacy BeeAI compat — use GoogleA2ABridge instead |
|
|
70
|
+
|
|
71
|
+
```python
|
|
72
|
+
from theprotocol.bridges.google_a2a import GoogleA2ABridge
|
|
73
|
+
from theprotocol.bridges.anp import ANPBridge
|
|
74
|
+
from theprotocol.bridges.mcp import MCPBridge
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Platform Compatibility
|
|
78
|
+
|
|
79
|
+
Any platform that speaks A2A v1.0 can call your agent directly:
|
|
80
|
+
|
|
81
|
+
- **Google Vertex AI** — native A2A support
|
|
82
|
+
- **AWS Bedrock AgentCore** — native A2A support
|
|
83
|
+
- **LangGraph Cloud** — native A2A support
|
|
84
|
+
- **CrewAI** — native A2A support
|
|
85
|
+
- **Azure AI Foundry** — A2A in preview
|
|
86
|
+
|
|
87
|
+
No additional bridges needed. The SDK's JSON-RPC endpoint is the universal interface.
|
|
88
|
+
|
|
89
|
+
## Smart Send (one call, any destination) — new in 0.5.0
|
|
90
|
+
|
|
91
|
+
The registry auto-routes value movement: local / cross-registry 2PC / async / cross-frame FX (AVT↔BVT) — derived from the federated agent-card cache. You never pick an endpoint.
|
|
92
|
+
|
|
93
|
+
```python
|
|
94
|
+
from theprotocol.transfer import TransferClient
|
|
95
|
+
|
|
96
|
+
client = TransferClient("https://api.theprotocol.cloud")
|
|
97
|
+
plan = await client.preview(agent_jwt, "did:theprotocol:receiver", "2.5")
|
|
98
|
+
print(plan["method"], plan["currency_sent"], "->", plan["currency_received"])
|
|
99
|
+
|
|
100
|
+
result = await client.send(agent_jwt, "did:theprotocol:receiver", "2.5",
|
|
101
|
+
message="invoice 42", idempotency_key="inv-42")
|
|
102
|
+
print(result["status"], result["transfer_id"])
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
`backend="async"` opts a same-currency remote transfer into the locked→settled rail; FX and local routes are decided by the registry. Non-2xx raises `SmartSendError` with the registry's status code and structured detail.
|
|
106
|
+
|
|
107
|
+
## Changelog
|
|
108
|
+
|
|
109
|
+
**0.5.1** (2026-07-09)
|
|
110
|
+
- FastAPI ≥ 0.139 / Starlette 1.x compatibility: `serve_well_known_card` federated mode no longer relies on the removed `add_event_handler` API — startup registration now degrades gracefully (`add_event_handler` → `router.on_startup` → lifespan-context wrap). Verified 173/173 tests on both fastapi 0.111 (floor) and 0.139.
|
|
111
|
+
|
|
112
|
+
**0.5.0** (2026-06-12)
|
|
113
|
+
- NEW `theprotocol.transfer.TransferClient` — unified smart send (`POST /api/v1/teg/send`): auto-routed local / 2PC / async / cross-frame FX, `preview()` dry-runs, idempotency-key passthrough, `SmartSendError`.
|
|
114
|
+
- `PaymentVerifier(reject_reused_tokens=True)` — opt-in single-acceptance enforcement per process (P25-001 refinement). Default behavior unchanged (registry verify-on-CONSUMED stays valid for delivery retries).
|
|
115
|
+
- A2A v1.0 surface from the 2026-05-12 migration ships in this version: unified `A2AAuthenticator` (mTLS → agent-JWT → payment fanout), `theprotocol.auth.did_jwt` EdDSA helpers, v1.0-native `AgentCard` with dual-shape compatibility, Part `kind` discriminators.
|
|
116
|
+
|
|
117
|
+
## Payment & mTLS Authentication
|
|
118
|
+
|
|
119
|
+
Enforce payment on your agent's A2A endpoints and authenticate callers via SPIFFE mTLS:
|
|
120
|
+
|
|
121
|
+
```python
|
|
122
|
+
# Agent side — require payment tokens on all A2A calls
|
|
123
|
+
from theprotocol.payment import PaymentVerifier
|
|
124
|
+
from theprotocol.agent import create_a2a_router
|
|
125
|
+
from fastapi import Depends
|
|
126
|
+
|
|
127
|
+
verifier = PaymentVerifier(
|
|
128
|
+
registry_url="https://api.theprotocol.cloud",
|
|
129
|
+
agent_did="did:theprotocol:my-agent",
|
|
130
|
+
)
|
|
131
|
+
router = create_a2a_router(my_agent, dependencies=[Depends(verifier)])
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
```python
|
|
135
|
+
# Caller side — acquire payment token before calling an agent
|
|
136
|
+
from theprotocol.payment import PaymentClient
|
|
137
|
+
|
|
138
|
+
client = PaymentClient("https://api.theprotocol.cloud")
|
|
139
|
+
token = await client.get_token(agent_jwt, target_did="...", amount="0.5")
|
|
140
|
+
headers = {"X-Payment-Token": token}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
```python
|
|
144
|
+
# mTLS — authenticate agent-to-agent calls with SPIFFE SVIDs
|
|
145
|
+
from theprotocol.payment import A2AAuthenticator, MtlsAgentClient
|
|
146
|
+
|
|
147
|
+
# Verify incoming mTLS + payment tokens (hybrid auth)
|
|
148
|
+
auth = A2AAuthenticator(registry_url="https://api.theprotocol.cloud")
|
|
149
|
+
|
|
150
|
+
# Make outbound mTLS calls using your agent's SVID
|
|
151
|
+
client = MtlsAgentClient(cert_dir="/certs")
|
|
152
|
+
result = await client.call(target_url, payload)
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## MCP Tools
|
|
156
|
+
|
|
157
|
+
For governance, staking, transfers, and discovery — use [MCP tools](https://api.theprotocol.cloud/mcp/sse) (19 tools via Claude Desktop or any MCP client).
|
|
158
|
+
|
|
159
|
+
## License
|
|
160
|
+
|
|
161
|
+
Apache-2.0
|
|
@@ -4,15 +4,15 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "theprotocol-sdk"
|
|
7
|
-
version = "0.
|
|
8
|
-
description = "
|
|
7
|
+
version = "0.5.1"
|
|
8
|
+
description = "A2A v1.0 SDK — Build sovereign AI agents with DID identity. Protocol bridges for Google A2A, ANP, MCP. Works with Vertex AI, AWS AgentCore, LangGraph, CrewAI."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = {text = "Apache-2.0"}
|
|
11
11
|
requires-python = ">=3.10"
|
|
12
12
|
authors = [
|
|
13
13
|
{name = "TheProtocol", email = "sdk@theprotocol.cloud"},
|
|
14
14
|
]
|
|
15
|
-
keywords = ["agents", "a2a", "protocol", "ai", "mcp", "theprotocol"]
|
|
15
|
+
keywords = ["agents", "a2a", "a2a-protocol", "agent-to-agent", "ai-agents", "mcp", "did", "sovereign", "federation", "vertex-ai", "langchain", "crewai", "fastapi", "json-rpc", "theprotocol"]
|
|
16
16
|
classifiers = [
|
|
17
17
|
"Development Status :: 3 - Alpha",
|
|
18
18
|
"Intended Audience :: Developers",
|
|
@@ -5,6 +5,7 @@ Usage:
|
|
|
5
5
|
from theprotocol.models import Message, Task, AgentCard
|
|
6
6
|
from theprotocol.agent import BaseA2AAgent, create_a2a_router
|
|
7
7
|
from theprotocol.client import A2AClient, KeyManager
|
|
8
|
+
from theprotocol.transfer import TransferClient # smart send (auto-routed value movement)
|
|
8
9
|
"""
|
|
9
10
|
|
|
10
|
-
__version__ = "0.1
|
|
11
|
+
__version__ = "0.5.1"
|
|
@@ -15,3 +15,10 @@ try:
|
|
|
15
15
|
__all__.append("create_a2a_router")
|
|
16
16
|
except ImportError:
|
|
17
17
|
pass
|
|
18
|
+
|
|
19
|
+
# A2A v1.0 §4.1.2 well-known discovery — also requires fastapi.
|
|
20
|
+
try:
|
|
21
|
+
from .well_known import serve_well_known_card
|
|
22
|
+
__all__.append("serve_well_known_card")
|
|
23
|
+
except ImportError:
|
|
24
|
+
pass
|
|
@@ -1,9 +1,21 @@
|
|
|
1
1
|
"""FastAPI router factory for A2A agents.
|
|
2
|
-
Accepts both A2A v1.0 and v0.3 wire formats. Responds in v1.0.
|
|
2
|
+
Accepts both A2A v1.0 and v0.3 wire formats. Responds in v1.0.
|
|
3
|
+
|
|
4
|
+
Payment enforcement (f045):
|
|
5
|
+
When REGISTRY_URL env var is set, a PaymentVerifier is automatically
|
|
6
|
+
injected into the router dependencies. This means every SDK-based agent
|
|
7
|
+
gets payment verification by default when deployed to the network.
|
|
8
|
+
|
|
9
|
+
- REGISTRY_URL set → payment verification ON (required=True by default)
|
|
10
|
+
- PAYMENT_REQUIRED=false → verification runs but doesn't reject (log only)
|
|
11
|
+
- REGISTRY_URL not set → no verification (dev/testing mode)
|
|
12
|
+
- Price=0 agents: caller still gets a 0 AVT token (proves identity), no funds move
|
|
13
|
+
"""
|
|
3
14
|
|
|
4
15
|
import json
|
|
5
16
|
import inspect
|
|
6
17
|
import logging
|
|
18
|
+
import os
|
|
7
19
|
from typing import Any, Dict, Optional, Union, List, Callable, AsyncGenerator
|
|
8
20
|
|
|
9
21
|
import pydantic
|
|
@@ -87,10 +99,19 @@ def create_a2a_router(
|
|
|
87
99
|
tags: Optional[List[str]] = None,
|
|
88
100
|
task_store: Optional[BaseTaskStore] = None,
|
|
89
101
|
dependencies: Optional[list] = None,
|
|
102
|
+
payment_required: Optional[bool] = None,
|
|
90
103
|
) -> APIRouter:
|
|
91
104
|
"""
|
|
92
105
|
Create a FastAPI router that exposes an A2A agent via JSON-RPC 2.0.
|
|
93
106
|
|
|
107
|
+
Payment enforcement is automatic when REGISTRY_URL env var is set:
|
|
108
|
+
- REGISTRY_URL → PaymentVerifier injected (agents must receive payment tokens)
|
|
109
|
+
- AGENT_DID → identifies this agent for token target validation
|
|
110
|
+
- PAYMENT_REQUIRED=false → verify but don't reject (log only mode)
|
|
111
|
+
- TRUSTED_REGISTRIES → comma-separated URLs for cross-registry tokens
|
|
112
|
+
|
|
113
|
+
Override with payment_required=False to explicitly disable.
|
|
114
|
+
|
|
94
115
|
Example::
|
|
95
116
|
|
|
96
117
|
from theprotocol.agent import BaseA2AAgent, create_a2a_router
|
|
@@ -102,7 +123,11 @@ def create_a2a_router(
|
|
|
102
123
|
...
|
|
103
124
|
|
|
104
125
|
app = FastAPI()
|
|
126
|
+
# Payment verification is automatic if REGISTRY_URL is set
|
|
105
127
|
app.include_router(create_a2a_router(MyAgent()))
|
|
128
|
+
|
|
129
|
+
# Explicitly disable payment verification
|
|
130
|
+
app.include_router(create_a2a_router(MyAgent(), payment_required=False))
|
|
106
131
|
"""
|
|
107
132
|
if tags is None:
|
|
108
133
|
tags = ["A2A Protocol"]
|
|
@@ -110,7 +135,60 @@ def create_a2a_router(
|
|
|
110
135
|
task_store = InMemoryTaskStore()
|
|
111
136
|
store = task_store
|
|
112
137
|
|
|
113
|
-
|
|
138
|
+
# Build dependencies list
|
|
139
|
+
all_deps = list(dependencies or [])
|
|
140
|
+
|
|
141
|
+
# Auto-inject PaymentVerifier if REGISTRY_URL is configured
|
|
142
|
+
_registry_url = os.environ.get("REGISTRY_URL", "")
|
|
143
|
+
_agent_did = os.environ.get("AGENT_DID", "")
|
|
144
|
+
_pay_required = payment_required
|
|
145
|
+
if _pay_required is None:
|
|
146
|
+
_pay_required = os.environ.get("PAYMENT_REQUIRED", "true").lower() != "false"
|
|
147
|
+
|
|
148
|
+
if _registry_url and _agent_did and _pay_required is not False:
|
|
149
|
+
try:
|
|
150
|
+
from ..payment.verifier import PaymentVerifier
|
|
151
|
+
_trusted_raw = os.environ.get("TRUSTED_REGISTRIES", "")
|
|
152
|
+
_trusted = [u.strip() for u in _trusted_raw.split(",") if u.strip()] if _trusted_raw else None
|
|
153
|
+
_verifier = PaymentVerifier(
|
|
154
|
+
registry_url=_registry_url,
|
|
155
|
+
agent_did=_agent_did,
|
|
156
|
+
required=bool(_pay_required),
|
|
157
|
+
trusted_registries=_trusted,
|
|
158
|
+
)
|
|
159
|
+
all_deps.append(Depends(_verifier))
|
|
160
|
+
logger.info(
|
|
161
|
+
f"[A2A Router] PaymentVerifier auto-injected "
|
|
162
|
+
f"(registry={_registry_url}, did={_agent_did[:30]}, required={_pay_required})"
|
|
163
|
+
)
|
|
164
|
+
except ImportError:
|
|
165
|
+
logger.warning("[A2A Router] PaymentVerifier import failed — payment module not installed")
|
|
166
|
+
except Exception as e:
|
|
167
|
+
logger.warning(f"[A2A Router] PaymentVerifier setup failed: {e}")
|
|
168
|
+
elif _registry_url and not _agent_did:
|
|
169
|
+
logger.warning("[A2A Router] REGISTRY_URL set but AGENT_DID missing — payment verification disabled")
|
|
170
|
+
|
|
171
|
+
# f052: Auto-inject A2AAuthenticator if SPIRE socket is available (IRONHAND)
|
|
172
|
+
_spiffe_socket = os.environ.get("SPIFFE_ENDPOINT_SOCKET", "")
|
|
173
|
+
_enable_mtls = os.environ.get("ENABLE_MTLS", "").lower() == "true"
|
|
174
|
+
if _spiffe_socket and _enable_mtls:
|
|
175
|
+
try:
|
|
176
|
+
from ..payment.authenticator import A2AAuthenticator
|
|
177
|
+
_authenticator = A2AAuthenticator(
|
|
178
|
+
payment_verifier=_verifier if "_verifier" in dir() else None,
|
|
179
|
+
required=False,
|
|
180
|
+
)
|
|
181
|
+
all_deps.append(Depends(_authenticator))
|
|
182
|
+
logger.info(
|
|
183
|
+
f"[A2A Router] A2AAuthenticator auto-injected (mTLS enabled, "
|
|
184
|
+
f"socket={_spiffe_socket[:40]})"
|
|
185
|
+
)
|
|
186
|
+
except ImportError:
|
|
187
|
+
logger.warning("[A2A Router] A2AAuthenticator import failed — mTLS module not installed")
|
|
188
|
+
except Exception as e:
|
|
189
|
+
logger.warning(f"[A2A Router] A2AAuthenticator setup failed: {e}")
|
|
190
|
+
|
|
191
|
+
router = APIRouter(prefix=prefix, tags=tags, dependencies=all_deps)
|
|
114
192
|
|
|
115
193
|
# Discover @a2a_method decorated handlers
|
|
116
194
|
decorated: Dict[str, Callable] = {}
|
|
@@ -118,6 +196,9 @@ def create_a2a_router(
|
|
|
118
196
|
if hasattr(method, '_a2a_method_name'):
|
|
119
197
|
decorated[getattr(method, '_a2a_method_name')] = method
|
|
120
198
|
|
|
199
|
+
# Register on both the bare prefix and the trailing slash so `POST /a2a`
|
|
200
|
+
# works without a 307 redirect (which drops the POST body for many clients).
|
|
201
|
+
@router.post("", summary="A2A JSON-RPC Endpoint", include_in_schema=False)
|
|
121
202
|
@router.post("/", summary="A2A JSON-RPC Endpoint")
|
|
122
203
|
async def handle_request(request: Request) -> Response:
|
|
123
204
|
req_id = None
|
|
@@ -101,7 +101,13 @@ class BaseTaskStore(ABC):
|
|
|
101
101
|
|
|
102
102
|
|
|
103
103
|
class InMemoryTaskStore(BaseTaskStore):
|
|
104
|
-
"""In-memory task store. Suitable for development and single-process agents.
|
|
104
|
+
"""In-memory task store. Suitable for development and single-process agents.
|
|
105
|
+
|
|
106
|
+
Bounded: keeps at most ``_MAX_TASKS`` tasks, evicting oldest-first (FIFO) on
|
|
107
|
+
insert so long-running agents don't leak memory over time.
|
|
108
|
+
"""
|
|
109
|
+
|
|
110
|
+
_MAX_TASKS = 2000
|
|
105
111
|
|
|
106
112
|
def __init__(self):
|
|
107
113
|
self._tasks: Dict[str, TaskContext] = {}
|
|
@@ -119,6 +125,13 @@ class InMemoryTaskStore(BaseTaskStore):
|
|
|
119
125
|
ctx = TaskContext(task_id=task_id, current_state=TaskState.SUBMITTED)
|
|
120
126
|
self._tasks[task_id] = ctx
|
|
121
127
|
self._listeners[task_id] = []
|
|
128
|
+
# Bounded GC: evict oldest entries (FIFO) to cap memory.
|
|
129
|
+
while len(self._tasks) > self._MAX_TASKS:
|
|
130
|
+
old_id = next(iter(self._tasks))
|
|
131
|
+
if old_id == task_id:
|
|
132
|
+
break
|
|
133
|
+
self._tasks.pop(old_id, None)
|
|
134
|
+
self._listeners.pop(old_id, None)
|
|
122
135
|
await self.notify_status_update(task_id, TaskState.SUBMITTED)
|
|
123
136
|
return ctx
|
|
124
137
|
|