diff --git a/services/minecraft/src/agents/actions.ts b/services/minecraft/src/agents/actions.ts index 25e2997b8..42317c2dd 100644 --- a/services/minecraft/src/agents/actions.ts +++ b/services/minecraft/src/agents/actions.ts @@ -2,136 +2,113 @@ import type { BotContext } from 'src/composables/bot' import { z } from 'zod' import * as skills from '../skills' -function runAsAction(actionFn, resume = false, timeout = -1) { - let actionLabel = null // Will be set on first use - - const wrappedAction = async function (agent, ...args) { - // Set actionLabel only once, when the action is first created - if (!actionLabel) { - const actionObj = actionsList.find(a => a.perform === wrappedAction) - actionLabel = actionObj.name.substring(1) // Remove the ! prefix - } - - const actionFnWithAgent = async () => { - await actionFn(agent, ...args) - } - const code_return = await agent.actions.runAction(`action:${actionLabel}`, actionFnWithAgent, { timeout, resume }) - if (code_return.interrupted && !code_return.timedout) - return - return code_return.message - } - - return wrappedAction -} - type ActionResult = string | Promise interface Action { readonly name: string readonly description: string readonly schema: z.ZodObject - readonly perform: (ctx: BotContext) => () => ActionResult + readonly perform: (ctx: BotContext) => (...args: any[]) => ActionResult } -function getNewAction(): Action { - return { - name: '!newAction', - description: 'Perform new and unknown custom behaviors that are not available as a command.', - schema: z.object({ - prompt: z.string().describe('A natural language prompt to guide code generation. Make a detailed step-by-step plan.'), - }), - perform: (agent: BotContext) => async () => { - if (!settings.allow_insecure_coding) - return 'newAction not allowed! Code writing is disabled in settings. Notify the user.' - return await agent.coder.generateCode(agent.history) - }, - } -} +export const actionsList: Action[] = [ + // getNewAction(): Action { + // return { + // name: 'newAction', + // description: 'Perform new and unknown custom behaviors that are not available as a command.', + // schema: z.object({ + // prompt: z.string().describe('A natural language prompt to guide code generation. Make a detailed step-by-step plan.'), + // }), + // perform: (ctx: BotContext) => async (prompt: string) => { + // if (!settings.allow_insecure_coding) + // return 'newAction not allowed! Code writing is disabled in settings. Notify the user.' + // return await ctx.coder.generateCode(ctx.history) + // }, + // } + // }, -function getStopAction(): Action { - return { - name: '!stop', - description: 'Force stop all actions and commands that are currently executing.', - schema: z.object({}), - perform: (agent: BotContext) => async () => { - await agent.actions.stop() - agent.clearBotLogs() - agent.actions.cancelResume() - agent.bot.emit('idle') - let msg = 'Agent stopped.' - if (agent.self_prompter.on) - msg += ' Self-prompting still active.' - return msg - }, - } -} + // getStopAction(): Action { + // return { + // name: 'stop', + // description: 'Force stop all actions and commands that are currently executing.', + // schema: z.object({}), + // perform: (ctx: BotContext) => async () => { + // await ctx.actions.stop() + // ctx.clearBotLogs() + // ctx.actions.cancelResume() + // ctx.bot.emit('idle') + // let msg = 'Agent stopped.' + // if (ctx.self_prompter.on) + // msg += ' Self-prompting still active.' + // return msg + // }, + // } + // }, -function getStfuAction(): Action { - return { - name: '!stfu', - description: 'Stop all chatting and self prompting, but continue current action.', - schema: z.object({}), - perform: (agent: BotContext) => async () => { - agent.openChat('Shutting up.') - agent.shutUp() - }, - } -} + // getStfuAction(): Action { + // return { + // name: 'stfu', + // description: 'Stop all chatting and self prompting, but continue current action.', + // schema: z.object({}), + // perform: (ctx: BotContext) => async () => { + // ctx.openChat('Shutting up.') + // ctx.shutUp() + // return 'Shutting up.' + // }, + // } + // }, -function getRestartAction(): Action { - return { - name: '!restart', - description: 'Restart the agent process.', - schema: z.object({}), - perform: (agent: BotContext) => async () => { - agent.cleanKill() - }, - } -} + // getRestartAction(): Action { + // return { + // name: 'restart', + // description: 'Restart the agent process.', + // schema: z.object({}), + // perform: (ctx: BotContext) => async () => { + // ctx.cleanKill() + // return 'Restarting agent...' + // }, + // } + // }, -function getClearChatAction(): Action { - return { - name: '!clearChat', - description: 'Clear the chat history.', - schema: z.object({}), - perform: (agent: BotContext) => async () => { - agent.history.clear() - return `${agent.name}'s chat history was cleared, starting new conversation from scratch.` - }, - } -} - -function getGoToPlayerAction(): Action { - return { - name: '!goToPlayer', + // getClearChatAction(): Action { + // return { + // name: 'clearChat', + // description: 'Clear the chat history.', + // schema: z.object({}), + // perform: (ctx: BotContext) => async () => { + // ctx.history.clear() + // return `${ctx.name}'s chat history was cleared, starting new conversation from scratch.` + // }, + // } + // }, + { + name: 'goToPlayer', description: 'Go to the given player.', schema: z.object({ player_name: z.string().describe('The name of the player to go to.'), closeness: z.number().describe('How close to get to the player.').min(0), }), - perform: (agent: BotContext) => runAsAction(async (agent, player_name, closeness) => { - return await skills.goToPlayer(agent.bot, player_name, closeness) - }), - } -} + perform: (ctx: BotContext) => async (player_name: string, closeness: number) => { + await skills.goToPlayer(ctx.bot, player_name, closeness) + return 'Moving to player...' + }, + }, -function getFollowPlayerAction(): Action { - return { - name: '!followPlayer', + { + name: 'followPlayer', description: 'Endlessly follow the given player.', schema: z.object({ player_name: z.string().describe('name of the player to follow.'), follow_dist: z.number().describe('The distance to follow from.').min(0), }), - perform: (agent: BotContext) => runAsAction(async (agent, player_name, follow_dist) => { - await skills.followPlayer(agent.bot, player_name, follow_dist) - }, true), - } -} + perform: (ctx: BotContext) => async (player_name: string, follow_dist: number) => { + await skills.followPlayer(ctx.bot, player_name, follow_dist) + return 'Following player...' + }, + }, -function getGoToCoordinatesAction(): Action { - return { - name: '!goToCoordinates', + { + name: 'goToCoordinates', description: 'Go to the given x, y, z location.', schema: z.object({ x: z.number().describe('The x coordinate.'), @@ -139,445 +116,350 @@ function getGoToCoordinatesAction(): Action { z: z.number().describe('The z coordinate.'), closeness: z.number().describe('How close to get to the location.').min(0), }), - perform: (agent: BotContext) => runAsAction(async (agent, x, y, z, closeness) => { - await skills.goToPosition(agent.bot, x, y, z, closeness) - }), - } -} + perform: (ctx: BotContext) => async (x: number, y: number, z: number, closeness: number) => { + await skills.goToPosition(ctx.bot, x, y, z, closeness) + return 'Moving to coordinates...' + }, + }, -function getSearchForBlockAction(): Action { - return { - name: '!searchForBlock', + { + name: 'searchForBlock', description: 'Find and go to the nearest block of a given type in a given range.', schema: z.object({ type: z.string().describe('The block type to go to.'), search_range: z.number().describe('The range to search for the block.').min(32).max(512), }), - perform: (agent: BotContext) => runAsAction(async (agent, block_type, range) => { - await skills.goToNearestBlock(agent.bot, block_type, 4, range) - }), - } -} + perform: (ctx: BotContext) => async (block_type: string, range: number) => { + await skills.goToNearestBlock(ctx.bot, block_type, 4, range) + return 'Searching for block...' + }, + }, -function getSearchForEntityAction(): Action { - return { - name: '!searchForEntity', + { + name: 'searchForEntity', description: 'Find and go to the nearest entity of a given type in a given range.', schema: z.object({ type: z.string().describe('The type of entity to go to.'), search_range: z.number().describe('The range to search for the entity.').min(32).max(512), }), - perform: (agent: BotContext) => runAsAction(async (agent, entity_type, range) => { - await skills.goToNearestEntity(agent.bot, entity_type, 4, range) - }), - } -} + perform: (ctx: BotContext) => async (entity_type: string, range: number) => { + await skills.goToNearestEntity(ctx.bot, entity_type, 4, range) + return 'Searching for entity...' + }, + }, -function getMoveAwayAction(): Action { - return { - name: '!moveAway', + { + name: 'moveAway', description: 'Move away from the current location in any direction by a given distance.', schema: z.object({ distance: z.number().describe('The distance to move away.').min(0), }), - perform: (agent: BotContext) => runAsAction(async (agent, distance) => { - await skills.moveAway(agent.bot, distance) - }), - } -} - -function getRememberHereAction(): Action { - return { - name: '!rememberHere', - description: 'Save the current location with a given name.', - schema: z.object({ - name: z.string().describe('The name to remember the location as.'), - }), - perform: (agent: BotContext) => async (name: string) => { - const pos = agent.bot.entity.position - agent.memory_bank.rememberPlace(name, pos.x, pos.y, pos.z) - return `Location saved as "${name}".` + perform: (ctx: BotContext) => async (distance: number) => { + await skills.moveAway(ctx.bot, distance) + return 'Moving away...' }, - } -} + }, -function getGoToRememberedPlaceAction(): Action { - return { - name: '!goToRememberedPlace', - description: 'Go to a saved location.', - schema: z.object({ - name: z.string().describe('The name of the location to go to.'), - }), - perform: (agent: BotContext) => runAsAction(async (agent, name) => { - const pos = agent.memory_bank.recallPlace(name) - if (!pos) { - skills.log(agent.bot, `No location named "${name}" saved.`) - return - } - await skills.goToPosition(agent.bot, pos[0], pos[1], pos[2], 1) - }), - } -} - -function getGivePlayerAction(): Action { - return { - name: '!givePlayer', + { + name: 'givePlayer', description: 'Give the specified item to the given player.', schema: z.object({ player_name: z.string().describe('The name of the player to give the item to.'), item_name: z.string().describe('The name of the item to give.'), num: z.number().int().describe('The number of items to give.').min(1), }), - perform: (agent: BotContext) => runAsAction(async (agent, player_name, item_name, num) => { - await skills.giveToPlayer(agent.bot, item_name, player_name, num) - }), - } -} + perform: (ctx: BotContext) => async (player_name: string, item_name: string, num: number) => { + await skills.giveToPlayer(ctx.bot, item_name, player_name, num) + return 'Giving items to player...' + }, + }, -function getConsumeAction(): Action { - return { - name: '!consume', + { + name: 'consume', description: 'Eat/drink the given item.', schema: z.object({ item_name: z.string().describe('The name of the item to consume.'), }), - perform: (agent: BotContext) => runAsAction(async (agent, item_name) => { - await skills.consume(agent.bot, item_name) - }), - } -} + perform: (ctx: BotContext) => async (item_name: string) => { + await skills.consume(ctx.bot, item_name) + return 'Consuming item...' + }, + }, -function getEquipAction(): Action { - return { - name: '!equip', + { + name: 'equip', description: 'Equip the given item.', schema: z.object({ item_name: z.string().describe('The name of the item to equip.'), }), - perform: (agent: BotContext) => runAsAction(async (agent, item_name) => { - await skills.equip(agent.bot, item_name) - }), - } -} + perform: (ctx: BotContext) => async (item_name: string) => { + await skills.equip(ctx.bot, item_name) + return 'Equipping item...' + }, + }, -function getPutInChestAction(): Action { - return { - name: '!putInChest', + { + name: 'putInChest', description: 'Put the given item in the nearest chest.', schema: z.object({ item_name: z.string().describe('The name of the item to put in the chest.'), num: z.number().int().describe('The number of items to put in the chest.').min(1), }), - perform: (agent: BotContext) => runAsAction(async (agent, item_name, num) => { - await skills.putInChest(agent.bot, item_name, num) - }), - } -} + perform: (ctx: BotContext) => async (item_name: string, num: number) => { + await skills.putInChest(ctx.bot, item_name, num) + return 'Putting items in chest...' + }, + }, -function getTakeFromChestAction(): Action { - return { - name: '!takeFromChest', + { + name: 'takeFromChest', description: 'Take the given items from the nearest chest.', schema: z.object({ item_name: z.string().describe('The name of the item to take.'), num: z.number().int().describe('The number of items to take.').min(1), }), - perform: (agent: BotContext) => runAsAction(async (agent, item_name, num) => { - await skills.takeFromChest(agent.bot, item_name, num) - }), - } -} + perform: (ctx: BotContext) => async (item_name: string, num: number) => { + await skills.takeFromChest(ctx.bot, item_name, num) + return 'Taking items from chest...' + }, + }, -function getViewChestAction(): Action { - return { - name: '!viewChest', + { + name: 'viewChest', description: 'View the items/counts of the nearest chest.', schema: z.object({}), - perform: (agent: BotContext) => runAsAction(async (agent) => { - await skills.viewChest(agent.bot) - }), - } -} + perform: (ctx: BotContext) => async () => { + await skills.viewChest(ctx.bot) + return 'Viewing chest contents...' + }, + }, -function getDiscardAction(): Action { - return { - name: '!discard', + { + name: 'discard', description: 'Discard the given item from the inventory.', schema: z.object({ item_name: z.string().describe('The name of the item to discard.'), num: z.number().int().describe('The number of items to discard.').min(1), }), - perform: (agent: BotContext) => runAsAction(async (agent, item_name, num) => { - const start_loc = agent.bot.entity.position - await skills.moveAway(agent.bot, 5) - await skills.discard(agent.bot, item_name, num) - await skills.goToPosition(agent.bot, start_loc.x, start_loc.y, start_loc.z, 0) - }), - } -} + perform: (ctx: BotContext) => async (item_name: string, num: number) => { + const start_loc = ctx.bot.entity.position + await skills.moveAway(ctx.bot, 5) + await skills.discard(ctx.bot, item_name, num) + await skills.goToPosition(ctx.bot, start_loc.x, start_loc.y, start_loc.z, 0) + return 'Discarding items...' + }, + }, -function getCollectBlocksAction(): Action { - return { - name: '!collectBlocks', + { + name: 'collectBlocks', description: 'Collect the nearest blocks of a given type.', schema: z.object({ type: z.string().describe('The block type to collect.'), num: z.number().int().describe('The number of blocks to collect.').min(1), }), - perform: (agent: BotContext) => runAsAction(async (agent, type, num) => { - await skills.collectBlock(agent.bot, type, num) - }, false, 10), // 10 minute timeout - } -} + perform: (ctx: BotContext) => async (type: string, num: number) => { + await skills.collectBlock(ctx.bot, type, num) + return 'Collecting blocks...' + }, + }, -function getCraftRecipeAction(): Action { - return { - name: '!craftRecipe', + { + name: 'craftRecipe', description: 'Craft the given recipe a given number of times.', schema: z.object({ recipe_name: z.string().describe('The name of the output item to craft.'), num: z.number().int().describe('The number of times to craft the recipe. This is NOT the number of output items, as it may craft many more items depending on the recipe.').min(1), }), - perform: (agent: BotContext) => runAsAction(async (agent, recipe_name, num) => { - await skills.craftRecipe(agent.bot, recipe_name, num) - }), - } -} + perform: (ctx: BotContext) => async (recipe_name: string, num: number) => { + await skills.craftRecipe(ctx.bot, recipe_name, num) + return 'Crafting items...' + }, + }, -function getSmeltItemAction(): Action { - return { - name: '!smeltItem', + { + name: 'smeltItem', description: 'Smelt the given item the given number of times.', schema: z.object({ item_name: z.string().describe('The name of the input item to smelt.'), num: z.number().int().describe('The number of times to smelt the item.').min(1), }), - perform: (agent: BotContext) => runAsAction(async (agent, item_name, num) => { - const success = await skills.smeltItem(agent.bot, item_name, num) - if (success) { - setTimeout(() => { - agent.cleanKill('Safely restarting to update inventory.') - }, 500) - } - }), - } -} + perform: (ctx: BotContext) => async (item_name: string, num: number) => { + await skills.smeltItem(ctx.bot, item_name, num) + return 'Smelting items...' + }, + }, -function getClearFurnaceAction(): Action { - return { - name: '!clearFurnace', + { + name: 'clearFurnace', description: 'Take all items out of the nearest furnace.', schema: z.object({}), - perform: (agent: BotContext) => runAsAction(async (agent) => { - await skills.clearNearestFurnace(agent.bot) - }), - } -} + perform: (ctx: BotContext) => async () => { + await skills.clearNearestFurnace(ctx.bot) + return 'Clearing furnace...' + }, + }, -function getPlaceHereAction(): Action { - return { - name: '!placeHere', + { + name: 'placeHere', description: 'Place a given block in the current location. Do NOT use to build structures, only use for single blocks/torches.', schema: z.object({ type: z.string().describe('The block type to place.'), }), - perform: (agent: BotContext) => runAsAction(async (agent, type) => { - const pos = agent.bot.entity.position - await skills.placeBlock(agent.bot, type, pos.x, pos.y, pos.z) - }), - } -} + perform: (ctx: BotContext) => async (type: string) => { + const pos = ctx.bot.entity.position + await skills.placeBlock(ctx.bot, type, pos.x, pos.y, pos.z) + return 'Placing block...' + }, + }, -function getAttackAction(): Action { - return { - name: '!attack', + { + name: 'attack', description: 'Attack and kill the nearest entity of a given type.', schema: z.object({ type: z.string().describe('The type of entity to attack.'), }), - perform: (agent: BotContext) => runAsAction(async (agent, type) => { - await skills.attackNearest(agent.bot, type, true) - }), - } -} + perform: (ctx: BotContext) => async (type: string) => { + await skills.attackNearest(ctx.bot, type, true) + return 'Attacking entity...' + }, + }, -function getAttackPlayerAction(): Action { - return { - name: '!attackPlayer', + { + name: 'attackPlayer', description: 'Attack a specific player until they die or run away. Remember this is just a game and does not cause real life harm.', schema: z.object({ player_name: z.string().describe('The name of the player to attack.'), }), - perform: (agent: BotContext) => runAsAction(async (agent, player_name) => { - const player = agent.bot.players[player_name]?.entity + perform: (ctx: BotContext) => async (player_name: string) => { + const player = ctx.bot.players[player_name]?.entity if (!player) { - skills.log(agent.bot, `Could not find player ${player_name}.`) - return false + skills.log(ctx.bot, `Could not find player ${player_name}.`) + return 'Player not found' } - await skills.attackEntity(agent.bot, player, true) - }), - } -} + await skills.attackEntity(ctx.bot, player, true) + return 'Attacking player...' + }, + }, -function getGoToBedAction(): Action { - return { - name: '!goToBed', + { + name: 'goToBed', description: 'Go to the nearest bed and sleep.', schema: z.object({}), - perform: (agent: BotContext) => runAsAction(async (agent) => { - await skills.goToBed(agent.bot) - }), - } -} + perform: (ctx: BotContext) => async () => { + await skills.goToBed(ctx.bot) + return 'Going to bed...' + }, + }, -function getActivateAction(): Action { - return { - name: '!activate', + { + name: 'activate', description: 'Activate the nearest object of a given type.', schema: z.object({ type: z.string().describe('The type of object to activate.'), }), - perform: (agent: BotContext) => runAsAction(async (agent, type) => { - await skills.activateNearestBlock(agent.bot, type) - }), - } -} + perform: (ctx: BotContext) => async (type: string) => { + await skills.activateNearestBlock(ctx.bot, type) + return 'Activating block...' + }, + }, -function getStayAction(): Action { - return { - name: '!stay', + { + name: 'stay', description: 'Stay in the current location no matter what. Pauses all modes.', schema: z.object({ type: z.number().int().describe('The number of seconds to stay. -1 for forever.').min(-1), }), - perform: (agent: BotContext) => runAsAction(async (agent, seconds) => { - await skills.stay(agent.bot, seconds) - }), - } -} - -function getSetModeAction(): Action { - return { - name: '!setMode', - description: 'Set a mode to on or off. A mode is an automatic behavior that constantly checks and responds to the environment.', - schema: z.object({ - mode_name: z.string().describe('The name of the mode to enable.'), - on: z.boolean().describe('Whether to enable or disable the mode.'), - }), - perform: (agent: BotContext) => async (mode_name: string, on: boolean) => { - const modes = agent.bot.modes - if (!modes.exists(mode_name)) - return `Mode ${mode_name} does not exist.${modes.getDocs()}` - if (modes.isOn(mode_name) === on) - return `Mode ${mode_name} is already ${on ? 'on' : 'off'}.` - modes.setOn(mode_name, on) - return `Mode ${mode_name} is now ${on ? 'on' : 'off'}.` + perform: (ctx: BotContext) => async (seconds: number) => { + await skills.stay(ctx.bot, seconds) + return 'Staying in place...' }, - } -} + }, + // getSetModeAction(): Action { + // return { + // name: 'setMode', + // description: 'Set a mode to on or off. A mode is an automatic behavior that constantly checks and responds to the environment.', + // schema: z.object({ + // mode_name: z.string().describe('The name of the mode to enable.'), + // on: z.boolean().describe('Whether to enable or disable the mode.'), + // }), + // perform: (ctx: BotContext) => async (mode_name: string, on: boolean) => { + // const modes = ctx.bot.modes + // if (!modes.exists(mode_name)) + // return `Mode ${mode_name} does not exist.${modes.getDocs()}` + // if (modes.isOn(mode_name) === on) + // return `Mode ${mode_name} is already ${on ? 'on' : 'off'}.` + // modes.setOn(mode_name, on) + // return `Mode ${mode_name} is now ${on ? 'on' : 'off'}.` + // }, + // } + // }, -function getGoalAction(): Action { - return { - name: '!goal', - description: 'Set a goal prompt to endlessly work towards with continuous self-prompting.', - schema: z.object({ - selfPrompt: z.string().describe('The goal prompt.'), - }), - perform: (agent: BotContext) => async (prompt: string) => { - if (convoManager.inConversation()) { - agent.self_prompter.setPrompt(prompt) - convoManager.scheduleSelfPrompter() - } - else { - agent.self_prompter.start(prompt) - } - }, - } -} + // getGoalAction(): Action { + // return { + // name: 'goal', + // description: 'Set a goal prompt to endlessly work towards with continuous self-prompting.', + // schema: z.object({ + // selfPrompt: z.string().describe('The goal prompt.'), + // }), + // perform: (ctx: BotContext) => async (prompt: string) => { + // if (convoManager.inConversation()) { + // ctx.self_prompter.setPrompt(prompt) + // convoManager.scheduleSelfPrompter() + // } + // else { + // ctx.self_prompter.start(prompt) + // } + // return 'Goal set...' + // }, + // } + // }, -function getEndGoalAction(): Action { - return { - name: '!endGoal', - description: 'Call when you have accomplished your goal. It will stop self-prompting and the current action.', - schema: z.object({}), - perform: (agent: BotContext) => async () => { - agent.self_prompter.stop() - convoManager.cancelSelfPrompter() - return 'Self-prompting stopped.' - }, - } -} + // getEndGoalAction(): Action { + // return { + // name: 'endGoal', + // description: 'Call when you have accomplished your goal. It will stop self-prompting and the current action.', + // schema: z.object({}), + // perform: (ctx: BotContext) => async () => { + // ctx.self_prompter.stop() + // convoManager.cancelSelfPrompter() + // return 'Self-prompting stopped.' + // }, + // } + // }, -function getStartConversationAction(): Action { - return { - name: '!startConversation', - description: 'Start a conversation with a player. Use for bots only.', - schema: z.object({ - player_name: z.string().describe('The name of the player to send the message to.'), - message: z.string().describe('The message to send.'), - }), - perform: (agent: BotContext) => async (player_name: string, message: string) => { - if (!convoManager.isOtherAgent(player_name)) - return `${player_name} is not a bot, cannot start conversation.` - if (convoManager.inConversation() && !convoManager.inConversation(player_name)) - convoManager.forceEndCurrentConversation() - else if (convoManager.inConversation(player_name)) - agent.history.add('system', `You are already in conversation with ${player_name}. Don't use this command to talk to them.`) - convoManager.startConversation(player_name, message) - }, - } -} + // getStartConversationAction(): Action { + // return { + // name: 'startConversation', + // description: 'Start a conversation with a player. Use for bots only.', + // schema: z.object({ + // player_name: z.string().describe('The name of the player to send the message to.'), + // message: z.string().describe('The message to send.'), + // }), + // perform: (ctx: BotContext) => async (player_name: string, message: string) => { + // if (!convoManager.isOtherAgent(player_name)) + // return `${player_name} is not a bot, cannot start conversation.` + // if (convoManager.inConversation() && !convoManager.inConversation(player_name)) + // convoManager.forceEndCurrentConversation() + // else if (convoManager.inConversation(player_name)) + // ctx.history.add('system', `You are already in conversation with ${player_name}. Don't use this command to talk to them.`) + // convoManager.startConversation(player_name, message) + // }, + // } + // }, -function getEndConversationAction(): Action { - return { - name: '!endConversation', - description: 'End the conversation with the given player.', - schema: z.object({ - player_name: z.string().describe('The name of the player to end the conversation with.'), - }), - perform: (agent: BotContext) => async (player_name: string) => { - if (!convoManager.inConversation(player_name)) - return `Not in conversation with ${player_name}.` - convoManager.endConversation(player_name) - return `Converstaion with ${player_name} ended.` - }, - } -} - -export const actionsList = [ - getNewAction(), - getStopAction(), - getStfuAction(), - getRestartAction(), - getClearChatAction(), - getGoToPlayerAction(), - getFollowPlayerAction(), - getGoToCoordinatesAction(), - getSearchForBlockAction(), - getSearchForEntityAction(), - getMoveAwayAction(), - getRememberHereAction(), - getGoToRememberedPlaceAction(), - getGivePlayerAction(), - getConsumeAction(), - getEquipAction(), - getPutInChestAction(), - getTakeFromChestAction(), - getViewChestAction(), - getDiscardAction(), - getCollectBlocksAction(), - getCraftRecipeAction(), - getSmeltItemAction(), - getClearFurnaceAction(), - getPlaceHereAction(), - getAttackAction(), - getAttackPlayerAction(), - getGoToBedAction(), - getActivateAction(), - getStayAction(), - getSetModeAction(), - getGoalAction(), - getEndGoalAction(), - getStartConversationAction(), - getEndConversationAction(), + // getEndConversationAction(): Action { + // return { + // name: 'endConversation', + // description: 'End the conversation with the given player.', + // schema: z.object({ + // player_name: z.string().describe('The name of the player to end the conversation with.'), + // }), + // perform: (ctx: BotContext) => async (player_name: string) => { + // if (!convoManager.inConversation(player_name)) + // return `Not in conversation with ${player_name}.` + // convoManager.endConversation(player_name) + // return `Converstaion with ${player_name} ended.` + // }, + // } + // }, ] diff --git a/services/minecraft/src/agents/agent.ts b/services/minecraft/src/agents/agent.ts deleted file mode 100644 index e69de29bb..000000000 diff --git a/services/minecraft/src/agents/openai.ts b/services/minecraft/src/agents/openai.ts index 6989b7999..865f293ba 100644 --- a/services/minecraft/src/agents/openai.ts +++ b/services/minecraft/src/agents/openai.ts @@ -3,7 +3,8 @@ import type { BotContext } from '../composables/bot' import { useLogg } from '@guiiai/logg' import { agent, neuri } from 'neuri' import { openaiConfig } from '../composables/config' -import { queryList } from './queries' +import { actionsList } from './actions' +import { queriesList } from './queries' const agents = new Set>() @@ -14,6 +15,7 @@ export async function initAgent(ctx: BotContext): Promise { let n = neuri() agents.add(initQueryAgent(ctx)) + agents.add(initActionAgent(ctx)) agents.forEach(agent => n = n.agent(agent)) @@ -29,7 +31,7 @@ export async function initQueryAgent(ctx: BotContext): Promise { logger.log('Initializing query agent') let queryAgent = agent('query') - queryList.forEach((query) => { + Object.values(queriesList).forEach((query) => { queryAgent = queryAgent.tool( query.name, query.schema, @@ -40,3 +42,19 @@ export async function initQueryAgent(ctx: BotContext): Promise { return queryAgent.build() } + +export async function initActionAgent(ctx: BotContext): Promise { + logger.log('Initializing action agent') + let actionAgent = agent('action') + + Object.values(actionsList).forEach((action) => { + actionAgent = actionAgent.tool( + action.name, + action.schema, + action.perform(ctx), + { description: action.description }, + ) + }) + + return actionAgent.build() +} diff --git a/services/minecraft/src/agents/queries.ts b/services/minecraft/src/agents/queries.ts index b872a5d24..6db4d84bf 100644 --- a/services/minecraft/src/agents/queries.ts +++ b/services/minecraft/src/agents/queries.ts @@ -1,7 +1,6 @@ import type { BotContext } from '../composables/bot' import { z } from 'zod' import { getStatusToString } from '../components/status' -import * as world from '../composables/world' // Core types type QueryResult = string | Promise @@ -24,93 +23,71 @@ function formatWearingItem(slot: string, item: string | undefined): string { return item ? `\n${slot}: ${item}` : '' } -// Query implementations -function createStatsQuery(): Query { - return { +export const queriesList: Query[] = [ + { name: 'stats', description: 'Get your bot\'s location, health, hunger, and time of day.', schema: z.object({}), perform: (ctx: BotContext) => (): string => getStatusToString(ctx), - } -} + }, + // { + // name: 'inventory', + // description: 'Get your bot\'s inventory.', + // schema: z.object({}), + // perform: (ctx: BotContext) => (): string => { + // const { bot } = ctx + // const inventory = world.getInventoryCounts({ bot, botCtx: ctx }) + // const items = Object.entries(inventory) + // .map(([item, count]) => formatInventoryItem(item, count)) + // .join('') -function createInventoryQuery(): Query { - return { - name: 'inventory', - description: 'Get your bot\'s inventory.', - schema: z.object({}), - perform: (ctx: BotContext) => (): string => { - const { bot } = ctx - const inventory = world.getInventoryCounts({ bot, botCtx: ctx }) - const items = Object.entries(inventory) - .map(([item, count]) => formatInventoryItem(item, count)) - .join('') + // const wearing = [ + // formatWearingItem('Head', bot.inventory.slots[5]?.name), + // formatWearingItem('Torso', bot.inventory.slots[6]?.name), + // formatWearingItem('Legs', bot.inventory.slots[7]?.name), + // formatWearingItem('Feet', bot.inventory.slots[8]?.name), + // ].filter(Boolean).join('') - const wearing = [ - formatWearingItem('Head', bot.inventory.slots[5]?.name), - formatWearingItem('Torso', bot.inventory.slots[6]?.name), - formatWearingItem('Legs', bot.inventory.slots[7]?.name), - formatWearingItem('Feet', bot.inventory.slots[8]?.name), - ].filter(Boolean).join('') + // return pad(`INVENTORY${items || ': Nothing'} + // ${bot.game.gameMode === 'creative' ? '\n(You have infinite items in creative mode. You do not need to gather resources!!)' : ''} + // WEARING: ${wearing || 'Nothing'}`) + // }, + // }, + // { + // name: 'nearbyBlocks', + // description: 'Get the blocks near the bot.', + // schema: z.object({}), + // perform: (ctx: BotContext) => (): string => { + // const blocks = world.getNearbyBlockTypes({ bot: ctx.bot, botCtx: ctx }) + // return pad(`NEARBY_BLOCKS${blocks.map(b => `\n- ${b}`).join('') || ': none'}`) + // }, + // }, + // { + // name: 'craftable', + // description: 'Get the craftable items with the bot\'s inventory.', + // schema: z.object({}), + // perform: (ctx: BotContext) => (): string => { + // const craftable = world.getCraftableItems({ bot: ctx.bot, botCtx: ctx }) + // return pad(`CRAFTABLE_ITEMS${craftable.map(i => `\n- ${i}`).join('') || ': none'}`) + // }, + // }, + // { + // name: 'entities', + // description: 'Get the nearby players and entities.', + // schema: z.object({}), + // perform: (ctx: BotContext) => (): string => { + // const { bot } = ctx + // const worldCtx = { bot, botCtx: ctx } + // const players = world.getNearbyPlayerNames(worldCtx) + // const entities = world.getNearbyEntityTypes(worldCtx) + // .filter((e: string) => e !== 'player' && e !== 'item') - return pad(`INVENTORY${items || ': Nothing'} -${bot.game.gameMode === 'creative' ? '\n(You have infinite items in creative mode. You do not need to gather resources!!)' : ''} -WEARING: ${wearing || 'Nothing'}`) - }, - } -} + // const result = [ + // ...players.map((p: string) => `- Human player: ${p}`), + // ...entities.map((e: string) => `- entities: ${e}`), + // ] -function createNearbyBlocksQuery(): Query { - return { - name: 'nearbyBlocks', - description: 'Get the blocks near the bot.', - schema: z.object({}), - perform: (ctx: BotContext) => (): string => { - const blocks = world.getNearbyBlockTypes({ bot: ctx.bot, botCtx: ctx }) - return pad(`NEARBY_BLOCKS${blocks.map(b => `\n- ${b}`).join('') || ': none'}`) - }, - } -} - -function createCraftableQuery(): Query { - return { - name: 'craftable', - description: 'Get the craftable items with the bot\'s inventory.', - schema: z.object({}), - perform: (ctx: BotContext) => (): string => { - const craftable = world.getCraftableItems({ bot: ctx.bot, botCtx: ctx }) - return pad(`CRAFTABLE_ITEMS${craftable.map(i => `\n- ${i}`).join('') || ': none'}`) - }, - } -} - -function createEntitiesQuery(): Query { - return { - name: 'entities', - description: 'Get the nearby players and entities.', - schema: z.object({}), - perform: (ctx: BotContext) => (): string => { - const { bot } = ctx - const worldCtx = { bot, botCtx: ctx } - const players = world.getNearbyPlayerNames(worldCtx) - const entities = world.getNearbyEntityTypes(worldCtx) - .filter((e: string) => e !== 'player' && e !== 'item') - - const result = [ - ...players.map((p: string) => `- Human player: ${p}`), - ...entities.map((e: string) => `- entities: ${e}`), - ] - - return pad(`NEARBY_ENTITIES${result.length ? `\n${result.join('\n')}` : ': none'}`) - }, - } -} - -// Export query list -export const queryList: readonly Query[] = [ - createStatsQuery(), - // createInventoryQuery(), - // createNearbyBlocksQuery(), - // createCraftableQuery(), - // createEntitiesQuery(), + // return pad(`NEARBY_ENTITIES${result.length ? `\n${result.join('\n')}` : ': none'}`) + // }, + // }, ] as const diff --git a/services/minecraft/src/skills/blocks.ts b/services/minecraft/src/skills/blocks.ts index a96e97e51..cca0cb14f 100644 --- a/services/minecraft/src/skills/blocks.ts +++ b/services/minecraft/src/skills/blocks.ts @@ -6,6 +6,87 @@ import * as mc from '../utils/mcdata' import { log } from './base' import { goToPosition } from './movement' +export async function collectBlock( + bot: Bot, + blockType: string, + num: number = 1, + exclude: typeof Vec3[] | null = null, +): Promise { + if (num < 1) { + log(bot, `Invalid number of blocks to collect: ${num}.`) + return false + } + + const blocktypes: string[] = [blockType] + if (blockType === 'coal' || blockType === 'diamond' || blockType === 'emerald' + || blockType === 'iron' || blockType === 'gold' || blockType === 'lapis_lazuli' + || blockType === 'redstone') { + blocktypes.push(`${blockType}_ore`) + } + if (blockType.endsWith('ore')) { + blocktypes.push(`deepslate_${blockType}`) + } + if (blockType === 'dirt') { + blocktypes.push('grass_block') + } + + let collected = 0 + + for (let i = 0; i < num; i++) { + let blocks = world.getNearestBlocks(bot, blocktypes, 64) + if (exclude) { + blocks = blocks.filter( + block => !exclude.some(pos => + pos.x === block.position.x + && pos.y === block.position.y + && pos.z === block.position.z, + ), + ) + } + + const movements = new bot.pathfinder.Movements(bot) + movements.dontMineUnderFallingBlock = false + blocks = blocks.filter(block => movements.safeToBreak(block)) + + if (blocks.length === 0) { + log(bot, collected === 0 + ? `No ${blockType} nearby to collect.` + : `No more ${blockType} nearby to collect.`) + break + } + + const block = blocks[0] + await bot.tool.equipForBlock(block) + const itemId = bot.heldItem ? bot.heldItem.type : null + + if (!block.canHarvest(itemId)) { + log(bot, `Don't have right tools to harvest ${blockType}.`) + return false + } + + try { + await bot.collectBlock.collect(block) + collected++ + await autoLight(bot) + } + catch (err) { + if (err.name === 'NoChests') { + log(bot, `Failed to collect ${blockType}: Inventory full, no place to deposit.`) + break + } + log(bot, `Failed to collect ${blockType}: ${err}.`) + continue + } + + if (bot.interrupt_code) { + break + } + } + + log(bot, `Collected ${collected} ${blockType}.`) + return collected > 0 +} + /** * Place a torch if needed */ diff --git a/services/minecraft/src/skills/combat.ts b/services/minecraft/src/skills/combat.ts index 8afd98aca..a6fdc989e 100644 --- a/services/minecraft/src/skills/combat.ts +++ b/services/minecraft/src/skills/combat.ts @@ -1,8 +1,8 @@ import type { Bot } from 'mineflayer' import type { Entity } from 'prismarine-entity' -import * as mc from '../../../utils/mcdata.js' -import * as world from '../world.js' -import { log } from './base.js' +import * as world from '../composables/world' +import * as mc from '../utils/mcdata' +import { log } from './base' /** * Equip the item with highest attack damage diff --git a/services/minecraft/src/skills/index.ts b/services/minecraft/src/skills/index.ts index ed2a3589e..6d1ceb0cf 100644 --- a/services/minecraft/src/skills/index.ts +++ b/services/minecraft/src/skills/index.ts @@ -1,50 +1,6 @@ -// Base utilities -export { log } from './base' -export type { BlockFace, Position } from './base' - -// Block interaction functions -export { - activateNearestBlock, - breakBlockAt, - placeBlock, - tillAndSow, - useDoor, -} from './blocks.js' - -// Combat related functions -export { - attackEntity, - attackNearest, - defendSelf, -} from './combat.js' - -// Crafting and smelting functions -export { - clearNearestFurnace, - craftRecipe, - smeltItem, -} from './crafting.js' - -// Inventory management functions -export { - consume, - discard, - equip, - giveToPlayer, - pickupNearbyItems, - putInChest, - takeFromChest, - viewChest, -} from './inventory.js' - -// Movement related functions -export { - followPlayer, - goToNearestBlock, - goToNearestEntity, - goToPlayer, - goToPosition, - moveAway, - moveAwayFromEntity, - stay, -} from './movement.js' +export * from './base' +export * from './blocks.js' +export * from './combat.js' +export * from './crafting.js' +export * from './inventory.js' +export * from './movement.js' diff --git a/services/minecraft/src/skills/movement.ts b/services/minecraft/src/skills/movement.ts index ac2778243..0e809cc26 100644 --- a/services/minecraft/src/skills/movement.ts +++ b/services/minecraft/src/skills/movement.ts @@ -206,3 +206,34 @@ export async function stay(bot: Bot, seconds = 30): Promise { log(bot, `Stayed for ${(Date.now() - start) / 1000} seconds.`) return true } +/** + * Sleep in the nearest bed within 32 blocks + */ +export async function goToBed(bot: Bot): Promise { + const beds: Vec3[] = bot.findBlocks({ + matching: (block: Block) => block.name.includes('bed'), + maxDistance: 32, + count: 1, + }) + + if (beds.length === 0) { + log(bot, 'Could not find a bed to sleep in.') + return false + } + + const loc: Vec3 = beds[0] + await goToPosition(bot, loc.x, loc.y, loc.z) + + const bed: Block | null = bot.blockAt(loc) + await bot.sleep(bed) + log(bot, 'You are in bed.') + + bot.modes.pause('unstuck') + + while (bot.isSleeping) { + await new Promise(resolve => setTimeout(resolve, 500)) + } + + log(bot, 'You have woken up.') + return true +}