mech-client 0.2.10__py3-none-any.whl → 0.2.12__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.
mech_client/__init__.py CHANGED
@@ -1,3 +1,3 @@
1
1
  """Mech client."""
2
2
 
3
- __version__ = "0.2.10"
3
+ __version__ = "0.2.12"
mech_client/acn.py CHANGED
@@ -19,7 +19,6 @@
19
19
 
20
20
  """ACN helpers."""
21
21
 
22
- import asyncio
23
22
  from pathlib import Path
24
23
  from typing import Optional, Type, cast
25
24
 
@@ -171,19 +170,3 @@ async def watch_for_data_url_from_mech(crypto: Crypto) -> Optional[str]:
171
170
  return None
172
171
  finally:
173
172
  await connection.disconnect()
174
-
175
-
176
- def watch_for_data_url_from_mech_sync(crypto: Crypto) -> Optional[str]:
177
- """
178
- Request and wait for data from agent
179
-
180
- :param crypto: instance of Crypto
181
- :type crypto: Crypto
182
- :return: Data URL
183
- :rtype: str
184
- :raises: None
185
- """
186
- loop = asyncio.new_event_loop()
187
- task = loop.create_task(watch_for_data_url_from_mech(crypto=crypto))
188
- loop.run_until_complete(task)
189
- return task.result()
mech_client/cli.py CHANGED
@@ -19,7 +19,7 @@
19
19
 
20
20
  """Mech client CLI module."""
21
21
 
22
- from typing import Optional
22
+ from typing import Any, Dict, List, Optional
23
23
 
24
24
  import click
25
25
 
@@ -40,15 +40,22 @@ def cli() -> None:
40
40
  @click.command()
41
41
  @click.argument("prompt")
42
42
  @click.argument("agent_id", type=int)
43
+ @click.option(
44
+ "--key",
45
+ type=click.Path(exists=True, file_okay=True, dir_okay=False),
46
+ help="Path to private key to use for request minting",
47
+ )
43
48
  @click.option(
44
49
  "--tool",
45
50
  type=str,
46
51
  help="Name of the tool to be used",
47
52
  )
48
53
  @click.option(
49
- "--key",
50
- type=click.Path(exists=True, file_okay=True, dir_okay=False),
51
- help="Path to private key to use for request minting",
54
+ "--extra-attribute",
55
+ type=str,
56
+ multiple=True,
57
+ help="Extra attribute (key=value) to be included in the request metadata",
58
+ metavar="KEY=VALUE",
52
59
  )
53
60
  @click.option(
54
61
  "--confirm",
@@ -72,23 +79,37 @@ def cli() -> None:
72
79
  type=float,
73
80
  help="Amount of sleep before retrying the transaction",
74
81
  )
82
+ @click.option(
83
+ "--chain-config",
84
+ type=str,
85
+ help="Id of the mech's chain configuration (stored configs/mechs.json)",
86
+ )
75
87
  def interact( # pylint: disable=too-many-arguments
76
88
  prompt: str,
77
89
  agent_id: int,
78
- tool: Optional[str],
79
90
  key: Optional[str],
91
+ tool: Optional[str],
92
+ extra_attribute: Optional[List[str]] = None,
80
93
  confirm: Optional[str] = None,
81
94
  retries: Optional[int] = None,
82
95
  timeout: Optional[float] = None,
83
96
  sleep: Optional[float] = None,
97
+ chain_config: Optional[str] = None,
84
98
  ) -> None:
85
99
  """Interact with a mech specifying a prompt and tool."""
86
100
  try:
101
+ extra_attributes_dict: Dict[str, Any] = {}
102
+ if extra_attribute:
103
+ for pair in extra_attribute:
104
+ k, v = pair.split("=")
105
+ extra_attributes_dict[k] = v
106
+
87
107
  interact_(
88
108
  prompt=prompt,
89
109
  agent_id=agent_id,
90
110
  private_key_path=key,
91
111
  tool=tool,
112
+ extra_attributes=extra_attributes_dict,
92
113
  confirmation_type=(
93
114
  ConfirmationType(confirm)
94
115
  if confirm is not None
@@ -97,6 +118,7 @@ def interact( # pylint: disable=too-many-arguments
97
118
  retries=retries,
98
119
  timeout=timeout,
99
120
  sleep=sleep,
121
+ chain_config=chain_config,
100
122
  )
101
123
  except (ValueError, FileNotFoundError) as e:
102
124
  raise click.ClickException(str(e)) from e
@@ -0,0 +1,92 @@
1
+ {
2
+ "gnosis": {
3
+ "agent_registry_contract": "0xE49CB081e8d96920C38aA7AB90cb0294ab4Bc8EA",
4
+ "rpc_url": "https://rpc.eu-central-2.gateway.fm/v4/gnosis/non-archival/mainnet",
5
+ "wss_endpoint": "wss://rpc.eu-central-2.gateway.fm/ws/v4/gnosis/non-archival/mainnet",
6
+ "ledger_config": {
7
+ "address": "https://rpc.eu-central-2.gateway.fm/v4/gnosis/non-archival/mainnet",
8
+ "chain_id": 100,
9
+ "poa_chain": false,
10
+ "default_gas_price_strategy": "eip1559",
11
+ "is_gas_estimation_enabled": false
12
+ },
13
+ "gas_limit": 100000,
14
+ "contract_abi_url": "https://gnosis.blockscout.com/api/v2/smart-contracts/{contract_address}",
15
+ "subgraph_url": "https://api.studio.thegraph.com/query/57238/mech/version/latest"
16
+ },
17
+ "arbitrum": {
18
+ "agent_registry_contract": "0xa4799B083E0068732456EF45ff9fe5c683658327",
19
+ "rpc_url": "https://arbitrum.llamarpc.com",
20
+ "wss_endpoint": "wss://arbitrum.gateway.tenderly.co",
21
+ "ledger_config": {
22
+ "address": "https://arbitrum.llamarpc.com",
23
+ "chain_id": 42161,
24
+ "poa_chain": false,
25
+ "default_gas_price_strategy": "eip1559",
26
+ "is_gas_estimation_enabled": false
27
+ },
28
+ "gas_limit": 100000,
29
+ "contract_abi_url": "https://api.arbiscan.io/api?module=contract&action=getabi&address={contract_address}",
30
+ "subgraph_url": ""
31
+ },
32
+ "polygon": {
33
+ "agent_registry_contract": "0x984cf72FDe8B5aA910e9e508aC5e007ae5BDcC9C",
34
+ "rpc_url": "https://polygon-bor-rpc.publicnode.com",
35
+ "wss_endpoint": "wss://polygon.gateway.tenderly.co",
36
+ "ledger_config": {
37
+ "address": "https://polygon-bor-rpc.publicnode.com",
38
+ "chain_id": 137,
39
+ "poa_chain": true,
40
+ "default_gas_price_strategy": "eip1559",
41
+ "is_gas_estimation_enabled": false
42
+ },
43
+ "gas_limit": 100000,
44
+ "contract_abi_url": "https://api.polygonscan.com/api?module=contract&action=getabi&address={contract_address}",
45
+ "subgraph_url": ""
46
+ },
47
+ "base": {
48
+ "agent_registry_contract": "0x88DE734655184a09B70700aE4F72364d1ad23728",
49
+ "rpc_url": "https://base.llamarpc.com",
50
+ "wss_endpoint": "wss://base.gateway.tenderly.co",
51
+ "ledger_config": {
52
+ "address": "https://base.llamarpc.com",
53
+ "chain_id": 8453,
54
+ "poa_chain": false,
55
+ "default_gas_price_strategy": "eip1559",
56
+ "is_gas_estimation_enabled": false
57
+ },
58
+ "gas_limit": 100000,
59
+ "contract_abi_url": "https://api.basescan.org/api?module=contract&action=getabi&address={contract_address}",
60
+ "subgraph_url": ""
61
+ },
62
+ "celo": {
63
+ "agent_registry_contract": "0xE49CB081e8d96920C38aA7AB90cb0294ab4Bc8EA",
64
+ "rpc_url": "https://forno.celo.org",
65
+ "wss_endpoint": "wss://forno.celo.org/ws",
66
+ "ledger_config": {
67
+ "address": "https://forno.celo.org",
68
+ "chain_id": 42220,
69
+ "poa_chain": false,
70
+ "default_gas_price_strategy": "eip1559",
71
+ "is_gas_estimation_enabled": false
72
+ },
73
+ "gas_limit": 100000,
74
+ "contract_abi_url": "https://api.celoscan.io/api?module=contract&action=getabi&address={contract_address}",
75
+ "subgraph_url": ""
76
+ },
77
+ "optimism": {
78
+ "agent_registry_contract": "0x75D529FAe220bC8db714F0202193726b46881B76",
79
+ "rpc_url": "https://mainnet.optimism.io",
80
+ "wss_endpoint": "wss://optimism.gateway.tenderly.co",
81
+ "ledger_config": {
82
+ "address": "https://mainnet.optimism.io",
83
+ "chain_id": 10,
84
+ "poa_chain": false,
85
+ "default_gas_price_strategy": "eip1559",
86
+ "is_gas_estimation_enabled": false
87
+ },
88
+ "gas_limit": 100000,
89
+ "contract_abi_url": "https://api-optimistic.etherscan.io/api?module=contract&action=getabi&address={contract_address}",
90
+ "subgraph_url": ""
91
+ }
92
+ }
mech_client/interact.py CHANGED
@@ -26,9 +26,12 @@ python client.py <prompt> <tool>
26
26
  """
27
27
 
28
28
  import asyncio
29
+ import json
29
30
  import os
31
+ import sys
30
32
  import time
31
33
  import warnings
34
+ from dataclasses import asdict, dataclass
32
35
  from datetime import datetime
33
36
  from enum import Enum
34
37
  from pathlib import Path
@@ -41,47 +44,18 @@ from aea_ledger_ethereum import EthereumApi, EthereumCrypto
41
44
  from web3 import Web3
42
45
  from web3.contract import Contract as Web3Contract
43
46
 
44
- from mech_client.acn import (
45
- watch_for_data_url_from_mech,
46
- watch_for_data_url_from_mech_sync,
47
- )
47
+ from mech_client.acn import watch_for_data_url_from_mech
48
48
  from mech_client.prompt_to_ipfs import push_metadata_to_ipfs
49
- from mech_client.subgraph import query_agent_address
49
+ from mech_client.subgraph import query_agent_address, watch_for_data_url_from_subgraph
50
50
  from mech_client.wss import (
51
51
  register_event_handlers,
52
52
  watch_for_data_url_from_wss,
53
- watch_for_data_url_from_wss_sync,
54
53
  watch_for_request_id,
55
54
  )
56
55
 
57
56
 
58
- AGENT_REGISTRY_CONTRACT = "0xE49CB081e8d96920C38aA7AB90cb0294ab4Bc8EA"
59
- MECHX_CHAIN_RPC = os.environ.get(
60
- "MECHX_CHAIN_RPC",
61
- "https://rpc.eu-central-2.gateway.fm/v4/gnosis/non-archival/mainnet",
62
- )
63
- LEDGER_CONFIG = {
64
- "address": MECHX_CHAIN_RPC,
65
- "chain_id": 100,
66
- "poa_chain": False,
67
- "default_gas_price_strategy": "eip1559",
68
- "is_gas_estimation_enabled": False,
69
- }
70
57
  PRIVATE_KEY_FILE_PATH = "ethereum_private_key.txt"
71
-
72
- WSS_ENDPOINT = os.getenv(
73
- "WEBSOCKET_ENDPOINT",
74
- "wss://rpc.eu-central-2.gateway.fm/ws/v4/gnosis/non-archival/mainnet",
75
- )
76
- MANUAL_GAS_LIMIT = int(
77
- os.getenv(
78
- "MANUAL_GAS_LIMIT",
79
- "100_000",
80
- )
81
- )
82
- BLOCKSCOUT_API_URL = (
83
- "https://gnosis.blockscout.com/api/v2/smart-contracts/{contract_address}"
84
- )
58
+ MECH_CONFIGS = Path(__file__).parent / "configs" / "mechs.json"
85
59
 
86
60
  MAX_RETRIES = 3
87
61
  WAIT_SLEEP = 3.0
@@ -91,6 +65,80 @@ TIMEOUT = 60.0
91
65
  warnings.filterwarnings("ignore", "The log with transaction hash.*")
92
66
 
93
67
 
68
+ @dataclass
69
+ class LedgerConfig:
70
+ """Ledger configuration"""
71
+
72
+ address: str
73
+ chain_id: int
74
+ poa_chain: bool
75
+ default_gas_price_strategy: str
76
+ is_gas_estimation_enabled: bool
77
+
78
+ def __post_init__(self) -> None:
79
+ """Post initialization to override with environment variables."""
80
+ address = os.getenv("MECHX_LEDGER_ADDRESS")
81
+ if address:
82
+ self.address = address
83
+
84
+ chain_id = os.getenv("MECHX_LEDGER_CHAIN_ID")
85
+ if chain_id:
86
+ self.chain_id = int(chain_id)
87
+
88
+ poa_chain = os.getenv("MECHX_LEDGER_POA_CHAIN")
89
+ if poa_chain:
90
+ self.poa_chain = bool(poa_chain)
91
+
92
+ default_gas_price_strategy = os.getenv(
93
+ "MECHX_LEDGER_DEFAULT_GAS_PRICE_STRATEGY"
94
+ )
95
+ if default_gas_price_strategy:
96
+ self.default_gas_price_strategy = default_gas_price_strategy
97
+
98
+ is_gas_estimation_enabled = os.getenv("MECHX_LEDGER_IS_GAS_ESTIMATION_ENABLED")
99
+ if is_gas_estimation_enabled:
100
+ self.is_gas_estimation_enabled = bool(is_gas_estimation_enabled)
101
+
102
+
103
+ @dataclass
104
+ class MechConfig:
105
+ """Mech configuration"""
106
+
107
+ agent_registry_contract: str
108
+ rpc_url: str
109
+ wss_endpoint: str
110
+ ledger_config: LedgerConfig
111
+ gas_limit: int
112
+ contract_abi_url: str
113
+ subgraph_url: str
114
+
115
+ def __post_init__(self) -> None:
116
+ """Post initialization to override with environment variables."""
117
+ agent_registry_contract = os.getenv("MECHX_AGENT_REGISTRY_CONTRACT")
118
+ if agent_registry_contract:
119
+ self.agent_registry_contract = agent_registry_contract
120
+
121
+ rpc_url = os.getenv("MECHX_CHAIN_RPC")
122
+ if rpc_url:
123
+ self.rpc_url = rpc_url
124
+
125
+ wss_endpoint = os.getenv("MECHX_WSS_ENDPOINT")
126
+ if wss_endpoint:
127
+ self.wss_endpoint = wss_endpoint
128
+
129
+ gas_limit = os.getenv("MECHX_GAS_LIMIT")
130
+ if gas_limit:
131
+ self.gas_limit = int(gas_limit)
132
+
133
+ contract_abi_url = os.getenv("MECHX_CONTRACT_ABI_URL")
134
+ if contract_abi_url:
135
+ self.contract_abi_url = contract_abi_url
136
+
137
+ subgraph_url = os.getenv("MECHX_SUBGRAPH_URL")
138
+ if subgraph_url:
139
+ self.subgraph_url = subgraph_url
140
+
141
+
94
142
  class ConfirmationType(Enum):
95
143
  """Verification type."""
96
144
 
@@ -99,6 +147,21 @@ class ConfirmationType(Enum):
99
147
  WAIT_FOR_BOTH = "wait-for-both"
100
148
 
101
149
 
150
+ def get_mech_config(chain_config: Optional[str] = None) -> MechConfig:
151
+ """Get `MechConfig` configuration"""
152
+ with open(MECH_CONFIGS, "r", encoding="UTF-8") as file:
153
+ data = json.load(file)
154
+
155
+ if chain_config is None:
156
+ chain_config = next(iter(data))
157
+
158
+ print(f"Chain configuration: {chain_config}")
159
+ entry = data[chain_config].copy()
160
+ ledger_config = LedgerConfig(**entry.pop("ledger_config"))
161
+ mech_config = MechConfig(**entry, ledger_config=ledger_config)
162
+ return mech_config
163
+
164
+
102
165
  def calculate_topic_id(event: Dict) -> str:
103
166
  """Caclulate topic ID"""
104
167
  text = event["name"]
@@ -124,11 +187,22 @@ def get_event_signatures(abi: List) -> Tuple[str, str]:
124
187
  return request, deliver
125
188
 
126
189
 
127
- def get_abi(contract_address: str) -> List:
190
+ def get_abi(contract_address: str, contract_abi_url: str) -> List:
128
191
  """Get contract abi"""
129
- abi_request_url = BLOCKSCOUT_API_URL.format(contract_address=contract_address)
192
+ abi_request_url = contract_abi_url.format(contract_address=contract_address)
130
193
  response = requests.get(abi_request_url).json()
131
- return response["abi"]
194
+
195
+ if "result" in response:
196
+ result = response["result"]
197
+ try:
198
+ abi = json.loads(result)
199
+ except json.JSONDecodeError:
200
+ print("Error: Failed to parse 'result' field as JSON")
201
+ sys.exit(1)
202
+ else:
203
+ abi = response.get("abi")
204
+
205
+ return abi if abi else []
132
206
 
133
207
 
134
208
  def get_contract(
@@ -194,7 +268,11 @@ def _tool_selector_prompt(available_tools: List[str]) -> str:
194
268
 
195
269
 
196
270
  def verify_or_retrieve_tool(
197
- agent_id: int, ledger_api: EthereumApi, tool: Optional[str] = None
271
+ agent_id: int,
272
+ ledger_api: EthereumApi,
273
+ agent_registry_contract: str,
274
+ contract_abi_url: str,
275
+ tool: Optional[str] = None,
198
276
  ) -> str:
199
277
  """
200
278
  Checks if the tool is valid and for what agent.
@@ -203,12 +281,21 @@ def verify_or_retrieve_tool(
203
281
  :type agent_id: int
204
282
  :param ledger_api: The Ethereum API used for interacting with the ledger.
205
283
  :type ledger_api: EthereumApi
284
+ :param agent_registry_contract: Agent registry contract address.
285
+ :type agent_registry_contract: str
286
+ :param contract_abi_url: Block explorer URL.
287
+ :type contract_abi_url: str
206
288
  :param tool: The tool to verify or retrieve (optional).
207
289
  :type tool: Optional[str]
208
290
  :return: The result of the verification or retrieval.
209
291
  :rtype: str
210
292
  """
211
- available_tools = fetch_tools(agent_id=agent_id, ledger_api=ledger_api)
293
+ available_tools = fetch_tools(
294
+ agent_id=agent_id,
295
+ ledger_api=ledger_api,
296
+ agent_registry_contract=agent_registry_contract,
297
+ contract_abi_url=contract_abi_url,
298
+ )
212
299
  if tool is not None and tool not in available_tools:
213
300
  raise ValueError(
214
301
  f"Provided tool `{tool}` not in the list of available tools; Available tools={available_tools}"
@@ -218,11 +305,16 @@ def verify_or_retrieve_tool(
218
305
  return _tool_selector_prompt(available_tools=available_tools)
219
306
 
220
307
 
221
- def fetch_tools(agent_id: int, ledger_api: EthereumApi) -> List[str]:
308
+ def fetch_tools(
309
+ agent_id: int,
310
+ ledger_api: EthereumApi,
311
+ agent_registry_contract: str,
312
+ contract_abi_url: str,
313
+ ) -> List[str]:
222
314
  """Fetch tools for specified agent ID."""
223
315
  mech_registry = get_contract(
224
- contract_address=AGENT_REGISTRY_CONTRACT,
225
- abi=get_abi(AGENT_REGISTRY_CONTRACT),
316
+ contract_address=agent_registry_contract,
317
+ abi=get_abi(agent_registry_contract, contract_abi_url),
226
318
  ledger_api=ledger_api,
227
319
  )
228
320
  token_uri = mech_registry.functions.tokenURI(agent_id).call()
@@ -234,13 +326,15 @@ def send_request( # pylint: disable=too-many-arguments,too-many-locals
234
326
  crypto: EthereumCrypto,
235
327
  ledger_api: EthereumApi,
236
328
  mech_contract: Web3Contract,
329
+ gas_limit: int,
237
330
  prompt: str,
238
331
  tool: str,
332
+ extra_attributes: Optional[Dict[str, Any]] = None,
239
333
  price: int = 10_000_000_000_000_000,
240
334
  retries: Optional[int] = None,
241
335
  timeout: Optional[float] = None,
242
336
  sleep: Optional[float] = None,
243
- ) -> None:
337
+ ) -> Optional[str]:
244
338
  """
245
339
  Sends a request to the mech.
246
340
 
@@ -250,10 +344,14 @@ def send_request( # pylint: disable=too-many-arguments,too-many-locals
250
344
  :type ledger_api: EthereumApi
251
345
  :param mech_contract: The mech contract instance.
252
346
  :type mech_contract: Web3Contract
347
+ :param gas_limit: Gas limit.
348
+ :type gas_limit: int
253
349
  :param prompt: The request prompt.
254
350
  :type prompt: str
255
351
  :param tool: The requested tool.
256
352
  :type tool: str
353
+ :param extra_attributes: Extra attributes to be included in the request metadata.
354
+ :type extra_attributes: Optional[Dict[str,Any]]
257
355
  :param price: The price for the request (default: 10_000_000_000_000_000).
258
356
  :type price: int
259
357
  :param retries: Number of retries for sending a transaction
@@ -262,15 +360,19 @@ def send_request( # pylint: disable=too-many-arguments,too-many-locals
262
360
  :type timeout: float
263
361
  :param sleep: Amount of sleep before retrying the transaction
264
362
  :type sleep: float
363
+ :return: The transaction hash.
364
+ :rtype: Optional[str]
265
365
  """
266
- v1_file_hash_hex_truncated, v1_file_hash_hex = push_metadata_to_ipfs(prompt, tool)
366
+ v1_file_hash_hex_truncated, v1_file_hash_hex = push_metadata_to_ipfs(
367
+ prompt, tool, extra_attributes
368
+ )
267
369
  print(f"Prompt uploaded: https://gateway.autonolas.tech/ipfs/{v1_file_hash_hex}")
268
370
  method_name = "request"
269
371
  methord_args = {"data": v1_file_hash_hex_truncated}
270
372
  tx_args = {
271
373
  "sender_address": crypto.address,
272
374
  "value": price,
273
- "gas": MANUAL_GAS_LIMIT,
375
+ "gas": gas_limit,
274
376
  }
275
377
 
276
378
  tries = 0
@@ -295,21 +397,24 @@ def send_request( # pylint: disable=too-many-arguments,too-many-locals
295
397
  raise_on_try=True,
296
398
  )
297
399
  print(f"Transaction sent: https://gnosisscan.io/tx/{transaction_digest}")
298
- return
400
+ return transaction_digest
299
401
  except Exception as e: # pylint: disable=broad-except
300
402
  print(
301
403
  f"Error occured while sending the transaction: {e}; Retrying in {sleep}"
302
404
  )
303
405
  time.sleep(sleep)
406
+ return None
304
407
 
305
408
 
306
409
  def wait_for_data_url( # pylint: disable=too-many-arguments
307
410
  request_id: str,
308
411
  wss: websocket.WebSocket,
309
412
  mech_contract: Web3Contract,
413
+ subgraph_url: str,
310
414
  deliver_signature: str,
311
415
  ledger_api: EthereumApi,
312
416
  crypto: Crypto,
417
+ confirmation_type: ConfirmationType = ConfirmationType.WAIT_FOR_BOTH,
313
418
  ) -> Any:
314
419
  """
315
420
  Wait for data from on-chain/off-chain.
@@ -320,32 +425,54 @@ def wait_for_data_url( # pylint: disable=too-many-arguments
320
425
  :type wss: websocket.WebSocket
321
426
  :param mech_contract: The mech contract instance.
322
427
  :type mech_contract: Web3Contract
428
+ :param subgraph_url: Subgraph URL.
429
+ :type subgraph_url: str
323
430
  :param deliver_signature: Topic signature for Deliver event
324
431
  :type deliver_signature: str
325
432
  :param ledger_api: The Ethereum API used for interacting with the ledger.
326
433
  :type ledger_api: EthereumApi
327
434
  :param crypto: The cryptographic object.
328
435
  :type crypto: Crypto
436
+ :param confirmation_type: The confirmation type for the interaction (default: ConfirmationType.WAIT_FOR_BOTH).
437
+ :type confirmation_type: ConfirmationType
329
438
  :return: The data received from on-chain/off-chain.
330
439
  :rtype: Any
331
440
  """
332
441
  loop = asyncio.new_event_loop()
333
- off_chain_task = loop.create_task(watch_for_data_url_from_mech(crypto=crypto))
334
- on_chain_task = loop.create_task(
335
- watch_for_data_url_from_wss(
336
- request_id=request_id,
337
- wss=wss,
338
- mech_contract=mech_contract,
339
- deliver_signature=deliver_signature,
340
- ledger_api=ledger_api,
341
- loop=loop,
442
+ tasks = []
443
+
444
+ if confirmation_type in (
445
+ ConfirmationType.OFF_CHAIN,
446
+ ConfirmationType.WAIT_FOR_BOTH,
447
+ ):
448
+ off_chain_task = loop.create_task(watch_for_data_url_from_mech(crypto=crypto))
449
+ tasks.append(off_chain_task)
450
+
451
+ if confirmation_type in (
452
+ ConfirmationType.ON_CHAIN,
453
+ ConfirmationType.WAIT_FOR_BOTH,
454
+ ):
455
+ on_chain_task = loop.create_task(
456
+ watch_for_data_url_from_wss(
457
+ request_id=request_id,
458
+ wss=wss,
459
+ mech_contract=mech_contract,
460
+ deliver_signature=deliver_signature,
461
+ ledger_api=ledger_api,
462
+ loop=loop,
463
+ )
342
464
  )
343
- )
465
+ mech_task = loop.create_task(
466
+ watch_for_data_url_from_subgraph(request_id=request_id, url=subgraph_url)
467
+ )
468
+ tasks.append(mech_task)
469
+ tasks.append(on_chain_task)
344
470
 
345
471
  async def _wait_for_tasks() -> Any: # type: ignore
346
472
  """Wait for tasks to finish."""
347
473
  (finished, *_), unfinished = await asyncio.wait(
348
- [off_chain_task, on_chain_task], return_when=asyncio.FIRST_COMPLETED
474
+ tasks,
475
+ return_when=asyncio.FIRST_COMPLETED,
349
476
  )
350
477
  for task in unfinished:
351
478
  task.cancel()
@@ -360,11 +487,13 @@ def interact( # pylint: disable=too-many-arguments,too-many-locals
360
487
  prompt: str,
361
488
  agent_id: int,
362
489
  tool: Optional[str] = None,
490
+ extra_attributes: Optional[Dict[str, Any]] = None,
363
491
  private_key_path: Optional[str] = None,
364
492
  confirmation_type: ConfirmationType = ConfirmationType.WAIT_FOR_BOTH,
365
493
  retries: Optional[int] = None,
366
494
  timeout: Optional[float] = None,
367
495
  sleep: Optional[float] = None,
496
+ chain_config: Optional[str] = None,
368
497
  ) -> Any:
369
498
  """
370
499
  Interact with agent mech contract.
@@ -375,6 +504,8 @@ def interact( # pylint: disable=too-many-arguments,too-many-locals
375
504
  :type agent_id: int
376
505
  :param tool: The tool to interact with (optional).
377
506
  :type tool: Optional[str]
507
+ :param extra_attributes: Extra attributes to be included in the request metadata (optional).
508
+ :type extra_attributes: Optional[Dict[str, Any]]
378
509
  :param private_key_path: The path to the private key file (optional).
379
510
  :type private_key_path: Optional[str]
380
511
  :param confirmation_type: The confirmation type for the interaction (default: ConfirmationType.WAIT_FOR_BOTH).
@@ -386,9 +517,18 @@ def interact( # pylint: disable=too-many-arguments,too-many-locals
386
517
  :type timeout: float
387
518
  :param sleep: Amount of sleep before retrying the transaction
388
519
  :type sleep: float
520
+ :param chain_config: Id of the mech's chain configuration (stored configs/mechs.json)
521
+ :type chain_config: str:
389
522
  :rtype: Any
390
523
  """
391
- contract_address = query_agent_address(agent_id=agent_id, timeout=timeout)
524
+ mech_config = get_mech_config(chain_config)
525
+ ledger_config = mech_config.ledger_config
526
+ contract_address = query_agent_address(
527
+ agent_id=agent_id,
528
+ timeout=timeout,
529
+ url=mech_config.subgraph_url,
530
+ chain_config=chain_config,
531
+ )
392
532
  if contract_address is None:
393
533
  raise ValueError(f"Agent with ID {agent_id} does not exist!")
394
534
 
@@ -398,12 +538,21 @@ def interact( # pylint: disable=too-many-arguments,too-many-locals
398
538
  f"Private key file `{private_key_path}` does not exist!"
399
539
  )
400
540
 
401
- wss = websocket.create_connection(WSS_ENDPOINT)
541
+ wss = websocket.create_connection(mech_config.wss_endpoint)
402
542
  crypto = EthereumCrypto(private_key_path=private_key_path)
403
- ledger_api = EthereumApi(**LEDGER_CONFIG)
543
+ ledger_api = EthereumApi(**asdict(ledger_config))
404
544
 
405
- tool = verify_or_retrieve_tool(agent_id=agent_id, ledger_api=ledger_api, tool=tool)
406
- abi = get_abi(contract_address=contract_address)
545
+ tool = verify_or_retrieve_tool(
546
+ agent_id=agent_id,
547
+ ledger_api=ledger_api,
548
+ tool=tool,
549
+ agent_registry_contract=mech_config.agent_registry_contract,
550
+ contract_abi_url=mech_config.contract_abi_url,
551
+ )
552
+ abi = get_abi(
553
+ contract_address=contract_address,
554
+ contract_abi_url=mech_config.contract_abi_url,
555
+ )
407
556
  mech_contract = get_contract(
408
557
  contract_address=contract_address, abi=abi, ledger_api=ledger_api
409
558
  )
@@ -419,12 +568,15 @@ def interact( # pylint: disable=too-many-arguments,too-many-locals
419
568
  crypto=crypto,
420
569
  ledger_api=ledger_api,
421
570
  mech_contract=mech_contract,
571
+ gas_limit=mech_config.gas_limit,
422
572
  prompt=prompt,
423
573
  tool=tool,
574
+ extra_attributes=extra_attributes,
424
575
  retries=retries,
425
576
  timeout=timeout,
426
577
  sleep=sleep,
427
578
  )
579
+ print("Waiting for transaction receipt...")
428
580
  request_id = watch_for_request_id(
429
581
  wss=wss,
430
582
  mech_contract=mech_contract,
@@ -432,25 +584,17 @@ def interact( # pylint: disable=too-many-arguments,too-many-locals
432
584
  request_signature=request_event_signature,
433
585
  )
434
586
  print(f"Created on-chain request with ID {request_id}")
435
- if confirmation_type == ConfirmationType.OFF_CHAIN:
436
- data_url = watch_for_data_url_from_mech_sync(crypto=crypto)
437
- elif confirmation_type == ConfirmationType.ON_CHAIN:
438
- data_url = watch_for_data_url_from_wss_sync(
439
- request_id=request_id,
440
- wss=wss,
441
- deliver_signature=deliver_event_signature,
442
- mech_contract=mech_contract,
443
- ledger_api=ledger_api,
444
- )
445
- else:
446
- data_url = wait_for_data_url(
447
- request_id=request_id,
448
- wss=wss,
449
- mech_contract=mech_contract,
450
- deliver_signature=deliver_event_signature,
451
- ledger_api=ledger_api,
452
- crypto=crypto,
453
- )
587
+ data_url = wait_for_data_url(
588
+ request_id=request_id,
589
+ wss=wss,
590
+ mech_contract=mech_contract,
591
+ subgraph_url=mech_config.subgraph_url,
592
+ deliver_signature=deliver_event_signature,
593
+ ledger_api=ledger_api,
594
+ crypto=crypto,
595
+ confirmation_type=confirmation_type,
596
+ )
597
+
454
598
  print(f"Data arrived: {data_url}")
455
599
  data = requests.get(f"{data_url}/{request_id}").json()
456
600
  print(f"Data from agent: {data}")
@@ -29,12 +29,14 @@ import json
29
29
  import shutil
30
30
  import tempfile
31
31
  import uuid
32
- from typing import Tuple
32
+ from typing import Any, Dict, Optional, Tuple
33
33
 
34
34
  from mech_client.push_to_ipfs import push_to_ipfs
35
35
 
36
36
 
37
- def push_metadata_to_ipfs(prompt: str, tool: str) -> Tuple[str, str]:
37
+ def push_metadata_to_ipfs(
38
+ prompt: str, tool: str, extra_attributes: Optional[Dict[str, Any]] = None
39
+ ) -> Tuple[str, str]:
38
40
  """
39
41
  Pushes metadata object to IPFS.
40
42
 
@@ -42,10 +44,14 @@ def push_metadata_to_ipfs(prompt: str, tool: str) -> Tuple[str, str]:
42
44
  :type prompt: str
43
45
  :param tool: Tool string.
44
46
  :type tool: str
47
+ :param extra_attributes: Extra attributes to be included in the request metadata.
48
+ :type extra_attributes: Optional[Dict[str,Any]]
45
49
  :return: Tuple containing the IPFS hash and truncated IPFS hash.
46
50
  :rtype: Tuple[str, str]
47
51
  """
48
52
  metadata = {"prompt": prompt, "tool": tool, "nonce": str(uuid.uuid4())}
53
+ if extra_attributes:
54
+ metadata.update(extra_attributes)
49
55
  dirpath = tempfile.mkdtemp()
50
56
  file_name = dirpath + "metadata.json"
51
57
  with open(file_name, "w", encoding="utf-8") as f:
mech_client/subgraph.py CHANGED
@@ -19,6 +19,7 @@
19
19
 
20
20
  """Subgraph client for mech."""
21
21
 
22
+ import asyncio
22
23
  from string import Template
23
24
  from typing import Optional
24
25
 
@@ -26,7 +27,6 @@ from gql import Client, gql
26
27
  from gql.transport.aiohttp import AIOHTTPTransport
27
28
 
28
29
 
29
- MECH_SUBGRAPH_URL = "https://api.studio.thegraph.com/query/57238/mech/version/latest"
30
30
  AGENT_QUERY_TEMPLATE = Template(
31
31
  """{
32
32
  createMeches(where:{agentId:$agent_id}) {
@@ -35,22 +35,53 @@ AGENT_QUERY_TEMPLATE = Template(
35
35
  }
36
36
  """
37
37
  )
38
+ DELIVER_QUERY_TEMPLATE = Template(
39
+ """{
40
+ delivers(
41
+ orderBy: blockTimestamp
42
+ where: {requestId:"$request_id"}
43
+ orderDirection: desc
44
+ ) {
45
+ ipfsHash
46
+ }
47
+ }
48
+ """
49
+ )
50
+ DEFAULT_TIMEOUT = 600.0
38
51
 
39
52
 
40
- def query_agent_address(
41
- agent_id: int, timeout: Optional[float] = None
53
+ def query_agent_address( # pylint: disable=too-many-return-statements
54
+ agent_id: int,
55
+ url: str,
56
+ timeout: Optional[float] = None,
57
+ chain_config: Optional[str] = None,
42
58
  ) -> Optional[str]:
43
59
  """
44
60
  Query agent address from subgraph.
45
61
 
46
62
  :param agent_id: The ID of the agent.
47
- :param timeout: Timeout for the request.
48
63
  :type agent_id: int
64
+ :param url: Subgraph URL.
65
+ :type url: str
66
+ :param timeout: Timeout for the request.
67
+ :type timeout: Optional[float]
68
+ :type chain_config: Optional[str]:
49
69
  :return: The agent address if found, None otherwise.
50
70
  :rtype: Optional[str]
51
71
  """
72
+ # temporary hard coded until subgraph present
73
+ if chain_config == "base" and agent_id == 2:
74
+ return "0x111D7DB1B752AB4D2cC0286983D9bd73a49bac6c"
75
+ if chain_config == "arbitrum" and agent_id == 2:
76
+ return "0x1FDAD3a5af5E96e5a64Fc0662B1814458F114597"
77
+ if chain_config == "polygon" and agent_id == 2:
78
+ return "0xbF92568718982bf65ee4af4F7020205dE2331a8a"
79
+ if chain_config == "celo" and agent_id == 2:
80
+ return "0x230eD015735c0D01EA0AaD2786Ed6Bd3C6e75912"
81
+ if chain_config == "optimism" and agent_id == 2:
82
+ return "0xDd40E7D93c37eFD860Bd53Ab90b2b0a8D05cf71a"
52
83
  client = Client(
53
- transport=AIOHTTPTransport(url=MECH_SUBGRAPH_URL),
84
+ transport=AIOHTTPTransport(url=url),
54
85
  execute_timeout=timeout or 30.0,
55
86
  )
56
87
  response = client.execute(
@@ -64,3 +95,66 @@ def query_agent_address(
64
95
 
65
96
  (record,) = mechs
66
97
  return record["mech"]
98
+
99
+
100
+ async def query_deliver_hash(
101
+ request_id: str, url: str, timeout: Optional[float] = None
102
+ ) -> Optional[str]:
103
+ """
104
+ Query deliver IPFS hash from subgraph.
105
+
106
+ :param request_id: The ID of the mech request.
107
+ :type request_id: str
108
+ :param url: Subgraph URL.
109
+ :type url: str
110
+ :param timeout: Timeout for the request.
111
+ :type timeout: Optional[float]
112
+ :return: The deliver IPFS hash if found, None otherwise.
113
+ :rtype: Optional[str]
114
+ """
115
+ client = Client(
116
+ transport=AIOHTTPTransport(url=url),
117
+ execute_timeout=timeout or 30.0,
118
+ )
119
+ response = await client.execute_async(
120
+ document=gql(
121
+ request_string=DELIVER_QUERY_TEMPLATE.substitute({"request_id": request_id})
122
+ )
123
+ )
124
+ delivers = response["delivers"] # pylint: disable=unsubscriptable-object
125
+ if len(delivers) == 0:
126
+ return None
127
+
128
+ (record,) = delivers
129
+ return record["ipfsHash"]
130
+
131
+
132
+ async def watch_for_data_url_from_subgraph(
133
+ request_id: str, url: str, timeout: Optional[float] = None
134
+ ) -> Optional[str]:
135
+ """
136
+ Continuously query for data URL until it's available or timeout is reached.
137
+
138
+ :param request_id: The ID of the mech request.
139
+ :type request_id: str
140
+ :param url: Subgraph URL.
141
+ :type url: str
142
+ :param timeout: Maximum time to wait for the data URL in seconds. Defaults to DEFAULT_TIMEOUT.
143
+ :type timeout: Optional[float]
144
+ :return: Data URL if available within timeout, otherwise None.
145
+ :rtype: Optional[str]
146
+ """
147
+ timeout = timeout or DEFAULT_TIMEOUT
148
+ start_time = asyncio.get_event_loop().time()
149
+ while True:
150
+ response = await query_deliver_hash(request_id=request_id, url=url)
151
+ if response is not None:
152
+ return f"https://gateway.autonolas.tech/ipfs/{response}"
153
+
154
+ if asyncio.get_event_loop().time() - start_time >= timeout:
155
+ print(f"Error: No response received after {timeout} seconds.")
156
+ break
157
+
158
+ await asyncio.sleep(5)
159
+
160
+ return None
mech_client/wss.py CHANGED
@@ -186,41 +186,3 @@ async def watch_for_data_url_from_wss( # pylint: disable=too-many-arguments
186
186
  if request_id != str(rich_logs[0]["args"]["requestId"]):
187
187
  continue
188
188
  return f"https://gateway.autonolas.tech/ipfs/f01701220{data.hex()}"
189
-
190
-
191
- def watch_for_data_url_from_wss_sync(
192
- request_id: str,
193
- wss: websocket.WebSocket,
194
- mech_contract: Web3Contract,
195
- deliver_signature: str,
196
- ledger_api: EthereumApi,
197
- ) -> Any:
198
- """
199
- Watches for data on-chain.
200
-
201
- :param request_id: The ID of the request.
202
- :type request_id: str
203
- :param wss: The WebSocket connection object.
204
- :type wss: websocket.WebSocket
205
- :param mech_contract: The mech contract instance.
206
- :type mech_contract: Web3Contract
207
- :param deliver_signature: Topic signature for Deliver event
208
- :type deliver_signature: str
209
- :param ledger_api: The Ethereum API used for interacting with the ledger.
210
- :type ledger_api: EthereumApi
211
- :return: The data received from on-chain.
212
- :rtype: Any
213
- """
214
- loop = asyncio.new_event_loop()
215
- task = loop.create_task(
216
- watch_for_data_url_from_wss(
217
- request_id=request_id,
218
- wss=wss,
219
- mech_contract=mech_contract,
220
- deliver_signature=deliver_signature,
221
- ledger_api=ledger_api,
222
- loop=loop,
223
- )
224
- )
225
- loop.run_until_complete(task)
226
- return task.result()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: mech-client
3
- Version: 0.2.10
3
+ Version: 0.2.12
4
4
  Summary: Basic client to interact with a mech
5
5
  License: Apache-2.0
6
6
  Author: David Minarsch
@@ -31,10 +31,10 @@ Basic client to interact with a mech
31
31
  pip install mech-client
32
32
  ```
33
33
 
34
- Then, set a websocket endpoint for Gnosis RPC like so:
34
+ Note: If you encounter an "Out of gas" error when executing the tool, you will need to increase the gas limit by setting, e.g.,
35
35
 
36
36
  ```bash
37
- export WEBSOCKET_ENDPOINT=<YOUR ENDPOINT>
37
+ export MECHX_GAS_LIMIT=200000
38
38
  ```
39
39
 
40
40
  ## CLI:
@@ -103,6 +103,7 @@ mechx interact <prompt> <agent_id> --key <key_file>
103
103
  Example output:
104
104
  ```bash
105
105
  mechx interact "write a short poem" 3 --key ~/gnosis_key --tool openai-text-davinci-003
106
+ Chain configuration: gnosis
106
107
  Prompt uploaded: https://gateway.autonolas.tech/ipfs/f01701220ad773628911d12e28f005e3f249e990d684e5dba07542259195602f9afed30bf
107
108
  Transaction sent: https://gnosisscan.io/tx/0x0d9209e32e965a820b9e80accfcd71ea3b1174b9758dd251c2e627a60ec426a5
108
109
  Created on-chain request with ID 111240237160304797537720810617416341148235899500021985333360197012735240803849
@@ -110,10 +111,11 @@ Data arrived: https://gateway.autonolas.tech/ipfs/bafybeifk2h35ncszlze7t64rpblfo
110
111
  Data from agent: {'requestId': 111240237160304797537720810617416341148235899500021985333360197012735240803849, 'result': "\n\nI am brave and I'm strong\nI don't hide away my song\nI am here and I'm proud\nMy voice will be heard loud!"}
111
112
  ```
112
113
 
113
- By default the client will wait for data to arrive from on-chain using the websocket subscription and off-chain using the ACN and show you the result which arrives first. You can specify the type of confirmation you want using `--confirm` flag like this
114
+ By default the client will wait for data to arrive from on-chain using the websocket subscription and subgraph, and off-chain using the ACN and show you the result which arrives first. You can specify the type of confirmation you want using `--confirm` flag like this
114
115
 
115
116
  ```bash
116
117
  mechx interact "write a short poem" 3 --key ~/gnosis_key --tool openai-text-davinci-003 --confirm on-chain
118
+ Chain configuration: gnosis
117
119
  Prompt uploaded: https://gateway.autonolas.tech/ipfs/f017012205e37f761221a8ba4005e91c36b94153e9432b8888ff2acae6b101dd5a5de6768
118
120
  Transaction sent: https://gnosisscan.io/tx/0xf1ef63f617717bbb8deb09699af99aa39f10155d33796de2fd7eb61c9c1458b6
119
121
  Created on-chain request with ID 81653153529124597849081567361606842861262371002932574194580478443414142139857
@@ -121,6 +123,30 @@ Data arrived: https://gateway.autonolas.tech/ipfs/f0170122069b55e077430a00f3cbc3
121
123
  Data from agent: {'requestId': 81653153529124597849081567361606842861262371002932574194580478443414142139857, 'result': "\n\nA summer breeze, so sweet,\nA gentle reminder of summer's heat.\nThe sky so blue, no cloud in sight,\nA perfect day, a wondrous sight."}
122
124
  ```
123
125
 
126
+ ### Chain configuration
127
+
128
+ Configurations for different chains are stored in the file `configs/mechs.json`. By default, `mech interact` will choose the first configuration on the JSON. You can specify which config you want to use using the `--chain-config` flag, for example,
129
+
130
+ ```bash
131
+ mechx interact <prompt> <agent_id> --chain-config gnosis
132
+ ```
133
+
134
+ Additionally, you can override any configuration parameter by exporting any of the following environment variables:
135
+
136
+ ```bash
137
+ MECHX_CHAIN_RPC
138
+ MECHX_WSS_ENDPOINT
139
+ MECHX_GAS_LIMIT
140
+ MECHX_CONTRACT_ABI_URL
141
+ MECHX_SUBGRAPH_URL
142
+
143
+ MECHX_LEDGER_ADDRESS
144
+ MECHX_LEDGER_CHAIN_ID
145
+ MECHX_LEDGER_POA_CHAIN
146
+ MECHX_LEDGER_DEFAULT_GAS_PRICE_STRATEGY
147
+ MECHX_LEDGER_IS_GAS_ESTIMATION_ENABLED
148
+ ```
149
+
124
150
  ## Programmatic Usage:
125
151
 
126
152
  ```python
@@ -149,7 +175,7 @@ poetry install && poetry shell
149
175
 
150
176
  ## Release guide:
151
177
 
152
- - Bump versions in `pyproject.toml` and `mech_client/__init__.py`
178
+ - Bump versions in `pyproject.toml`, `mech_client/__init__.py` and `SECURITY.md`
153
179
  - `poetry lock`
154
180
  - `rm -rf dist`
155
181
  - `autonomy packages sync --update-packages`
@@ -1,6 +1,7 @@
1
- mech_client/__init__.py,sha256=No9VVJJY-aVrNiGRM83B1m0Bb-fXIOl_diX4tfBS_BM,43
2
- mech_client/acn.py,sha256=Cz93ykB8Vavd6QSEBnoNv0wylfw2RLOqqu7fIYo4YRY,5790
3
- mech_client/cli.py,sha256=e6trIN_uoprbvxz41A-x2aUu8kxlyxTAfYv_yZzxmOU,3897
1
+ mech_client/__init__.py,sha256=s8E8bTccJTXUKsK6N8pSwD7FabU-dQUps-y98raJYZQ,43
2
+ mech_client/acn.py,sha256=Rj_jLPvJ5loDQfGbu3a_O24cJC4SwIErLceSz_zVYS8,5356
3
+ mech_client/cli.py,sha256=QnC3Z_vth__ZiU12chRl8q5LYDLjyjzaofB41mWlq8c,4608
4
+ mech_client/configs/mechs.json,sha256=ed7mp5Z6qC8QuLIx4iqV9Y_u3KmIOhf3zMfpdZ951cQ,4026
4
5
  mech_client/helpers/__init__.py,sha256=f13zpwGDaKQUjML-5Iq66rRfzg8IS5UNK5I8gBr7w54,1028
5
6
  mech_client/helpers/acn/README.md,sha256=WMXR2Lk0IpWjr3vpZ8cxcTHk4gwsx4wC06UPkwj9dbQ,1641
6
7
  mech_client/helpers/acn/__init__.py,sha256=72WrGEDIuYKvlWQnTajFQ9bNrDy2sFfTYDP62he4doI,1141
@@ -29,14 +30,14 @@ mech_client/helpers/p2p_libp2p_client/README.md,sha256=6x9s6P7TdKkcvAS1wMFHXRz4a
29
30
  mech_client/helpers/p2p_libp2p_client/__init__.py,sha256=-GOP3D_JnmXTDomrMLCbnRk7vRQmihIqTYvyIPzx-q4,879
30
31
  mech_client/helpers/p2p_libp2p_client/connection.py,sha256=pvfHtI-NhgDbay1wLNit6m8InH4c0p00c3hQo0I2jwQ,27887
31
32
  mech_client/helpers/p2p_libp2p_client/connection.yaml,sha256=giYV5FwwugD7ha9IqFHJtvs-Oz1jC5og9rpkstrTqoc,1763
32
- mech_client/interact.py,sha256=Dy0WiiATpp6jdPe5iJ14UzXAnRz13GbNjn_UOHugs8Y,14925
33
- mech_client/prompt_to_ipfs.py,sha256=IR0NHBd-f4p9bj_HuindJSwrGcc08fdBxavJmpo6GKA,2250
33
+ mech_client/interact.py,sha256=uRJEcJ8idIm-erGNNBugLGVgYwVO9HmSl2NKPvy248w,19631
34
+ mech_client/prompt_to_ipfs.py,sha256=XqSIBko15MEkpWOQNT97fRI6jNxMF5EDBDEPOJFdhyk,2533
34
35
  mech_client/push_to_ipfs.py,sha256=IfvgaPU79N_ZmCPF9d7sPCYz2uduZH0KjT_HQ2LHXoQ,2059
35
- mech_client/subgraph.py,sha256=QR9yAxGSDQJDl3J3PeOwUYLMnCNMQyF4UZlNVTAKoMw,1955
36
+ mech_client/subgraph.py,sha256=4vY6QFyUVs15gS0SvanJbvAxb3aie07IuxQnfMMnStc,4931
36
37
  mech_client/to_png.py,sha256=pjUcFJ63MJj_r73eqnfqCWMtlpsrj6H4ZmgvIEmRcFw,2581
37
- mech_client/wss.py,sha256=a9aKz1WADxjx67zH8I48w9L_z8J1N2xWRrBrtyk-UjY,7353
38
- mech_client-0.2.10.dist-info/LICENSE,sha256=mdBDB-mWKV5Cz4ejBzBiKqan6Z8zVLAh9xwM64O2FW4,11339
39
- mech_client-0.2.10.dist-info/METADATA,sha256=bPnUfuqr6OX-vv73FqH-Z0Ssnadk0Z8HX5TeMRQCruY,5583
40
- mech_client-0.2.10.dist-info/WHEEL,sha256=7Z8_27uaHI_UZAc4Uox4PpBhQ9Y5_modZXWMxtUi4NU,88
41
- mech_client-0.2.10.dist-info/entry_points.txt,sha256=SbRMRsayzD8XfNXhgwPuXEqQsdZ5Bw9XDPnUuaDExyY,45
42
- mech_client-0.2.10.dist-info/RECORD,,
38
+ mech_client/wss.py,sha256=klvSOMoUbt8-NwVBNH1G3Tlu4Ll9192dgmr6fA1hGNc,6214
39
+ mech_client-0.2.12.dist-info/LICENSE,sha256=mdBDB-mWKV5Cz4ejBzBiKqan6Z8zVLAh9xwM64O2FW4,11339
40
+ mech_client-0.2.12.dist-info/METADATA,sha256=orPKVVfYHtc3tJn0u_FH258fNBiDWpEXddPyn5Buboo,6443
41
+ mech_client-0.2.12.dist-info/WHEEL,sha256=7Z8_27uaHI_UZAc4Uox4PpBhQ9Y5_modZXWMxtUi4NU,88
42
+ mech_client-0.2.12.dist-info/entry_points.txt,sha256=SbRMRsayzD8XfNXhgwPuXEqQsdZ5Bw9XDPnUuaDExyY,45
43
+ mech_client-0.2.12.dist-info/RECORD,,