olas-operate-middleware 0.6.3__py3-none-any.whl → 0.7.0__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.
@@ -18,116 +18,193 @@
18
18
  #
19
19
  # -------------------------------------------------------------
20
20
  """Source dode to download and run agent from the repos."""
21
+ import hashlib
21
22
  import os
22
23
  import platform
23
24
  import shutil
24
25
  import stat
26
+ from dataclasses import dataclass
25
27
  from pathlib import Path
26
28
  from tempfile import TemporaryDirectory
27
29
  from typing import Tuple
28
30
 
29
31
  import requests
30
32
  from aea.configurations.data_types import PublicId
33
+ from aea.helpers.logging import setup_logger
31
34
 
32
35
 
36
+ @dataclass
37
+ class AgentRelease:
38
+ """Agent release dataclass."""
39
+
40
+ owner: str
41
+ repo: str
42
+ release: str
43
+
44
+ @property
45
+ def release_url(self) -> str:
46
+ """Get release api url."""
47
+ return f"https://api.github.com/repos/{self.owner}/{self.repo}/releases/tags/{self.release}"
48
+
49
+ def get_url_and_hash(self, asset_name: str) -> tuple[str, str]:
50
+ """Get download url and asset sha256 hash."""
51
+ release_data = requests.get(self.release_url).json()
52
+
53
+ assets_filtered = [i for i in release_data["assets"] if i["name"] == asset_name]
54
+ if not assets_filtered:
55
+ raise ValueError(
56
+ f"Asset {asset_name} not found in release {self.release_url}"
57
+ )
58
+ asset = assets_filtered[0]
59
+ file_hash = asset["digest"]
60
+ file_url = asset["browser_download_url"]
61
+
62
+ return file_url, file_hash
63
+
64
+
65
+ # list of agents releases supported
33
66
  AGENTS_SUPPORTED = {
34
- "valory": {
35
- "trader": "https://github.com/valory-xyz/trader/releases/download/v0.0.101/",
36
- "optimus": "https://github.com/valory-xyz/optimus/releases/download/v0.0.101/",
37
- },
38
- "dvilela": {
39
- "memeooorr": "https://github.com/valory-xyz/meme-ooorr-test/releases/download/v0.0.3/"
40
- },
67
+ "valory/trader": AgentRelease(
68
+ owner="valory-xyz", repo="trader", release="v0.0.101"
69
+ ),
70
+ "valory/optimus": AgentRelease(
71
+ owner="valory-xyz", repo="optimus", release="v0.0.103"
72
+ ),
73
+ "dvilela/memeooorr": AgentRelease(
74
+ owner="valory-xyz", repo="meme-ooorr-test", release="v0.0.101"
75
+ ),
41
76
  }
42
77
 
43
78
 
44
- def get_agent_runner_executable_name() -> str:
45
- """Get runner executable name by platform running."""
46
- if platform.system() == "Darwin":
47
- os_name = "macos"
48
- elif platform.system() == "Windows":
49
- os_name = "windows"
50
- else:
51
- raise ValueError("Platform not supported!")
52
-
53
- if platform.machine().lower() in ("x86_64", "amd64"):
54
- arch = "x64"
55
- elif platform.machine().lower() == "arm64":
56
- arch = "arm64"
57
- if os_name == "windows":
58
- raise ValueError("Windows arm64 is not supported!")
59
- else:
60
- raise ValueError(f"unsupported arch: {platform.machine()}")
61
-
62
- exec_name = f"agent_runner_{os_name}_{arch}"
63
- if platform.system() == "Windows":
64
- exec_name += ".exe"
65
- return exec_name
66
-
67
-
68
- def parse_agent(public_id_str: str) -> Tuple[str, str]:
69
- """Get authorn and name from agent public string id."""
70
- public_id = PublicId.from_str(public_id_string=public_id_str)
71
- return (public_id.author, public_id.name)
72
-
73
-
74
- def download_file(url: str, save_path: Path) -> None:
75
- """Download file of agent runner."""
76
- try:
77
- # Send a GET request to the URL
78
- response = requests.get(url, stream=True)
79
- response.raise_for_status() # Raise an error for bad status codes (4xx or 5xx)
80
-
81
- # Open the file in binary write mode and save the content
82
- with open(save_path, "wb") as file:
83
- for chunk in response.iter_content(chunk_size=8192):
84
- file.write(chunk)
85
-
86
- print(f"File downloaded and saved to {save_path}")
87
- except requests.exceptions.RequestException as e:
88
- print(f"Error downloading file: {e}")
89
- raise
90
-
91
-
92
- def download_agent_runner(
93
- target_path: Path, agent_runner_name: str, agent_public_id_str: str
94
- ) -> None:
95
- """Download agent runner."""
96
- agent_author, agent_name = parse_agent(public_id_str=agent_public_id_str)
97
- if agent_author not in AGENTS_SUPPORTED:
98
- raise ValueError(f"No agents supported for author {agent_author}")
99
- if agent_name not in AGENTS_SUPPORTED[agent_author]:
100
- raise ValueError(
101
- f"No agent named {agent_name} supported for author {agent_author}"
79
+ class AgentRunnerManager:
80
+ """Agent Runner Manager."""
81
+
82
+ logger = setup_logger(name="operate.agent_runner_manager")
83
+ AGENTS = AGENTS_SUPPORTED
84
+
85
+ @staticmethod
86
+ def get_agent_runner_executable_name() -> str:
87
+ """Get runner executable name by platform running."""
88
+ if platform.system() == "Darwin":
89
+ os_name = "macos"
90
+ elif platform.system() == "Windows":
91
+ os_name = "windows"
92
+ else:
93
+ raise ValueError("Platform not supported!")
94
+
95
+ if platform.machine().lower() in ("x86_64", "amd64"):
96
+ arch = "x64"
97
+ elif platform.machine().lower() == "arm64":
98
+ arch = "arm64"
99
+ if os_name == "windows":
100
+ raise ValueError("Windows arm64 is not supported!")
101
+ else:
102
+ raise ValueError(f"unsupported arch: {platform.machine()}")
103
+
104
+ exec_name = f"agent_runner_{os_name}_{arch}"
105
+ if platform.system() == "Windows":
106
+ exec_name += ".exe"
107
+ return exec_name
108
+
109
+ @staticmethod
110
+ def parse_agent(public_id_str: str) -> Tuple[str, str]:
111
+ """Get authorn and name from agent public string id."""
112
+ public_id = PublicId.from_str(public_id_string=public_id_str)
113
+ return (public_id.author, public_id.name)
114
+
115
+ @classmethod
116
+ def download_file(cls, url: str, save_path: Path) -> None:
117
+ """Download file of agent runner."""
118
+ try:
119
+ # Send a GET request to the URL
120
+ response = requests.get(url, stream=True)
121
+ response.raise_for_status() # Raise an error for bad status codes (4xx or 5xx)
122
+
123
+ # Open the file in binary write mode and save the content
124
+ with open(save_path, "wb") as file:
125
+ for chunk in response.iter_content(chunk_size=8192):
126
+ file.write(chunk)
127
+
128
+ cls.logger.info(f"File downloaded and saved to {save_path}")
129
+ except requests.exceptions.RequestException as e:
130
+ cls.logger.error(f"Error downloading file: {e}")
131
+ raise
132
+
133
+ @classmethod
134
+ def get_agent_release_by_public_id(cls, agent_public_id_str: str) -> AgentRelease:
135
+ """Get agent release object according to public id."""
136
+ agent_author, agent_name = cls.parse_agent(public_id_str=agent_public_id_str)
137
+
138
+ agent_name = f"{agent_author}/{agent_name}"
139
+ agent_release = cls.AGENTS.get(agent_name, None)
140
+ if agent_release is None:
141
+ raise ValueError(f"{agent_name} is not supported!")
142
+ return agent_release
143
+
144
+ @staticmethod
145
+ def get_local_file_sha256(path: Path) -> str:
146
+ """Get local file sha256."""
147
+ sha256_hash = hashlib.sha256()
148
+ with open(path, "rb") as f:
149
+ for byte_block in iter(lambda: f.read(4096), b""):
150
+ sha256_hash.update(byte_block)
151
+ return "sha256:" + sha256_hash.hexdigest()
152
+
153
+ @classmethod
154
+ def update_agent_runner(
155
+ cls, target_path: Path, agent_runner_name: str, agent_release: AgentRelease
156
+ ) -> None:
157
+ """Download agent runner."""
158
+ download_url, remote_file_hash = agent_release.get_url_and_hash(
159
+ agent_runner_name
102
160
  )
103
- repo_url = AGENTS_SUPPORTED[agent_author][agent_name]
104
- download_url = f"{repo_url}{agent_runner_name}"
105
- try:
106
- with TemporaryDirectory() as tmp_dir:
107
- tmp_file = Path(tmp_dir) / "agent_runner"
108
- download_file(download_url, tmp_file)
109
- shutil.copy2(tmp_file, target_path)
110
- if os.name == "posix":
111
- target_path.chmod(target_path.stat().st_mode | stat.S_IEXEC)
112
- except Exception:
113
- # remove in cae of errors
114
- if target_path.exists():
115
- target_path.unlink(missing_ok=True)
116
- raise
117
161
 
162
+ if target_path.exists():
163
+ # check sha
164
+ current_file_hash = cls.get_local_file_sha256(target_path)
165
+ if remote_file_hash == current_file_hash:
166
+ cls.logger.info(
167
+ "local and remote files hashes are match, nothing to download"
168
+ )
169
+ return
170
+ cls.logger.info(
171
+ "local and remote files hashes does not match, go to download"
172
+ )
173
+ else:
174
+ cls.logger.info("local file not found, go to download")
175
+
176
+ try:
177
+ with TemporaryDirectory() as tmp_dir:
178
+ tmp_file = Path(tmp_dir) / "agent_runner"
179
+ cls.download_file(download_url, tmp_file)
180
+ shutil.copy2(tmp_file, target_path)
181
+ if os.name == "posix":
182
+ target_path.chmod(target_path.stat().st_mode | stat.S_IEXEC)
183
+ except Exception:
184
+ # remove in caae of errors
185
+ if target_path.exists():
186
+ target_path.unlink(missing_ok=True)
187
+ raise
188
+
189
+ @classmethod
190
+ def get_agent_runner_path(cls, service_dir: Path, agent_public_id_str: str) -> str:
191
+ """Get path to the agent runner bin palced."""
192
+ agent_runner_name = cls.get_agent_runner_executable_name()
193
+ agent_runner_path: Path = service_dir / agent_runner_name
194
+ agent_release = cls.get_agent_release_by_public_id(
195
+ agent_public_id_str=agent_public_id_str
196
+ )
118
197
 
119
- def get_agent_runner_path(service_dir: Path, agent_public_id_str: str) -> str:
120
- """Get path to the agent runner bin palced."""
121
- agent_runner_name = get_agent_runner_executable_name()
122
- agent_runner_path: Path = service_dir / agent_runner_name
123
-
124
- if agent_runner_path.exists():
125
- print(f"agent runner {agent_runner_path} already exists. dont download it.")
126
- else:
127
- print(f"agent runner {agent_runner_path} does not exists. downloading it.")
128
- download_agent_runner(
198
+ cls.update_agent_runner(
129
199
  target_path=agent_runner_path,
130
200
  agent_runner_name=agent_runner_name,
131
- agent_public_id_str=agent_public_id_str,
201
+ agent_release=agent_release,
132
202
  )
133
- return str(agent_runner_path)
203
+ return str(agent_runner_path)
204
+
205
+
206
+ def get_agent_runner_path(service_dir: Path, agent_public_id_str: str) -> str:
207
+ """Get path to the agent runner bin placed."""
208
+ return AgentRunnerManager.get_agent_runner_path(
209
+ service_dir=service_dir, agent_public_id_str=agent_public_id_str
210
+ )
@@ -52,12 +52,14 @@ from operate.ledger import PUBLIC_RPCS, get_currency_denom
52
52
  from operate.ledger.profiles import (
53
53
  CONTRACTS,
54
54
  DEFAULT_MASTER_EOA_FUNDS,
55
- DEFAULT_MECH_MARKETPLACE_PRIORITY_MECH,
55
+ DEFAULT_PRIORITY_MECH_ADDRESS,
56
+ DEFAULT_PRIORITY_MECH_SERVICE_ID,
56
57
  OLAS,
57
58
  STAKING,
58
59
  USDC,
59
60
  WRAPPED_NATIVE_ASSET,
60
61
  get_staking_contract,
62
+ get_staking_program_mech_type,
61
63
  )
62
64
  from operate.operate_types import (
63
65
  Chain,
@@ -554,6 +556,10 @@ class ServiceManager:
554
556
  self.logger.info(f"Service state: {on_chain_state.name}")
555
557
 
556
558
  current_staking_program = self._get_current_staking_program(service, chain)
559
+ staking_program_mech_type = get_staking_program_mech_type(
560
+ user_params.staking_program_id
561
+ )
562
+ self.logger.info(f"{staking_program_mech_type=}")
557
563
  fallback_params = dict( # nosec
558
564
  staking_contract=NULL_ADDRESS,
559
565
  agent_ids=[user_params.agent_id],
@@ -602,6 +608,9 @@ class ServiceManager:
602
608
  use_mech_marketplace = False
603
609
  mech_marketplace_address = ZERO_ADDRESS
604
610
  priority_mech_address = ZERO_ADDRESS
611
+ priority_mech_service_id = DEFAULT_PRIORITY_MECH_SERVICE_ID.get(
612
+ staking_program_mech_type, 0
613
+ )
605
614
 
606
615
  except Exception: # pylint: disable=broad-except
607
616
  # Try if activity checker is a RequesterActivityChecker contract
@@ -638,17 +647,35 @@ class ServiceManager:
638
647
  else:
639
648
  agent_mech = (
640
649
  priority_mech_address
641
- ) = DEFAULT_MECH_MARKETPLACE_PRIORITY_MECH
650
+ ) = DEFAULT_PRIORITY_MECH_ADDRESS[staking_program_mech_type]
651
+
652
+ if (
653
+ "PRIORITY_MECH_SERVICE_ID" in service.env_variables
654
+ and service.env_variables["PRIORITY_MECH_SERVICE_ID"][
655
+ "provision_type"
656
+ ]
657
+ == ServiceEnvProvisionType.USER
658
+ ):
659
+ priority_mech_service_id = service.env_variables[
660
+ "PRIORITY_MECH_SERVICE_ID"
661
+ ]["value"]
662
+ else:
663
+ priority_mech_service_id = DEFAULT_PRIORITY_MECH_SERVICE_ID.get(
664
+ staking_program_mech_type, 0
665
+ )
642
666
 
643
667
  except Exception: # pylint: disable=broad-except
644
668
  self.logger.warning(
645
669
  "Cannot determine type of activity checker contract. Using default parameters. "
646
670
  "NOTE: This will be an exception in the future!"
647
671
  )
648
- agent_mech = "0x77af31De935740567Cf4fF1986D04B2c964A786a" # nosec
672
+ agent_mech = DEFAULT_PRIORITY_MECH_ADDRESS[
673
+ staking_program_mech_type
674
+ ]
649
675
  use_mech_marketplace = False
650
676
  mech_marketplace_address = ZERO_ADDRESS
651
677
  priority_mech_address = ZERO_ADDRESS
678
+ priority_mech_service_id = 0
652
679
 
653
680
  env_var_to_value.update(
654
681
  {
@@ -669,7 +696,7 @@ class ServiceManager:
669
696
  f'{{"mech_marketplace_address":"{mech_marketplace_address}",'
670
697
  f'"priority_mech_address":"{priority_mech_address}",'
671
698
  f'"priority_mech_staking_instance_address":"0x998dEFafD094817EF329f6dc79c703f1CF18bC90",'
672
- f'"priority_mech_service_id":{service.env_variables.get("PRIORITY_MECH_SERVICE_ID", {"value": 975})["value"]},'
699
+ f'"priority_mech_service_id":{priority_mech_service_id},'
673
700
  f'"requester_staking_instance_address":"{target_staking_params.get("staking_contract")}",'
674
701
  f'"response_timeout":300}}'
675
702
  ),