Skip to content

Feat/acp nego feature #10

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 76 additions & 0 deletions examples/chat-negotiation/test/buyer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// TODO: Point the imports to acp-node after publishing

import AcpClient from "../../../src/acpClient";
import AcpContractClient, { AcpJobPhases, AcpNegoStatus } from "../../../src/acpContractClient";
import AcpJob from "../../../src/acpJob";
import AcpMessage from "../../../src/acpMessage";
import { baseSepoliaAcpConfig } from "../../../src";
import { SimpleNegotiationManager } from "./negotiationManager";
import dotenv from 'dotenv';

dotenv.config();

const BUYER_WALLET_ADDRESS = process.env.BUYER_WALLET_ADDRESS!;
const SELLER_WALLET_ADDRESS = process.env.SELLER_WALLET_ADDRESS!;
const WHITELISTED_WALLET_ENTITY_ID = process.env.WHITELISTED_WALLET_ENTITY_ID!;
const WHITELISTED_WALLET_PRIVATE_KEY = process.env.WHITELISTED_WALLET_PRIVATE_KEY!;

async function buyer() {
console.log("Starting AI Buyer...");

const acpClient = new AcpClient({
acpContractClient: await AcpContractClient.build(
WHITELISTED_WALLET_PRIVATE_KEY as `0x${string}`,
Number(WHITELISTED_WALLET_ENTITY_ID),
BUYER_WALLET_ADDRESS as `0x${string}`,
baseSepoliaAcpConfig
),
onNewTask: async (job: AcpJob) => {
console.log(`BUYER received task: Job ${job.id}, Phase: ${job.phase}, NegoStatus: ${job.negoStatus}`);

if (job.phase === AcpJobPhases.NEGOTIATION ) {
console.log("Starting negotiation with REAL job object...");
// Ensure negoStatus is PENDING before starting negotiation
job.negoStatus = AcpNegoStatus.PENDING;
console.log(`Set job ${job.id} negoStatus to PENDING`);

await SimpleNegotiationManager.negotiateChatWithoutSocket(
BUYER_WALLET_ADDRESS,
SELLER_WALLET_ADDRESS,
'Meme generator service',
1,
2
);
}
},
onNewMsg: async (msg: AcpMessage, job: AcpJob) => {
// Handle messages during negotiation
if (msg.messages && msg.messages.length > 0) {
const latestMessage = msg.messages[msg.messages.length - 1];

if (latestMessage.sender !== BUYER_WALLET_ADDRESS) {
const isDone = await SimpleNegotiationManager.handleMessage(
BUYER_WALLET_ADDRESS,
latestMessage.content,
msg,
job
);

if (isDone) {
console.log("Negotiation complete - paying...");
await job.pay(1000);
}
}
}
},
onEvaluate: async (job: AcpJob) => {
await job.evaluate(true, "AI buyer approved");
},
});

console.log("Starting job...");
const jobId = await acpClient.initiateJob(SELLER_WALLET_ADDRESS as `0x${string}`, "Meme generator", undefined);
console.log(`Job ${jobId} initiated - waiting for seller response...`);
}

buyer();
96 changes: 96 additions & 0 deletions examples/chat-negotiation/test/negotiationAgent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { ChatAgent } from "@virtuals-protocol/game";
import { GameFunction } from "@virtuals-protocol/game";
import { AcpNegoStatus } from "../../../src/acpContractClient";
import { NegotiationState } from "../newNegotiationAgent";

// Use ACP negotiation states instead of custom ones
export { AcpNegoStatus as NegotiationState } from "../../../src/acpContractClient";

export interface NegotiationTerms {
quantity: number;
pricePerUnit: number;
requirements: string;
}

export interface AgentConfig {
role: 'client' | 'provider';
budget?: number; // For buyers
minPrice?: number; // For sellers
maxPrice?: number; // For sellers
}

// Add helper type for external API
export type BuyerConfig = {
budget?: number;
};

export type SellerConfig = {
minPrice?: number;
maxPrice?: number;
};

export class NegotiationAgent {
private chatAgent: ChatAgent;
private chat: any;
private agentName: string;
private partnerId: string;

constructor(
apiKey: string,
systemPrompt: string,
agentName: string,
actionSpace: GameFunction<any>[],
partnerId: string = "negotiation-partner"
) {
this.chatAgent = new ChatAgent(apiKey, systemPrompt);
this.agentName = agentName;
this.partnerId = partnerId;
}

async initialize(actionSpace: GameFunction<any>[]) {
// Create chat with the action space
this.chat = await this.chatAgent.createChat({
partnerId: this.partnerId,
partnerName: this.partnerId,
actionSpace: actionSpace,
});

console.log(`🤖 ${this.agentName} initialized with ChatAgent`);
}

async sendMessage(incomingMessage: string): Promise<{
message?: string;
functionCall?: {
fn_name: string;
arguments: any;
};
isFinished?: boolean;
}> {
try {
// Use the ChatAgent's next method to process the message
const response = await this.chat.next(incomingMessage);

return {
message: response.message,
functionCall: response.functionCall ? {
fn_name: response.functionCall.fn_name,
arguments: response.functionCall.arguments
} : undefined,
isFinished: response.isFinished
};

} catch (error: any) {
console.error(`${this.agentName} error:`, error.message);

// Fallback response
return {
message: "I'm having trouble processing that. Could you rephrase?"
};
}
}

// Get conversation history for debugging
getHistory(): any {
return this.chat?.getHistory() || [];
}
}
Loading