diff --git a/examples/twitter-example/twitter.ts b/examples/twitter-example/twitter.ts index 91dbf3e0..52fdbf35 100644 --- a/examples/twitter-example/twitter.ts +++ b/examples/twitter-example/twitter.ts @@ -6,11 +6,11 @@ import { GameWorker, LLMModel, } from "@virtuals-protocol/game"; -import dotenv from 'dotenv'; -import path from 'path'; +import dotenv from "dotenv"; +import path from "path"; // Load environment variables from the correct location -dotenv.config({ path: path.join(__dirname, '.env') }); +dotenv.config({ path: path.join(__dirname, ".env") }); const postTweetFunction = new GameFunction({ name: "post_tweet", @@ -114,6 +114,7 @@ const agent = new GameAgent(process.env.API_KEY!, { goal: "Search and reply to tweets", description: "A bot that searches for tweets and replies to them", workers: [postTweetWorker], + v2Engine: true, llmModel: LLMModel.DeepSeek_R1, // Optional: Set the LLM model default (LLMModel.Llama_3_1_405B_Instruct) // Optional: Get the agent state getAgentState: async () => { diff --git a/src/agent.ts b/src/agent.ts index 091a2c2b..925c812f 100644 --- a/src/agent.ts +++ b/src/agent.ts @@ -11,6 +11,8 @@ interface IGameAgent { workers: GameWorker[]; getAgentState?: () => Promise>; llmModel?: LLMModel | string; + v2Engine?: boolean; + sessionId?: string; } class GameAgent implements IGameAgent { @@ -19,7 +21,8 @@ class GameAgent implements IGameAgent { public description: string; public workers: GameWorker[]; public getAgentState?: () => Promise>; - + public v2Engine?: boolean; + public sessionId: string | undefined = undefined; private workerId: string; private gameClient: IGameClient; @@ -33,9 +36,10 @@ class GameAgent implements IGameAgent { constructor(apiKey: string, options: IGameAgent) { const llmModel = options.llmModel || LLMModel.Llama_3_1_405B_Instruct; + const v2Engine = options.v2Engine || false; this.gameClient = apiKey.startsWith("apt-") - ? new GameClientV2(apiKey, llmModel) + ? new GameClientV2(apiKey, llmModel, v2Engine) : new GameClient(apiKey, llmModel); this.workerId = options.workers[0].id; @@ -44,6 +48,8 @@ class GameAgent implements IGameAgent { this.description = options.description; this.workers = options.workers; this.getAgentState = options.getAgentState; + + this.sessionId = undefined; } async init() { @@ -77,7 +83,7 @@ class GameAgent implements IGameAgent { return worker; } - async step(options?: { verbose: boolean }) { + async step(sessionId: string, options?: { verbose: boolean }) { if (!this.agentId || !this.mapId) { throw new Error("Agent not initialized"); } @@ -106,7 +112,8 @@ class GameAgent implements IGameAgent { worker, this.gameActionResult, environment, - agentState + agentState, + sessionId ); options?.verbose && @@ -142,6 +149,8 @@ class GameAgent implements IGameAgent { this.gameActionResult = result.toJSON(action.action_args.fn_id); + this.log(`Function result: ${JSON.stringify(this.gameActionResult)}.`); + break; case ActionType.GoTo: this.workerId = action.action_args.location_id; @@ -163,8 +172,14 @@ class GameAgent implements IGameAgent { throw new Error("Agent not initialized"); } + this.sessionId = await this.gameClient.createSession(this.agentId); + + if (!this.sessionId) { + throw new Error("Session not created"); + } + while (true) { - const action = await this.step({ + const action = await this.step(this.sessionId, { verbose: options?.verbose || false, }); diff --git a/src/api.ts b/src/api.ts index 5de57002..5f3f95e8 100644 --- a/src/api.ts +++ b/src/api.ts @@ -8,6 +8,7 @@ import { LLMModel, Map, } from "./interface/GameClient"; +import { randomUUID } from "node:crypto"; class GameClient implements IGameClient { public client: Axios | null = null; @@ -42,7 +43,11 @@ class GameClient implements IGameClient { return result.data.data.accessToken; } - private async post(url: string, data: any) { + private async post( + url: string, + data: any, + options?: { headers?: Record } + ) { await this.init(); if (!this.client) { @@ -54,6 +59,7 @@ class GameClient implements IGameClient { method: "post", headers: { "Content-Type": "application/json", + ...options?.headers, }, route: url, data, @@ -91,7 +97,8 @@ class GameClient implements IGameClient { worker: GameWorker, gameActionResult: ExecutableGameFunctionResponseJSON | null, environment: Record, - agentState: Record + agentState: Record, + sessionId: string ) { const payload: { [key in string]: any } = { location: worker.id, @@ -108,7 +115,12 @@ class GameClient implements IGameClient { const result = await this.post<{ data: GameAction }>( `/v2/agents/${agentId}/actions`, - payload + { payload }, + { + headers: { + session_id: sessionId, + }, + } ); return result.data; @@ -148,6 +160,10 @@ class GameClient implements IGameClient { return result.data; } + + async createSession(): Promise { + return randomUUID(); + } } export default GameClient; diff --git a/src/apiV2.ts b/src/apiV2.ts index 50318a30..38537ae3 100644 --- a/src/apiV2.ts +++ b/src/apiV2.ts @@ -9,12 +9,17 @@ import { } from "./interface/GameClient"; import GameWorker from "./worker"; import { GameChatResponse } from "./chatAgent"; +import { randomUUID } from "node:crypto"; class GameClientV2 implements IGameClient { public client: Axios; private baseUrl = "https://sdk.game.virtuals.io/v2"; - constructor(private apiKey: string, private llmModel: LLMModel | string) { + constructor( + private apiKey: string, + private llmModel: LLMModel | string, + private v2Engine: boolean + ) { this.client = axios.create({ baseURL: this.baseUrl, headers: { @@ -26,17 +31,22 @@ class GameClientV2 implements IGameClient { } async createMap(workers: GameWorker[]): Promise { - const result = await this.client.post<{ data: Map }>("/maps", { - data: { - locations: workers.map((worker) => ({ - id: worker.id, - name: worker.name, - description: worker.description, - })), - }, - }); - - return result.data.data; + try { + const result = await this.client.post<{ data: Map }>("/maps", { + data: { + locations: workers.map((worker) => ({ + id: worker.id, + name: worker.name, + description: worker.description, + })), + }, + }); + + return result.data.data; + } catch (error) { + console.error("Error creating map:", error); + throw error; + } } async createAgent( @@ -61,7 +71,8 @@ class GameClientV2 implements IGameClient { worker: GameWorker, gameActionResult: ExecutableGameFunctionResponseJSON | null, environment: Record, - agentState: Record + agentState: Record, + sessionId: string ): Promise { const payload: { [key in string]: any } = { location: worker.id, @@ -76,11 +87,17 @@ class GameClientV2 implements IGameClient { payload.current_action = gameActionResult; } + const headers = { + ...this.client.defaults.headers.common, + session_id: sessionId, + }; + const result = await this.client.post<{ data: GameAction }>( `/agents/${agentId}/actions`, { - data: payload, - } + data: { ...payload, v2_engine: this.v2Engine }, + }, + { headers } ); return result.data.data; @@ -89,7 +106,7 @@ class GameClientV2 implements IGameClient { const result = await this.client.post<{ data: { submission_id: string } }>( `/agents/${agentId}/tasks`, { - data: { task }, + data: { task, v2_engine: this.v2Engine }, } ); @@ -115,7 +132,7 @@ class GameClientV2 implements IGameClient { const result = await this.client.post<{ data: GameAction }>( `/agents/${agentId}/tasks/${submissionId}/next`, { - data: payload, + data: { ...payload, v2_engine: this.v2Engine }, } ); @@ -169,6 +186,32 @@ class GameClientV2 implements IGameClient { return response.data.data; } + + async createSession(agentId: string): Promise { + try { + let sessionId: string; + + if (this.v2Engine) { + const response = await this.client.post<{ + data: { session_id: string }; + }>(`/agents/${agentId}/actions/start`); + + sessionId = response.data.data.session_id; + } else { + // Generate UUID v4 for non-v2 engine + sessionId = randomUUID(); + } + + if (!sessionId) { + throw new Error("Failed to create session."); + } + + return sessionId; + } catch (error) { + console.error("Error creating session:", error); + throw error; + } + } } export default GameClientV2; diff --git a/src/chatAgent.ts b/src/chatAgent.ts index dc62c087..4cc293b4 100644 --- a/src/chatAgent.ts +++ b/src/chatAgent.ts @@ -151,12 +151,16 @@ export class ChatAgent { public prompt: string; private client: GAMEClientV2; - constructor(api_key: string, prompt: string) { + constructor(api_key: string, prompt: string, v2Engine: boolean) { this._api_key = api_key; this.prompt = prompt; if (api_key.startsWith("apt-")) { - this.client = new GAMEClientV2(api_key, LLMModel.Llama_3_1_405B_Instruct); + this.client = new GAMEClientV2( + api_key, + LLMModel.Llama_3_1_405B_Instruct, + v2Engine + ); } else { throw new Error("Please use V2 API key to use ChatAgent"); } diff --git a/src/interface/GameClient.ts b/src/interface/GameClient.ts index ff0e06d4..3d0af7ff 100644 --- a/src/interface/GameClient.ts +++ b/src/interface/GameClient.ts @@ -60,7 +60,8 @@ export interface IGameClient { worker: GameWorker, gameActionResult: ExecutableGameFunctionResponseJSON | null, environment: Record, - agentState: Record + agentState: Record, + sessionId: string ): Promise; setTask(agentId: string, task: string): Promise; getTaskAction( @@ -70,4 +71,5 @@ export interface IGameClient { gameActionResult: ExecutableGameFunctionResponseJSON | null, environment: Record ): Promise; + createSession(agentId: string): Promise; }