diff --git a/src/agent/agent.js b/src/agent/agent.js index 3a438de..4e652ef 100644 --- a/src/agent/agent.js +++ b/src/agent/agent.js @@ -2,7 +2,7 @@ import { initBot } from '../utils/mcdata.js'; import { sendRequest } from '../utils/gpt.js'; import { History } from './history.js'; import { Coder } from './coder.js'; -import { getCommand, containsCommand } from './commands.js'; +import { containsCommand, executeCommand } from './commands.js'; import { Events } from './events.js'; @@ -56,7 +56,7 @@ export class Agent { const user_command_name = containsCommand(message); if (user_command_name) { this.bot.chat(`*${source} used ${user_command_name.substring(1)}*`); - let execute_res = await getCommand(user_command_name).perform(this); + let execute_res = await executeCommand(this, message); if (execute_res) this.bot.chat(execute_res); else @@ -73,10 +73,10 @@ export class Agent { if (command_name) { // contains query or command console.log('Query/Command response:', res); - let message = res.substring(0, res.indexOf(command_name)).trim(); + let pre_message = res.substring(0, res.indexOf(command_name)).trim(); - this.bot.chat(`${message} *used ${command_name.substring(1)}*`); - let execute_res = await getCommand(command_name).perform(this); + this.bot.chat(`${pre_message} *used ${command_name.substring(1)}*`); + let execute_res = await executeCommand(this, res); console.log('Agent executed:', command_name, 'and got:', execute_res); diff --git a/src/agent/commands.js b/src/agent/commands.js index 8015fae..88dcc7e 100644 --- a/src/agent/commands.js +++ b/src/agent/commands.js @@ -12,21 +12,84 @@ export function getCommand(name) { return commandMap[name]; } +const commandRegex = /!(\w+)(?:\(((?:[^)(]+|'[^']*'|"[^"]*")*)\))?/ +const argRegex = /(?:"[^"]*"|'[^']*'|[^,])+/g; + export function containsCommand(message) { - for (let command of commandList) { - if (message.includes(command.name)) { - return command.name; - } + const commandMatch = message.match(commandRegex); + if (commandMatch) { + const commandName = "!"+commandMatch[1]; + if (commandList.some((command) => command.name === commandName)) + return commandName; } return null; } +function parseCommandMessage(message) { + const commandMatch = message.match(commandRegex); + if (commandMatch) { + const commandName = "!"+commandMatch[1]; + if (!commandMatch[2]) + return { commandName, args: [] }; + let args = commandMatch[2].match(argRegex); + if (args) { + for (let i = 0; i < args.length; i++) { + args[i] = args[i].trim(); + } + + for (let i = 0; i < args.length; i++) { + let arg = args[i]; + if ((arg.startsWith('"') && arg.endsWith('"')) || (arg.startsWith("'") && arg.endsWith("'"))) { + args[i] = arg.substring(1, arg.length-1); + } else if (!isNaN(arg)) { + args[i] = Number(arg); + } + } + } + else + args = []; + + return { commandName, args }; + } + return null; +} + +function numParams(command) { + if (!command.params) + return 0; + return Object.keys(command.params).length; +} + +export async function executeCommand(agent, message) { + let parsed = parseCommandMessage(message); + if (parsed) { + const command = getCommand(parsed.commandName); + let numArgs = 0; + if (parsed.args) { + numArgs = parsed.args.length; + } + console.log('parsed command:', parsed); + if (numArgs !== numParams(command)) + return `Command ${command.name} was given incorrect number of arguments`; + else + return await command.perform(agent, ...parsed.args); + } + else + return `Command is incorrectly formatted`; +} + export function getCommandDocs() { - let docs = `\n*Command DOCS\n You can use the following commands to perform actions and get information about the world. - Use the commands with the syntax: !commandName \n - Don't use codeblocks. Only use one command in each response, trailing commands will be ignored. Use these commands frequently in your responses!\n`; + let docs = `\n*COMMAND DOCS\n You can use the following commands to perform actions and get information about the world. + Use the commands with the syntax: !commandName or !commandName("arg1", 1.2, ...) if the command takes arguments.\n + Do not use codeblocks. Only use one command in each response, trailing commands will be ignored. Use these commands frequently in your responses!\n`; for (let command of commandList) { docs += command.name + ': ' + command.description + '\n'; + if (command.params) { + docs += 'Params:\n'; + for (let param in command.params) { + docs += param + ': ' + command.params[param] + '\n'; + } + } } return docs + '*\n'; } \ No newline at end of file diff --git a/src/agent/commands/actions.js b/src/agent/commands/actions.js index 1ff0419..98b3af2 100644 --- a/src/agent/commands/actions.js +++ b/src/agent/commands/actions.js @@ -2,10 +2,10 @@ import * as skills from '../skills.js'; import * as world from '../world.js'; function wrapExecution(func) { - return async function (agent) { + return async function (agent, ...args) { agent.bot.output = ''; agent.coder.executing = true; - let res = await func(agent); + let res = await func(agent, ...args); if (res) agent.bot.output += '\n' + res; agent.coder.executing = false; @@ -16,8 +16,8 @@ function wrapExecution(func) { // const actionsList = [ export const actionsList = [ { - name: '!execute_action', - description: 'Write and execute code to perform custom behaviors not available as a command.', + name: '!newAction', + description: 'Perform new and unknown custom behaviors that are not available as a command by writing code.', perform: async function (agent) { let res = await agent.coder.generateCode(agent.history); if (res) @@ -32,24 +32,19 @@ export const actionsList = [ return 'Agent stopped.'; } }, + { + name: '!goToPlayer', + description: 'Go to the nearest player. Ex: !goToPlayer("steve")', + params: {'player_name': '(string) The name of the player to go to.'}, + perform: wrapExecution(async (agent, player_name) => { + return await skills.goToPlayer(agent.bot, player_name); + }) + }, // { - // name: '!gotoplayer', - // description: 'Go to the nearest player.', - // perform: wrapExecution(async (agent) => { - // let player_name = world.getNearbyPlayerNames(agent.bot); - // if (player_name.length == 0) - // return 'No players nearby.'; - // await skills.goToPlayer(agent.bot, player_name[0]); - // }) - // }, - // { - // name: '!followplayer', + // name: '!followPlayer', // description: 'Follow the nearest player.', - // perform: wrapExecution(async (agent) => { - // let player_name = world.getNearbyPlayerNames(agent.bot); - // if (player_name.length == 0) - // return 'No players nearby.'; - // await skills.followPlayer(agent.bot, player_name[0]); + // perform: wrapExecution(async (agent, player_name) => { + // await skills.followPlayer(agent.bot, player_name); // }) // }, // { diff --git a/src/agent/commands/queries.js b/src/agent/commands/queries.js index ae98164..ac5f198 100644 --- a/src/agent/commands/queries.js +++ b/src/agent/commands/queries.js @@ -96,7 +96,7 @@ export const queryList = [ } }, { - name: "!action", + name: "!currentAction", description: "Get the currently executing code.", perform: function (agent) { return pad("Current code:\n`" + agent.coder.current_code +"`"); diff --git a/src/examples.json b/src/examples.json index 6e07d6b..df329e1 100644 --- a/src/examples.json +++ b/src/examples.json @@ -15,56 +15,56 @@ {"role": "user", "content": "greg: Collect 10 wood"}, {"role": "assistant", "content": "Let me see what's nearby... !blocks"}, {"role": "system", "content": "NEARBY_BLOCKS\n- oak_log\n- dirt\n- cobblestone"}, - {"role": "assistant", "content": "I see some oak logs, dirt, and cobblestone. I'll collect oak logs. !execute_action"} + {"role": "assistant", "content": "I see some oak logs, dirt, and cobblestone. I'll collect oak logs. !newAction"} ], [ {"role": "user", "content": "bobby: Craft a plank"}, {"role": "assistant", "content": "Okay! !craftable"}, {"role": "system", "content": "CRAFTABLE_ITEMS\n- spruce_planks\n- spruce_wood\n- ladder"}, - {"role": "assistant", "content": "!execute_action"} + {"role": "assistant", "content": "!newAction"} ], [ {"role": "user", "content": "zZZn98: come here"}, - {"role": "assistant", "content": "!execute_action"}, + {"role": "assistant", "content": "!goToPlayer('sumixam0')"}, {"role": "system", "content": "Code execution finished successfully."}, {"role": "assistant", "content": "Here!"} ], [ {"role": "user", "content": "hanky: collect some sand and give it to me please"}, - {"role": "assistant", "content": "Collecting sand... !execute_action"}, + {"role": "assistant", "content": "Collecting sand... !newAction"}, {"role": "system", "content": "Code Output:\nYou have reached player hanky.\nCode execution finished successfully."}, {"role": "assistant", "content": "Here!"} ], [ {"role": "user", "content": "joe: Follow me!"}, - {"role": "assistant", "content": "Sure! !execute_action"} + {"role": "assistant", "content": "Sure! !newAction"} ], [ {"role": "user", "content": "hanky: kill that zombie!"}, - {"role": "assistant", "content": "I'm attacking! !execute_action"}, + {"role": "assistant", "content": "I'm attacking! !newAction"}, {"role": "system", "content": "Code Output:\nNo zombie nearby"}, {"role": "assistant", "content": "I could not find a zombie nearby."} ], [ {"role": "user", "content": "billybob: stop"}, - {"role": "assistant", "content": "!execute_action"} + {"role": "assistant", "content": "!newAction"} ], [ {"role": "user", "content": "maya: go to the nearest oak log"}, - {"role": "assistant", "content": "!execute_action`"}, + {"role": "assistant", "content": "!newAction`"}, {"role": "system", "content": "Code output:\n!!Code threw exception!! Error: ReferenceError: nearestOakLog is not defined\n Write code to fix the problem and try again."}, {"role": "assistant", "content": "Apologies, I made an error. Would you like me to try again?"} ], [ {"role": "user", "content": "trixy88: craft some sticks"}, - {"role": "assistant", "content": "!execute_action"} + {"role": "assistant", "content": "!newAction"} ] ] \ No newline at end of file