traia-iatp 0.1.2__py3-none-any.whl → 0.1.67__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.
Files changed (95) hide show
  1. traia_iatp/__init__.py +105 -8
  2. traia_iatp/cli/main.py +85 -1
  3. traia_iatp/client/__init__.py +28 -3
  4. traia_iatp/client/crewai_a2a_tools.py +32 -12
  5. traia_iatp/client/d402_a2a_client.py +348 -0
  6. traia_iatp/contracts/__init__.py +11 -0
  7. traia_iatp/contracts/data/abis/contract-abis-localhost.json +4091 -0
  8. traia_iatp/contracts/data/abis/contract-abis-sepolia.json +4890 -0
  9. traia_iatp/contracts/data/addresses/contract-addresses.json +17 -0
  10. traia_iatp/contracts/data/addresses/contract-proxies.json +12 -0
  11. traia_iatp/contracts/iatp_contracts_config.py +263 -0
  12. traia_iatp/contracts/wallet_creator.py +369 -0
  13. traia_iatp/core/models.py +17 -3
  14. traia_iatp/d402/MIDDLEWARE_ARCHITECTURE.md +205 -0
  15. traia_iatp/d402/PRICE_BUILDER_USAGE.md +249 -0
  16. traia_iatp/d402/README.md +489 -0
  17. traia_iatp/d402/__init__.py +54 -0
  18. traia_iatp/d402/asgi_wrapper.py +469 -0
  19. traia_iatp/d402/chains.py +102 -0
  20. traia_iatp/d402/client.py +150 -0
  21. traia_iatp/d402/clients/__init__.py +7 -0
  22. traia_iatp/d402/clients/base.py +218 -0
  23. traia_iatp/d402/clients/httpx.py +266 -0
  24. traia_iatp/d402/common.py +114 -0
  25. traia_iatp/d402/encoding.py +28 -0
  26. traia_iatp/d402/examples/client_example.py +197 -0
  27. traia_iatp/d402/examples/server_example.py +171 -0
  28. traia_iatp/d402/facilitator.py +481 -0
  29. traia_iatp/d402/mcp_middleware.py +296 -0
  30. traia_iatp/d402/models.py +116 -0
  31. traia_iatp/d402/networks.py +98 -0
  32. traia_iatp/d402/path.py +43 -0
  33. traia_iatp/d402/payment_introspection.py +126 -0
  34. traia_iatp/d402/payment_signing.py +183 -0
  35. traia_iatp/d402/price_builder.py +164 -0
  36. traia_iatp/d402/servers/__init__.py +61 -0
  37. traia_iatp/d402/servers/base.py +139 -0
  38. traia_iatp/d402/servers/example_general_server.py +140 -0
  39. traia_iatp/d402/servers/fastapi.py +253 -0
  40. traia_iatp/d402/servers/mcp.py +304 -0
  41. traia_iatp/d402/servers/starlette.py +878 -0
  42. traia_iatp/d402/starlette_middleware.py +529 -0
  43. traia_iatp/d402/types.py +300 -0
  44. traia_iatp/mcp/D402_MCP_ADAPTER_FLOW.md +357 -0
  45. traia_iatp/mcp/__init__.py +3 -0
  46. traia_iatp/mcp/d402_mcp_tool_adapter.py +526 -0
  47. traia_iatp/mcp/mcp_agent_template.py +78 -13
  48. traia_iatp/mcp/templates/Dockerfile.j2 +27 -4
  49. traia_iatp/mcp/templates/README.md.j2 +104 -8
  50. traia_iatp/mcp/templates/cursor-rules.md.j2 +194 -0
  51. traia_iatp/mcp/templates/deployment_params.json.j2 +1 -2
  52. traia_iatp/mcp/templates/docker-compose.yml.j2 +13 -3
  53. traia_iatp/mcp/templates/env.example.j2 +60 -0
  54. traia_iatp/mcp/templates/mcp_health_check.py.j2 +2 -2
  55. traia_iatp/mcp/templates/pyproject.toml.j2 +11 -5
  56. traia_iatp/mcp/templates/pyrightconfig.json.j2 +22 -0
  57. traia_iatp/mcp/templates/run_local_docker.sh.j2 +320 -10
  58. traia_iatp/mcp/templates/server.py.j2 +174 -197
  59. traia_iatp/mcp/traia_mcp_adapter.py +182 -20
  60. traia_iatp/registry/__init__.py +47 -12
  61. traia_iatp/registry/atlas_search_indexes.json +108 -54
  62. traia_iatp/registry/iatp_search_api.py +169 -39
  63. traia_iatp/registry/mongodb_registry.py +241 -69
  64. traia_iatp/registry/readmes/EMBEDDINGS_SETUP.md +1 -1
  65. traia_iatp/registry/readmes/IATP_SEARCH_API_GUIDE.md +8 -8
  66. traia_iatp/registry/readmes/MONGODB_X509_AUTH.md +1 -1
  67. traia_iatp/registry/readmes/README.md +3 -3
  68. traia_iatp/registry/readmes/REFACTORING_SUMMARY.md +6 -6
  69. traia_iatp/scripts/__init__.py +2 -0
  70. traia_iatp/scripts/create_wallet.py +244 -0
  71. traia_iatp/server/a2a_server.py +22 -7
  72. traia_iatp/server/iatp_server_template_generator.py +23 -0
  73. traia_iatp/server/templates/.dockerignore.j2 +48 -0
  74. traia_iatp/server/templates/Dockerfile.j2 +23 -1
  75. traia_iatp/server/templates/README.md +2 -2
  76. traia_iatp/server/templates/README.md.j2 +5 -5
  77. traia_iatp/server/templates/__main__.py.j2 +374 -66
  78. traia_iatp/server/templates/agent.py.j2 +12 -11
  79. traia_iatp/server/templates/agent_config.json.j2 +3 -3
  80. traia_iatp/server/templates/agent_executor.py.j2 +45 -27
  81. traia_iatp/server/templates/env.example.j2 +32 -4
  82. traia_iatp/server/templates/gitignore.j2 +7 -0
  83. traia_iatp/server/templates/pyproject.toml.j2 +13 -12
  84. traia_iatp/server/templates/run_local_docker.sh.j2 +143 -11
  85. traia_iatp/server/templates/server.py.j2 +197 -10
  86. traia_iatp/special_agencies/registry_search_agency.py +1 -1
  87. traia_iatp/utils/iatp_utils.py +6 -6
  88. traia_iatp-0.1.67.dist-info/METADATA +320 -0
  89. traia_iatp-0.1.67.dist-info/RECORD +117 -0
  90. traia_iatp-0.1.2.dist-info/METADATA +0 -414
  91. traia_iatp-0.1.2.dist-info/RECORD +0 -72
  92. {traia_iatp-0.1.2.dist-info → traia_iatp-0.1.67.dist-info}/WHEEL +0 -0
  93. {traia_iatp-0.1.2.dist-info → traia_iatp-0.1.67.dist-info}/entry_points.txt +0 -0
  94. {traia_iatp-0.1.2.dist-info → traia_iatp-0.1.67.dist-info}/licenses/LICENSE +0 -0
  95. {traia_iatp-0.1.2.dist-info → traia_iatp-0.1.67.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,348 @@
1
+ """D402-enabled A2A client for IATP with payment support.
2
+
3
+ This client automatically handles D402 payments when communicating with
4
+ utility agents that require payment. It supports:
5
+ - Automatic payment creation for 402 responses
6
+ - Payment header injection
7
+ - Multiple payment schemes (exact, facilitator-verified)
8
+ - Network and token selection
9
+ """
10
+
11
+ import asyncio
12
+ import logging
13
+ import json
14
+ import uuid
15
+ from typing import Optional, Dict, Any
16
+ from a2a.client import A2AClient, A2ACardResolver
17
+ from a2a.types import Message, TextPart, TaskState, SendMessageRequest, MessageSendParams
18
+ import httpx
19
+
20
+ from ..d402.client import D402IATPClient, create_iatp_payment_client
21
+ from ..d402.models import D402PaymentInfo
22
+ from ..d402.types import d402PaymentRequiredResponse, PaymentRequirements
23
+
24
+ logger = logging.getLogger(__name__)
25
+
26
+
27
+ class D402A2AClient:
28
+ """A2A client with d402 payment support for IATP.
29
+
30
+ This client automatically handles d402 payments when communicating with
31
+ utility agents that require payment.
32
+ """
33
+
34
+ def __init__(
35
+ self,
36
+ agent_endpoint: str,
37
+ agent_card: Any,
38
+ httpx_client: Any,
39
+ payment_client: Optional[D402IATPClient] = None,
40
+ max_payment_usd: Optional[float] = None
41
+ ):
42
+ """Private init - use create() classmethod instead."""
43
+ self.agent_endpoint = agent_endpoint
44
+ self.agent_card = agent_card
45
+ self._httpx_client = httpx_client
46
+ self.payment_client = payment_client
47
+ self.max_payment_usd = max_payment_usd
48
+
49
+ # Extract d402 payment information if available
50
+ self.d402_info = self._extract_d402_info()
51
+
52
+ # Initialize A2A client (a2a-sdk 0.3.x API)
53
+ self.a2a_client = A2AClient(
54
+ httpx_client=self._httpx_client,
55
+ agent_card=self.agent_card,
56
+ url=agent_endpoint
57
+ )
58
+
59
+ @classmethod
60
+ async def create(
61
+ cls,
62
+ agent_endpoint: str,
63
+ payment_client: Optional[D402IATPClient] = None,
64
+ max_payment_usd: Optional[float] = None
65
+ ):
66
+ """Create a D402A2AClient instance (async factory method).
67
+
68
+ Args:
69
+ agent_endpoint: URL of the utility agent
70
+ payment_client: Optional pre-configured D402IATPClient
71
+ max_payment_usd: Optional maximum payment amount per request
72
+
73
+ Returns:
74
+ Initialized D402A2AClient instance
75
+ """
76
+ import httpx
77
+
78
+ # Create HTTP client and resolve agent card
79
+ httpx_client = httpx.AsyncClient(timeout=30.0)
80
+ card_resolver = A2ACardResolver(
81
+ httpx_client=httpx_client,
82
+ base_url=agent_endpoint,
83
+ agent_card_path='/.well-known/agent-card.json'
84
+ )
85
+ agent_card = await card_resolver.get_agent_card()
86
+
87
+ # Create instance
88
+ return cls(
89
+ agent_endpoint=agent_endpoint,
90
+ agent_card=agent_card,
91
+ httpx_client=httpx_client,
92
+ payment_client=payment_client,
93
+ max_payment_usd=max_payment_usd
94
+ )
95
+
96
+ async def aclose(self):
97
+ """Close the HTTP client."""
98
+ if hasattr(self, '_httpx_client'):
99
+ await self._httpx_client.aclose()
100
+
101
+ async def __aenter__(self):
102
+ """Async context manager entry."""
103
+ return self
104
+
105
+ async def __aexit__(self, exc_type, exc_val, exc_tb):
106
+ """Async context manager exit."""
107
+ await self.aclose()
108
+ return False
109
+
110
+ def _extract_d402_info(self) -> Optional[D402PaymentInfo]:
111
+ """Extract d402 payment information from agent card."""
112
+ # In a2a-sdk 0.3.x, AgentCard doesn't have metadata field
113
+ # D402 info will be discovered via 402 responses instead
114
+ # So we always assume payment might be required and handle 402 dynamically
115
+ return None
116
+
117
+ async def send_message_with_payment(
118
+ self,
119
+ message: str,
120
+ skill_id: Optional[str] = None
121
+ ) -> str:
122
+ """Send a message to the agent, automatically handling d402 payment if required.
123
+
124
+ Args:
125
+ message: The message to send to the agent
126
+ skill_id: Optional specific skill to invoke
127
+
128
+ Returns:
129
+ Agent's response text
130
+
131
+ Raises:
132
+ ValueError: If payment is required but no payment client is configured
133
+ RuntimeError: If task execution fails
134
+ """
135
+ # Prepare the message (a2a-sdk 0.3.x requires message_id)
136
+ import uuid
137
+ msg = Message(
138
+ message_id=str(uuid.uuid4()),
139
+ role="user",
140
+ parts=[TextPart(text=message)]
141
+ )
142
+
143
+ # Prepare headers (will add payment header if needed)
144
+ headers = {}
145
+
146
+ # Try to send the request
147
+ async with httpx.AsyncClient(timeout=300.0) as http_client:
148
+ # First attempt without payment to see if it's required
149
+ try:
150
+ # Use A2A client's send_task method
151
+ task = await self.a2a_client.send_task(
152
+ id=str(asyncio.get_event_loop().time()),
153
+ message=msg
154
+ )
155
+
156
+ # Extract response
157
+ return self._extract_response(task)
158
+
159
+ except httpx.HTTPStatusError as e:
160
+ if e.response.status_code == 402:
161
+ # Payment required - handle d402 flow
162
+ return await self._handle_payment_required(
163
+ http_client, e.response, message, skill_id
164
+ )
165
+ else:
166
+ raise
167
+
168
+ async def _handle_payment_required(
169
+ self,
170
+ http_client: httpx.AsyncClient,
171
+ response: httpx.Response,
172
+ message: str,
173
+ skill_id: Optional[str]
174
+ ) -> str:
175
+ """Handle 402 Payment Required response.
176
+
177
+ Args:
178
+ http_client: HTTP client for making requests
179
+ response: 402 response with payment requirements
180
+ message: Original message to send
181
+ skill_id: Optional skill ID
182
+
183
+ Returns:
184
+ Agent's response after successful payment
185
+
186
+ Raises:
187
+ ValueError: If payment client not configured or requirements not met
188
+ """
189
+ if not self.payment_client:
190
+ raise ValueError(
191
+ "Payment is required but no payment client configured. "
192
+ "Please provide a payment_client when initializing D402A2AClient."
193
+ )
194
+
195
+ # Parse payment requirements
196
+ try:
197
+ payment_response = d402PaymentRequiredResponse(**response.json())
198
+ except Exception as e:
199
+ raise ValueError(f"Invalid payment requirements: {e}")
200
+
201
+ # Select payment requirements
202
+ try:
203
+ selected_requirements = self.payment_client.select_payment_requirements(
204
+ accepts=payment_response.accepts,
205
+ network_filter=None, # Accept any network
206
+ scheme_filter="exact" # Only support exact scheme
207
+ )
208
+ except Exception as e:
209
+ raise ValueError(f"Cannot satisfy payment requirements: {e}")
210
+
211
+ # Check if amount is within maximum
212
+ if self.max_payment_usd:
213
+ # Convert to USD (assuming USDC with 6 decimals)
214
+ amount_usd = int(selected_requirements.max_amount_required) / 1_000_000
215
+ if amount_usd > self.max_payment_usd:
216
+ raise ValueError(
217
+ f"Payment amount ${amount_usd:.2f} exceeds maximum ${self.max_payment_usd:.2f}"
218
+ )
219
+
220
+ # Create payment header
221
+ payment_header = self.payment_client.create_payment_header(
222
+ payment_requirements=selected_requirements,
223
+ d402_version=1
224
+ )
225
+
226
+ # Retry request with payment header
227
+ headers = {"X-PAYMENT": payment_header}
228
+
229
+ # Make the A2A request with payment
230
+ msg = Message(
231
+ role="user",
232
+ parts=[TextPart(text=message)]
233
+ )
234
+
235
+ # Send task with payment (we need to make raw HTTP request with headers)
236
+ # Since A2AClient doesn't support custom headers, we'll make the request directly
237
+ request_data = {
238
+ "jsonrpc": "2.0",
239
+ "id": str(asyncio.get_event_loop().time()),
240
+ "method": "message/send",
241
+ "params": {
242
+ "message": msg.model_dump()
243
+ }
244
+ }
245
+
246
+ response = await http_client.post(
247
+ self.agent_endpoint,
248
+ json=request_data,
249
+ headers={"Content-Type": "application/json", **headers}
250
+ )
251
+
252
+ if response.status_code == 402:
253
+ raise RuntimeError(f"Payment failed: {response.json().get('error', 'Unknown error')}")
254
+
255
+ response.raise_for_status()
256
+
257
+ # Parse JSON-RPC response
258
+ result = response.json()
259
+ if "error" in result:
260
+ raise RuntimeError(f"Agent error: {result['error']}")
261
+
262
+ # Extract the response text
263
+ task_result = result.get("result", {})
264
+ if isinstance(task_result, dict):
265
+ messages = task_result.get("messages", [])
266
+ for msg in messages:
267
+ if msg.get("role") == "agent":
268
+ parts = msg.get("parts", [])
269
+ return " ".join(
270
+ part.get("text", "")
271
+ for part in parts
272
+ if part.get("type") == "text"
273
+ )
274
+
275
+ return str(task_result)
276
+
277
+ def _extract_response(self, task) -> str:
278
+ """Extract text response from task result."""
279
+ if task.status.state == TaskState.COMPLETED:
280
+ # Look for agent's response in messages
281
+ for msg in task.messages:
282
+ if msg.role == "agent":
283
+ response_text = ""
284
+ for part in msg.parts:
285
+ if hasattr(part, 'text'):
286
+ response_text += part.text
287
+ return response_text
288
+
289
+ # If no agent message, check artifacts
290
+ if task.artifacts:
291
+ response_text = ""
292
+ for artifact in task.artifacts:
293
+ for part in artifact.parts:
294
+ if hasattr(part, 'text'):
295
+ response_text += part.text
296
+ return response_text
297
+
298
+ return "Task completed but no response found"
299
+ else:
300
+ raise RuntimeError(f"Task failed with state: {task.status.state}")
301
+
302
+
303
+ async def create_d402_a2a_client(
304
+ agent_endpoint: str,
305
+ payment_private_key: Optional[str] = None,
306
+ max_payment_usd: Optional[float] = 10.0,
307
+ agent_contract_address: Optional[str] = None
308
+ ) -> D402A2AClient:
309
+ """Convenience function to create an d402-enabled A2A client (async).
310
+
311
+ Args:
312
+ agent_endpoint: URL of the utility agent
313
+ payment_private_key: Optional private key for payments (hex encoded)
314
+ max_payment_usd: Maximum payment per request in USD
315
+ agent_contract_address: Optional client agent contract address
316
+
317
+ Returns:
318
+ Configured D402A2AClient
319
+
320
+ Example:
321
+ # Create client with payment support
322
+ client = await create_d402_a2a_client(
323
+ agent_endpoint="https://agent.example.com",
324
+ payment_private_key="0x...",
325
+ max_payment_usd=5.0
326
+ )
327
+
328
+ # Send message (automatically handles payment if required)
329
+ response = await client.send_message_with_payment(
330
+ "Analyze sentiment of: 'Stock prices are rising'"
331
+ )
332
+ print(response)
333
+ """
334
+ payment_client = None
335
+ if payment_private_key:
336
+ payment_client = create_iatp_payment_client(
337
+ private_key=payment_private_key,
338
+ max_value_usd=max_payment_usd,
339
+ agent_contract_address=agent_contract_address
340
+ )
341
+
342
+ # Use async factory method for a2a-sdk 0.3.x compatibility
343
+ return await D402A2AClient.create(
344
+ agent_endpoint=agent_endpoint,
345
+ payment_client=payment_client,
346
+ max_payment_usd=max_payment_usd
347
+ )
348
+
@@ -0,0 +1,11 @@
1
+ """IATP Contracts utilities."""
2
+
3
+ from .wallet_creator import get_contract_config
4
+ from .iatp_contracts_config import get_contract_address, get_contract_abi, get_rpc_url
5
+
6
+ __all__ = [
7
+ "get_contract_config",
8
+ "get_contract_address",
9
+ "get_contract_abi",
10
+ "get_rpc_url"
11
+ ]