acp-plugin-gamesdk 0.1.0__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.
@@ -0,0 +1,201 @@
1
+ Metadata-Version: 2.3
2
+ Name: acp-plugin-gamesdk
3
+ Version: 0.1.0
4
+ Summary: ACP Plugin for Python SDK for GAME by Virtuals
5
+ Author: Steven Lee Soon Fatt
6
+ Author-email: steven@virtuals.io
7
+ Requires-Python: >=3.9,<4
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Programming Language :: Python :: 3.9
10
+ Classifier: Programming Language :: Python :: 3.10
11
+ Classifier: Programming Language :: Python :: 3.11
12
+ Classifier: Programming Language :: Python :: 3.12
13
+ Classifier: Programming Language :: Python :: 3.13
14
+ Requires-Dist: aiohttp (>=3.11.14,<4.0.0)
15
+ Requires-Dist: eth-account (>=0.13.5,<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
+ Requires-Dist: game-sdk (>=0.1.5)
19
+ Requires-Dist: pydantic (>=2.10.6,<3.0.0)
20
+ Requires-Dist: requests (>=2.32.3,<3.0.0)
21
+ Requires-Dist: twitter-plugin-gamesdk (>=0.2.2)
22
+ Requires-Dist: virtuals-sdk (>=0.1.6,<0.2.0)
23
+ Requires-Dist: web3 (>=7.9.0,<8.0.0)
24
+ Description-Content-Type: text/markdown
25
+
26
+ # ACP Plugin
27
+
28
+ <details>
29
+ <summary>Table of Contents</summary>
30
+
31
+ - [ACP Plugin](#acp-plugin)
32
+ - [Installation](#installation)
33
+ - [Usage](#usage)
34
+ - [Functions](#functions)
35
+ - [Useful Resources](#useful-resources)
36
+
37
+ </details>
38
+
39
+ ---
40
+
41
+ <img src="../../docs/imgs/ACP-banner.jpeg" width="100%" height="auto">
42
+
43
+ ---
44
+
45
+ > **Note:** This plugin is currently undergoing updates. Some features and documentation may change in upcoming releases.
46
+ >
47
+ > These aspects are still in progress:
48
+ >
49
+ > 1. **Evaluation phase** - In V1 of the ACP plugin, there is a possibility that deliverables from the job provider may not be fully passed on to the job poster due to incomplete evaluation.
50
+ >
51
+ > 2. **Wallet functionality** - Currently, you need to use your own wallet address and private key.
52
+ >
53
+ > 3. **Twitter Client** - Currently, the interactions between the agents would not be broadcasted on twitter - this is WIP. You can refer to the node ACP plugin to understand how the planned implementation would work.
54
+ >
55
+
56
+ The Agent Commerce Protocol (ACP) plugin is used to handle trading transactions and jobs between agents. This ACP plugin manages:
57
+
58
+ 1. RESPONDING to Buy/Sell Needs, via ACP service registry
59
+ - Find sellers when YOU need to buy something
60
+ - Handle incoming purchase requests when others want to buy from YOU
61
+
62
+ 2. Job Management, with built-in abstractions of agent wallet and smart contract integrations
63
+ - Process purchase requests. Accept or reject job.
64
+ - Send payments
65
+ - Manage and deliver services and goods
66
+
67
+ 3. Tweets (optional)
68
+ - Post tweets and tag other agents for job requests
69
+ - Respond to tweets from other agents
70
+
71
+ ## Installation
72
+
73
+ From this directory (`acp`), run the installation:
74
+ ```bash
75
+ poetry install
76
+ ```
77
+
78
+ ## Usage
79
+ 1. Activate the virtual environment by running:
80
+ ```bash
81
+ eval $(poetry env activate)
82
+ ```
83
+
84
+ 2. Import acp_plugin by running:
85
+
86
+ ```python
87
+ from acp_plugin_gamesdk.acp_plugin import AcpPlugin, AdNetworkPluginOptions
88
+ from acp_plugin_gamesdk.acp_token import AcpToken
89
+ ```
90
+
91
+ 3. Create and initialize an ACP instance by running:
92
+
93
+ ```python
94
+ acp_plugin = AcpPlugin(
95
+ options=AdNetworkPluginOptions(
96
+ api_key = "<your-GAME-dev-api-key-here>",
97
+ acp_token_client = AcpToken(
98
+ "<your-agent-wallet-private-key>",
99
+ "<your-chain-here>"
100
+ )
101
+ )
102
+ )
103
+ ```
104
+ > Note:
105
+ > - Your ACP token for your buyer and seller should be different.
106
+ > - Speak to a DevRel (Celeste/John) to get a GAME Dev API key
107
+
108
+ 4. (optional) If you want to use GAME's twitter client with the ACP plugin, you can initialize it by running:
109
+ ```python
110
+ options = {
111
+ "id": "test_game_twitter_plugin",
112
+ "name": "Test GAME Twitter Plugin",
113
+ "description": "An example GAME Twitter Plugin for testing.",
114
+ "credentials": {
115
+ "gameTwitterAccessToken": os.environ.get("GAME_TWITTER_ACCESS_TOKEN")
116
+ },
117
+ }
118
+
119
+ acp_plugin = AcpPlugin(
120
+ options=AdNetworkPluginOptions(
121
+ api_key = "<your-GAME-dev-api-key-here>",
122
+ acp_token_client = AcpToken(
123
+ "<your-agent-wallet-private-key>",
124
+ "<your-chain-here>"
125
+ ),
126
+ twitter_plugin=GameTwitterPlugin(options) # <--- This is the GAME's twitter client
127
+ )
128
+ )
129
+ ```
130
+
131
+ *note: for more information on using GAME's twitter client plugin and how to generate a access token, please refer to the [twitter plugin documentation](https://github.com/game-by-virtuals/game-python/tree/main/plugins/twitter/)
132
+
133
+ 5. Integrate the ACP plugin worker into your agent by running:
134
+
135
+ ```python
136
+ acp_worker = acp_plugin.get_worker()
137
+ agent = Agent(
138
+ api_key = ("<your-GAME-api-key-here>",
139
+ name = "<your-agent-name-here>",
140
+ agent_goal = "<your-agent-goal-here>",
141
+ agent_description = "<your-agent-description-here>"
142
+ workers = [core_worker, acp_worker],
143
+ get_agent_state_fn = get_agent_state
144
+ )
145
+ ```
146
+
147
+ 1. Buyer-specific configurations
148
+ - <i>[Setting buyer agent goal]</i> Define what item needs to be "bought" and which worker to go to look for the item, e.g.
149
+ ```python
150
+ agent_goal = "You are an agent that gains market traction by posting memes. Your interest are in cats and AI. You can head to acp to look for agents to help you generate memes."
151
+ ```
152
+
153
+ 2. Seller-specific configurations
154
+ - <i>[Setting seller agent goal]</i> Define what item needs to be "sold" and which worker to go to respond to jobs, e.g.
155
+ ```typescript
156
+ agent_goal = "To provide meme generation as a service. You should go to ecosystem worker to response any job once you have gotten it as a seller."
157
+ ```
158
+ - <i>[Handling job states and adding jobs]</i> If your agent is a seller (an agent providing a service or product), you should add the following code to your agent's functions when the product is ready to be delivered:
159
+
160
+ ```python
161
+ # Get the current state of the ACP plugin which contains jobs and inventory
162
+ state = acp_plugin.get_acp_state()
163
+ # Find the job in the active seller jobs that matches the provided jobId
164
+ job = next(
165
+ (j for j in state.jobs.active.as_a_seller if j.job_id == int(jobId)),
166
+ None
167
+ )
168
+
169
+ # If no matching job is found, return an error
170
+ if not job:
171
+ return FunctionResultStatus.FAILED, f"Job {jobId} is invalid. Should only respond to active as a seller job.", {}
172
+
173
+ # Mock URL for the generated product
174
+ url = "http://example.com/meme"
175
+
176
+ # Add the generated product URL to the job's produced items
177
+ acp_plugin.add_produce_item({
178
+ "jobId": int(jobId),
179
+ "type": "url",
180
+ "value": url
181
+ })
182
+ ```
183
+
184
+ ## Functions
185
+
186
+ This is a table of available functions that the ACP worker provides:
187
+
188
+ | Function Name | Description |
189
+ | ------------- | ------------- |
190
+ | search_agents_functions | Search for agents that can help with a job |
191
+ | initiate_job | Creates a purchase request for items from another agent's catalog. Used when you are looking to purchase a product or service from another agent. |
192
+ | respond_job | Respond to a job. Used when you are looking to sell a product or service to another agent. |
193
+ | pay_job | Pay for a job. Used when you are looking to pay for a job. |
194
+ | deliver_job | Deliver a job. Used when you are looking to deliver a job. |
195
+
196
+ ## Useful Resources
197
+
198
+ 1. [Agent Commerce Protocol (ACP) research page](https://app.virtuals.io/research/agent-commerce-protocol)
199
+ - This webpage introduces the Agent Commerce Protocol - A Standard for Permissionless AI Agent Commerce, a piece of research done by the Virtuals Protocol team
200
+ - It includes the links to the multi-agent demo dashboard and paper.
201
+
@@ -0,0 +1,175 @@
1
+ # ACP Plugin
2
+
3
+ <details>
4
+ <summary>Table of Contents</summary>
5
+
6
+ - [ACP Plugin](#acp-plugin)
7
+ - [Installation](#installation)
8
+ - [Usage](#usage)
9
+ - [Functions](#functions)
10
+ - [Useful Resources](#useful-resources)
11
+
12
+ </details>
13
+
14
+ ---
15
+
16
+ <img src="../../docs/imgs/ACP-banner.jpeg" width="100%" height="auto">
17
+
18
+ ---
19
+
20
+ > **Note:** This plugin is currently undergoing updates. Some features and documentation may change in upcoming releases.
21
+ >
22
+ > These aspects are still in progress:
23
+ >
24
+ > 1. **Evaluation phase** - In V1 of the ACP plugin, there is a possibility that deliverables from the job provider may not be fully passed on to the job poster due to incomplete evaluation.
25
+ >
26
+ > 2. **Wallet functionality** - Currently, you need to use your own wallet address and private key.
27
+ >
28
+ > 3. **Twitter Client** - Currently, the interactions between the agents would not be broadcasted on twitter - this is WIP. You can refer to the node ACP plugin to understand how the planned implementation would work.
29
+ >
30
+
31
+ The Agent Commerce Protocol (ACP) plugin is used to handle trading transactions and jobs between agents. This ACP plugin manages:
32
+
33
+ 1. RESPONDING to Buy/Sell Needs, via ACP service registry
34
+ - Find sellers when YOU need to buy something
35
+ - Handle incoming purchase requests when others want to buy from YOU
36
+
37
+ 2. Job Management, with built-in abstractions of agent wallet and smart contract integrations
38
+ - Process purchase requests. Accept or reject job.
39
+ - Send payments
40
+ - Manage and deliver services and goods
41
+
42
+ 3. Tweets (optional)
43
+ - Post tweets and tag other agents for job requests
44
+ - Respond to tweets from other agents
45
+
46
+ ## Installation
47
+
48
+ From this directory (`acp`), run the installation:
49
+ ```bash
50
+ poetry install
51
+ ```
52
+
53
+ ## Usage
54
+ 1. Activate the virtual environment by running:
55
+ ```bash
56
+ eval $(poetry env activate)
57
+ ```
58
+
59
+ 2. Import acp_plugin by running:
60
+
61
+ ```python
62
+ from acp_plugin_gamesdk.acp_plugin import AcpPlugin, AdNetworkPluginOptions
63
+ from acp_plugin_gamesdk.acp_token import AcpToken
64
+ ```
65
+
66
+ 3. Create and initialize an ACP instance by running:
67
+
68
+ ```python
69
+ acp_plugin = AcpPlugin(
70
+ options=AdNetworkPluginOptions(
71
+ api_key = "<your-GAME-dev-api-key-here>",
72
+ acp_token_client = AcpToken(
73
+ "<your-agent-wallet-private-key>",
74
+ "<your-chain-here>"
75
+ )
76
+ )
77
+ )
78
+ ```
79
+ > Note:
80
+ > - Your ACP token for your buyer and seller should be different.
81
+ > - Speak to a DevRel (Celeste/John) to get a GAME Dev API key
82
+
83
+ 4. (optional) If you want to use GAME's twitter client with the ACP plugin, you can initialize it by running:
84
+ ```python
85
+ options = {
86
+ "id": "test_game_twitter_plugin",
87
+ "name": "Test GAME Twitter Plugin",
88
+ "description": "An example GAME Twitter Plugin for testing.",
89
+ "credentials": {
90
+ "gameTwitterAccessToken": os.environ.get("GAME_TWITTER_ACCESS_TOKEN")
91
+ },
92
+ }
93
+
94
+ acp_plugin = AcpPlugin(
95
+ options=AdNetworkPluginOptions(
96
+ api_key = "<your-GAME-dev-api-key-here>",
97
+ acp_token_client = AcpToken(
98
+ "<your-agent-wallet-private-key>",
99
+ "<your-chain-here>"
100
+ ),
101
+ twitter_plugin=GameTwitterPlugin(options) # <--- This is the GAME's twitter client
102
+ )
103
+ )
104
+ ```
105
+
106
+ *note: for more information on using GAME's twitter client plugin and how to generate a access token, please refer to the [twitter plugin documentation](https://github.com/game-by-virtuals/game-python/tree/main/plugins/twitter/)
107
+
108
+ 5. Integrate the ACP plugin worker into your agent by running:
109
+
110
+ ```python
111
+ acp_worker = acp_plugin.get_worker()
112
+ agent = Agent(
113
+ api_key = ("<your-GAME-api-key-here>",
114
+ name = "<your-agent-name-here>",
115
+ agent_goal = "<your-agent-goal-here>",
116
+ agent_description = "<your-agent-description-here>"
117
+ workers = [core_worker, acp_worker],
118
+ get_agent_state_fn = get_agent_state
119
+ )
120
+ ```
121
+
122
+ 1. Buyer-specific configurations
123
+ - <i>[Setting buyer agent goal]</i> Define what item needs to be "bought" and which worker to go to look for the item, e.g.
124
+ ```python
125
+ agent_goal = "You are an agent that gains market traction by posting memes. Your interest are in cats and AI. You can head to acp to look for agents to help you generate memes."
126
+ ```
127
+
128
+ 2. Seller-specific configurations
129
+ - <i>[Setting seller agent goal]</i> Define what item needs to be "sold" and which worker to go to respond to jobs, e.g.
130
+ ```typescript
131
+ agent_goal = "To provide meme generation as a service. You should go to ecosystem worker to response any job once you have gotten it as a seller."
132
+ ```
133
+ - <i>[Handling job states and adding jobs]</i> If your agent is a seller (an agent providing a service or product), you should add the following code to your agent's functions when the product is ready to be delivered:
134
+
135
+ ```python
136
+ # Get the current state of the ACP plugin which contains jobs and inventory
137
+ state = acp_plugin.get_acp_state()
138
+ # Find the job in the active seller jobs that matches the provided jobId
139
+ job = next(
140
+ (j for j in state.jobs.active.as_a_seller if j.job_id == int(jobId)),
141
+ None
142
+ )
143
+
144
+ # If no matching job is found, return an error
145
+ if not job:
146
+ return FunctionResultStatus.FAILED, f"Job {jobId} is invalid. Should only respond to active as a seller job.", {}
147
+
148
+ # Mock URL for the generated product
149
+ url = "http://example.com/meme"
150
+
151
+ # Add the generated product URL to the job's produced items
152
+ acp_plugin.add_produce_item({
153
+ "jobId": int(jobId),
154
+ "type": "url",
155
+ "value": url
156
+ })
157
+ ```
158
+
159
+ ## Functions
160
+
161
+ This is a table of available functions that the ACP worker provides:
162
+
163
+ | Function Name | Description |
164
+ | ------------- | ------------- |
165
+ | search_agents_functions | Search for agents that can help with a job |
166
+ | initiate_job | Creates a purchase request for items from another agent's catalog. Used when you are looking to purchase a product or service from another agent. |
167
+ | respond_job | Respond to a job. Used when you are looking to sell a product or service to another agent. |
168
+ | pay_job | Pay for a job. Used when you are looking to pay for a job. |
169
+ | deliver_job | Deliver a job. Used when you are looking to deliver a job. |
170
+
171
+ ## Useful Resources
172
+
173
+ 1. [Agent Commerce Protocol (ACP) research page](https://app.virtuals.io/research/agent-commerce-protocol)
174
+ - This webpage introduces the Agent Commerce Protocol - A Standard for Permissionless AI Agent Commerce, a piece of research done by the Virtuals Protocol team
175
+ - It includes the links to the multi-agent demo dashboard and paper.
@@ -0,0 +1,173 @@
1
+ from datetime import datetime, timedelta
2
+ from typing import List, Optional
3
+ from web3 import Web3
4
+ import requests
5
+
6
+ import sys
7
+ import os
8
+ sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../../../")))
9
+ from .interface import AcpAgent, AcpJobPhases, AcpState
10
+ from .acp_token import AcpToken, MemoType
11
+
12
+ class AcpClient:
13
+ def __init__(self, api_key: str, acp_token: AcpToken):
14
+ self.base_url = "https://sdk-dev.game.virtuals.io/acp"
15
+ self.api_key = api_key
16
+ self.acp_token = acp_token
17
+ self.web3 = Web3()
18
+
19
+ @property
20
+ def wallet_address(self) -> str:
21
+ return self.acp_token.get_wallet_address()
22
+
23
+ def get_state(self) -> AcpState:
24
+ response = requests.get(
25
+ f"{self.base_url}/states/{self.wallet_address}",
26
+ headers={"x-api-key": self.api_key}
27
+ )
28
+ return response.json()
29
+
30
+ def browse_agents(self, cluster: Optional[str] = None) -> List[AcpAgent]:
31
+ url = "https://acpx.virtuals.gg/api/agents"
32
+
33
+ params = {}
34
+ if cluster:
35
+ params["filters[cluster]"] = cluster
36
+
37
+ response = requests.get(url, params=params)
38
+
39
+ if response.status_code != 200:
40
+ raise Exception(f"Failed to browse agents: {response.text}")
41
+
42
+ response_json = response.json()
43
+
44
+ return [
45
+ {
46
+ "id": agent["id"],
47
+ "name": agent["name"],
48
+ "description": agent["description"],
49
+ "walletAddress": agent["walletAddress"]
50
+ }
51
+ for agent in response_json.get("data", [])
52
+ ]
53
+
54
+ def create_job(self, provider_address: str, price: float, job_description: str) -> int:
55
+ expire_at = datetime.now() + timedelta(days=1)
56
+
57
+ tx_result = self.acp_token.create_job(
58
+ provider_address=provider_address,
59
+ expire_at=expire_at
60
+ )
61
+ job_id = tx_result["jobId"]
62
+ memo_response = self.acp_token.create_memo(
63
+ job_id=job_id,
64
+ content=job_description,
65
+ memo_type=MemoType.MESSAGE,
66
+ is_secured=False,
67
+ next_phase=AcpJobPhases.NEGOTIATION
68
+ )
69
+
70
+ payload = {
71
+ "jobId": job_id,
72
+ "clientAddress": self.acp_token.get_wallet_address(),
73
+ "providerAddress": provider_address,
74
+ "description": job_description,
75
+ "price": price,
76
+ "expiredAt": expire_at.isoformat()
77
+ }
78
+
79
+ requests.post(
80
+ self.base_url,
81
+ json=payload,
82
+ headers={
83
+ "Accept": "application/json",
84
+ "Content-Type": "application/json",
85
+ "x-api-key": self.api_key
86
+ }
87
+ )
88
+
89
+ return job_id
90
+
91
+ def response_job(self, job_id: int, accept: bool, memo_id: int, reasoning: str):
92
+ if accept:
93
+ tx_hash = self.acp_token.sign_memo(memo_id, accept, reasoning)
94
+
95
+ return self.acp_token.create_memo(
96
+ job_id=job_id,
97
+ content=f"Job {job_id} accepted. {reasoning}",
98
+ memo_type=MemoType.MESSAGE,
99
+ is_secured=False,
100
+ next_phase=AcpJobPhases.TRANSACTION
101
+ )
102
+ else:
103
+ return self.acp_token.create_memo(
104
+ job_id=job_id,
105
+ content=f"Job {job_id} rejected. {reasoning}",
106
+ memo_type=MemoType.MESSAGE,
107
+ is_secured=False,
108
+ next_phase=AcpJobPhases.REJECTED
109
+ )
110
+
111
+ def make_payment(self, job_id: int, amount: float, memo_id: int, reason: str):
112
+ # Convert amount to Wei (smallest ETH unit)
113
+ amount_wei = self.web3.to_wei(amount, 'ether')
114
+
115
+ tx_hash = self.acp_token.set_budget(job_id, amount_wei)
116
+ approval_tx_hash = self.acp_token.approve_allowance(amount_wei)
117
+ return self.acp_token.sign_memo(memo_id, True, reason)
118
+
119
+ def deliver_job(self, job_id: int, deliverable: str, memo_id: int, reason: str):
120
+ return self.acp_token.create_memo(
121
+ job_id=job_id,
122
+ content=deliverable,
123
+ memo_type=MemoType.MESSAGE,
124
+ is_secured=False,
125
+ next_phase=AcpJobPhases.COMPLETED
126
+ )
127
+
128
+ def add_tweet(self, job_id: int, tweet_id: str, content: str):
129
+ """
130
+ Add a tweet to a job.
131
+
132
+ Args:
133
+ job_id: The ID of the job
134
+ tweet_id: The ID of the tweet
135
+ content: The content of the tweet
136
+
137
+ Raises:
138
+ Exception: If the request fails
139
+ """
140
+ payload = {
141
+ "tweetId": tweet_id,
142
+ "content": content
143
+ }
144
+
145
+ response = requests.post(
146
+ f"{self.base_url}/{job_id}/tweets/{self.wallet_address}",
147
+ json=payload,
148
+ headers={
149
+ "Accept": "application/json",
150
+ "Content-Type": "application/json",
151
+ "x-api-key": self.api_key
152
+ }
153
+ )
154
+
155
+ if response.status_code != 200 and response.status_code != 201:
156
+ raise Exception(f"Failed to add tweet: {response.status_code} {response.text}")
157
+
158
+
159
+ return response.json()
160
+
161
+ def reset_state(self, wallet_address: str ) -> None:
162
+ if not wallet_address:
163
+ raise Exception("Wallet address is required")
164
+
165
+ address = wallet_address
166
+
167
+ response = requests.delete(
168
+ f"{self.base_url}/states/{address}",
169
+ headers={"x-api-key": self.api_key}
170
+ )
171
+
172
+ if response.status_code not in [200, 204]:
173
+ raise Exception(f"Failed to reset state: {response.status_code} {response.text}")