Skip to content

Implement V2 GAME changes #78

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 2 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
7 changes: 4 additions & 3 deletions examples/twitter-example/twitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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 () => {
Expand Down
25 changes: 20 additions & 5 deletions src/agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ interface IGameAgent {
workers: GameWorker[];
getAgentState?: () => Promise<Record<string, any>>;
llmModel?: LLMModel | string;
v2Engine?: boolean;
sessionId?: string;
}

class GameAgent implements IGameAgent {
Expand All @@ -19,7 +21,8 @@ class GameAgent implements IGameAgent {
public description: string;
public workers: GameWorker[];
public getAgentState?: () => Promise<Record<string, any>>;

public v2Engine?: boolean;
public sessionId: string | undefined = undefined;
private workerId: string;
private gameClient: IGameClient;

Expand All @@ -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;

Expand All @@ -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() {
Expand Down Expand Up @@ -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");
}
Expand Down Expand Up @@ -106,7 +112,8 @@ class GameAgent implements IGameAgent {
worker,
this.gameActionResult,
environment,
agentState
agentState,
sessionId
);

options?.verbose &&
Expand Down Expand Up @@ -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;
Expand All @@ -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,
});

Expand Down
22 changes: 19 additions & 3 deletions src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
LLMModel,
Map,
} from "./interface/GameClient";
import { randomUUID } from "node:crypto";

class GameClient implements IGameClient {
public client: Axios | null = null;
Expand Down Expand Up @@ -42,7 +43,11 @@ class GameClient implements IGameClient {
return result.data.data.accessToken;
}

private async post<T>(url: string, data: any) {
private async post<T>(
url: string,
data: any,
options?: { headers?: Record<string, any> }
) {
await this.init();

if (!this.client) {
Expand All @@ -54,6 +59,7 @@ class GameClient implements IGameClient {
method: "post",
headers: {
"Content-Type": "application/json",
...options?.headers,
},
route: url,
data,
Expand Down Expand Up @@ -91,7 +97,8 @@ class GameClient implements IGameClient {
worker: GameWorker,
gameActionResult: ExecutableGameFunctionResponseJSON | null,
environment: Record<string, any>,
agentState: Record<string, any>
agentState: Record<string, any>,
sessionId: string
) {
const payload: { [key in string]: any } = {
location: worker.id,
Expand All @@ -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;
Expand Down Expand Up @@ -148,6 +160,10 @@ class GameClient implements IGameClient {

return result.data;
}

async createSession(): Promise<string> {
return randomUUID();
}
}

export default GameClient;
77 changes: 60 additions & 17 deletions src/apiV2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand All @@ -26,17 +31,22 @@ class GameClientV2 implements IGameClient {
}

async createMap(workers: GameWorker[]): Promise<Map> {
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(
Expand All @@ -61,7 +71,8 @@ class GameClientV2 implements IGameClient {
worker: GameWorker,
gameActionResult: ExecutableGameFunctionResponseJSON | null,
environment: Record<string, any>,
agentState: Record<string, any>
agentState: Record<string, any>,
sessionId: string
): Promise<GameAction> {
const payload: { [key in string]: any } = {
location: worker.id,
Expand All @@ -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;
Expand All @@ -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 },
}
);

Expand All @@ -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 },
}
);

Expand Down Expand Up @@ -169,6 +186,32 @@ class GameClientV2 implements IGameClient {

return response.data.data;
}

async createSession(agentId: string): Promise<string> {
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;
8 changes: 6 additions & 2 deletions src/chatAgent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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");
}
Expand Down
4 changes: 3 additions & 1 deletion src/interface/GameClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,8 @@ export interface IGameClient {
worker: GameWorker,
gameActionResult: ExecutableGameFunctionResponseJSON | null,
environment: Record<string, any>,
agentState: Record<string, any>
agentState: Record<string, any>,
sessionId: string
): Promise<GameAction>;
setTask(agentId: string, task: string): Promise<string>;
getTaskAction(
Expand All @@ -70,4 +71,5 @@ export interface IGameClient {
gameActionResult: ExecutableGameFunctionResponseJSON | null,
environment: Record<string, any>
): Promise<GameAction>;
createSession(agentId: string): Promise<string>;
}