acp-plugin-gamesdk 0.1.1__tar.gz → 0.1.3__tar.gz

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,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: acp-plugin-gamesdk
3
- Version: 0.1.1
3
+ Version: 0.1.3
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
@@ -109,7 +109,7 @@ acp_plugin = AcpPlugin(
109
109
  ```
110
110
 
111
111
  > Note:
112
- > - Your ACP token for your buyer and seller should be different.
112
+ > - Your agent wallet address for your buyer and seller should be different.
113
113
  > - Speak to a DevRel (Celeste/John) to get a GAME Dev API key
114
114
 
115
115
  > To Whitelist your Wallet:
@@ -84,7 +84,7 @@ acp_plugin = AcpPlugin(
84
84
  ```
85
85
 
86
86
  > Note:
87
- > - Your ACP token for your buyer and seller should be different.
87
+ > - Your agent wallet address for your buyer and seller should be different.
88
88
  > - Speak to a DevRel (Celeste/John) to get a GAME Dev API key
89
89
 
90
90
  > To Whitelist your Wallet:
@@ -2,20 +2,18 @@ from datetime import datetime, timedelta
2
2
  from typing import List, Optional
3
3
  from web3 import Web3
4
4
  import requests
5
- import sys
6
- import os
7
- sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../../../")))
8
- from .interface import AcpAgent, AcpJobPhases, AcpState
9
- from .acp_token import AcpToken, MemoType
5
+ from acp_plugin_gamesdk.interface import AcpAgent, AcpJobPhases, AcpOffering, AcpState
6
+ from acp_plugin_gamesdk.acp_token import AcpToken, MemoType
10
7
  import time
11
8
 
12
9
 
13
10
  class AcpClient:
14
- def __init__(self, api_key: str, acp_token: AcpToken):
11
+ def __init__(self, api_key: str, acp_token: AcpToken, acp_base_url: Optional[str] = None):
15
12
  self.base_url = "https://sdk-dev.game.virtuals.io/acp"
16
13
  self.api_key = api_key
17
14
  self.acp_token = acp_token
18
15
  self.web3 = Web3()
16
+ self.acp_base_url = acp_base_url if acp_base_url else "https://acpx-staging.virtuals.io/api"
19
17
 
20
18
  @property
21
19
  def agent_wallet_address(self) -> str:
@@ -29,16 +27,17 @@ class AcpClient:
29
27
  return response.json()
30
28
 
31
29
  def browse_agents(self, cluster: Optional[str] = None, query: Optional[str] = None) -> List[AcpAgent]:
32
- url = "https://acpx.virtuals.gg/api/agents"
30
+ url = f"{self.acp_base_url}/agents"
33
31
 
34
- params = {}
35
32
  if query:
36
- params["search"] = query
33
+ url += f"?search={requests.utils.quote(query)}"
37
34
 
38
35
  if cluster:
39
- params["filters[cluster]"] = cluster
36
+ # Add & if there's already a parameter, otherwise add ?
37
+ separator = "&" if query else "?"
38
+ url += f"{separator}filters[cluster]={requests.utils.quote(cluster)}"
40
39
 
41
- response = requests.get(url, params=params)
40
+ response = requests.get(url)
42
41
 
43
42
  if response.status_code != 200:
44
43
  raise Exception(f"Failed to browse agents: {response.text}")
@@ -49,24 +48,25 @@ class AcpClient:
49
48
 
50
49
  for agent in response_json.get("data", []):
51
50
  result.append(
52
- {
53
- "id": agent["id"],
54
- "name": agent["name"],
55
- "description": agent["description"],
56
- "walletAddress": agent["walletAddress"]
57
- }
51
+ AcpAgent(
52
+ id=agent["id"],
53
+ name=agent["name"],
54
+ description=agent["description"],
55
+ wallet_address=agent["walletAddress"],
56
+ offerings=[AcpOffering(name=offering["name"], price=offering["price"]) for offering in agent["offerings"]]
57
+ )
58
58
  )
59
59
 
60
60
  return result
61
61
 
62
62
  def create_job(self, provider_address: str, price: float, job_description: str) -> int:
63
63
  expire_at = datetime.now() + timedelta(days=1)
64
-
65
64
  tx_result = self.acp_token.create_job(
66
65
  provider_address=provider_address,
67
66
  evaluator_address=provider_address,
68
67
  expire_at=expire_at
69
68
  )
69
+
70
70
  job_id = None
71
71
  retry_count = 3
72
72
  retry_delay = 3
@@ -74,7 +74,7 @@ class AcpClient:
74
74
  time.sleep(retry_delay)
75
75
  for attempt in range(retry_count):
76
76
  try:
77
- response = self.acp_token.await_transaction(tx_result["txHash"])
77
+ response = self.acp_token.validate_transaction(tx_result["txHash"])
78
78
  data = response.get("data", {})
79
79
  if not data:
80
80
  raise Exception("Invalid tx_hash!")
@@ -133,7 +133,6 @@ class AcpClient:
133
133
  def response_job(self, job_id: int, accept: bool, memo_id: int, reasoning: str):
134
134
  if accept:
135
135
  self.acp_token.sign_memo(memo_id, accept, reasoning)
136
-
137
136
  time.sleep(5)
138
137
 
139
138
  return self.acp_token.create_memo(
@@ -162,7 +161,7 @@ class AcpClient:
162
161
  time.sleep(5)
163
162
  return self.acp_token.sign_memo(memo_id, True, reason)
164
163
 
165
- def deliver_job(self, job_id: int, deliverable: str, memo_id: int, reason: str):
164
+ def deliver_job(self, job_id: int, deliverable: str):
166
165
  return self.acp_token.create_memo(
167
166
  job_id=int(job_id),
168
167
  content=deliverable,
@@ -172,17 +171,6 @@ class AcpClient:
172
171
  )
173
172
 
174
173
  def add_tweet(self, job_id: int, tweet_id: str, content: str):
175
- """
176
- Add a tweet to a job.
177
-
178
- Args:
179
- job_id: The ID of the job
180
- tweet_id: The ID of the tweet
181
- content: The content of the tweet
182
-
183
- Raises:
184
- Exception: If the request fails
185
- """
186
174
  payload = {
187
175
  "tweetId": tweet_id,
188
176
  "content": content
@@ -5,13 +5,11 @@ from datetime import datetime
5
5
 
6
6
  from game_sdk.game.agent import WorkerConfig
7
7
  from game_sdk.game.custom_types import Function, FunctionResultStatus
8
- from twitter_plugin_gamesdk.twitter_plugin import TwitterPlugin
9
-
10
8
  from twitter_plugin_gamesdk.twitter_plugin import TwitterPlugin
11
9
  from twitter_plugin_gamesdk.game_twitter_plugin import GameTwitterPlugin
12
- from .acp_client import AcpClient
13
- from .acp_token import AcpToken
14
- from .interface import AcpJobPhasesDesc, IInventory
10
+ from acp_plugin_gamesdk.acp_client import AcpClient
11
+ from acp_plugin_gamesdk.acp_token import AcpToken
12
+ from acp_plugin_gamesdk.interface import AcpJobPhasesDesc, IInventory
15
13
 
16
14
  @dataclass
17
15
  class AcpPluginOptions:
@@ -19,12 +17,13 @@ class AcpPluginOptions:
19
17
  acp_token_client: AcpToken
20
18
  twitter_plugin: TwitterPlugin | GameTwitterPlugin = None
21
19
  cluster: Optional[str] = None
20
+ acp_base_url: Optional[str] = None
21
+
22
22
 
23
23
  class AcpPlugin:
24
24
  def __init__(self, options: AcpPluginOptions):
25
25
  print("Initializing AcpPlugin")
26
- self.acp_client = AcpClient(options.api_key, options.acp_token_client)
27
-
26
+ self.acp_client = AcpClient(options.api_key, options.acp_token_client, options.acp_base_url)
28
27
  self.id = "acp_worker"
29
28
  self.name = "ACP Worker"
30
29
  self.description = """
@@ -45,6 +44,9 @@ class AcpPlugin:
45
44
  self.cluster = options.cluster
46
45
  self.twitter_plugin = options.twitter_plugin
47
46
  self.produced_inventory: List[IInventory] = []
47
+ self.acp_base_url = options.acp_base_url if options.acp_base_url else "https://acpx-staging.virtuals.io/api"
48
+
49
+
48
50
 
49
51
  def add_produce_item(self, item: IInventory) -> None:
50
52
  self.produced_inventory.append(item)
@@ -108,9 +110,9 @@ class AcpPlugin:
108
110
 
109
111
  if not agents:
110
112
  return FunctionResultStatus.FAILED, "No other trading agents found in the system. Please try again later when more agents are available.", {}
111
-
113
+
112
114
  return FunctionResultStatus.DONE, json.dumps({
113
- "availableAgents": agents,
115
+ "availableAgents": [{"id": agent.id, "name": agent.name, "description": agent.description, "wallet_address": agent.wallet_address, "offerings": [{"name": offering.name, "price": offering.price} for offering in agent.offerings]} for agent in agents],
114
116
  "totalAgentsFound": len(agents),
115
117
  "timestamp": datetime.now().timestamp(),
116
118
  "note": "Use the walletAddress when initiating a job with your chosen trading partner."
@@ -439,8 +441,6 @@ class AcpPlugin:
439
441
  self.acp_client.deliver_job(
440
442
  int(jobId),
441
443
  json.dumps(deliverable),
442
- job["memo"][0]["id"],
443
- reasoning
444
444
  )
445
445
 
446
446
  if (self.twitter_plugin is not None):
@@ -4,10 +4,7 @@ from typing import Optional, Tuple, TypedDict
4
4
  from datetime import datetime
5
5
  from web3 import Web3
6
6
  from eth_account import Account
7
- import sys
8
- import os
9
- sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../../../")))
10
- from .acp_token_abi import ACP_TOKEN_ABI
7
+ from acp_plugin_gamesdk.acp_token_abi import ACP_TOKEN_ABI
11
8
  import requests
12
9
  from eth_account.messages import encode_defunct
13
10
  import json
@@ -48,9 +45,9 @@ class AcpToken:
48
45
  wallet_private_key: str,
49
46
  agent_wallet_address: str,
50
47
  network_url: str,
48
+ acp_base_url: Optional[str] = None,
51
49
  contract_address: str = "0x2422c1c43451Eb69Ff49dfD39c4Dc8C5230fA1e6",
52
50
  virtuals_token_address: str = "0xbfAB80ccc15DF6fb7185f9498d6039317331846a",
53
- base_url: str = "https://acpx.virtuals.gg/api"
54
51
  ):
55
52
  self.web3 = Web3(Web3.HTTPProvider(network_url))
56
53
  self.account = Account.from_key(wallet_private_key)
@@ -88,21 +85,19 @@ class AcpToken:
88
85
  "type": "function"
89
86
  }]
90
87
  )
91
- self.base_url = base_url
92
-
88
+ self.acp_base_url = acp_base_url if acp_base_url else "https://acpx-staging.virtuals.io/api"
93
89
  def get_agent_wallet_address(self) -> str:
94
90
  return self.agent_wallet_address
95
91
 
96
92
  def get_contract_address(self) -> str:
97
93
  return self.contract_address
98
94
 
99
- def await_transaction(self, hash_value: str) -> object:
95
+ def validate_transaction(self, hash_value: str) -> object:
100
96
  try:
101
- response = requests.post(f"{self.base_url}/acp-agent-wallets/trx-result", json={"userOpHash": hash_value})
97
+ response = requests.post(f"{self.acp_base_url}/acp-agent-wallets/trx-result", json={"userOpHash": hash_value})
102
98
  return response.json()
103
99
  except Exception as error:
104
- print(f"Error getting job_id: {error}")
105
- raise Exception("Failed to get job_id")
100
+ raise Exception(f"Failed to get job_id {error}")
106
101
 
107
102
  def create_job(
108
103
  self,
@@ -128,15 +123,18 @@ class AcpToken:
128
123
  }
129
124
 
130
125
  # Submit to custom API
131
- api_url = f"{self.base_url}/acp-agent-wallets/transactions"
126
+ api_url = f"{self.acp_base_url}/acp-agent-wallets/transactions"
132
127
  response = requests.post(api_url, json=payload)
133
-
128
+
129
+
130
+ if response.json().get("error"):
131
+ raise Exception(f"Failed to create job {response.json().get('error').get('status')}, Message: {response.json().get('error').get('message')}")
132
+
134
133
  # Return transaction hash or response ID
135
- return { "txHash": response.json().get("data", {}).get("userOpHash", "")}
134
+ return {"txHash": response.json().get("data", {}).get("userOpHash", "")}
136
135
 
137
136
  except Exception as error:
138
- print(f"Error creating job: {error}")
139
- raise Exception("Failed to create job")
137
+ raise Exception(f"{error}")
140
138
 
141
139
  def approve_allowance(self, price_in_wei: int) -> str:
142
140
  try:
@@ -152,16 +150,15 @@ class AcpToken:
152
150
  "signature": signature
153
151
  }
154
152
 
155
- api_url = f"{self.base_url}/acp-agent-wallets/transactions"
153
+ api_url = f"{self.acp_base_url}/acp-agent-wallets/transactions"
156
154
  response = requests.post(api_url, json=payload)
157
155
 
158
- if (response.status_code != 200):
159
- raise Exception("Failed to approve allowance")
156
+ if (response.json().get("error")):
157
+ raise Exception(f"Failed to approve allowance {response.json().get('error').get('status')}, Message: {response.json().get('error').get('message')}")
160
158
 
161
159
  return response.json()
162
160
  except Exception as error:
163
- print(f"Error approving allowance: {error}")
164
- raise Exception("Failed to approve allowance")
161
+ raise Exception(f"{error}")
165
162
 
166
163
  def create_memo(
167
164
  self,
@@ -185,19 +182,19 @@ class AcpToken:
185
182
  "signature": signature
186
183
  }
187
184
 
188
- api_url = f"{self.base_url}/acp-agent-wallets/transactions"
185
+ api_url = f"{self.acp_base_url}/acp-agent-wallets/transactions"
189
186
  response = requests.post(api_url, json=payload)
190
187
 
191
- if (response.status_code != 200):
192
- raise Exception("Failed to create memo")
188
+ if (response.json().get("error")):
189
+ raise Exception(f"Failed to create memo {response.json().get('error').get('status')}, Message: {response.json().get('error').get('message')}")
193
190
 
194
191
  return { "txHash": response.json().get("txHash", response.json().get("id", "")), "memoId": response.json().get("memoId", "")}
195
192
  except Exception as error:
196
- print(f"Error creating memo: {error}")
193
+ print(f"{error}")
197
194
  retries -= 1
198
195
  time.sleep(2 * (3 - retries))
199
196
 
200
- raise Exception("Failed to create memo")
197
+ raise Exception(f"{error}")
201
198
 
202
199
  def _sign_transaction(self, method_name: str, args: list, contract_address: Optional[str] = None) -> Tuple[dict, str]:
203
200
  if contract_address:
@@ -240,20 +237,20 @@ class AcpToken:
240
237
  "signature": signature
241
238
  }
242
239
 
243
- api_url = f"{self.base_url}/acp-agent-wallets/transactions"
240
+ api_url = f"{self.acp_base_url}/acp-agent-wallets/transactions"
244
241
  response = requests.post(api_url, json=payload)
245
242
 
246
- if (response.status_code != 200):
247
- raise Exception("Failed to sign memo")
243
+ if (response.json().get("error")):
244
+ raise Exception(f"Failed to sign memo {response.json().get('error').get('status')}, Message: {response.json().get('error').get('message')}")
248
245
 
249
- return response.json().get("txHash", response.json().get("id", ""))
246
+ return response.json()
250
247
 
251
248
  except Exception as error:
252
- print(f"Error signing memo: {error}")
249
+ print(f"{error}")
253
250
  retries -= 1
254
251
  time.sleep(2 * (3 - retries))
255
252
 
256
- raise Exception("Failed to sign memo")
253
+ raise Exception(f"Failed to sign memo {error}")
257
254
 
258
255
  def set_budget(self, job_id: int, budget: int) -> str:
259
256
  try:
@@ -268,16 +265,15 @@ class AcpToken:
268
265
  "signature": signature
269
266
  }
270
267
 
271
- api_url = f"{self.base_url}/acp-agent-wallets/transactions"
268
+ api_url = f"{self.acp_base_url}/acp-agent-wallets/transactions"
272
269
  response = requests.post(api_url, json=payload)
273
270
 
274
- if (response.status_code != 200):
275
- raise Exception("Failed to set budget")
271
+ if (response.json().get("error")):
272
+ raise Exception(f"Failed to set budget {response.json().get('error').get('status')}, Message: {response.json().get('error').get('message')}")
276
273
 
277
274
  return response.json()
278
275
  except Exception as error:
279
- print(f"Error setting budget: {error}")
280
- raise Exception("Failed to set budget")
276
+ raise Exception(f"{error}")
281
277
 
282
278
  def get_job(self, job_id: int) -> Optional[IJob]:
283
279
  try:
@@ -298,8 +294,7 @@ class AcpToken:
298
294
  'evaluatorCount': int(job_data[8])
299
295
  }
300
296
  except Exception as error:
301
- print(f"Error getting job: {error}")
302
- raise Exception("Failed to get job")
297
+ raise Exception(f"{error}")
303
298
 
304
299
  def get_memo_by_job(
305
300
  self,
@@ -315,8 +310,7 @@ class AcpToken:
315
310
  else:
316
311
  return memos[-1] if memos else None
317
312
  except Exception as error:
318
- print(f"Error getting memo: {error}")
319
- raise Exception("Failed to get memo")
313
+ raise Exception(f"Failed to get memo by job {error}")
320
314
 
321
315
  def get_memos_for_phase(
322
316
  self,
@@ -330,5 +324,4 @@ class AcpToken:
330
324
  target_memos = [m for m in memos if m['nextPhase'] == target_phase]
331
325
  return target_memos[-1] if target_memos else None
332
326
  except Exception as error:
333
- print(f"Error getting memos: {error}")
334
- raise Exception("Failed to get memos")
327
+ raise Exception(f"Failed to get memos for phase {error}")
@@ -2,13 +2,18 @@ from dataclasses import dataclass
2
2
  from enum import IntEnum, Enum
3
3
  from typing import List, Dict
4
4
 
5
+ @dataclass
6
+ class AcpOffering:
7
+ name: str
8
+ price: float
5
9
  @dataclass
6
10
  class AcpAgent:
7
11
  id: str
8
12
  name: str
9
13
  description: str
10
14
  wallet_address: str
11
-
15
+ offerings: List[AcpOffering]
16
+
12
17
  class AcpJobPhases(IntEnum):
13
18
  REQUEST = 0
14
19
  NEGOTIATION = 1
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "acp-plugin-gamesdk"
3
- version = "0.1.1"
3
+ version = "0.1.3"
4
4
  description = "ACP Plugin for Python SDK for GAME by Virtuals"
5
5
  authors = ["Steven Lee Soon Fatt <steven@virtuals.io>"]
6
6
  readme = "README.md"