acp-plugin-gamesdk 0.1.17__py3-none-any.whl → 0.1.18__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.
- acp_plugin_gamesdk/acp_client.py +10 -8
- acp_plugin_gamesdk/acp_plugin.py +78 -98
- acp_plugin_gamesdk/acp_token.py +8 -8
- acp_plugin_gamesdk/configs.py +42 -0
- acp_plugin_gamesdk/interface.py +1 -0
- acp_plugin_gamesdk-0.1.18.dist-info/METADATA +308 -0
- acp_plugin_gamesdk-0.1.18.dist-info/RECORD +9 -0
- acp_plugin_gamesdk-0.1.17.dist-info/METADATA +0 -309
- acp_plugin_gamesdk-0.1.17.dist-info/RECORD +0 -8
- {acp_plugin_gamesdk-0.1.17.dist-info → acp_plugin_gamesdk-0.1.18.dist-info}/WHEEL +0 -0
acp_plugin_gamesdk/acp_client.py
CHANGED
@@ -13,12 +13,13 @@ from dacite import from_dict, Config
|
|
13
13
|
|
14
14
|
|
15
15
|
class AcpClient:
|
16
|
-
def __init__(self, api_key: str, acp_token: AcpToken
|
17
|
-
self.base_url = "https://sdk-dev.game.virtuals.io/acp"
|
16
|
+
def __init__(self, api_key: str, acp_token: AcpToken):
|
18
17
|
self.api_key = api_key
|
19
18
|
self.acp_token = acp_token
|
20
19
|
self.web3 = Web3()
|
21
|
-
|
20
|
+
|
21
|
+
self.acp_base_url = self.acp_token.acp_base_url
|
22
|
+
self.base_url = self.acp_token.game_api_url + "/acp"
|
22
23
|
|
23
24
|
@property
|
24
25
|
def agent_wallet_address(self) -> str:
|
@@ -74,6 +75,7 @@ class AcpClient:
|
|
74
75
|
AcpAgent(
|
75
76
|
id=agent["id"],
|
76
77
|
name=agent["name"],
|
78
|
+
twitter_handle=agent["twitterHandle"],
|
77
79
|
description=agent["description"],
|
78
80
|
wallet_address=agent["walletAddress"],
|
79
81
|
offerings=offerings,
|
@@ -110,16 +112,16 @@ class AcpClient:
|
|
110
112
|
if not data:
|
111
113
|
raise Exception("Invalid tx_hash!")
|
112
114
|
|
113
|
-
if
|
115
|
+
if data.get("status") == "retry":
|
114
116
|
raise Exception("Transaction failed, retrying...")
|
115
117
|
|
116
|
-
if
|
118
|
+
if data.get("status") == "failed":
|
117
119
|
break
|
118
120
|
|
119
|
-
if
|
121
|
+
if data.get("status") == "success":
|
120
122
|
job_id = int(data.get("result").get("jobId"))
|
121
123
|
|
122
|
-
if
|
124
|
+
if job_id is not None and job_id != "":
|
123
125
|
break
|
124
126
|
|
125
127
|
except Exception as e:
|
@@ -130,7 +132,7 @@ class AcpClient:
|
|
130
132
|
else:
|
131
133
|
raise
|
132
134
|
|
133
|
-
if
|
135
|
+
if job_id is None or job_id == "":
|
134
136
|
raise Exception("Failed to create job")
|
135
137
|
|
136
138
|
self.acp_token.create_memo(
|
acp_plugin_gamesdk/acp_plugin.py
CHANGED
@@ -42,7 +42,7 @@ class AcpPlugin:
|
|
42
42
|
def __init__(self, options: AcpPluginOptions):
|
43
43
|
print("Initializing AcpPlugin")
|
44
44
|
self.acp_token_client = options.acp_token_client
|
45
|
-
self.acp_client = AcpClient(options.api_key, options.acp_token_client
|
45
|
+
self.acp_client = AcpClient(options.api_key, options.acp_token_client)
|
46
46
|
self.id = "acp_worker"
|
47
47
|
self.name = "ACP Worker"
|
48
48
|
self.description = """
|
@@ -63,11 +63,11 @@ class AcpPlugin:
|
|
63
63
|
self.cluster = options.cluster
|
64
64
|
self.evaluator_cluster = options.evaluator_cluster
|
65
65
|
self.twitter_plugin = None
|
66
|
-
if
|
66
|
+
if options.twitter_plugin is not None:
|
67
67
|
self.twitter_plugin = options.twitter_plugin
|
68
68
|
|
69
69
|
self.produced_inventory: List[IInventory] = []
|
70
|
-
self.acp_base_url = self.acp_token_client.acp_base_url
|
70
|
+
self.acp_base_url = self.acp_token_client.acp_base_url
|
71
71
|
if options.on_evaluate is not None or options.on_phase_change is not None:
|
72
72
|
print("Initializing socket")
|
73
73
|
self.socket = None
|
@@ -75,12 +75,12 @@ class AcpPlugin:
|
|
75
75
|
self.on_evaluate = options.on_evaluate
|
76
76
|
if options.on_phase_change is not None:
|
77
77
|
self.on_phase_change = options.on_phase_change
|
78
|
-
self.
|
78
|
+
self.initialize_socket()
|
79
79
|
self.job_expiry_duration_mins = options.job_expiry_duration_mins if options.job_expiry_duration_mins is not None else 1440
|
80
80
|
|
81
81
|
|
82
82
|
|
83
|
-
def
|
83
|
+
def initialize_socket(self) -> Tuple[bool, str]:
|
84
84
|
"""
|
85
85
|
Initialize socket connection for real-time communication.
|
86
86
|
Returns a tuple of (success, message).
|
@@ -94,9 +94,9 @@ class AcpPlugin:
|
|
94
94
|
}
|
95
95
|
|
96
96
|
# Connect socket to GAME SDK dev server
|
97
|
-
self.socket.connect(
|
97
|
+
self.socket.connect(self.acp_client.base_url, auth=self.socket.auth)
|
98
98
|
|
99
|
-
if
|
99
|
+
if self.socket.connected:
|
100
100
|
self.socket.emit(SocketEvents["JOIN_EVALUATOR_ROOM"], self.acp_token_client.agent_wallet_address)
|
101
101
|
|
102
102
|
|
@@ -128,7 +128,7 @@ class AcpPlugin:
|
|
128
128
|
|
129
129
|
|
130
130
|
|
131
|
-
def signal_handler(
|
131
|
+
def signal_handler(_sig, _frame):
|
132
132
|
cleanup()
|
133
133
|
sys.exit(0)
|
134
134
|
|
@@ -168,7 +168,7 @@ class AcpPlugin:
|
|
168
168
|
self.deliver_job,
|
169
169
|
]
|
170
170
|
|
171
|
-
def get_environment(
|
171
|
+
def get_environment(_function_result, _current_state) -> Dict[str, Any]:
|
172
172
|
environment = data.get_environment() if hasattr(data, "get_environment") else {}
|
173
173
|
return {
|
174
174
|
**environment,
|
@@ -219,6 +219,7 @@ class AcpPlugin:
|
|
219
219
|
{
|
220
220
|
"id": agent.id,
|
221
221
|
"name": agent.name,
|
222
|
+
"twitter_handle": agent.twitter_handle,
|
222
223
|
"description": agent.description,
|
223
224
|
"wallet_address": agent.wallet_address,
|
224
225
|
"offerings": (
|
@@ -266,7 +267,7 @@ class AcpPlugin:
|
|
266
267
|
@property
|
267
268
|
def initiate_job(self) -> Function:
|
268
269
|
seller_wallet_address_arg = Argument(
|
269
|
-
name="
|
270
|
+
name="seller_wallet_address",
|
270
271
|
type="string",
|
271
272
|
description="The seller's agent wallet address you want to buy from",
|
272
273
|
)
|
@@ -284,19 +285,19 @@ class AcpPlugin:
|
|
284
285
|
)
|
285
286
|
|
286
287
|
service_requirements_arg = Argument(
|
287
|
-
name="
|
288
|
+
name="service_requirements",
|
288
289
|
type="string",
|
289
290
|
description="Detailed specifications for service-based items",
|
290
291
|
)
|
291
292
|
|
292
293
|
require_evaluation_arg = Argument(
|
293
|
-
name="
|
294
|
+
name="require_evaluation",
|
294
295
|
type="boolean",
|
295
296
|
description="Decide if your job request is complex enough to spend money for evaluator agent to assess the relevancy of the output. For simple job request like generate image, insights, facts does not require evaluation. For complex and high level job like generating a promotion video, a marketing narrative, a trading signal should require evaluator to assess result relevancy.",
|
296
297
|
)
|
297
298
|
|
298
299
|
evaluator_keyword_arg = Argument(
|
299
|
-
name="
|
300
|
+
name="evaluator_keyword",
|
300
301
|
type="string",
|
301
302
|
description="Keyword to search for a evaluator",
|
302
303
|
)
|
@@ -305,7 +306,7 @@ class AcpPlugin:
|
|
305
306
|
|
306
307
|
if hasattr(self, 'twitter_plugin') and self.twitter_plugin is not None:
|
307
308
|
tweet_content_arg = Argument(
|
308
|
-
name="
|
309
|
+
name="tweet_content",
|
309
310
|
type="string",
|
310
311
|
description="Tweet content that will be posted about this job. Must include the seller's Twitter handle (with @ symbol) to notify them",
|
311
312
|
)
|
@@ -318,11 +319,11 @@ class AcpPlugin:
|
|
318
319
|
executable=self._initiate_job_executable
|
319
320
|
)
|
320
321
|
|
321
|
-
def _initiate_job_executable(self,
|
322
|
-
if isinstance(
|
323
|
-
require_evaluation =
|
324
|
-
elif isinstance(
|
325
|
-
require_evaluation =
|
322
|
+
def _initiate_job_executable(self, seller_wallet_address: str, price: str, reasoning: str, service_requirements: str, require_evaluation: str, evaluator_keyword: str, tweet_content: Optional[str] = None) -> Tuple[FunctionResultStatus, str, dict]:
|
323
|
+
if isinstance(require_evaluation, str):
|
324
|
+
require_evaluation = require_evaluation.lower() == 'true'
|
325
|
+
elif isinstance(require_evaluation, bool):
|
326
|
+
require_evaluation = require_evaluation
|
326
327
|
else:
|
327
328
|
require_evaluation = False
|
328
329
|
|
@@ -337,51 +338,51 @@ class AcpPlugin:
|
|
337
338
|
|
338
339
|
existing_job = next(
|
339
340
|
(job for job in state["jobs"]["active"]["asABuyer"]
|
340
|
-
if job["providerAddress"] ==
|
341
|
+
if job["providerAddress"] == seller_wallet_address),
|
341
342
|
None
|
342
343
|
)
|
343
344
|
|
344
345
|
if existing_job:
|
345
346
|
return FunctionResultStatus.FAILED, f"You already have an active job as a buyer with {existing_job['providerAddress']} - complete the current job before initiating a new one", {}
|
346
347
|
|
347
|
-
if not
|
348
|
+
if not seller_wallet_address:
|
348
349
|
return FunctionResultStatus.FAILED, "Missing seller wallet address - specify the agent you want to buy from", {}
|
349
350
|
|
350
|
-
if require_evaluation and not
|
351
|
+
if require_evaluation and not evaluator_keyword:
|
351
352
|
return FunctionResultStatus.FAILED, "Missing validator keyword - provide a keyword to search for a validator", {}
|
352
353
|
|
353
|
-
|
354
|
+
evaluator_address = self.acp_token_client.get_agent_wallet_address()
|
354
355
|
|
355
356
|
if require_evaluation:
|
356
|
-
validators = self.acp_client.browse_agents(self.evaluator_cluster,
|
357
|
+
validators = self.acp_client.browse_agents(self.evaluator_cluster, evaluator_keyword, rerank=True, top_k=1)
|
357
358
|
|
358
359
|
if len(validators) == 0:
|
359
360
|
return FunctionResultStatus.FAILED, "No evaluator found - try a different keyword", {}
|
360
|
-
|
361
|
-
|
361
|
+
|
362
|
+
evaluator_address = validators[0].wallet_address
|
362
363
|
|
363
364
|
# ... Rest of validation logic ...
|
364
365
|
expired_at = datetime.now(timezone.utc) + timedelta(minutes=self.job_expiry_duration_mins)
|
365
366
|
job_id = self.acp_client.create_job(
|
366
|
-
|
367
|
+
seller_wallet_address,
|
367
368
|
float(price),
|
368
|
-
|
369
|
-
|
369
|
+
service_requirements,
|
370
|
+
evaluator_address,
|
370
371
|
expired_at
|
371
372
|
)
|
372
|
-
|
373
|
-
if
|
373
|
+
|
374
|
+
if hasattr(self, 'twitter_plugin') and self.twitter_plugin is not None and tweet_content is not None:
|
374
375
|
post_tweet_fn = self.twitter_plugin.get_function('post_tweet')
|
375
|
-
tweet_id = post_tweet_fn(
|
376
|
-
if
|
377
|
-
self.acp_client.add_tweet(job_id, tweet_id,
|
376
|
+
tweet_id = post_tweet_fn(tweet_content).get('data', {}).get('id')
|
377
|
+
if tweet_id is not None:
|
378
|
+
self.acp_client.add_tweet(job_id, tweet_id, tweet_content)
|
378
379
|
print("Tweet has been posted")
|
379
380
|
|
380
381
|
return FunctionResultStatus.DONE, json.dumps({
|
381
382
|
"jobId": job_id,
|
382
|
-
"sellerWalletAddress":
|
383
|
+
"sellerWalletAddress": seller_wallet_address,
|
383
384
|
"price": float(price),
|
384
|
-
"serviceRequirements":
|
385
|
+
"serviceRequirements": service_requirements,
|
385
386
|
"timestamp": datetime.now().timestamp(),
|
386
387
|
}), {}
|
387
388
|
except Exception as e:
|
@@ -391,7 +392,7 @@ class AcpPlugin:
|
|
391
392
|
@property
|
392
393
|
def respond_job(self) -> Function:
|
393
394
|
job_id_arg = Argument(
|
394
|
-
name="
|
395
|
+
name="job_id",
|
395
396
|
type="integer",
|
396
397
|
description="The job ID you are responding to",
|
397
398
|
)
|
@@ -412,9 +413,9 @@ class AcpPlugin:
|
|
412
413
|
|
413
414
|
if hasattr(self, 'twitter_plugin') and self.twitter_plugin is not None:
|
414
415
|
tweet_content_arg = Argument(
|
415
|
-
name="
|
416
|
+
name="tweet_content",
|
416
417
|
type="string",
|
417
|
-
description="Tweet content
|
418
|
+
description="Tweet content about your decision for the specific job. MUST NOT TAG THE BUYER. This is to avoid spamming the buyer's feed with your decision.",
|
418
419
|
)
|
419
420
|
args.append(tweet_content_arg)
|
420
421
|
|
@@ -425,8 +426,8 @@ class AcpPlugin:
|
|
425
426
|
executable=self._respond_job_executable
|
426
427
|
)
|
427
428
|
|
428
|
-
def _respond_job_executable(self,
|
429
|
-
if not
|
429
|
+
def _respond_job_executable(self, job_id: int, decision: str, reasoning: str, tweet_content: Optional[str] = None) -> Tuple[FunctionResultStatus, str, dict]:
|
430
|
+
if not job_id:
|
430
431
|
return FunctionResultStatus.FAILED, "Missing job ID - specify which job you're responding to", {}
|
431
432
|
|
432
433
|
if not decision or decision not in ["ACCEPT", "REJECT"]:
|
@@ -439,7 +440,7 @@ class AcpPlugin:
|
|
439
440
|
state = self.get_acp_state()
|
440
441
|
|
441
442
|
job = next(
|
442
|
-
(c for c in state["jobs"]["active"]["asASeller"] if c["jobId"] ==
|
443
|
+
(c for c in state["jobs"]["active"]["asASeller"] if c["jobId"] == job_id),
|
443
444
|
None
|
444
445
|
)
|
445
446
|
|
@@ -450,24 +451,16 @@ class AcpPlugin:
|
|
450
451
|
return FunctionResultStatus.FAILED, f"Cannot respond - job is in '{job['phase']}' phase, must be in 'request' phase", {}
|
451
452
|
|
452
453
|
self.acp_client.response_job(
|
453
|
-
|
454
|
+
job_id,
|
454
455
|
decision == "ACCEPT",
|
455
456
|
job["memo"][0]["id"],
|
456
457
|
reasoning
|
457
458
|
)
|
458
|
-
|
459
|
-
|
460
|
-
tweet_history = job.get("tweetHistory", [])
|
461
|
-
tweet_id = tweet_history[-1].get("tweetId") if tweet_history else None
|
462
|
-
if (tweet_id is not None):
|
463
|
-
reply_tweet_fn = self.twitter_plugin.get_function('reply_tweet')
|
464
|
-
tweet_id = reply_tweet_fn(tweet_id,tweetContent, None).get('data', {}).get('id')
|
465
|
-
if (tweet_id is not None):
|
466
|
-
self.acp_client.add_tweet(jobId ,tweet_id, tweetContent)
|
467
|
-
print("Tweet has been posted")
|
459
|
+
|
460
|
+
self._reply_tweet(job, tweet_content)
|
468
461
|
|
469
462
|
return FunctionResultStatus.DONE, json.dumps({
|
470
|
-
"jobId":
|
463
|
+
"jobId": job_id,
|
471
464
|
"decision": decision,
|
472
465
|
"timestamp": datetime.now().timestamp()
|
473
466
|
}), {}
|
@@ -477,7 +470,7 @@ class AcpPlugin:
|
|
477
470
|
@property
|
478
471
|
def pay_job(self) -> Function:
|
479
472
|
job_id_arg = Argument(
|
480
|
-
name="
|
473
|
+
name="job_id",
|
481
474
|
type="integer",
|
482
475
|
description="The job ID you are paying for",
|
483
476
|
)
|
@@ -498,9 +491,9 @@ class AcpPlugin:
|
|
498
491
|
|
499
492
|
if hasattr(self, 'twitter_plugin') and self.twitter_plugin is not None:
|
500
493
|
tweet_content_arg = Argument(
|
501
|
-
name="
|
494
|
+
name="tweet_content",
|
502
495
|
type="string",
|
503
|
-
description="Tweet content
|
496
|
+
description="Tweet content about your payment for the specific job. MUST NOT TAG THE BUYER. This is to avoid spamming the buyer's feed with your payment.",
|
504
497
|
)
|
505
498
|
args.append(tweet_content_arg)
|
506
499
|
|
@@ -511,8 +504,8 @@ class AcpPlugin:
|
|
511
504
|
executable=self._pay_job_executable
|
512
505
|
)
|
513
506
|
|
514
|
-
def _pay_job_executable(self,
|
515
|
-
if not
|
507
|
+
def _pay_job_executable(self, job_id: int, amount: float, reasoning: str, tweet_content: Optional[str] = None) -> Tuple[FunctionResultStatus, str, dict]:
|
508
|
+
if not job_id:
|
516
509
|
return FunctionResultStatus.FAILED, "Missing job ID - specify which job you're paying for", {}
|
517
510
|
|
518
511
|
if not amount:
|
@@ -525,7 +518,7 @@ class AcpPlugin:
|
|
525
518
|
state = self.get_acp_state()
|
526
519
|
|
527
520
|
job = next(
|
528
|
-
(c for c in state["jobs"]["active"]["asABuyer"] if c["jobId"] ==
|
521
|
+
(c for c in state["jobs"]["active"]["asABuyer"] if c["jobId"] == job_id),
|
529
522
|
None
|
530
523
|
)
|
531
524
|
|
@@ -537,24 +530,16 @@ class AcpPlugin:
|
|
537
530
|
|
538
531
|
|
539
532
|
self.acp_client.make_payment(
|
540
|
-
|
533
|
+
job_id,
|
541
534
|
amount,
|
542
535
|
job["memo"][0]["id"],
|
543
536
|
reasoning
|
544
537
|
)
|
545
|
-
|
546
|
-
|
547
|
-
tweet_history = job.get("tweetHistory", [])
|
548
|
-
tweet_id = tweet_history[-1].get("tweetId") if tweet_history else None
|
549
|
-
if (tweet_id is not None):
|
550
|
-
reply_tweet_fn = self.twitter_plugin.get_function('reply_tweet')
|
551
|
-
tweet_id = reply_tweet_fn(tweet_id,tweetContent, None).get('data', {}).get('id')
|
552
|
-
if (tweet_id is not None):
|
553
|
-
self.acp_client.add_tweet(jobId ,tweet_id, tweetContent)
|
554
|
-
print("Tweet has been posted")
|
538
|
+
|
539
|
+
self._reply_tweet(job, tweet_content)
|
555
540
|
|
556
541
|
return FunctionResultStatus.DONE, json.dumps({
|
557
|
-
"jobId":
|
542
|
+
"jobId": job_id,
|
558
543
|
"amountPaid": amount,
|
559
544
|
"timestamp": datetime.now().timestamp()
|
560
545
|
}), {}
|
@@ -565,17 +550,11 @@ class AcpPlugin:
|
|
565
550
|
@property
|
566
551
|
def deliver_job(self) -> Function:
|
567
552
|
job_id_arg = Argument(
|
568
|
-
name="
|
553
|
+
name="job_id",
|
569
554
|
type="integer",
|
570
555
|
description="The job ID you are delivering for",
|
571
556
|
)
|
572
557
|
|
573
|
-
deliverable_type_arg = Argument(
|
574
|
-
name="deliverableType",
|
575
|
-
type="string",
|
576
|
-
description="Type of the deliverable",
|
577
|
-
)
|
578
|
-
|
579
558
|
deliverable_arg = Argument(
|
580
559
|
name="deliverable",
|
581
560
|
type="string",
|
@@ -588,13 +567,13 @@ class AcpPlugin:
|
|
588
567
|
description="Why you are making this delivery",
|
589
568
|
)
|
590
569
|
|
591
|
-
args = [job_id_arg,
|
570
|
+
args = [job_id_arg, deliverable_arg, reasoning_arg]
|
592
571
|
|
593
572
|
if hasattr(self, 'twitter_plugin') and self.twitter_plugin is not None:
|
594
573
|
tweet_content_arg = Argument(
|
595
|
-
name="
|
574
|
+
name="tweet_content",
|
596
575
|
type="string",
|
597
|
-
description="Tweet content
|
576
|
+
description="Tweet content about your delivery for the specific job. MUST NOT TAG THE BUYER. This is to avoid spamming the buyer's feed with your delivery.",
|
598
577
|
)
|
599
578
|
args.append(tweet_content_arg)
|
600
579
|
|
@@ -605,8 +584,8 @@ class AcpPlugin:
|
|
605
584
|
executable=self._deliver_job_executable
|
606
585
|
)
|
607
586
|
|
608
|
-
def _deliver_job_executable(self,
|
609
|
-
if not
|
587
|
+
def _deliver_job_executable(self, job_id: int, deliverable: str, reasoning: str, tweet_content: Optional[str] = None) -> Tuple[FunctionResultStatus, str, dict]:
|
588
|
+
if not job_id:
|
610
589
|
return FunctionResultStatus.FAILED, "Missing job ID - specify which job you're delivering for", {}
|
611
590
|
|
612
591
|
if not reasoning:
|
@@ -619,7 +598,7 @@ class AcpPlugin:
|
|
619
598
|
state = self.get_acp_state()
|
620
599
|
|
621
600
|
job = next(
|
622
|
-
(c for c in state["jobs"]["active"]["asASeller"] if c["jobId"] ==
|
601
|
+
(c for c in state["jobs"]["active"]["asASeller"] if c["jobId"] == job_id),
|
623
602
|
None
|
624
603
|
)
|
625
604
|
|
@@ -643,27 +622,28 @@ class AcpPlugin:
|
|
643
622
|
}
|
644
623
|
|
645
624
|
self.acp_client.deliver_job(
|
646
|
-
|
625
|
+
job_id,
|
647
626
|
json.dumps(deliverable),
|
648
627
|
)
|
649
|
-
|
650
|
-
if (hasattr(self, 'twitter_plugin') and self.twitter_plugin is not None and tweetContent is not None):
|
651
|
-
|
652
|
-
tweet_history = job.get("tweetHistory", [])
|
653
|
-
tweet_id = tweet_history[-1].get("tweetId") if tweet_history else None
|
654
|
-
if (tweet_id is not None):
|
655
|
-
reply_tweet_fn = self.twitter_plugin.get_function('reply_tweet')
|
656
|
-
tweet_id = reply_tweet_fn(tweet_id,tweetContent, None).get('data', {}).get('id')
|
657
|
-
if (tweet_id is not None):
|
658
|
-
self.acp_client.add_tweet(jobId ,tweet_id, tweetContent)
|
659
|
-
print("Tweet has been posted")
|
660
628
|
|
629
|
+
self._reply_tweet(job, tweet_content)
|
661
630
|
return FunctionResultStatus.DONE, json.dumps({
|
662
631
|
"status": "success",
|
663
|
-
"jobId":
|
632
|
+
"jobId": job_id,
|
664
633
|
"deliverable": deliverable,
|
665
634
|
"timestamp": datetime.now().timestamp()
|
666
635
|
}), {}
|
667
636
|
except Exception as e:
|
668
637
|
print(traceback.format_exc())
|
669
638
|
return FunctionResultStatus.FAILED, f"System error while delivering items - try again after a short delay. {str(e)}", {}
|
639
|
+
|
640
|
+
def _reply_tweet(self, job: dict, tweet_content: str):
|
641
|
+
if hasattr(self, 'twitter_plugin') and self.twitter_plugin is not None and tweet_content is not None:
|
642
|
+
tweet_history = job.get("tweetHistory", [])
|
643
|
+
tweet_id = tweet_history[-1].get("tweetId") if tweet_history else None
|
644
|
+
if tweet_id is not None:
|
645
|
+
reply_tweet_fn = self.twitter_plugin.get_function('reply_tweet')
|
646
|
+
tweet_id = reply_tweet_fn(tweet_id,tweet_content, None).get('data', {}).get('id')
|
647
|
+
if tweet_id is not None:
|
648
|
+
self.acp_client.add_tweet(job.get("jobId") ,tweet_id, tweet_content)
|
649
|
+
print("Tweet has been posted")
|
acp_plugin_gamesdk/acp_token.py
CHANGED
@@ -2,6 +2,7 @@ from enum import IntEnum
|
|
2
2
|
import time
|
3
3
|
from typing import Optional, Tuple, TypedDict
|
4
4
|
from datetime import datetime
|
5
|
+
from acp_plugin_gamesdk.configs import ACPContractConfig
|
5
6
|
from web3 import Web3
|
6
7
|
from eth_account import Account
|
7
8
|
from acp_plugin_gamesdk.acp_token_abi import ACP_TOKEN_ABI
|
@@ -45,16 +46,13 @@ class AcpToken:
|
|
45
46
|
self,
|
46
47
|
wallet_private_key: str,
|
47
48
|
agent_wallet_address: str,
|
48
|
-
|
49
|
-
acp_base_url: Optional[str] = None,
|
50
|
-
contract_address: str = "0x2422c1c43451Eb69Ff49dfD39c4Dc8C5230fA1e6",
|
51
|
-
virtuals_token_address: str = "0xbfAB80ccc15DF6fb7185f9498d6039317331846a",
|
49
|
+
config: ACPContractConfig,
|
52
50
|
):
|
53
|
-
self.web3 = Web3(Web3.HTTPProvider(
|
51
|
+
self.web3 = Web3(Web3.HTTPProvider(config.rpc_url))
|
54
52
|
self.account = Account.from_key(wallet_private_key)
|
55
53
|
self.agent_wallet_address = agent_wallet_address
|
56
|
-
self.contract_address = Web3.to_checksum_address(contract_address)
|
57
|
-
self.virtuals_token_address = Web3.to_checksum_address(virtuals_token_address)
|
54
|
+
self.contract_address = Web3.to_checksum_address(config.contract_address)
|
55
|
+
self.virtuals_token_address = Web3.to_checksum_address(config.virtuals_token_address)
|
58
56
|
self.contract = self.web3.eth.contract(
|
59
57
|
address=self.contract_address,
|
60
58
|
abi=ACP_TOKEN_ABI
|
@@ -86,7 +84,9 @@ class AcpToken:
|
|
86
84
|
"type": "function"
|
87
85
|
}]
|
88
86
|
)
|
89
|
-
self.acp_base_url =
|
87
|
+
self.acp_base_url = config.acp_api_url
|
88
|
+
self.game_api_url = config.game_api_url
|
89
|
+
|
90
90
|
def get_agent_wallet_address(self) -> str:
|
91
91
|
return self.agent_wallet_address
|
92
92
|
|
@@ -0,0 +1,42 @@
|
|
1
|
+
from dataclasses import dataclass
|
2
|
+
from typing import Literal
|
3
|
+
|
4
|
+
ChainEnv = Literal["base-sepolia", "base"]
|
5
|
+
|
6
|
+
@dataclass
|
7
|
+
class ACPContractConfig:
|
8
|
+
chain_env: ChainEnv
|
9
|
+
rpc_url: str
|
10
|
+
chain_id: int
|
11
|
+
contract_address: str
|
12
|
+
virtuals_token_address: str
|
13
|
+
acp_api_url: str
|
14
|
+
game_api_url: str
|
15
|
+
|
16
|
+
# Configuration for Base Sepolia
|
17
|
+
BASE_SEPOLIA_CONFIG = ACPContractConfig(
|
18
|
+
chain_env="base-sepolia",
|
19
|
+
rpc_url="https://sepolia.base.org",
|
20
|
+
chain_id=84532,
|
21
|
+
contract_address="0x2422c1c43451Eb69Ff49dfD39c4Dc8C5230fA1e6",
|
22
|
+
virtuals_token_address="0xbfAB80ccc15DF6fb7185f9498d6039317331846a",
|
23
|
+
acp_api_url="https://acpx-staging.virtuals.io/api",
|
24
|
+
game_api_url="https://sdk-dev.game.virtuals.io"
|
25
|
+
)
|
26
|
+
|
27
|
+
# Configuration for Base Mainnet
|
28
|
+
BASE_MAINNET_CONFIG = ACPContractConfig(
|
29
|
+
chain_env="base",
|
30
|
+
rpc_url="https://mainnet.base.org",
|
31
|
+
chain_id=8453,
|
32
|
+
contract_address="0x6a1FE26D54ab0d3E1e3168f2e0c0cDa5cC0A0A4A",
|
33
|
+
virtuals_token_address="0x0b3e328455c4059EEb9e3f84b5543F74E24e7E1b",
|
34
|
+
acp_api_url="https://acpx.virtuals.io/api", # PROD
|
35
|
+
game_api_url="https://sdk.game.virtuals.io"
|
36
|
+
)
|
37
|
+
|
38
|
+
# Define the default configuration for the SDK
|
39
|
+
# For a production-ready SDK, this would typically be BASE_MAINNET_CONFIG.
|
40
|
+
# For initial development/testing, BASE_SEPOLIA_CONFIG might be more appropriate.
|
41
|
+
DEFAULT_CONFIG = BASE_MAINNET_CONFIG
|
42
|
+
# Or: DEFAULT_CONFIG = BASE_SEPOLIA_CONFIG
|
acp_plugin_gamesdk/interface.py
CHANGED
@@ -0,0 +1,308 @@
|
|
1
|
+
Metadata-Version: 2.3
|
2
|
+
Name: acp-plugin-gamesdk
|
3
|
+
Version: 0.1.18
|
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,<3.13
|
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
|
+
Requires-Dist: aiohttp (>=3.11.14,<4.0.0)
|
14
|
+
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
|
+
Requires-Dist: game-sdk (>=0.1.5)
|
19
|
+
Requires-Dist: pydantic (>=2.10.6,<3.0.0)
|
20
|
+
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
|
+
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)
|
28
|
+
Description-Content-Type: text/markdown
|
29
|
+
|
30
|
+
# ACP Plugin
|
31
|
+
|
32
|
+
<details>
|
33
|
+
<summary>Table of Contents</summary>
|
34
|
+
|
35
|
+
- [ACP Plugin](#acp-plugin)
|
36
|
+
- [Prerequisite](#prerequisite)
|
37
|
+
- [Installation](#installation)
|
38
|
+
- [Usage](#usage)
|
39
|
+
- [Functions](#functions)
|
40
|
+
- [Tools](#tools)
|
41
|
+
- [Agent Registry](#agent-registry)
|
42
|
+
- [Useful Resources](#useful-resources)
|
43
|
+
|
44
|
+
</details>
|
45
|
+
|
46
|
+
---
|
47
|
+
|
48
|
+
<img src="../../docs/imgs/ACP-banner.jpeg" width="100%" height="auto">
|
49
|
+
|
50
|
+
---
|
51
|
+
|
52
|
+
The Agent Commerce Protocol (ACP) plugin is used to handle trading transactions and jobs between agents. This ACP plugin manages:
|
53
|
+
|
54
|
+
1. RESPONDING to Buy/Sell Needs, via ACP service registry
|
55
|
+
|
56
|
+
- Find sellers when YOU need to buy something
|
57
|
+
- Handle incoming purchase requests when others want to buy from YOU
|
58
|
+
|
59
|
+
2. Job Management, with built-in abstractions of agent wallet and smart contract integrations
|
60
|
+
|
61
|
+
- Process purchase requests. Accept or reject job.
|
62
|
+
- Send payments
|
63
|
+
- Manage and deliver services and goods
|
64
|
+
|
65
|
+
3. Tweets (optional)
|
66
|
+
- Post tweets and tag other agents for job requests
|
67
|
+
- Respond to tweets from other agents
|
68
|
+
|
69
|
+
## Prerequisite
|
70
|
+
|
71
|
+
⚠️ Important: Before testing your agent's services with a counterpart agent, you must register your agent with the [Service Registry](https://acp-staging.virtuals.io/).
|
72
|
+
This step is a critical precursor. Without registration, the counterpart agent will not be able to discover or interact with your agent.
|
73
|
+
|
74
|
+
## Installation
|
75
|
+
|
76
|
+
From this directory (`acp`), run the installation:
|
77
|
+
|
78
|
+
```bash
|
79
|
+
poetry install
|
80
|
+
```
|
81
|
+
|
82
|
+
or install it with pip:
|
83
|
+
```bash
|
84
|
+
pip install acp-plugin-gamesdk
|
85
|
+
```
|
86
|
+
|
87
|
+
## Usage
|
88
|
+
|
89
|
+
1. Activate the virtual environment by running:
|
90
|
+
```bash
|
91
|
+
eval $(poetry env activate)
|
92
|
+
```
|
93
|
+
|
94
|
+
2. Import acp_plugin and load the environment variables by running:
|
95
|
+
|
96
|
+
```python
|
97
|
+
from acp_plugin_gamesdk.acp_plugin import AcpPlugin, AcpPluginOptions
|
98
|
+
from acp_plugin_gamesdk.acp_token import AcpToken
|
99
|
+
from dotenv import load_dotenv
|
100
|
+
|
101
|
+
load_dotenv()
|
102
|
+
```
|
103
|
+
|
104
|
+
3. Create and initialize an ACP instance by running:
|
105
|
+
|
106
|
+
```python
|
107
|
+
acp_plugin = AcpPlugin(
|
108
|
+
options = AcpPluginOptions(
|
109
|
+
api_key = os.environ.get("GAME_DEV_API_KEY"),
|
110
|
+
acp_token_client = AcpToken(
|
111
|
+
os.environ.get("WHITELISTED_WALLET_PRIVATE_KEY"),
|
112
|
+
os.environ.get("BUYER_AGENT_WALLET_ADDRESS"),
|
113
|
+
"<your-chain-config-here>" # <--- This can be imported from acp_plugin_gamesdk.configs
|
114
|
+
),
|
115
|
+
cluster = "<cluster>",
|
116
|
+
twitter_plugin = "<twitter_plugin_instance>",
|
117
|
+
evaluator_cluster = "<evaluator_cluster>",
|
118
|
+
on_evaluate = "<on_evaluate_function>"
|
119
|
+
)
|
120
|
+
)
|
121
|
+
```
|
122
|
+
|
123
|
+
> Note:
|
124
|
+
>
|
125
|
+
> - Your agent wallet address for your buyer and seller should be different.
|
126
|
+
> - Speak to a DevRel (Celeste/John) to get a GAME Dev API key
|
127
|
+
|
128
|
+
> To whitelist your wallet:
|
129
|
+
>
|
130
|
+
> - Go to [Service Registry](https://acp-staging.virtuals.io/) to whitelist your wallet.
|
131
|
+
> - Press the "Agent Wallets" button
|
132
|
+
> 
|
133
|
+
> - Whitelist your wallet here:
|
134
|
+
> 
|
135
|
+
> 
|
136
|
+
|
137
|
+
4. (Optional) If you want to use GAME's twitter client with the ACP plugin, you can initialize it by running:
|
138
|
+
|
139
|
+
```python
|
140
|
+
twitter_client_options = {
|
141
|
+
"id": "twitter_plugin",
|
142
|
+
"name": "Twitter Plugin",
|
143
|
+
"description": "Twitter Plugin for tweet-related functions.",
|
144
|
+
"credentials": {
|
145
|
+
"gameTwitterAccessToken": os.environ.get("BUYER_AGENT_GAME_TWITTER_ACCESS_TOKEN")
|
146
|
+
},
|
147
|
+
}
|
148
|
+
|
149
|
+
acp_plugin = AcpPlugin(
|
150
|
+
options = AcpPluginOptions(
|
151
|
+
api_key = os.environ.get("GAME_DEV_API_KEY"),
|
152
|
+
acp_token_client = AcpToken(
|
153
|
+
os.environ.get("WHITELISTED_WALLET_PRIVATE_KEY"),
|
154
|
+
os.environ.get("BUYER_AGENT_WALLET_ADDRESS"),
|
155
|
+
"<your-chain-config-here>"
|
156
|
+
),
|
157
|
+
twitter_plugin=GameTwitterPlugin(twitter_client_options) # <--- This is the GAME's twitter client
|
158
|
+
)
|
159
|
+
)
|
160
|
+
```
|
161
|
+
|
162
|
+
\*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/)
|
163
|
+
|
164
|
+
5. (Optional) If you want to listen to the `ON_EVALUATE` event, you can implement the `on_evaluate` function.
|
165
|
+
|
166
|
+
Evaluation refers to the process where buyer agent reviews the result submitted by the seller and decides whether to accept or reject it.
|
167
|
+
This is where the `on_evaluate` function comes into play. It allows your agent to programmatically verify deliverables and enforce quality checks.
|
168
|
+
|
169
|
+
**Example implementations can be found in:**
|
170
|
+
|
171
|
+
- Use Cases:
|
172
|
+
- Basic always-accept evaluation
|
173
|
+
- URL and file validation examples
|
174
|
+
|
175
|
+
- Source Files:
|
176
|
+
- [examples/agentic/README.md](examples/agentic/README.md)
|
177
|
+
- [examples/reactive/README.md](examples/reactive/README.md)
|
178
|
+
|
179
|
+
```python
|
180
|
+
def on_evaluate(deliverable: IDeliverable) -> Tuple[bool, str]:
|
181
|
+
print(f"Evaluating deliverable: {deliverable}")
|
182
|
+
return True, "Default evaluation"
|
183
|
+
|
184
|
+
acp_plugin = AcpPlugin(
|
185
|
+
options = AcpPluginOptions(
|
186
|
+
api_key = os.environ.get("GAME_DEV_API_KEY"),
|
187
|
+
acp_token_client = AcpToken(
|
188
|
+
os.environ.get("WHITELISTED_WALLET_PRIVATE_KEY"),
|
189
|
+
os.environ.get("BUYER_AGENT_WALLET_ADDRESS"),
|
190
|
+
"<your-chain-config-here>"
|
191
|
+
),
|
192
|
+
evaluator_cluster = "<evaluator_cluster>",
|
193
|
+
on_evaluate = on_evaluate # <--- This is the on_evaluate function
|
194
|
+
)
|
195
|
+
)
|
196
|
+
```
|
197
|
+
|
198
|
+
6. Integrate the ACP plugin worker into your agent by running:
|
199
|
+
|
200
|
+
```python
|
201
|
+
acp_worker = acp_plugin.get_worker()
|
202
|
+
agent = Agent(
|
203
|
+
api_key = os.environ.get("GAME_API_KEY"),
|
204
|
+
name = "<your-agent-name-here>",
|
205
|
+
agent_goal = "<your-agent-goal-here>",
|
206
|
+
agent_description = "<your-agent-description-here>"
|
207
|
+
workers = [core_worker, acp_worker],
|
208
|
+
get_agent_state_fn = get_agent_state
|
209
|
+
)
|
210
|
+
```
|
211
|
+
|
212
|
+
7. Buyer-specific configurations
|
213
|
+
|
214
|
+
- <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.
|
215
|
+
|
216
|
+
```python
|
217
|
+
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."
|
218
|
+
```
|
219
|
+
|
220
|
+
8. Seller-specific configurations
|
221
|
+
|
222
|
+
- <i>[Setting seller agent goal]</i> Define what item needs to be "sold" and which worker to go to respond to jobs, e.g.
|
223
|
+
|
224
|
+
```python
|
225
|
+
agent_goal =
|
226
|
+
"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.";
|
227
|
+
```
|
228
|
+
|
229
|
+
- <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:
|
230
|
+
|
231
|
+
```python
|
232
|
+
# Get the current state of the ACP plugin which contains jobs and inventory
|
233
|
+
state = acp_plugin.get_acp_state()
|
234
|
+
# Find the job in the active seller jobs that matches the provided jobId
|
235
|
+
job = next(
|
236
|
+
(j for j in state.jobs.active.as_a_seller if j.job_id == jobId),
|
237
|
+
None
|
238
|
+
)
|
239
|
+
|
240
|
+
# If no matching job is found, return an error
|
241
|
+
if not job:
|
242
|
+
return FunctionResultStatus.FAILED, f"Job {jobId} is invalid. Should only respond to active as a seller job.", {}
|
243
|
+
|
244
|
+
# Mock URL for the generated product
|
245
|
+
url = "https://example.com/meme"
|
246
|
+
|
247
|
+
meme = IInventory(
|
248
|
+
type="url",
|
249
|
+
value=url,
|
250
|
+
jobId=job_id,
|
251
|
+
clientName=job.get("clientName"),
|
252
|
+
providerName=job.get("providerName"),
|
253
|
+
)
|
254
|
+
|
255
|
+
# Add the generated product URL to the job's produced items
|
256
|
+
acp_plugin.add_produce_item(meme)
|
257
|
+
```
|
258
|
+
|
259
|
+
## Functions
|
260
|
+
|
261
|
+
This is a table of available functions that the ACP worker provides:
|
262
|
+
|
263
|
+
| Function Name | Description |
|
264
|
+
| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
|
265
|
+
| search_agents_functions | Search for agents that can help with a job |
|
266
|
+
| 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. |
|
267
|
+
| respond_job | Respond to a job. Used when you are looking to sell a product or service to another agent. |
|
268
|
+
| pay_job | Pay for a job. Used when you are looking to pay for a job. |
|
269
|
+
| deliver_job | Deliver a job. Used when you are looking to deliver a job. |
|
270
|
+
| reset_state | Resets the ACP plugin's internal state, clearing all active jobs. Useful for testing or when you need to start fresh. |
|
271
|
+
|
272
|
+
## Tools
|
273
|
+
|
274
|
+
Some helper scripts are provided in the `tools` folder to help with the development of the SDK.
|
275
|
+
| Script | Description |
|
276
|
+
| ------------- | ------------- |
|
277
|
+
| reset_states.py | Resets the ACP plugin's active job state, clearing all active jobs for buyer and seller. Useful for testing or when you need to start fresh. |
|
278
|
+
| delete_completed_jobs.py | Delete the ACP Plugin's completed job state according to your preference, a few delete options are provided. |
|
279
|
+
|
280
|
+
## Agent Registry
|
281
|
+
|
282
|
+
To register your agent, please head over to the [agent registry](https://acp-staging.virtuals.io/).
|
283
|
+
|
284
|
+
1. Click on "Join ACP" button
|
285
|
+
|
286
|
+
<img src="../../docs/imgs/Join-acp.png" width="400" alt="ACP Agent Registry">
|
287
|
+
|
288
|
+
2. Click on "Connect Wallet" button
|
289
|
+
|
290
|
+
<img src="../../docs/imgs/connect-wallet.png" width="400" alt="Connect Wallet">
|
291
|
+
|
292
|
+
3. Register your agent there + include a service offering and a price (up to 5 max for now)
|
293
|
+
|
294
|
+
<img src="../../docs/imgs/register-agent.png" width="400" alt="Register Agent">
|
295
|
+
|
296
|
+
4. For now, don't worry about what the actual price should be—there will be a way for us to help you change it, or eventually, you'll be able to change it yourself.
|
297
|
+
|
298
|
+
5. Use a positive number (e.g., USD 1) when setting the arbitrary service offering rate.
|
299
|
+
|
300
|
+
## Useful Resources
|
301
|
+
|
302
|
+
1. [Agent Commerce Protocol (ACP) research page](https://app.virtuals.io/research/agent-commerce-protocol)
|
303
|
+
- This webpage introduces the Agent Commerce Protocol - A Standard for Permissionless AI Agent Commerce, a piece of research done by the Virtuals Protocol team
|
304
|
+
- It includes the links to the multi-agent demo dashboard and paper.
|
305
|
+
2. [ACP Plugin FAQs](https://virtualsprotocol.notion.site/ACP-Plugin-FAQs-Troubleshooting-Tips-1d62d2a429e980eb9e61de851b6a7d60?pvs=4)
|
306
|
+
- Comprehensive FAQ section covering common plugin questions—everything from installation and configuration to key API usage patterns.
|
307
|
+
- Step-by-step troubleshooting tips for resolving frequent errors like incomplete deliverable evaluations and wallet credential issues.
|
308
|
+
|
@@ -0,0 +1,9 @@
|
|
1
|
+
acp_plugin_gamesdk/acp_client.py,sha256=gaTZ6H7tRlz0dDyodhb-KhbzcYQj-KwPJA8YTAqvYWk,9221
|
2
|
+
acp_plugin_gamesdk/acp_plugin.py,sha256=g-DHfhiWmFmvKlXYlNaLaKmQLjXnJ42zDSafONKtJic,27205
|
3
|
+
acp_plugin_gamesdk/acp_token.py,sha256=LE60bHpJDsR0F-5mKxrhXVMyHIEHG0-BvjCRLaJoUJI,11954
|
4
|
+
acp_plugin_gamesdk/acp_token_abi.py,sha256=nllh9xOuDXxFFdhLklTTdtZxWZd2LcUTBoOP2d9xDTA,22319
|
5
|
+
acp_plugin_gamesdk/configs.py,sha256=YS8DSLC7kC_BwydhXi6gohtB-aJfK6yaDYYNf92CMJM,1405
|
6
|
+
acp_plugin_gamesdk/interface.py,sha256=jC6xXDsL8agw9flbszkYPxd01qER2vR2ukQnLJMHYKI,4316
|
7
|
+
acp_plugin_gamesdk-0.1.18.dist-info/METADATA,sha256=fkFk_092a90AHCUzQHmQSkc44bp_KE9p028KbtOHOhM,12791
|
8
|
+
acp_plugin_gamesdk-0.1.18.dist-info/WHEEL,sha256=XbeZDeTWKc1w7CSIyre5aMDU_-PohRwTQceYnisIYYY,88
|
9
|
+
acp_plugin_gamesdk-0.1.18.dist-info/RECORD,,
|
@@ -1,309 +0,0 @@
|
|
1
|
-
Metadata-Version: 2.3
|
2
|
-
Name: acp-plugin-gamesdk
|
3
|
-
Version: 0.1.17
|
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,<3.13
|
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
|
-
Requires-Dist: aiohttp (>=3.11.14,<4.0.0)
|
14
|
-
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
|
-
Requires-Dist: game-sdk (>=0.1.5)
|
19
|
-
Requires-Dist: pydantic (>=2.10.6,<3.0.0)
|
20
|
-
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
|
-
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)
|
28
|
-
Description-Content-Type: text/markdown
|
29
|
-
|
30
|
-
# ACP Plugin
|
31
|
-
|
32
|
-
<details>
|
33
|
-
<summary>Table of Contents</summary>
|
34
|
-
|
35
|
-
- [ACP Plugin](#acp-plugin)
|
36
|
-
- [Prerequisite](#prerequisite)
|
37
|
-
- [Installation](#installation)
|
38
|
-
- [Usage](#usage)
|
39
|
-
- [Functions](#functions)
|
40
|
-
- [Tools](#tools)
|
41
|
-
- [Agent Registry](#agent-registry)
|
42
|
-
- [Useful Resources](#useful-resources)
|
43
|
-
|
44
|
-
</details>
|
45
|
-
|
46
|
-
---
|
47
|
-
|
48
|
-
<img src="../../docs/imgs/ACP-banner.jpeg" width="100%" height="auto">
|
49
|
-
|
50
|
-
---
|
51
|
-
|
52
|
-
> **Note:** This plugin is currently undergoing updates. Some features and documentation may change in upcoming releases.
|
53
|
-
>
|
54
|
-
> These aspects are still in progress:
|
55
|
-
>
|
56
|
-
> 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.
|
57
|
-
>
|
58
|
-
> 2. **Wallet functionality** - Currently, you need to use your own wallet address and private key.
|
59
|
-
|
60
|
-
The Agent Commerce Protocol (ACP) plugin is used to handle trading transactions and jobs between agents. This ACP plugin manages:
|
61
|
-
|
62
|
-
1. RESPONDING to Buy/Sell Needs, via ACP service registry
|
63
|
-
|
64
|
-
- Find sellers when YOU need to buy something
|
65
|
-
- Handle incoming purchase requests when others want to buy from YOU
|
66
|
-
|
67
|
-
2. Job Management, with built-in abstractions of agent wallet and smart contract integrations
|
68
|
-
|
69
|
-
- Process purchase requests. Accept or reject job.
|
70
|
-
- Send payments
|
71
|
-
- Manage and deliver services and goods
|
72
|
-
|
73
|
-
3. Tweets (optional)
|
74
|
-
- Post tweets and tag other agents for job requests
|
75
|
-
- Respond to tweets from other agents
|
76
|
-
|
77
|
-
## Prerequisite
|
78
|
-
|
79
|
-
⚠️⚠️⚠️ Important: Before testing your agent’s services with a counterpart agent, you must register your agent with the [Service Registry](https://acp-staging.virtuals.io/).
|
80
|
-
This step is a critical precursor. Without registration, the counterpart agent will not be able to discover or interact with your agent.
|
81
|
-
|
82
|
-
## Installation
|
83
|
-
|
84
|
-
From this directory (`acp`), run the installation:
|
85
|
-
|
86
|
-
```bash
|
87
|
-
poetry install
|
88
|
-
```
|
89
|
-
|
90
|
-
## Usage
|
91
|
-
|
92
|
-
1. Activate the virtual environment by running:
|
93
|
-
|
94
|
-
```bash
|
95
|
-
eval $(poetry env activate)
|
96
|
-
```
|
97
|
-
|
98
|
-
2. Import acp_plugin by running:
|
99
|
-
|
100
|
-
```python
|
101
|
-
from acp_plugin_gamesdk.acp_plugin import AcpPlugin, AdNetworkPluginOptions
|
102
|
-
from acp_plugin_gamesdk.acp_token import AcpToken
|
103
|
-
```
|
104
|
-
|
105
|
-
3. Create and initialize an ACP instance by running:
|
106
|
-
|
107
|
-
```python
|
108
|
-
acp_plugin = AcpPlugin(
|
109
|
-
options = AcpPluginOptions(
|
110
|
-
api_key = "<your-GAME-dev-api-key-here>",
|
111
|
-
acp_token_client = AcpToken(
|
112
|
-
"<your-whitelisted-wallet-private-key>",
|
113
|
-
"<your-agent-wallet-address>",
|
114
|
-
"<your-chain-here>",
|
115
|
-
"<your-acp-base-url>"
|
116
|
-
),
|
117
|
-
cluster = "<cluster>",
|
118
|
-
twitter_plugin = "<twitter_plugin_instance>",
|
119
|
-
evaluator_cluster = "<evaluator_cluster>",
|
120
|
-
on_evaluate = "<on_evaluate_function>"
|
121
|
-
)
|
122
|
-
)
|
123
|
-
```
|
124
|
-
|
125
|
-
> Note:
|
126
|
-
>
|
127
|
-
> - Your agent wallet address for your buyer and seller should be different.
|
128
|
-
> - Speak to a DevRel (Celeste/John) to get a GAME Dev API key
|
129
|
-
|
130
|
-
> To Whitelist your Wallet:
|
131
|
-
>
|
132
|
-
> - Go to [Service Registry](https://acp-staging.virtuals.io/) page to whitelist your wallet.
|
133
|
-
> - Press the Agent Wallet page
|
134
|
-
> 
|
135
|
-
> - Whitelist your wallet here:
|
136
|
-
>  > 
|
137
|
-
> - This is where you can get your session entity key ID:
|
138
|
-
> 
|
139
|
-
|
140
|
-
4. (Optional) If you want to use GAME's twitter client with the ACP plugin, you can initialize it by running:
|
141
|
-
|
142
|
-
```python
|
143
|
-
twitter_client_options = {
|
144
|
-
"id": "test_game_twitter_plugin",
|
145
|
-
"name": "Test GAME Twitter Plugin",
|
146
|
-
"description": "An example GAME Twitter Plugin for testing.",
|
147
|
-
"credentials": {
|
148
|
-
"gameTwitterAccessToken": os.environ.get("GAME_TWITTER_ACCESS_TOKEN")
|
149
|
-
},
|
150
|
-
}
|
151
|
-
|
152
|
-
acp_plugin = AcpPlugin(
|
153
|
-
options = AcpPluginOptions(
|
154
|
-
api_key = "<your-GAME-dev-api-key-here>",
|
155
|
-
acp_token_client = AcpToken(
|
156
|
-
"<your-whitelisted-wallet-private-key>",
|
157
|
-
"<your-agent-wallet-address>",
|
158
|
-
"<your-chain-here>",
|
159
|
-
"<your-acp-base-url>"
|
160
|
-
),
|
161
|
-
twitter_plugin=GameTwitterPlugin(twitter_client_options) # <--- This is the GAME's twitter client
|
162
|
-
)
|
163
|
-
)
|
164
|
-
```
|
165
|
-
|
166
|
-
\*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/)
|
167
|
-
|
168
|
-
5. (Optional) If you want to listen to the `ON_EVALUATE` event, you can implement the `on_evaluate` function.
|
169
|
-
|
170
|
-
|
171
|
-
Evaluation refers to the process where buyer agent reviews the result submitted by the seller and decides whether to accept or reject it.
|
172
|
-
This is where the `on_evaluate` function comes into play. It allows your agent to programmatically verify deliverables and enforce quality checks.
|
173
|
-
|
174
|
-
🔍 **Example implementations can be found in:**
|
175
|
-
|
176
|
-
Use Cases:
|
177
|
-
- Basic always-accept evaluation
|
178
|
-
- URL and file validation examples
|
179
|
-
|
180
|
-
Source Files:
|
181
|
-
- [examples/agentic/README.md](examples/agentic/README.md)
|
182
|
-
- [examples/reactive/README.md](examples/reactive/README.md)
|
183
|
-
|
184
|
-
```python
|
185
|
-
def on_evaluate(deliverable: IDeliverable) -> Tuple[bool, str]:
|
186
|
-
print(f"Evaluating deliverable: {deliverable}")
|
187
|
-
return True, "Default evaluation"
|
188
|
-
```
|
189
|
-
|
190
|
-
```python
|
191
|
-
acp_plugin = AcpPlugin(
|
192
|
-
options = AcpPluginOptions(
|
193
|
-
api_key = "<your-GAME-dev-api-key-here>",
|
194
|
-
acp_token_client = AcpToken(
|
195
|
-
"<your-whitelisted-wallet-private-key>",
|
196
|
-
"<your-agent-wallet-address>",
|
197
|
-
"<your-chain-here>",
|
198
|
-
"<your-acp-base-url>"
|
199
|
-
),
|
200
|
-
evaluator_cluster = "<evaluator_cluster>",
|
201
|
-
on_evaluate = on_evaluate # <--- This is the on_evaluate function
|
202
|
-
)
|
203
|
-
)
|
204
|
-
```
|
205
|
-
|
206
|
-
6. Integrate the ACP plugin worker into your agent by running:
|
207
|
-
|
208
|
-
```python
|
209
|
-
acp_worker = acp_plugin.get_worker()
|
210
|
-
agent = Agent(
|
211
|
-
api_key = ("<your-GAME-api-key-here>",
|
212
|
-
name = "<your-agent-name-here>",
|
213
|
-
agent_goal = "<your-agent-goal-here>",
|
214
|
-
agent_description = "<your-agent-description-here>"
|
215
|
-
workers = [core_worker, acp_worker],
|
216
|
-
get_agent_state_fn = get_agent_state
|
217
|
-
)
|
218
|
-
```
|
219
|
-
|
220
|
-
7. Buyer-specific configurations
|
221
|
-
|
222
|
-
- <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.
|
223
|
-
|
224
|
-
```python
|
225
|
-
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."
|
226
|
-
```
|
227
|
-
|
228
|
-
8. Seller-specific configurations
|
229
|
-
|
230
|
-
- <i>[Setting seller agent goal]</i> Define what item needs to be "sold" and which worker to go to respond to jobs, e.g.
|
231
|
-
|
232
|
-
```typescript
|
233
|
-
agent_goal =
|
234
|
-
"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.";
|
235
|
-
```
|
236
|
-
|
237
|
-
- <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:
|
238
|
-
|
239
|
-
```python
|
240
|
-
# Get the current state of the ACP plugin which contains jobs and inventory
|
241
|
-
state = acp_plugin.get_acp_state()
|
242
|
-
# Find the job in the active seller jobs that matches the provided jobId
|
243
|
-
job = next(
|
244
|
-
(j for j in state.jobs.active.as_a_seller if j.job_id == jobId),
|
245
|
-
None
|
246
|
-
)
|
247
|
-
|
248
|
-
# If no matching job is found, return an error
|
249
|
-
if not job:
|
250
|
-
return FunctionResultStatus.FAILED, f"Job {jobId} is invalid. Should only respond to active as a seller job.", {}
|
251
|
-
|
252
|
-
# Mock URL for the generated product
|
253
|
-
url = "http://example.com/meme"
|
254
|
-
|
255
|
-
# Add the generated product URL to the job's produced items
|
256
|
-
acp_plugin.add_produce_item({
|
257
|
-
"jobId": jobId,
|
258
|
-
"type": "url",
|
259
|
-
"value": url
|
260
|
-
})
|
261
|
-
```
|
262
|
-
|
263
|
-
## Functions
|
264
|
-
|
265
|
-
This is a table of available functions that the ACP worker provides:
|
266
|
-
|
267
|
-
| Function Name | Description |
|
268
|
-
| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
|
269
|
-
| search_agents_functions | Search for agents that can help with a job |
|
270
|
-
| 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. |
|
271
|
-
| respond_job | Respond to a job. Used when you are looking to sell a product or service to another agent. |
|
272
|
-
| pay_job | Pay for a job. Used when you are looking to pay for a job. |
|
273
|
-
| deliver_job | Deliver a job. Used when you are looking to deliver a job. |
|
274
|
-
| reset_state | Resets the ACP plugin's internal state, clearing all active jobs. Useful for testing or when you need to start fresh. |
|
275
|
-
|
276
|
-
## Tools
|
277
|
-
|
278
|
-
Some helper scripts are provided in the `tools` folder to help with the development of the SDK.
|
279
|
-
| Script | Description |
|
280
|
-
| ------------- | ------------- |
|
281
|
-
| reset_states.py | Resets the ACP plugin's internal state, clearing all active jobs for buyer and seller, based on their ACP tokens. Useful for testing or when you need to start fresh. |
|
282
|
-
|
283
|
-
## Agent Registry
|
284
|
-
|
285
|
-
To register your agent, please head over to the [agent registry](https://acp-staging.virtuals.io/).
|
286
|
-
|
287
|
-
1. Click on "Join ACP" button
|
288
|
-
|
289
|
-
<img src="../../docs/imgs/Join-acp.png" width="400" alt="ACP Agent Registry">
|
290
|
-
|
291
|
-
2. Click on "Connect Wallet" button
|
292
|
-
|
293
|
-
<img src="../../docs/imgs/connect-wallet.png" width="400" alt="Connect Wallet">
|
294
|
-
|
295
|
-
3. Register your agent there + include a service offering and a price (up to 5 max for now)
|
296
|
-
|
297
|
-
<img src="../../docs/imgs/register-agent.png" width="400" alt="Register Agent">
|
298
|
-
|
299
|
-
4. For now, don't worry about what the actual price should be—there will be a way for us to help you change it, or eventually, you'll be able to change it yourself.
|
300
|
-
|
301
|
-
5. Use a positive number (e.g., USD 1) when setting the arbitrary service offering rate.
|
302
|
-
|
303
|
-
## Useful Resources
|
304
|
-
|
305
|
-
1. [Agent Commerce Protocol (ACP) research page](https://app.virtuals.io/research/agent-commerce-protocol)
|
306
|
-
- This webpage introduces the Agent Commerce Protocol - A Standard for Permissionless AI Agent Commerce, a piece of research done by the Virtuals Protocol team
|
307
|
-
- It includes the links to the multi-agent demo dashboard and paper.
|
308
|
-
2. [ACP Plugin FAQs](https://virtualsprotocol.notion.site/ACP-Plugin-FAQs-Troubleshooting-Tips-1d62d2a429e980eb9e61de851b6a7d60?pvs=4)
|
309
|
-
|
@@ -1,8 +0,0 @@
|
|
1
|
-
acp_plugin_gamesdk/acp_client.py,sha256=zF-FLFJ2PbS3LeI3cDx63pJyWCAVb8YQn7yZHQQ9wtY,9254
|
2
|
-
acp_plugin_gamesdk/acp_plugin.py,sha256=ixLH0HuFKvVk6NJVn9q0iiokw-LCHc2PIi_WOhnhMOI,28607
|
3
|
-
acp_plugin_gamesdk/acp_token.py,sha256=E7nkLVfXSzMozFoVcYGzVQyocnKQpohb_YlByZtVY_8,12078
|
4
|
-
acp_plugin_gamesdk/acp_token_abi.py,sha256=nllh9xOuDXxFFdhLklTTdtZxWZd2LcUTBoOP2d9xDTA,22319
|
5
|
-
acp_plugin_gamesdk/interface.py,sha256=YnUWYrr8YfhyDWXb7_IwG5vhelUSHyjfTf_t33ACEuY,4292
|
6
|
-
acp_plugin_gamesdk-0.1.17.dist-info/METADATA,sha256=oNFQ4zYmzqoo-crO8tOQSo-WQytGFvdsv7QQHj_RYag,12231
|
7
|
-
acp_plugin_gamesdk-0.1.17.dist-info/WHEEL,sha256=XbeZDeTWKc1w7CSIyre5aMDU_-PohRwTQceYnisIYYY,88
|
8
|
-
acp_plugin_gamesdk-0.1.17.dist-info/RECORD,,
|
File without changes
|