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.
- traia_iatp/__init__.py +105 -8
- traia_iatp/cli/main.py +85 -1
- traia_iatp/client/__init__.py +28 -3
- traia_iatp/client/crewai_a2a_tools.py +32 -12
- traia_iatp/client/d402_a2a_client.py +348 -0
- traia_iatp/contracts/__init__.py +11 -0
- traia_iatp/contracts/data/abis/contract-abis-localhost.json +4091 -0
- traia_iatp/contracts/data/abis/contract-abis-sepolia.json +4890 -0
- traia_iatp/contracts/data/addresses/contract-addresses.json +17 -0
- traia_iatp/contracts/data/addresses/contract-proxies.json +12 -0
- traia_iatp/contracts/iatp_contracts_config.py +263 -0
- traia_iatp/contracts/wallet_creator.py +369 -0
- traia_iatp/core/models.py +17 -3
- traia_iatp/d402/MIDDLEWARE_ARCHITECTURE.md +205 -0
- traia_iatp/d402/PRICE_BUILDER_USAGE.md +249 -0
- traia_iatp/d402/README.md +489 -0
- traia_iatp/d402/__init__.py +54 -0
- traia_iatp/d402/asgi_wrapper.py +469 -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 +266 -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 +481 -0
- traia_iatp/d402/mcp_middleware.py +296 -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 +126 -0
- traia_iatp/d402/payment_signing.py +183 -0
- traia_iatp/d402/price_builder.py +164 -0
- traia_iatp/d402/servers/__init__.py +61 -0
- traia_iatp/d402/servers/base.py +139 -0
- traia_iatp/d402/servers/example_general_server.py +140 -0
- traia_iatp/d402/servers/fastapi.py +253 -0
- traia_iatp/d402/servers/mcp.py +304 -0
- traia_iatp/d402/servers/starlette.py +878 -0
- traia_iatp/d402/starlette_middleware.py +529 -0
- traia_iatp/d402/types.py +300 -0
- traia_iatp/mcp/D402_MCP_ADAPTER_FLOW.md +357 -0
- traia_iatp/mcp/__init__.py +3 -0
- traia_iatp/mcp/d402_mcp_tool_adapter.py +526 -0
- traia_iatp/mcp/mcp_agent_template.py +78 -13
- traia_iatp/mcp/templates/Dockerfile.j2 +27 -4
- traia_iatp/mcp/templates/README.md.j2 +104 -8
- traia_iatp/mcp/templates/cursor-rules.md.j2 +194 -0
- traia_iatp/mcp/templates/deployment_params.json.j2 +1 -2
- traia_iatp/mcp/templates/docker-compose.yml.j2 +13 -3
- traia_iatp/mcp/templates/env.example.j2 +60 -0
- traia_iatp/mcp/templates/mcp_health_check.py.j2 +2 -2
- traia_iatp/mcp/templates/pyproject.toml.j2 +11 -5
- traia_iatp/mcp/templates/pyrightconfig.json.j2 +22 -0
- traia_iatp/mcp/templates/run_local_docker.sh.j2 +320 -10
- traia_iatp/mcp/templates/server.py.j2 +174 -197
- traia_iatp/mcp/traia_mcp_adapter.py +182 -20
- traia_iatp/registry/__init__.py +47 -12
- traia_iatp/registry/atlas_search_indexes.json +108 -54
- traia_iatp/registry/iatp_search_api.py +169 -39
- traia_iatp/registry/mongodb_registry.py +241 -69
- traia_iatp/registry/readmes/EMBEDDINGS_SETUP.md +1 -1
- traia_iatp/registry/readmes/IATP_SEARCH_API_GUIDE.md +8 -8
- traia_iatp/registry/readmes/MONGODB_X509_AUTH.md +1 -1
- traia_iatp/registry/readmes/README.md +3 -3
- traia_iatp/registry/readmes/REFACTORING_SUMMARY.md +6 -6
- traia_iatp/scripts/__init__.py +2 -0
- traia_iatp/scripts/create_wallet.py +244 -0
- traia_iatp/server/a2a_server.py +22 -7
- traia_iatp/server/iatp_server_template_generator.py +23 -0
- traia_iatp/server/templates/.dockerignore.j2 +48 -0
- traia_iatp/server/templates/Dockerfile.j2 +23 -1
- traia_iatp/server/templates/README.md +2 -2
- traia_iatp/server/templates/README.md.j2 +5 -5
- traia_iatp/server/templates/__main__.py.j2 +374 -66
- traia_iatp/server/templates/agent.py.j2 +12 -11
- traia_iatp/server/templates/agent_config.json.j2 +3 -3
- traia_iatp/server/templates/agent_executor.py.j2 +45 -27
- traia_iatp/server/templates/env.example.j2 +32 -4
- traia_iatp/server/templates/gitignore.j2 +7 -0
- traia_iatp/server/templates/pyproject.toml.j2 +13 -12
- traia_iatp/server/templates/run_local_docker.sh.j2 +143 -11
- traia_iatp/server/templates/server.py.j2 +197 -10
- traia_iatp/special_agencies/registry_search_agency.py +1 -1
- traia_iatp/utils/iatp_utils.py +6 -6
- traia_iatp-0.1.67.dist-info/METADATA +320 -0
- traia_iatp-0.1.67.dist-info/RECORD +117 -0
- traia_iatp-0.1.2.dist-info/METADATA +0 -414
- traia_iatp-0.1.2.dist-info/RECORD +0 -72
- {traia_iatp-0.1.2.dist-info → traia_iatp-0.1.67.dist-info}/WHEEL +0 -0
- {traia_iatp-0.1.2.dist-info → traia_iatp-0.1.67.dist-info}/entry_points.txt +0 -0
- {traia_iatp-0.1.2.dist-info → traia_iatp-0.1.67.dist-info}/licenses/LICENSE +0 -0
- {traia_iatp-0.1.2.dist-info → traia_iatp-0.1.67.dist-info}/top_level.txt +0 -0
|
@@ -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
|
+
|
traia_iatp/server/a2a_server.py
CHANGED
|
@@ -39,7 +39,7 @@ class UtilityAgencyExecutor(AgentExecutor):
|
|
|
39
39
|
# Get the user's request from context
|
|
40
40
|
user_message = context.get_last_user_message()
|
|
41
41
|
if not user_message:
|
|
42
|
-
event_queue.enqueue_event(
|
|
42
|
+
await event_queue.enqueue_event(
|
|
43
43
|
new_agent_text_message("No user message provided")
|
|
44
44
|
)
|
|
45
45
|
return
|
|
@@ -73,6 +73,10 @@ class UtilityAgencyExecutor(AgentExecutor):
|
|
|
73
73
|
new_agent_text_message(f"Error processing request: {str(e)}")
|
|
74
74
|
)
|
|
75
75
|
|
|
76
|
+
async def cancel(self, context: RequestContext, event_queue: EventQueue) -> None:
|
|
77
|
+
"""Cancel all tasks."""
|
|
78
|
+
pass
|
|
79
|
+
|
|
76
80
|
|
|
77
81
|
class MCPToolExecutor(AgentExecutor):
|
|
78
82
|
"""Agent executor for individual MCP tool execution."""
|
|
@@ -89,7 +93,7 @@ class MCPToolExecutor(AgentExecutor):
|
|
|
89
93
|
# Get tool arguments from context
|
|
90
94
|
user_message = context.get_last_user_message()
|
|
91
95
|
if not user_message:
|
|
92
|
-
event_queue.enqueue_event(
|
|
96
|
+
await event_queue.enqueue_event(
|
|
93
97
|
new_agent_text_message("No arguments provided")
|
|
94
98
|
)
|
|
95
99
|
return
|
|
@@ -110,7 +114,7 @@ class MCPToolExecutor(AgentExecutor):
|
|
|
110
114
|
|
|
111
115
|
try:
|
|
112
116
|
result = await mcp_client.call_tool(self.tool_name, arguments)
|
|
113
|
-
event_queue.enqueue_event(
|
|
117
|
+
await event_queue.enqueue_event(
|
|
114
118
|
new_agent_text_message(str(result))
|
|
115
119
|
)
|
|
116
120
|
finally:
|
|
@@ -118,7 +122,7 @@ class MCPToolExecutor(AgentExecutor):
|
|
|
118
122
|
|
|
119
123
|
except Exception as e:
|
|
120
124
|
logger.error(f"Error executing MCP tool {self.tool_name}: {e}")
|
|
121
|
-
event_queue.enqueue_event(
|
|
125
|
+
await event_queue.enqueue_event(
|
|
122
126
|
new_agent_text_message(f"Error executing tool: {str(e)}")
|
|
123
127
|
)
|
|
124
128
|
|
|
@@ -162,11 +166,22 @@ def create_a2a_server(
|
|
|
162
166
|
stateTransitionHistory=False
|
|
163
167
|
)
|
|
164
168
|
|
|
165
|
-
# Create agent card
|
|
169
|
+
# Create agent card with proper URL
|
|
170
|
+
# For Cloud Run: use PUBLIC_URL or SERVICE_URL environment variable
|
|
171
|
+
# For local: use http://{host}:{port}
|
|
172
|
+
import os
|
|
173
|
+
public_url = os.getenv("PUBLIC_URL") or os.getenv("SERVICE_URL")
|
|
174
|
+
if public_url:
|
|
175
|
+
# Cloud Run deployment - use the public URL
|
|
176
|
+
agent_url = public_url
|
|
177
|
+
else:
|
|
178
|
+
# Local deployment - use host:port
|
|
179
|
+
agent_url = f"http://{host}:{port}"
|
|
180
|
+
|
|
166
181
|
agent_card = AgentCard(
|
|
167
182
|
name=agency.name.replace(" ", "_").lower(),
|
|
168
183
|
description=agency.description,
|
|
169
|
-
url=
|
|
184
|
+
url=agent_url,
|
|
170
185
|
version="1.0.0",
|
|
171
186
|
capabilities=capabilities,
|
|
172
187
|
skills=skills,
|
|
@@ -194,7 +209,7 @@ def create_a2a_server(
|
|
|
194
209
|
# Create the A2A application
|
|
195
210
|
app = A2AStarletteApplication(
|
|
196
211
|
agent_card=agent_card,
|
|
197
|
-
|
|
212
|
+
http_handler=request_handler
|
|
198
213
|
)
|
|
199
214
|
|
|
200
215
|
return app
|
|
@@ -117,6 +117,18 @@ class IATPServerTemplateGenerator:
|
|
|
117
117
|
module_name = package_name
|
|
118
118
|
docker_image = f"traia/{agent_id}:latest"
|
|
119
119
|
|
|
120
|
+
# Extract D402 parameters from kwargs (populated by deploy_utility_agent)
|
|
121
|
+
d402_enabled = kwargs.get("d402_enabled", False)
|
|
122
|
+
d402_contract_address = kwargs.get("d402_contract_address", "")
|
|
123
|
+
d402_operator_private_key = kwargs.get("d402_operator_private_key", "")
|
|
124
|
+
d402_price_usd = kwargs.get("d402_price_usd", "0.01")
|
|
125
|
+
d402_token_symbol = kwargs.get("d402_token_symbol", "USDC")
|
|
126
|
+
d402_token_address = kwargs.get("d402_token_address", "")
|
|
127
|
+
d402_token_decimals = kwargs.get("d402_token_decimals", 6)
|
|
128
|
+
d402_network = kwargs.get("d402_network", "sepolia")
|
|
129
|
+
d402_facilitator_url = kwargs.get("d402_facilitator_url", "http://localhost:7070")
|
|
130
|
+
d402_testing_mode = kwargs.get("d402_testing_mode", "false")
|
|
131
|
+
|
|
120
132
|
# Prepare template context
|
|
121
133
|
context = {
|
|
122
134
|
"agent_name": agent_name,
|
|
@@ -141,6 +153,17 @@ class IATPServerTemplateGenerator:
|
|
|
141
153
|
"environment_variables": environment_variables,
|
|
142
154
|
"additional_ignores": additional_ignores,
|
|
143
155
|
"use_uv_lock": False, # Will be true after first uv sync
|
|
156
|
+
# D402 payment configuration
|
|
157
|
+
"d402_enabled": d402_enabled,
|
|
158
|
+
"d402_contract_address": d402_contract_address,
|
|
159
|
+
"d402_operator_private_key": d402_operator_private_key,
|
|
160
|
+
"d402_price_usd": d402_price_usd,
|
|
161
|
+
"d402_token_symbol": d402_token_symbol,
|
|
162
|
+
"d402_token_address": d402_token_address,
|
|
163
|
+
"d402_token_decimals": d402_token_decimals,
|
|
164
|
+
"d402_network": d402_network,
|
|
165
|
+
"d402_facilitator_url": d402_facilitator_url,
|
|
166
|
+
"d402_testing_mode": d402_testing_mode,
|
|
144
167
|
**kwargs
|
|
145
168
|
}
|
|
146
169
|
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Environment files (should be mounted, not built into image)
|
|
2
|
+
.env
|
|
3
|
+
.env.*
|
|
4
|
+
!.env.example
|
|
5
|
+
|
|
6
|
+
# Python
|
|
7
|
+
__pycache__/
|
|
8
|
+
*.pyc
|
|
9
|
+
*.pyo
|
|
10
|
+
*.pyd
|
|
11
|
+
.Python
|
|
12
|
+
*.egg-info/
|
|
13
|
+
dist/
|
|
14
|
+
build/
|
|
15
|
+
|
|
16
|
+
# Git
|
|
17
|
+
.git/
|
|
18
|
+
.gitignore
|
|
19
|
+
|
|
20
|
+
# IDE
|
|
21
|
+
.vscode/
|
|
22
|
+
.idea/
|
|
23
|
+
*.swp
|
|
24
|
+
*.swo
|
|
25
|
+
|
|
26
|
+
# OS
|
|
27
|
+
.DS_Store
|
|
28
|
+
Thumbs.db
|
|
29
|
+
|
|
30
|
+
# UV/Poetry
|
|
31
|
+
.venv/
|
|
32
|
+
venv/
|
|
33
|
+
env/
|
|
34
|
+
|
|
35
|
+
# Logs
|
|
36
|
+
*.log
|
|
37
|
+
logs/
|
|
38
|
+
|
|
39
|
+
# Testing
|
|
40
|
+
.pytest_cache/
|
|
41
|
+
.coverage
|
|
42
|
+
htmlcov/
|
|
43
|
+
.tox/
|
|
44
|
+
|
|
45
|
+
# Documentation
|
|
46
|
+
docs/
|
|
47
|
+
*.md
|
|
48
|
+
!README.md
|
|
@@ -24,6 +24,28 @@ USER appuser
|
|
|
24
24
|
# Copy project files
|
|
25
25
|
COPY --chown=appuser:appuser . .
|
|
26
26
|
|
|
27
|
+
# Copy IATP package if available (for local development only)
|
|
28
|
+
# This Dockerfile works for both local and remote deployments:
|
|
29
|
+
#
|
|
30
|
+
# LOCAL DEPLOYMENT (via run_local_docker.sh):
|
|
31
|
+
# - run_local_docker.sh detects local IATP path in pyproject.toml
|
|
32
|
+
# - Copies IATP to .docker-iatp/IATP before building
|
|
33
|
+
# - Temporarily modifies pyproject.toml to use file:///tmp/IATP
|
|
34
|
+
# - Dockerfile finds .docker-iatp/IATP and copies it to /tmp/IATP
|
|
35
|
+
# - uv install uses the local IATP from /tmp/IATP
|
|
36
|
+
#
|
|
37
|
+
# REMOTE DEPLOYMENT (Cloud Run, etc.):
|
|
38
|
+
# - .docker-iatp/IATP does NOT exist in build context
|
|
39
|
+
# - pyproject.toml has published version (traia-iatp>=0.1.40)
|
|
40
|
+
# - Dockerfile skips IATP copy, uv install uses published package
|
|
41
|
+
#
|
|
42
|
+
RUN if [ -d .docker-iatp/IATP ]; then \
|
|
43
|
+
cp -r .docker-iatp/IATP /tmp/IATP && \
|
|
44
|
+
echo "Using local IATP package (local development mode)"; \
|
|
45
|
+
else \
|
|
46
|
+
echo "Using published IATP package (remote deployment mode)"; \
|
|
47
|
+
fi
|
|
48
|
+
|
|
27
49
|
# Install Python dependencies
|
|
28
50
|
RUN uv venv .venv && \
|
|
29
51
|
uv pip install -r pyproject.toml
|
|
@@ -40,7 +62,7 @@ ENV {{ env_var.name }}="{{ env_var.value }}"
|
|
|
40
62
|
|
|
41
63
|
# Health check
|
|
42
64
|
HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
|
|
43
|
-
CMD curl -f http://localhost:${PORT:-8000}/.well-known/agent.json || exit 1
|
|
65
|
+
CMD curl -f http://localhost:${PORT:-8000}/.well-known/agent-card.json || exit 1
|
|
44
66
|
|
|
45
67
|
# Expose port (uses PORT environment variable with default)
|
|
46
68
|
EXPOSE ${PORT:-8000}
|
|
@@ -63,7 +63,7 @@ The templates expect the following variables:
|
|
|
63
63
|
Use the `UtilityAgencyTemplateGenerator` class to generate agencies:
|
|
64
64
|
|
|
65
65
|
```python
|
|
66
|
-
from
|
|
66
|
+
from traia_iatp.server.template_generator import UtilityAgencyTemplateGenerator
|
|
67
67
|
|
|
68
68
|
generator = UtilityAgencyTemplateGenerator()
|
|
69
69
|
output_path = generator.generate_agency(
|
|
@@ -114,7 +114,7 @@ output_dir/
|
|
|
114
114
|
|
|
115
115
|
The generated agencies follow the A2A protocol by:
|
|
116
116
|
|
|
117
|
-
1. **Agent Card**: Exposing agent capabilities at `/.well-known/agent.json`
|
|
117
|
+
1. **Agent Card**: Exposing agent capabilities at `/.well-known/agent-card.json`
|
|
118
118
|
2. **Task Processing**: Handling tasks via POST to `/tasks/send`
|
|
119
119
|
3. **Text I/O**: Supporting text input and output by default
|
|
120
120
|
4. **Request Context**: Processing request context and metadata
|
|
@@ -73,8 +73,8 @@ All configuration is managed through environment variables. See `.env.example` f
|
|
|
73
73
|
|
|
74
74
|
The A2A (Agent-to-Agent) protocol defines a minimal set of endpoints for agent communication:
|
|
75
75
|
|
|
76
|
-
#### 1. **`POST
|
|
77
|
-
This is the primary endpoint for all A2A communication.
|
|
76
|
+
#### 1. **`POST /a2a`** - Main JSON-RPC Endpoint
|
|
77
|
+
This is the primary endpoint for all A2A communication. The JSON-RPC endpoint is configured at `/a2a` to avoid conflicts with middleware and root path handling.
|
|
78
78
|
|
|
79
79
|
**Supported Methods:**
|
|
80
80
|
- `message/send` - Send a message to the agent
|
|
@@ -97,7 +97,7 @@ This is the primary endpoint for all A2A communication. Despite what you might e
|
|
|
97
97
|
}
|
|
98
98
|
```
|
|
99
99
|
|
|
100
|
-
#### 2. **`GET /.well-known/agent.json`** - Agent Card
|
|
100
|
+
#### 2. **`GET /.well-known/agent-card.json`** - Agent Card
|
|
101
101
|
Returns the agent's capabilities, skills, and metadata following the A2A standard.
|
|
102
102
|
|
|
103
103
|
**Example Response:**
|
|
@@ -135,7 +135,7 @@ Resume a subscription to task events, useful for handling connection drops.
|
|
|
135
135
|
|
|
136
136
|
### How Streaming Works
|
|
137
137
|
|
|
138
|
-
**Important:** Streaming uses the SAME
|
|
138
|
+
**Important:** Streaming uses the SAME endpoint (`/a2a`) as regular requests. The difference is in the request parameters:
|
|
139
139
|
|
|
140
140
|
**Regular Request:**
|
|
141
141
|
```json
|
|
@@ -311,7 +311,7 @@ This will log:
|
|
|
311
311
|
import httpx
|
|
312
312
|
|
|
313
313
|
# Get agent info
|
|
314
|
-
response = httpx.get("http://localhost:8000/.well-known/agent.json")
|
|
314
|
+
response = httpx.get("http://localhost:8000/.well-known/agent-card.json")
|
|
315
315
|
info = response.json()
|
|
316
316
|
|
|
317
317
|
# Send a message via A2A (note: endpoint is at root "/", not "/a2a")
|