traia-iatp 0.1.29__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.
Potentially problematic release.
This version of traia-iatp might be problematic. Click here for more details.
- traia_iatp/README.md +368 -0
- traia_iatp/__init__.py +54 -0
- traia_iatp/cli/__init__.py +5 -0
- traia_iatp/cli/main.py +483 -0
- traia_iatp/client/__init__.py +10 -0
- traia_iatp/client/a2a_client.py +274 -0
- traia_iatp/client/crewai_a2a_tools.py +335 -0
- traia_iatp/client/d402_a2a_client.py +293 -0
- traia_iatp/client/grpc_a2a_tools.py +349 -0
- traia_iatp/client/root_path_a2a_client.py +1 -0
- traia_iatp/contracts/__init__.py +12 -0
- traia_iatp/contracts/iatp_contracts_config.py +263 -0
- traia_iatp/contracts/wallet_creator.py +255 -0
- traia_iatp/core/__init__.py +43 -0
- traia_iatp/core/models.py +172 -0
- traia_iatp/d402/__init__.py +55 -0
- traia_iatp/d402/chains.py +102 -0
- traia_iatp/d402/client.py +150 -0
- traia_iatp/d402/clients/__init__.py +7 -0
- traia_iatp/d402/clients/base.py +218 -0
- traia_iatp/d402/clients/httpx.py +219 -0
- traia_iatp/d402/common.py +114 -0
- traia_iatp/d402/encoding.py +28 -0
- traia_iatp/d402/examples/client_example.py +197 -0
- traia_iatp/d402/examples/server_example.py +171 -0
- traia_iatp/d402/facilitator.py +453 -0
- traia_iatp/d402/fastapi_middleware/__init__.py +6 -0
- traia_iatp/d402/fastapi_middleware/middleware.py +225 -0
- traia_iatp/d402/fastmcp_middleware.py +147 -0
- traia_iatp/d402/mcp_middleware.py +434 -0
- traia_iatp/d402/middleware.py +193 -0
- traia_iatp/d402/models.py +116 -0
- traia_iatp/d402/networks.py +98 -0
- traia_iatp/d402/path.py +43 -0
- traia_iatp/d402/payment_introspection.py +104 -0
- traia_iatp/d402/payment_signing.py +178 -0
- traia_iatp/d402/paywall.py +119 -0
- traia_iatp/d402/starlette_middleware.py +326 -0
- traia_iatp/d402/template.py +1 -0
- traia_iatp/d402/types.py +300 -0
- traia_iatp/mcp/__init__.py +18 -0
- traia_iatp/mcp/client.py +201 -0
- traia_iatp/mcp/d402_mcp_tool_adapter.py +361 -0
- traia_iatp/mcp/mcp_agent_template.py +481 -0
- traia_iatp/mcp/templates/Dockerfile.j2 +80 -0
- traia_iatp/mcp/templates/README.md.j2 +310 -0
- traia_iatp/mcp/templates/cursor-rules.md.j2 +520 -0
- traia_iatp/mcp/templates/deployment_params.json.j2 +20 -0
- traia_iatp/mcp/templates/docker-compose.yml.j2 +32 -0
- traia_iatp/mcp/templates/dockerignore.j2 +47 -0
- traia_iatp/mcp/templates/env.example.j2 +57 -0
- traia_iatp/mcp/templates/gitignore.j2 +77 -0
- traia_iatp/mcp/templates/mcp_health_check.py.j2 +150 -0
- traia_iatp/mcp/templates/pyproject.toml.j2 +32 -0
- traia_iatp/mcp/templates/pyrightconfig.json.j2 +22 -0
- traia_iatp/mcp/templates/run_local_docker.sh.j2 +390 -0
- traia_iatp/mcp/templates/server.py.j2 +175 -0
- traia_iatp/mcp/traia_mcp_adapter.py +543 -0
- traia_iatp/preview_diagrams.html +181 -0
- traia_iatp/registry/__init__.py +26 -0
- traia_iatp/registry/atlas_search_indexes.json +280 -0
- traia_iatp/registry/embeddings.py +298 -0
- traia_iatp/registry/iatp_search_api.py +846 -0
- traia_iatp/registry/mongodb_registry.py +771 -0
- traia_iatp/registry/readmes/ATLAS_SEARCH_INDEXES.md +252 -0
- traia_iatp/registry/readmes/ATLAS_SEARCH_SETUP.md +134 -0
- traia_iatp/registry/readmes/AUTHENTICATION_UPDATE.md +124 -0
- traia_iatp/registry/readmes/EMBEDDINGS_SETUP.md +172 -0
- traia_iatp/registry/readmes/IATP_SEARCH_API_GUIDE.md +257 -0
- traia_iatp/registry/readmes/MONGODB_X509_AUTH.md +208 -0
- traia_iatp/registry/readmes/README.md +251 -0
- traia_iatp/registry/readmes/REFACTORING_SUMMARY.md +191 -0
- traia_iatp/scripts/__init__.py +2 -0
- traia_iatp/scripts/create_wallet.py +244 -0
- traia_iatp/server/__init__.py +15 -0
- traia_iatp/server/a2a_server.py +219 -0
- traia_iatp/server/example_template_usage.py +72 -0
- traia_iatp/server/iatp_server_agent_generator.py +237 -0
- traia_iatp/server/iatp_server_template_generator.py +235 -0
- traia_iatp/server/templates/.dockerignore.j2 +48 -0
- traia_iatp/server/templates/Dockerfile.j2 +49 -0
- traia_iatp/server/templates/README.md +137 -0
- traia_iatp/server/templates/README.md.j2 +425 -0
- traia_iatp/server/templates/__init__.py +1 -0
- traia_iatp/server/templates/__main__.py.j2 +565 -0
- traia_iatp/server/templates/agent.py.j2 +94 -0
- traia_iatp/server/templates/agent_config.json.j2 +22 -0
- traia_iatp/server/templates/agent_executor.py.j2 +279 -0
- traia_iatp/server/templates/docker-compose.yml.j2 +23 -0
- traia_iatp/server/templates/env.example.j2 +84 -0
- traia_iatp/server/templates/gitignore.j2 +78 -0
- traia_iatp/server/templates/grpc_server.py.j2 +218 -0
- traia_iatp/server/templates/pyproject.toml.j2 +78 -0
- traia_iatp/server/templates/run_local_docker.sh.j2 +103 -0
- traia_iatp/server/templates/server.py.j2 +243 -0
- traia_iatp/special_agencies/__init__.py +4 -0
- traia_iatp/special_agencies/registry_search_agency.py +392 -0
- traia_iatp/utils/__init__.py +10 -0
- traia_iatp/utils/docker_utils.py +251 -0
- traia_iatp/utils/general.py +64 -0
- traia_iatp/utils/iatp_utils.py +126 -0
- traia_iatp-0.1.29.dist-info/METADATA +423 -0
- traia_iatp-0.1.29.dist-info/RECORD +107 -0
- traia_iatp-0.1.29.dist-info/WHEEL +5 -0
- traia_iatp-0.1.29.dist-info/entry_points.txt +2 -0
- traia_iatp-0.1.29.dist-info/licenses/LICENSE +21 -0
- traia_iatp-0.1.29.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
# Registry and Search API Refactoring Summary
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
This refactoring separates the concerns between registry management (write operations) and search/query operations (read operations) to eliminate duplication and improve maintainability.
|
|
6
|
+
|
|
7
|
+
## Changes Made
|
|
8
|
+
|
|
9
|
+
### 1. `mongodb_registry.py` - Write Operations Only
|
|
10
|
+
|
|
11
|
+
**Removed Methods:**
|
|
12
|
+
- `query_agents()` - Moved to search API
|
|
13
|
+
- `atlas_search()` - Moved to search API
|
|
14
|
+
- `vector_search()` - Moved to search API
|
|
15
|
+
- `vector_search_text()` - Moved to search API
|
|
16
|
+
- `query_mcp_servers()` - Moved to search API
|
|
17
|
+
- `atlas_search()` (MCP) - Moved to search API
|
|
18
|
+
- `vector_search()` (MCP) - Moved to search API
|
|
19
|
+
- `vector_search_text()` (MCP) - Moved to search API
|
|
20
|
+
|
|
21
|
+
**Kept Methods:**
|
|
22
|
+
- `add_utility_agent()` - Registry management
|
|
23
|
+
- `update_health_status()` - Registry management
|
|
24
|
+
- `update_agent_base_url()` - Registry management
|
|
25
|
+
- `add_tags()` - Registry management
|
|
26
|
+
- `remove_agent()` - Registry management
|
|
27
|
+
- `update_utility_agent()` - Registry management
|
|
28
|
+
- `get_statistics()` - Registry statistics (read but specific to registry)
|
|
29
|
+
- `add_mcp_server()` - Registry management
|
|
30
|
+
- `get_mcp_server()` - Simple lookup (kept for backward compatibility)
|
|
31
|
+
|
|
32
|
+
**Updated Documentation:**
|
|
33
|
+
- Updated module docstring to clarify write-only purpose
|
|
34
|
+
- Updated class docstrings to indicate write operations only
|
|
35
|
+
- Added references to `iatp_search_api.py` for search operations
|
|
36
|
+
|
|
37
|
+
### 2. `iatp_search_api.py` - Search and Query Operations
|
|
38
|
+
|
|
39
|
+
**Existing Methods (Unchanged):**
|
|
40
|
+
- `find_utility_agent()` - Search by name, capability, tag, or query
|
|
41
|
+
- `list_utility_agents()` - List agents with filters
|
|
42
|
+
- `search_utility_agents()` - Vector search for agents
|
|
43
|
+
- `find_mcp_server()` - Search MCP servers
|
|
44
|
+
- `list_mcp_servers()` - List MCP servers
|
|
45
|
+
- `search_mcp_servers()` - Vector search for MCP servers
|
|
46
|
+
- `get_mcp_server()` - Get MCP server by name
|
|
47
|
+
|
|
48
|
+
**Features:**
|
|
49
|
+
- Atlas Search integration
|
|
50
|
+
- Vector search with embeddings
|
|
51
|
+
- Fallback to Atlas Search when vector search fails
|
|
52
|
+
- Read-only operations (no MongoDB write access needed)
|
|
53
|
+
- Cached connections for better performance
|
|
54
|
+
|
|
55
|
+
### 3. Updated Test Files
|
|
56
|
+
|
|
57
|
+
**`test_discovery_and_usage.py`:**
|
|
58
|
+
- Updated imports to use `iatp_search_api` instead of `mongodb_registry`
|
|
59
|
+
- Modified `discover_hyperliquid_agent()` to use `search_utility_agents()`
|
|
60
|
+
- Updated `create_tools_from_discovered_agent()` to work with `UtilityAgentInfo` structure
|
|
61
|
+
- Removed registry connection management
|
|
62
|
+
|
|
63
|
+
**`test_refactoring.py`:**
|
|
64
|
+
- New test script to verify refactoring works correctly
|
|
65
|
+
- Tests registry write operations
|
|
66
|
+
- Tests search API operations
|
|
67
|
+
- Tests discovery test imports
|
|
68
|
+
|
|
69
|
+
## Benefits
|
|
70
|
+
|
|
71
|
+
### 1. Clear Separation of Concerns
|
|
72
|
+
- **Registry**: Handles data persistence and lifecycle management
|
|
73
|
+
- **Search API**: Handles discovery and querying
|
|
74
|
+
|
|
75
|
+
### 2. Reduced Duplication
|
|
76
|
+
- Eliminated duplicate search methods between files
|
|
77
|
+
- Single source of truth for search logic
|
|
78
|
+
|
|
79
|
+
### 3. Better Performance
|
|
80
|
+
- Search API uses read-only connections
|
|
81
|
+
- Cached connections for repeated queries
|
|
82
|
+
- Optimized for search operations
|
|
83
|
+
|
|
84
|
+
### 4. Improved Maintainability
|
|
85
|
+
- Easier to modify search logic in one place
|
|
86
|
+
- Clearer responsibilities for each module
|
|
87
|
+
- Better testability
|
|
88
|
+
|
|
89
|
+
### 5. Enhanced Security
|
|
90
|
+
- Search API can use read-only credentials
|
|
91
|
+
- Registry operations require write permissions
|
|
92
|
+
- Reduced attack surface
|
|
93
|
+
|
|
94
|
+
## Usage Examples
|
|
95
|
+
|
|
96
|
+
### Registry Operations (Write)
|
|
97
|
+
```python
|
|
98
|
+
from traia_iatp.registry.mongodb_registry import UtilityAgentRegistry
|
|
99
|
+
|
|
100
|
+
# Add new agent
|
|
101
|
+
registry = UtilityAgentRegistry()
|
|
102
|
+
await registry.add_utility_agent(agent, tags=["trading", "hyperliquid"])
|
|
103
|
+
|
|
104
|
+
# Update agent
|
|
105
|
+
await registry.update_health_status(agent_id, is_healthy=True)
|
|
106
|
+
await registry.update_agent_base_url(agent_id, new_url)
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Search Operations (Read)
|
|
110
|
+
```python
|
|
111
|
+
from traia_iatp.registry.iatp_search_api import search_utility_agents, find_utility_agent
|
|
112
|
+
|
|
113
|
+
# Search for agents
|
|
114
|
+
agents = await search_utility_agents("hyperliquid trading", limit=5)
|
|
115
|
+
|
|
116
|
+
# Find specific agent
|
|
117
|
+
agent = find_utility_agent(capability="market_data")
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Discovery Test
|
|
121
|
+
```python
|
|
122
|
+
# The discovery test now uses the search API
|
|
123
|
+
from traia_iatp.registry.iatp_search_api import search_utility_agents
|
|
124
|
+
|
|
125
|
+
# Search for Hyperliquid agent
|
|
126
|
+
search_results = await search_utility_agents(
|
|
127
|
+
query="hyperliquid trading",
|
|
128
|
+
limit=5,
|
|
129
|
+
active_only=True
|
|
130
|
+
)
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## Migration Guide
|
|
134
|
+
|
|
135
|
+
### For Existing Code
|
|
136
|
+
|
|
137
|
+
1. **If using registry for search/query:**
|
|
138
|
+
```python
|
|
139
|
+
# OLD
|
|
140
|
+
from traia_iatp.registry.mongodb_registry import UtilityAgentRegistry
|
|
141
|
+
registry = UtilityAgentRegistry()
|
|
142
|
+
agents = await registry.query_agents(query="trading")
|
|
143
|
+
|
|
144
|
+
# NEW
|
|
145
|
+
from traia_iatp.registry.iatp_search_api import search_utility_agents
|
|
146
|
+
agents = await search_utility_agents("trading")
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
2. **If using registry for write operations:**
|
|
150
|
+
```python
|
|
151
|
+
# No changes needed - these methods are still available
|
|
152
|
+
from traia_iatp.registry.mongodb_registry import UtilityAgentRegistry
|
|
153
|
+
registry = UtilityAgentRegistry()
|
|
154
|
+
await registry.add_utility_agent(agent)
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### Environment Variables
|
|
158
|
+
|
|
159
|
+
No changes to environment variables are required. Both modules use the same authentication methods:
|
|
160
|
+
- `MONGODB_CONNECTION_STRING`
|
|
161
|
+
- `MONGODB_USER` + `MONGODB_PASSWORD`
|
|
162
|
+
- `MONGODB_X509_CERT_FILE`
|
|
163
|
+
|
|
164
|
+
## Testing
|
|
165
|
+
|
|
166
|
+
Run the refactoring test to verify everything works:
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
cd traia-centralized-backend
|
|
170
|
+
python test_refactoring.py
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
This will test:
|
|
174
|
+
- Registry write operations
|
|
175
|
+
- Search API operations
|
|
176
|
+
- Discovery test imports
|
|
177
|
+
|
|
178
|
+
## Future Improvements
|
|
179
|
+
|
|
180
|
+
1. **Connection Pooling**: Implement connection pooling for better performance
|
|
181
|
+
2. **Caching**: Add Redis caching for frequently accessed data
|
|
182
|
+
3. **Rate Limiting**: Add rate limiting for search operations
|
|
183
|
+
4. **Metrics**: Add metrics collection for search performance
|
|
184
|
+
5. **API Versioning**: Consider API versioning for future changes
|
|
185
|
+
|
|
186
|
+
## Backward Compatibility
|
|
187
|
+
|
|
188
|
+
- All existing registry write operations remain unchanged
|
|
189
|
+
- Search operations now use the dedicated search API
|
|
190
|
+
- No breaking changes to existing functionality
|
|
191
|
+
- Clear migration path provided
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Create an IATPWallet for a user.
|
|
3
|
+
|
|
4
|
+
This script:
|
|
5
|
+
1. Takes an owner private key as input
|
|
6
|
+
2. Generates a new operator keypair
|
|
7
|
+
3. Calls IATPWalletFactory.createWallet() to deploy an IATPWallet
|
|
8
|
+
4. Returns the operator keys and wallet address
|
|
9
|
+
|
|
10
|
+
Usage:
|
|
11
|
+
uv run python -m traia_iatp.scripts.create_wallet \\
|
|
12
|
+
--owner-key 0x... \\
|
|
13
|
+
--network sepolia \\
|
|
14
|
+
[--wait-confirmations 2]
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
import argparse
|
|
18
|
+
import json
|
|
19
|
+
import logging
|
|
20
|
+
import sys
|
|
21
|
+
from eth_account import Account
|
|
22
|
+
from web3 import Web3
|
|
23
|
+
|
|
24
|
+
from traia_iatp.contracts import (
|
|
25
|
+
get_contract_address,
|
|
26
|
+
load_contract,
|
|
27
|
+
ContractName
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
logging.basicConfig(
|
|
31
|
+
level=logging.INFO,
|
|
32
|
+
format='%(asctime)s - %(levelname)s - %(message)s'
|
|
33
|
+
)
|
|
34
|
+
logger = logging.getLogger(__name__)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def create_wallet(
|
|
38
|
+
owner_private_key: str,
|
|
39
|
+
network: str = "sepolia",
|
|
40
|
+
wait_confirmations: int = 2
|
|
41
|
+
) -> dict:
|
|
42
|
+
"""Create an IATP Wallet for an owner.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
owner_private_key: Owner's private key (0x...)
|
|
46
|
+
network: Network name (default: "sepolia")
|
|
47
|
+
wait_confirmations: Number of confirmations to wait (default: 2)
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
Dict with operator keys and wallet address:
|
|
51
|
+
{
|
|
52
|
+
"owner_address": "0x...",
|
|
53
|
+
"operator_address": "0x...",
|
|
54
|
+
"operator_private_key": "0x...",
|
|
55
|
+
"wallet_address": "0x...",
|
|
56
|
+
"transaction_hash": "0x...",
|
|
57
|
+
"network": "sepolia"
|
|
58
|
+
}
|
|
59
|
+
"""
|
|
60
|
+
logger.info(f"Creating IATP Wallet on {network}")
|
|
61
|
+
|
|
62
|
+
# Create owner account
|
|
63
|
+
if not owner_private_key.startswith("0x"):
|
|
64
|
+
owner_private_key = f"0x{owner_private_key}"
|
|
65
|
+
|
|
66
|
+
owner_account = Account.from_key(owner_private_key)
|
|
67
|
+
logger.info(f"Owner address: {owner_account.address}")
|
|
68
|
+
|
|
69
|
+
# Generate new operator keypair
|
|
70
|
+
operator_account = Account.create()
|
|
71
|
+
logger.info(f"Generated operator address: {operator_account.address}")
|
|
72
|
+
logger.info(f"Operator private key: {operator_account.key.hex()}")
|
|
73
|
+
|
|
74
|
+
# Load IATPWalletFactory contract
|
|
75
|
+
factory_contract, w3 = load_contract(
|
|
76
|
+
ContractName.IATP_WALLET_FACTORY,
|
|
77
|
+
network=network
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
factory_address = factory_contract.address
|
|
81
|
+
logger.info(f"IATPWalletFactory address: {factory_address}")
|
|
82
|
+
|
|
83
|
+
# Check owner balance
|
|
84
|
+
owner_balance = w3.eth.get_balance(owner_account.address)
|
|
85
|
+
logger.info(f"Owner balance: {w3.from_wei(owner_balance, 'ether')} ETH")
|
|
86
|
+
|
|
87
|
+
if owner_balance == 0:
|
|
88
|
+
logger.error("Owner has no ETH for gas. Please fund the account.")
|
|
89
|
+
raise ValueError("Insufficient balance for gas")
|
|
90
|
+
|
|
91
|
+
# Build transaction to create wallet
|
|
92
|
+
logger.info("Building createWallet transaction...")
|
|
93
|
+
|
|
94
|
+
tx = factory_contract.functions.createWallet(
|
|
95
|
+
operator_account.address
|
|
96
|
+
).build_transaction({
|
|
97
|
+
'from': owner_account.address,
|
|
98
|
+
'nonce': w3.eth.get_transaction_count(owner_account.address),
|
|
99
|
+
'gas': 2000000, # Estimate gas
|
|
100
|
+
'gasPrice': w3.eth.gas_price,
|
|
101
|
+
'chainId': w3.eth.chain_id
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
# Sign transaction
|
|
105
|
+
signed_tx = owner_account.sign_transaction(tx)
|
|
106
|
+
|
|
107
|
+
# Send transaction
|
|
108
|
+
logger.info("Sending transaction...")
|
|
109
|
+
tx_hash = w3.eth.send_raw_transaction(signed_tx.raw_transaction)
|
|
110
|
+
logger.info(f"Transaction hash: {tx_hash.hex()}")
|
|
111
|
+
|
|
112
|
+
# Wait for confirmation
|
|
113
|
+
logger.info(f"Waiting for {wait_confirmations} confirmation(s)...")
|
|
114
|
+
tx_receipt = w3.eth.wait_for_transaction_receipt(tx_hash, timeout=300)
|
|
115
|
+
|
|
116
|
+
if tx_receipt['status'] != 1:
|
|
117
|
+
logger.error("Transaction failed!")
|
|
118
|
+
raise Exception(f"Transaction failed: {tx_hash.hex()}")
|
|
119
|
+
|
|
120
|
+
logger.info(f"Transaction confirmed in block {tx_receipt['blockNumber']}")
|
|
121
|
+
|
|
122
|
+
# Parse WalletCreated event to get wallet address
|
|
123
|
+
wallet_address = None
|
|
124
|
+
for log in tx_receipt['logs']:
|
|
125
|
+
try:
|
|
126
|
+
event = factory_contract.events.WalletCreated().process_log(log)
|
|
127
|
+
wallet_address = event['args']['wallet']
|
|
128
|
+
logger.info(f"✅ IATPWallet created: {wallet_address}")
|
|
129
|
+
break
|
|
130
|
+
except:
|
|
131
|
+
continue
|
|
132
|
+
|
|
133
|
+
if not wallet_address:
|
|
134
|
+
logger.error("Could not find WalletCreated event in logs")
|
|
135
|
+
raise Exception("Wallet address not found in transaction logs")
|
|
136
|
+
|
|
137
|
+
# Return result
|
|
138
|
+
result = {
|
|
139
|
+
"owner_address": owner_account.address,
|
|
140
|
+
"operator_address": operator_account.address,
|
|
141
|
+
"operator_private_key": operator_account.key.hex(),
|
|
142
|
+
"wallet_address": wallet_address,
|
|
143
|
+
"transaction_hash": tx_hash.hex(),
|
|
144
|
+
"block_number": tx_receipt['blockNumber'],
|
|
145
|
+
"network": network,
|
|
146
|
+
"chain_id": w3.eth.chain_id
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return result
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def main():
|
|
153
|
+
"""Main CLI entry point."""
|
|
154
|
+
parser = argparse.ArgumentParser(
|
|
155
|
+
description="Create an IATP Wallet via IATPWalletFactory",
|
|
156
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
157
|
+
epilog="""
|
|
158
|
+
Examples:
|
|
159
|
+
# Create wallet on Sepolia
|
|
160
|
+
uv run python -m traia_iatp.scripts.create_wallet \\
|
|
161
|
+
--owner-key 0x0cdda2d9d744d6d2a3ba4b30ac51227590f0e8f44cf5251bb1ed0941d677c0a4 \\
|
|
162
|
+
--network sepolia
|
|
163
|
+
|
|
164
|
+
# Create wallet with output to JSON file
|
|
165
|
+
uv run python -m traia_iatp.scripts.create_wallet \\
|
|
166
|
+
--owner-key 0x... \\
|
|
167
|
+
--network sepolia \\
|
|
168
|
+
--output wallet_config.json
|
|
169
|
+
"""
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
parser.add_argument(
|
|
173
|
+
"--owner-key",
|
|
174
|
+
required=True,
|
|
175
|
+
help="Owner's private key (0x...)"
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
parser.add_argument(
|
|
179
|
+
"--network",
|
|
180
|
+
default="sepolia",
|
|
181
|
+
choices=["sepolia", "base-sepolia", "arbitrum-sepolia", "localhost"],
|
|
182
|
+
help="Network to deploy on (default: sepolia)"
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
parser.add_argument(
|
|
186
|
+
"--wait-confirmations",
|
|
187
|
+
type=int,
|
|
188
|
+
default=2,
|
|
189
|
+
help="Number of confirmations to wait (default: 2)"
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
parser.add_argument(
|
|
193
|
+
"--output",
|
|
194
|
+
"-o",
|
|
195
|
+
type=str,
|
|
196
|
+
help="Output JSON file path (prints to stdout if not specified)"
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
parser.add_argument(
|
|
200
|
+
"--verbose",
|
|
201
|
+
"-v",
|
|
202
|
+
action="store_true",
|
|
203
|
+
help="Enable verbose logging"
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
args = parser.parse_args()
|
|
207
|
+
|
|
208
|
+
if args.verbose:
|
|
209
|
+
logging.getLogger().setLevel(logging.DEBUG)
|
|
210
|
+
|
|
211
|
+
try:
|
|
212
|
+
# Create wallet
|
|
213
|
+
result = create_wallet(
|
|
214
|
+
owner_private_key=args.owner_key,
|
|
215
|
+
network=args.network,
|
|
216
|
+
wait_confirmations=args.wait_confirmations
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
# Output result
|
|
220
|
+
output_json = json.dumps(result, indent=2)
|
|
221
|
+
|
|
222
|
+
if args.output:
|
|
223
|
+
with open(args.output, 'w') as f:
|
|
224
|
+
f.write(output_json)
|
|
225
|
+
logger.info(f"✅ Configuration saved to: {args.output}")
|
|
226
|
+
else:
|
|
227
|
+
print("\n" + "=" * 80)
|
|
228
|
+
print("IATP Wallet Created Successfully!")
|
|
229
|
+
print("=" * 80)
|
|
230
|
+
print(output_json)
|
|
231
|
+
print("=" * 80)
|
|
232
|
+
|
|
233
|
+
return 0
|
|
234
|
+
|
|
235
|
+
except Exception as e:
|
|
236
|
+
logger.error(f"Failed to create wallet: {e}")
|
|
237
|
+
import traceback
|
|
238
|
+
traceback.print_exc()
|
|
239
|
+
return 1
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
if __name__ == "__main__":
|
|
243
|
+
sys.exit(main())
|
|
244
|
+
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""IATP server module for creating A2A servers from utility agents."""
|
|
2
|
+
|
|
3
|
+
from .a2a_server import (
|
|
4
|
+
UtilityAgencyExecutor,
|
|
5
|
+
create_a2a_server,
|
|
6
|
+
create_and_start_a2a_server
|
|
7
|
+
)
|
|
8
|
+
from .iatp_server_agent_generator import IATPServerAgentGenerator
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"UtilityAgencyExecutor",
|
|
12
|
+
"IATPServerAgentGenerator",
|
|
13
|
+
"create_a2a_server",
|
|
14
|
+
"create_and_start_a2a_server",
|
|
15
|
+
]
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
"""A2A server implementation for utility agencies using the official a2a-sdk."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import logging
|
|
5
|
+
from typing import Dict, Any, Optional, List
|
|
6
|
+
from a2a.server.apps import A2AStarletteApplication
|
|
7
|
+
from a2a.server.agent_execution import AgentExecutor, RequestContext
|
|
8
|
+
from a2a.server.events.event_queue import EventQueue
|
|
9
|
+
from a2a.server.tasks import InMemoryTaskStore
|
|
10
|
+
from a2a.server.request_handlers import DefaultRequestHandler
|
|
11
|
+
from a2a.types import AgentCard, AgentSkill, AgentCapabilities
|
|
12
|
+
from a2a.utils import new_agent_text_message, new_text_artifact
|
|
13
|
+
from dataclasses import dataclass
|
|
14
|
+
import uvicorn
|
|
15
|
+
|
|
16
|
+
from ..core.models import UtilityAgent
|
|
17
|
+
from .iatp_server_agent_generator import IATPServerAgentGenerator
|
|
18
|
+
|
|
19
|
+
logger = logging.getLogger(__name__)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class UtilityAgencyExecutor(AgentExecutor):
|
|
23
|
+
"""Agent executor that wraps a utility agency and executes tasks via CrewAI."""
|
|
24
|
+
|
|
25
|
+
def __init__(self, agency: UtilityAgent, agency_generator: IATPServerAgentGenerator):
|
|
26
|
+
self.agency = agency
|
|
27
|
+
self.agency_generator = agency_generator
|
|
28
|
+
self._crew = None
|
|
29
|
+
|
|
30
|
+
async def _get_crew(self):
|
|
31
|
+
"""Lazily build the crew from agency config."""
|
|
32
|
+
if self._crew is None:
|
|
33
|
+
self._crew = await self.agency_generator.build_crew_from_agency(self.agency)
|
|
34
|
+
return self._crew
|
|
35
|
+
|
|
36
|
+
async def execute(self, context: RequestContext, event_queue: EventQueue) -> None:
|
|
37
|
+
"""Execute a task using the utility agency crew."""
|
|
38
|
+
try:
|
|
39
|
+
# Get the user's request from context
|
|
40
|
+
user_message = context.get_last_user_message()
|
|
41
|
+
if not user_message:
|
|
42
|
+
await event_queue.enqueue_event(
|
|
43
|
+
new_agent_text_message("No user message provided")
|
|
44
|
+
)
|
|
45
|
+
return
|
|
46
|
+
|
|
47
|
+
# Extract text from message parts
|
|
48
|
+
user_text = ""
|
|
49
|
+
for part in user_message.parts:
|
|
50
|
+
if hasattr(part, 'text'):
|
|
51
|
+
user_text += part.text
|
|
52
|
+
|
|
53
|
+
if not user_text:
|
|
54
|
+
event_queue.enqueue_event(
|
|
55
|
+
new_agent_text_message("No text content in user message")
|
|
56
|
+
)
|
|
57
|
+
return
|
|
58
|
+
|
|
59
|
+
# Build crew and execute
|
|
60
|
+
crew = await self._get_crew()
|
|
61
|
+
|
|
62
|
+
# Run the crew with the request
|
|
63
|
+
result = crew.kickoff(inputs={"request": user_text})
|
|
64
|
+
|
|
65
|
+
# Send the result as agent message
|
|
66
|
+
event_queue.enqueue_event(
|
|
67
|
+
new_agent_text_message(str(result))
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
except Exception as e:
|
|
71
|
+
logger.error(f"Error executing task: {e}")
|
|
72
|
+
event_queue.enqueue_event(
|
|
73
|
+
new_agent_text_message(f"Error processing request: {str(e)}")
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
async def cancel(self, context: RequestContext, event_queue: EventQueue) -> None:
|
|
77
|
+
"""Cancel all tasks."""
|
|
78
|
+
pass
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class MCPToolExecutor(AgentExecutor):
|
|
82
|
+
"""Agent executor for individual MCP tool execution."""
|
|
83
|
+
|
|
84
|
+
def __init__(self, agency: UtilityAgent, tool_name: str):
|
|
85
|
+
self.agency = agency
|
|
86
|
+
self.tool_name = tool_name
|
|
87
|
+
|
|
88
|
+
async def execute(self, context: RequestContext, event_queue: EventQueue) -> None:
|
|
89
|
+
"""Execute a specific MCP tool."""
|
|
90
|
+
try:
|
|
91
|
+
from ..mcp.client import MCPClient
|
|
92
|
+
|
|
93
|
+
# Get tool arguments from context
|
|
94
|
+
user_message = context.get_last_user_message()
|
|
95
|
+
if not user_message:
|
|
96
|
+
await event_queue.enqueue_event(
|
|
97
|
+
new_agent_text_message("No arguments provided")
|
|
98
|
+
)
|
|
99
|
+
return
|
|
100
|
+
|
|
101
|
+
# Extract arguments (assuming they're passed as JSON in text)
|
|
102
|
+
import json
|
|
103
|
+
arguments = {}
|
|
104
|
+
for part in user_message.parts:
|
|
105
|
+
if hasattr(part, 'text'):
|
|
106
|
+
try:
|
|
107
|
+
arguments = json.loads(part.text)
|
|
108
|
+
except:
|
|
109
|
+
arguments = {"query": part.text} # Fallback to simple text
|
|
110
|
+
|
|
111
|
+
# Connect to MCP server and execute tool
|
|
112
|
+
mcp_client = MCPClient(self.agency.mcp_server)
|
|
113
|
+
await mcp_client.connect()
|
|
114
|
+
|
|
115
|
+
try:
|
|
116
|
+
result = await mcp_client.call_tool(self.tool_name, arguments)
|
|
117
|
+
await event_queue.enqueue_event(
|
|
118
|
+
new_agent_text_message(str(result))
|
|
119
|
+
)
|
|
120
|
+
finally:
|
|
121
|
+
await mcp_client.disconnect()
|
|
122
|
+
|
|
123
|
+
except Exception as e:
|
|
124
|
+
logger.error(f"Error executing MCP tool {self.tool_name}: {e}")
|
|
125
|
+
await event_queue.enqueue_event(
|
|
126
|
+
new_agent_text_message(f"Error executing tool: {str(e)}")
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def create_a2a_server(
|
|
131
|
+
agency: UtilityAgent,
|
|
132
|
+
agency_generator: IATPServerAgentGenerator,
|
|
133
|
+
host: str = "0.0.0.0",
|
|
134
|
+
port: int = 8000
|
|
135
|
+
) -> A2AStarletteApplication:
|
|
136
|
+
"""Create an A2A server for a utility agency."""
|
|
137
|
+
|
|
138
|
+
# Create skills from agency capabilities
|
|
139
|
+
skills = []
|
|
140
|
+
|
|
141
|
+
# Add main processing skill
|
|
142
|
+
main_skill = AgentSkill(
|
|
143
|
+
id="process_request",
|
|
144
|
+
name=f"Process request using {agency.name}",
|
|
145
|
+
description=f"Process a request using {agency.name} capabilities. {agency.description}",
|
|
146
|
+
examples=[
|
|
147
|
+
f"Help me with {cap}" for cap in agency.mcp_server.capabilities[:2]
|
|
148
|
+
] if agency.mcp_server.capabilities else ["Process this request for me"]
|
|
149
|
+
)
|
|
150
|
+
skills.append(main_skill)
|
|
151
|
+
|
|
152
|
+
# Add individual MCP tool skills
|
|
153
|
+
for capability in agency.mcp_server.capabilities:
|
|
154
|
+
skill = AgentSkill(
|
|
155
|
+
id=f"mcp_{capability}",
|
|
156
|
+
name=f"Execute {capability}",
|
|
157
|
+
description=f"Execute {capability} tool on MCP server",
|
|
158
|
+
examples=[f"Run {capability} with these parameters"]
|
|
159
|
+
)
|
|
160
|
+
skills.append(skill)
|
|
161
|
+
|
|
162
|
+
# Create capabilities
|
|
163
|
+
capabilities = AgentCapabilities(
|
|
164
|
+
streaming=False, # Not implementing streaming for now
|
|
165
|
+
pushNotifications=False, # Not implementing push notifications
|
|
166
|
+
stateTransitionHistory=False
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
# Create agent card
|
|
170
|
+
agent_card = AgentCard(
|
|
171
|
+
name=agency.name.replace(" ", "_").lower(),
|
|
172
|
+
description=agency.description,
|
|
173
|
+
url=f"http://{host}:{port}",
|
|
174
|
+
version="1.0.0",
|
|
175
|
+
capabilities=capabilities,
|
|
176
|
+
skills=skills,
|
|
177
|
+
# TODO: Add authentication when AgentAuthentication is available
|
|
178
|
+
# authentication=AgentAuthentication(schemes=["Bearer"]) if agency.auth_type else None
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
# Create executor mapping
|
|
182
|
+
executors = {
|
|
183
|
+
"process_request": UtilityAgencyExecutor(agency, agency_generator)
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
# Add MCP tool executors
|
|
187
|
+
for capability in agency.mcp_server.capabilities:
|
|
188
|
+
executors[f"mcp_{capability}"] = MCPToolExecutor(agency, capability)
|
|
189
|
+
|
|
190
|
+
# Create task store and request handler
|
|
191
|
+
task_store = InMemoryTaskStore()
|
|
192
|
+
request_handler = DefaultRequestHandler(
|
|
193
|
+
agent_card=agent_card,
|
|
194
|
+
executors=executors,
|
|
195
|
+
task_store=task_store
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
# Create the A2A application
|
|
199
|
+
app = A2AStarletteApplication(
|
|
200
|
+
agent_card=agent_card,
|
|
201
|
+
http_handler=request_handler
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
return app
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
async def create_and_start_a2a_server(
|
|
208
|
+
agency: UtilityAgent,
|
|
209
|
+
agency_generator: IATPServerAgentGenerator,
|
|
210
|
+
host: str = "0.0.0.0",
|
|
211
|
+
port: int = 8000
|
|
212
|
+
):
|
|
213
|
+
"""Create and start an A2A server for a utility agency."""
|
|
214
|
+
app = create_a2a_server(agency, agency_generator, host, port)
|
|
215
|
+
|
|
216
|
+
logger.info(f"Starting A2A server for {agency.name} on {host}:{port}")
|
|
217
|
+
config = uvicorn.Config(app, host=host, port=port, log_level="info")
|
|
218
|
+
server = uvicorn.Server(config)
|
|
219
|
+
await server.serve()
|