acp-plugin-gamesdk 0.1.24__py3-none-any.whl → 0.1.26__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.
@@ -1,51 +1,15 @@
1
- from dataclasses import dataclass
2
- from enum import Enum, IntEnum
3
- from typing import Any, Callable, Dict, List, Literal, Optional, Union
1
+ from enum import Enum
2
+ from typing import Optional, List, Literal, Dict, Any
3
+ from pydantic import BaseModel
4
4
 
5
+ from virtuals_acp.models import ACPJobPhase
5
6
 
6
- @dataclass
7
- class AcpOffering:
7
+ class AcpOffering(BaseModel):
8
8
  name: str
9
9
  price: float
10
10
 
11
11
  def __str__(self) -> str:
12
- output = (
13
- f"Offering(name={self.name}, price={self.price})"
14
- )
15
- return output
16
-
17
-
18
- @dataclass
19
- class AcpAgent:
20
- id: str
21
- name: str
22
- twitter_handle: str
23
- description: str
24
- wallet_address: str
25
- offerings: Optional[List[AcpOffering]]
26
-
27
- def __str__(self) -> str:
28
- offer = ""
29
- if self.offerings:
30
- for index, off in enumerate(self.offerings):
31
- offer += f"{index + 1}. {str(off)}\n"
32
-
33
- output = (
34
- f"😎 Agent ID={self.id}\n"
35
- f"Name={self.name}, Description={self.description}, Wallet={self.wallet_address}\n"
36
- f"Offerings:\n{offer}"
37
- )
38
- return output
39
-
40
-
41
- class AcpJobPhases(IntEnum):
42
- REQUEST = 0
43
- NEGOTIATION = 1
44
- TRANSACTION = 2
45
- EVALUATION = 3
46
- COMPLETED = 4
47
- REJECTED = 5
48
-
12
+ return f"Offering(name={self.name}, price={self.price})"
49
13
 
50
14
  class AcpJobPhasesDesc(str, Enum):
51
15
  REQUEST = "request"
@@ -55,133 +19,130 @@ class AcpJobPhasesDesc(str, Enum):
55
19
  COMPLETED = "completed"
56
20
  REJECTED = "rejected"
57
21
 
58
-
59
- @dataclass
60
- class AcpRequestMemo:
22
+ ACP_JOB_PHASE_MAP: Dict[ACPJobPhase, AcpJobPhasesDesc] = {
23
+ ACPJobPhase.REQUEST: AcpJobPhasesDesc.REQUEST,
24
+ ACPJobPhase.NEGOTIATION: AcpJobPhasesDesc.NEGOTIATION,
25
+ ACPJobPhase.TRANSACTION: AcpJobPhasesDesc.TRANSACTION,
26
+ ACPJobPhase.EVALUATION: AcpJobPhasesDesc.EVALUATION,
27
+ ACPJobPhase.COMPLETED: AcpJobPhasesDesc.COMPLETED,
28
+ ACPJobPhase.REJECTED: AcpJobPhasesDesc.REJECTED,
29
+ }
30
+
31
+ ACP_JOB_PHASE_REVERSE_MAP: Dict[str, ACPJobPhase] = {
32
+ "request": ACPJobPhase.REQUEST,
33
+ "pending_payment": ACPJobPhase.NEGOTIATION,
34
+ "in_progress": ACPJobPhase.TRANSACTION,
35
+ "evaluation": ACPJobPhase.EVALUATION,
36
+ "completed": ACPJobPhase.COMPLETED,
37
+ "rejected": ACPJobPhase.REJECTED,
38
+ }
39
+
40
+ class AcpRequestMemo(BaseModel):
61
41
  id: int
62
- createdAt: int
63
42
 
64
43
  def __repr__(self) -> str:
65
- output = f"Memo(ID: {self.id}, created at: {self.createdAt})"
66
- return output
67
-
68
-
69
- @dataclass
70
- class ITweet:
44
+ return f"Memo(ID: {self.id})"
45
+
46
+ class ITweet(BaseModel):
71
47
  type: Literal["buyer", "seller"]
72
48
  tweet_id: str
73
49
  content: str
74
50
  created_at: int
75
51
 
76
-
77
- @dataclass
78
- class AcpJob:
52
+ class IAcpJob(BaseModel):
79
53
  jobId: Optional[int]
80
54
  clientName: Optional[str]
81
55
  providerName: Optional[str]
82
- desc: Optional[str]
56
+ desc: str
83
57
  price: str
84
58
  providerAddress: Optional[str]
85
- clientAddress: Optional[str]
86
59
  phase: AcpJobPhasesDesc
87
60
  memo: List[AcpRequestMemo]
88
- tweetHistory: ITweet | List
89
- lastUpdated: int
90
- getAgentByWalletAddress: Optional[Callable[[str], AcpAgent]]
61
+ tweetHistory: Optional[List[Optional[ITweet]]]
91
62
 
92
63
  def __repr__(self) -> str:
93
- output = (
64
+ return (
94
65
  f"Job ID: {self.jobId}, "
95
66
  f"Client Name: {self.clientName}, "
96
67
  f"Provider Name: {self.providerName}, "
97
68
  f"Description: {self.desc}, "
98
69
  f"Price: {self.price}, "
99
70
  f"Provider Address: {self.providerAddress}, "
100
- f"Client Address: {self.clientAddress}, "
101
71
  f"Phase: {self.phase.value}, "
102
72
  f"Memo: {self.memo}, "
103
- f"Tweet History: {self.tweetHistory}, "
104
- f"Last Updated: {self.lastUpdated})"
73
+ f"Tweet History: {self.tweetHistory}"
105
74
  )
106
- return output
107
-
108
75
 
109
- @dataclass
110
- class IDeliverable:
76
+ class IDeliverable(BaseModel):
111
77
  type: str
112
- value: Union[str, Dict[str, Any], List[Any]]
78
+ value: str
113
79
  clientName: Optional[str]
114
80
  providerName: Optional[str]
115
81
 
116
82
 
117
- @dataclass
118
83
  class IInventory(IDeliverable):
119
84
  jobId: int
120
85
  clientName: Optional[str]
121
86
  providerName: Optional[str]
122
87
 
123
-
124
- @dataclass
125
- class AcpJobsSection:
126
- asABuyer: List[AcpJob]
127
- asASeller: List[AcpJob]
88
+ class AcpJobsSection(BaseModel):
89
+ asABuyer: List[IAcpJob]
90
+ asASeller: List[IAcpJob]
128
91
 
129
92
  def __str__(self) -> str:
130
- buyer_jobs = ""
131
- for index, job in enumerate(self.asABuyer):
132
- buyer_jobs += f"#{index + 1} {str(job)} \n"
133
-
134
- seller_jobs = ""
135
- for index, job in enumerate(self.asASeller):
136
- seller_jobs += f"#{index + 1} {str(job)} \n"
137
-
138
- output = (
139
- f"As Buyer:\n{buyer_jobs}\n"
140
- f"As Seller:\n{seller_jobs}\n"
141
- )
142
- return output
143
-
93
+ buyer_jobs = "\n".join([f"#{i+1} {str(job)}" for i, job in enumerate(self.asABuyer)])
94
+ seller_jobs = "\n".join([f"#{i+1} {str(job)}" for i, job in enumerate(self.asASeller)])
95
+ return f"As Buyer:\n{buyer_jobs}\n\nAs Seller:\n{seller_jobs}"
144
96
 
145
- @dataclass
146
- class AcpJobs:
97
+ class AcpJobs(BaseModel):
147
98
  active: AcpJobsSection
148
- completed: List[AcpJob]
149
- cancelled: List[AcpJob]
99
+ completed: List[IAcpJob]
100
+ cancelled: List[IAcpJob]
150
101
 
151
102
  def __str__(self) -> str:
152
- output = (
103
+ return (
153
104
  f"💻 Jobs\n"
154
105
  f"🌕 Active Jobs:\n{self.active}\n"
155
106
  f"🟢 Completed:\n{self.completed}\n"
156
- f"🔴 Cancelled:\n{self.cancelled}\n"
107
+ f"🔴 Cancelled:\n{self.cancelled}"
157
108
  )
158
- return output
159
-
160
-
161
- @dataclass
162
- class AcpInventory:
109
+
110
+ class AcpInventory(BaseModel):
163
111
  acquired: List[IInventory]
164
112
  produced: Optional[List[IInventory]]
165
113
 
166
114
  def __str__(self) -> str:
167
- output = (
115
+ return (
168
116
  f"💼 Inventory\n"
169
117
  f"Acquired: {self.acquired}\n"
170
- f"Produced: {self.produced}\n"
118
+ f"Produced: {self.produced}"
171
119
  )
172
- return output
173
-
174
120
 
175
- @dataclass
176
- class AcpState:
121
+ class AcpState(BaseModel):
177
122
  inventory: AcpInventory
178
123
  jobs: AcpJobs
179
124
 
180
125
  def __str__(self) -> str:
181
- output = (
182
- f"🤖 Agent State".center(50, '=') + "\n" + \
183
- f"{str(self.inventory)}\n" + \
184
- f"{str(self.jobs)}\n" + \
185
- f"State End".center(50, '=') + "\n"
126
+ return (
127
+ f"🤖 Agent State".center(50, '=') + "\n"
128
+ f"{str(self.inventory)}\n"
129
+ f"{str(self.jobs)}\n"
130
+ f"State End".center(50, '=')
186
131
  )
187
- return output
132
+
133
+ def to_serializable_dict(obj: Any) -> Any:
134
+ if isinstance(obj, Enum):
135
+ return obj.value
136
+ elif isinstance(obj, dict):
137
+ return {k: to_serializable_dict(v) for k, v in obj.items()}
138
+ elif isinstance(obj, list):
139
+ return [to_serializable_dict(item) for item in obj]
140
+ elif hasattr(obj, "__dict__"):
141
+ return {
142
+ k: to_serializable_dict(v)
143
+ for k, v in vars(obj).items()
144
+ if not k.startswith("_")
145
+ }
146
+ else:
147
+ return obj
148
+
@@ -1,30 +1,20 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: acp-plugin-gamesdk
3
- Version: 0.1.24
3
+ Version: 0.1.26
4
4
  Summary: ACP Plugin for Python SDK for GAME by Virtuals
5
5
  Author: Steven Lee Soon Fatt
6
6
  Author-email: steven@virtuals.io
7
- Requires-Python: >=3.9,<3.13
7
+ Requires-Python: >=3.10,<3.13
8
8
  Classifier: Programming Language :: Python :: 3
9
- Classifier: Programming Language :: Python :: 3.9
10
9
  Classifier: Programming Language :: Python :: 3.10
11
10
  Classifier: Programming Language :: Python :: 3.11
12
11
  Classifier: Programming Language :: Python :: 3.12
13
- Requires-Dist: aiohttp (>=3.11.14,<4.0.0)
14
12
  Requires-Dist: dacite (>=1.9.2,<2.0.0)
15
- Requires-Dist: eth-account (>=0.13.6,<0.14.0)
16
- Requires-Dist: eth-typing (>=5.2.0,<6.0.0)
17
- Requires-Dist: eth-utils (>=5.2.0,<6.0.0)
18
13
  Requires-Dist: game-sdk (>=0.1.5)
19
- Requires-Dist: pydantic (>=2.10.6,<3.0.0)
20
14
  Requires-Dist: python-dotenv (>=1.1.0,<2.0.0)
21
- Requires-Dist: python-socketio (>=5.11.1,<6.0.0)
22
- Requires-Dist: requests (>=2.32.3,<3.0.0)
23
15
  Requires-Dist: rich (>=13.9.4,<15.0.0)
24
- Requires-Dist: twitter-plugin-gamesdk (>=0.2.2,<0.2.4)
25
- Requires-Dist: virtuals-sdk (>=0.1.6,<0.2.0)
26
- Requires-Dist: web3 (>=7.9.0,<8.0.0)
27
- Requires-Dist: websocket-client (>=1.7.0,<2.0.0)
16
+ Requires-Dist: twitter-plugin-gamesdk (>=0.2.10,<0.3.0)
17
+ Requires-Dist: virtuals-acp (>=0.1.13)
28
18
  Description-Content-Type: text/markdown
29
19
 
30
20
  # ACP Plugin
@@ -0,0 +1,6 @@
1
+ acp_plugin_gamesdk/acp_plugin.py,sha256=0mh1lFLIm-BN9mlHQif1yIpyNfB7J1_KZpu7OV4vLW4,26673
2
+ acp_plugin_gamesdk/env.py,sha256=_1UQtONrfC9hkO2yGYfxawEk71nF_0GXC_79a0RVyv0,1367
3
+ acp_plugin_gamesdk/interface.py,sha256=4IdKjTJMH8Rmoz55Jv0vCgdu4wqgw3wqqK1C8JBHldM,4262
4
+ acp_plugin_gamesdk-0.1.26.dist-info/METADATA,sha256=qWf864Ss4sNNBH4m26QXQj2flZTwo6N7OLUmIb0jotE,12345
5
+ acp_plugin_gamesdk-0.1.26.dist-info/WHEEL,sha256=XbeZDeTWKc1w7CSIyre5aMDU_-PohRwTQceYnisIYYY,88
6
+ acp_plugin_gamesdk-0.1.26.dist-info/RECORD,,
@@ -1,307 +0,0 @@
1
- import time
2
- import traceback
3
- from datetime import datetime, timezone
4
- from typing import List, Optional
5
-
6
- import requests
7
- from dacite import Config, from_dict
8
- from web3 import Web3
9
-
10
- from acp_plugin_gamesdk.acp_token import AcpToken, MemoType
11
- from acp_plugin_gamesdk.interface import (
12
- AcpAgent,
13
- AcpJobPhases,
14
- AcpJobPhasesDesc,
15
- AcpOffering,
16
- AcpState,
17
- )
18
-
19
-
20
- class AcpClient:
21
- def __init__(self, api_key: str, acp_token: AcpToken):
22
- self.api_key = api_key
23
- self.acp_token = acp_token
24
- self.web3 = Web3()
25
-
26
- self.acp_base_url = self.acp_token.acp_base_url
27
- self.base_url = self.acp_token.game_api_url + "/acp"
28
-
29
- @property
30
- def agent_wallet_address(self) -> str:
31
- return self.acp_token.get_agent_wallet_address()
32
-
33
- def get_state(self) -> AcpState:
34
- response = requests.get(
35
- f"{self.base_url}/states/{self.agent_wallet_address}",
36
- headers={"x-api-key": self.api_key}
37
- )
38
- payload = response.json()
39
- result = from_dict(data_class=AcpState, data=payload,
40
- config=Config(type_hooks={AcpJobPhasesDesc: AcpJobPhasesDesc}))
41
- return result
42
-
43
- def browse_agents(
44
- self,
45
- cluster: Optional[str] = None,
46
- query: Optional[str] = None,
47
- rerank: Optional[bool] = True,
48
- top_k: Optional[int] = 1,
49
- ) -> List[AcpAgent]:
50
-
51
- url = f"{self.acp_base_url}/agents"
52
-
53
- params = {
54
- "search": query,
55
- "filters[cluster]": cluster,
56
- "filters[walletAddress][$notIn]": self.agent_wallet_address,
57
- "rerank": "true" if rerank else "false",
58
- "top_k": top_k,
59
- }
60
- response = requests.get(url, params=params)
61
-
62
- if response.status_code != 200:
63
- raise Exception(
64
- f"Error occured in browse_agents function. Failed to browse agents.\n"
65
- f"Response status code: {response.status_code}\n"
66
- f"Response description: {response.text}\n"
67
- )
68
-
69
- response_json = response.json()
70
-
71
- result = []
72
-
73
- for agent in response_json.get("data", []):
74
- if agent["offerings"]:
75
- offerings = [AcpOffering(name=offering["name"], price=offering["price"]) for offering in
76
- agent["offerings"]]
77
- else:
78
- offerings = None
79
-
80
- result.append(
81
- AcpAgent(
82
- id=agent["id"],
83
- name=agent["name"],
84
- twitter_handle=agent["twitterHandle"],
85
- description=agent["description"],
86
- wallet_address=agent["walletAddress"],
87
- offerings=offerings,
88
- )
89
- )
90
-
91
- return result
92
-
93
- def create_job(
94
- self,
95
- provider_address: str,
96
- price: float,
97
- job_description: str,
98
- evaluator_address: str,
99
- expired_at: datetime,
100
- ) -> int:
101
- tx_result = self.acp_token.create_job(
102
- provider_address=provider_address,
103
- evaluator_address=evaluator_address,
104
- expire_at=expired_at
105
- )
106
-
107
- job_id = None
108
- retry_count = 3
109
- retry_delay = 3
110
-
111
- time.sleep(retry_delay)
112
- for attempt in range(retry_count):
113
- try:
114
- response = self.acp_token.validate_transaction(tx_result["txHash"])
115
- data = response.get("data", {})
116
- if not data:
117
- raise Exception("Invalid tx_hash!")
118
-
119
- if data.get("status") == "retry":
120
- raise Exception("Transaction failed, retrying...")
121
-
122
- if data.get("status") == "failed":
123
- break
124
-
125
- if data.get("status") == "success":
126
- job_id = int(data.get("result").get("jobId"))
127
-
128
- if job_id is not None and job_id != "":
129
- break
130
-
131
- except Exception as e:
132
- print(f"Error in create_job function: {e}")
133
- print(traceback.format_exc())
134
- if attempt < retry_count - 1:
135
- time.sleep(retry_delay)
136
- else:
137
- raise
138
-
139
- if job_id is None or job_id == "":
140
- raise Exception("Failed to create job")
141
-
142
- self.acp_token.create_memo(
143
- job_id=job_id,
144
- content=job_description,
145
- memo_type=MemoType.MESSAGE,
146
- is_secured=False,
147
- next_phase=AcpJobPhases.NEGOTIATION
148
- )
149
-
150
- payload = {
151
- "jobId": job_id,
152
- "clientAddress": self.agent_wallet_address,
153
- "providerAddress": provider_address,
154
- "description": job_description,
155
- "price": price,
156
- "expiredAt": expired_at.astimezone(timezone.utc).isoformat(),
157
- "evaluatorAddress": evaluator_address
158
- }
159
-
160
- requests.post(
161
- self.base_url,
162
- json=payload,
163
- headers={
164
- "Accept": "application/json",
165
- "Content-Type": "application/json",
166
- "x-api-key": self.api_key
167
- }
168
- )
169
-
170
- return job_id
171
-
172
- def response_job(self, job_id: int, accept: bool, memo_id: int, reasoning: str):
173
- if accept:
174
- self.acp_token.sign_memo(memo_id, accept, reasoning)
175
- time.sleep(5)
176
-
177
- return self.acp_token.create_memo(
178
- job_id=job_id,
179
- content=f"Job {job_id} accepted. {reasoning}",
180
- memo_type=MemoType.MESSAGE,
181
- is_secured=False,
182
- next_phase=AcpJobPhases.TRANSACTION
183
- )
184
- else:
185
- return self.acp_token.create_memo(
186
- job_id=job_id,
187
- content=f"Job {job_id} rejected. {reasoning}",
188
- memo_type=MemoType.MESSAGE,
189
- is_secured=False,
190
- next_phase=AcpJobPhases.REJECTED
191
- )
192
-
193
- def make_payment(self, job_id: int, amount: float, memo_id: int, reason: str):
194
- # Convert amount to Wei (smallest ETH unit)
195
- amount_wei = self.web3.to_wei(amount, 'ether')
196
-
197
- self.acp_token.set_budget(job_id, amount_wei)
198
- time.sleep(5)
199
- self.acp_token.approve_allowance(amount_wei)
200
- time.sleep(5)
201
- self.acp_token.sign_memo(memo_id, True, reason)
202
- time.sleep(5)
203
- return self.acp_token.create_memo(
204
- job_id=job_id,
205
- content=f"Payment of {amount} made {reason}",
206
- memo_type=MemoType.MESSAGE,
207
- is_secured=False,
208
- next_phase=AcpJobPhases.EVALUATION
209
- )
210
-
211
- def deliver_job(self, job_id: int, deliverable: str):
212
- return self.acp_token.create_memo(
213
- job_id=job_id,
214
- content=deliverable,
215
- memo_type=MemoType.MESSAGE,
216
- is_secured=False,
217
- next_phase=AcpJobPhases.COMPLETED
218
- )
219
-
220
- def add_tweet(self, job_id: int, tweet_id: str, content: str):
221
- payload = {
222
- "tweetId": tweet_id,
223
- "content": content
224
- }
225
-
226
- response = requests.post(
227
- f"{self.base_url}/{job_id}/tweets/{self.agent_wallet_address}",
228
- json=payload,
229
- headers={
230
- "Accept": "application/json",
231
- "Content-Type": "application/json",
232
- "x-api-key": self.api_key
233
- }
234
- )
235
-
236
- if response.status_code != 200 and response.status_code != 201:
237
- raise Exception(
238
- f"Error occured in add_tweet function. Failed to add tweet.\n"
239
- f"Response status code: {response.status_code}\n"
240
- f"Response description: {response.text}\n"
241
- )
242
-
243
- return response.json()
244
-
245
- def reset_state(self) -> None:
246
- response = requests.delete(
247
- f"{self.base_url}/states/{self.agent_wallet_address}",
248
- headers={"x-api-key": self.api_key}
249
- )
250
-
251
- if response.status_code not in [200, 204]:
252
- raise Exception(
253
- f"Error occured in reset_state function. Failed to reset state\n"
254
- f"Response status code: {response.status_code}\n"
255
- f"Response description: {response.text}\n"
256
- )
257
-
258
- def delete_completed_job(self, job_id: int) -> None:
259
- response = requests.delete(
260
- f"{self.base_url}/{job_id}/wallet/{self.agent_wallet_address}",
261
- headers={"x-api-key": self.api_key}
262
- )
263
-
264
- if response.status_code not in [200, 204]:
265
- raise Exception(
266
- f"Error occurred in delete_completed_job function. Failed to delete job.\n"
267
- f"Response status code: {response.status_code}\n"
268
- f"Response description: {response.text}\n"
269
- )
270
-
271
- def get_agent_by_wallet_address(self, wallet_address: str) -> Optional[AcpAgent]:
272
- url = f"{self.acp_base_url}/agents?filters[walletAddress]={wallet_address}"
273
-
274
- response = requests.get(
275
- url,
276
- )
277
-
278
- if response.status_code != 200:
279
- raise Exception(
280
- f"Failed to get agent: {response.status_code} {response.text}"
281
- )
282
-
283
- response_json = response.json()
284
-
285
- result = []
286
-
287
- if len(response_json.get("data", [])) == 0:
288
- return None
289
-
290
- for agent in response_json.get("data", []):
291
- if agent["offerings"]:
292
- offerings = [AcpOffering(name=offering["name"], price=offering["price"]) for offering in
293
- agent["offerings"]]
294
- else:
295
- offerings = None
296
-
297
- result.append(
298
- AcpAgent(
299
- id=agent["id"],
300
- name=agent["name"],
301
- twitter_handle=agent["twitterHandle"],
302
- description=agent["description"],
303
- wallet_address=agent["walletAddress"],
304
- offerings=offerings,
305
- )
306
- )
307
- return result[0]