Skip to content

Commit bd7de6f

Browse files
committed
chatAgent negotiation test
1 parent 2502c35 commit bd7de6f

4 files changed

Lines changed: 656 additions & 0 deletions

File tree

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
// TODO: Point the imports to acp-node after publishing
2+
3+
import AcpClient from "../../../src/acpClient";
4+
import AcpContractClient, { AcpJobPhases, AcpNegoStatus } from "../../../src/acpContractClient";
5+
import AcpJob from "../../../src/acpJob";
6+
import AcpMessage from "../../../src/acpMessage";
7+
import { baseSepoliaAcpConfig } from "../../../src";
8+
import { SimpleNegotiationManager } from "./negotiationManager";
9+
import dotenv from 'dotenv';
10+
11+
dotenv.config();
12+
13+
const BUYER_WALLET_ADDRESS = process.env.BUYER_WALLET_ADDRESS!;
14+
const SELLER_WALLET_ADDRESS = process.env.SELLER_WALLET_ADDRESS!;
15+
const WHITELISTED_WALLET_ENTITY_ID = process.env.WHITELISTED_WALLET_ENTITY_ID!;
16+
const WHITELISTED_WALLET_PRIVATE_KEY = process.env.WHITELISTED_WALLET_PRIVATE_KEY!;
17+
18+
async function buyer() {
19+
console.log("Starting AI Buyer...");
20+
21+
const acpClient = new AcpClient({
22+
acpContractClient: await AcpContractClient.build(
23+
WHITELISTED_WALLET_PRIVATE_KEY as `0x${string}`,
24+
Number(WHITELISTED_WALLET_ENTITY_ID),
25+
BUYER_WALLET_ADDRESS as `0x${string}`,
26+
baseSepoliaAcpConfig
27+
),
28+
onNewTask: async (job: AcpJob) => {
29+
console.log(`BUYER received task: Job ${job.id}, Phase: ${job.phase}, NegoStatus: ${job.negoStatus}`);
30+
31+
if (job.phase === AcpJobPhases.NEGOTIATION ) {
32+
console.log("Starting negotiation with REAL job object...");
33+
// Ensure negoStatus is PENDING before starting negotiation
34+
job.negoStatus = AcpNegoStatus.PENDING;
35+
console.log(`Set job ${job.id} negoStatus to PENDING`);
36+
37+
await SimpleNegotiationManager.negotiateChatWithoutSocket(
38+
BUYER_WALLET_ADDRESS,
39+
SELLER_WALLET_ADDRESS,
40+
'Meme generator service',
41+
1,
42+
2
43+
);
44+
}
45+
},
46+
onNewMsg: async (msg: AcpMessage, job: AcpJob) => {
47+
// Handle messages during negotiation
48+
if (msg.messages && msg.messages.length > 0) {
49+
const latestMessage = msg.messages[msg.messages.length - 1];
50+
51+
if (latestMessage.sender !== BUYER_WALLET_ADDRESS) {
52+
const isDone = await SimpleNegotiationManager.handleMessage(
53+
BUYER_WALLET_ADDRESS,
54+
latestMessage.content,
55+
msg,
56+
job
57+
);
58+
59+
if (isDone) {
60+
console.log("Negotiation complete - paying...");
61+
await job.pay(1000);
62+
}
63+
}
64+
}
65+
},
66+
onEvaluate: async (job: AcpJob) => {
67+
await job.evaluate(true, "AI buyer approved");
68+
},
69+
});
70+
71+
console.log("Starting job...");
72+
const jobId = await acpClient.initiateJob(SELLER_WALLET_ADDRESS as `0x${string}`, "Meme generator", undefined);
73+
console.log(`Job ${jobId} initiated - waiting for seller response...`);
74+
}
75+
76+
buyer();
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import { ChatAgent } from "@virtuals-protocol/game";
2+
import { GameFunction } from "@virtuals-protocol/game";
3+
import { AcpNegoStatus } from "../../../src/acpContractClient";
4+
import { NegotiationState } from "../newNegotiationAgent";
5+
6+
// Use ACP negotiation states instead of custom ones
7+
export { AcpNegoStatus as NegotiationState } from "../../../src/acpContractClient";
8+
9+
export interface NegotiationTerms {
10+
quantity: number;
11+
pricePerUnit: number;
12+
requirements: string;
13+
}
14+
15+
export interface AgentConfig {
16+
role: 'client' | 'provider';
17+
budget?: number; // For buyers
18+
minPrice?: number; // For sellers
19+
maxPrice?: number; // For sellers
20+
}
21+
22+
// Add helper type for external API
23+
export type BuyerConfig = {
24+
budget?: number;
25+
};
26+
27+
export type SellerConfig = {
28+
minPrice?: number;
29+
maxPrice?: number;
30+
};
31+
32+
export class NegotiationAgent {
33+
private chatAgent: ChatAgent;
34+
private chat: any;
35+
private agentName: string;
36+
private partnerId: string;
37+
38+
constructor(
39+
apiKey: string,
40+
systemPrompt: string,
41+
agentName: string,
42+
actionSpace: GameFunction<any>[],
43+
partnerId: string = "negotiation-partner"
44+
) {
45+
this.chatAgent = new ChatAgent(apiKey, systemPrompt);
46+
this.agentName = agentName;
47+
this.partnerId = partnerId;
48+
}
49+
50+
async initialize(actionSpace: GameFunction<any>[]) {
51+
// Create chat with the action space
52+
this.chat = await this.chatAgent.createChat({
53+
partnerId: this.partnerId,
54+
partnerName: this.partnerId,
55+
actionSpace: actionSpace,
56+
});
57+
58+
console.log(`🤖 ${this.agentName} initialized with ChatAgent`);
59+
}
60+
61+
async sendMessage(incomingMessage: string): Promise<{
62+
message?: string;
63+
functionCall?: {
64+
fn_name: string;
65+
arguments: any;
66+
};
67+
isFinished?: boolean;
68+
}> {
69+
try {
70+
// Use the ChatAgent's next method to process the message
71+
const response = await this.chat.next(incomingMessage);
72+
73+
return {
74+
message: response.message,
75+
functionCall: response.functionCall ? {
76+
fn_name: response.functionCall.fn_name,
77+
arguments: response.functionCall.arguments
78+
} : undefined,
79+
isFinished: response.isFinished
80+
};
81+
82+
} catch (error: any) {
83+
console.error(`${this.agentName} error:`, error.message);
84+
85+
// Fallback response
86+
return {
87+
message: "I'm having trouble processing that. Could you rephrase?"
88+
};
89+
}
90+
}
91+
92+
// Get conversation history for debugging
93+
getHistory(): any {
94+
return this.chat?.getHistory() || [];
95+
}
96+
}

0 commit comments

Comments
 (0)