mindcraft 0.1.4-0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/FAQ.md +38 -0
- package/LICENSE +21 -0
- package/README.md +255 -0
- package/andy.json +6 -0
- package/bin/mindcraft.js +80 -0
- package/keys.example.json +19 -0
- package/main.js +80 -0
- package/package.json +78 -0
- package/patches/minecraft-data+3.97.0.patch +13 -0
- package/patches/mineflayer+4.33.0.patch +54 -0
- package/patches/mineflayer-pathfinder+2.4.5.patch +265 -0
- package/patches/mineflayer-pvp+1.3.2.patch +13 -0
- package/patches/prismarine-viewer+1.33.0.patch +13 -0
- package/patches/protodef+1.19.0.patch +15 -0
- package/profiles/andy-4-reasoning.json +14 -0
- package/profiles/andy-4.json +7 -0
- package/profiles/azure.json +19 -0
- package/profiles/claude.json +7 -0
- package/profiles/claude_thinker.json +15 -0
- package/profiles/deepseek.json +7 -0
- package/profiles/defaults/_default.json +256 -0
- package/profiles/defaults/assistant.json +14 -0
- package/profiles/defaults/creative.json +14 -0
- package/profiles/defaults/god_mode.json +14 -0
- package/profiles/defaults/survival.json +14 -0
- package/profiles/freeguy.json +7 -0
- package/profiles/gemini.json +9 -0
- package/profiles/gpt.json +12 -0
- package/profiles/grok.json +7 -0
- package/profiles/llama.json +10 -0
- package/profiles/mercury.json +9 -0
- package/profiles/mistral.json +5 -0
- package/profiles/qwen.json +17 -0
- package/profiles/tasks/construction_profile.json +42 -0
- package/profiles/tasks/cooking_profile.json +11 -0
- package/profiles/tasks/crafting_profile.json +71 -0
- package/profiles/vllm.json +10 -0
- package/settings.js +64 -0
- package/src/agent/action_manager.js +177 -0
- package/src/agent/agent.js +561 -0
- package/src/agent/coder.js +229 -0
- package/src/agent/commands/actions.js +504 -0
- package/src/agent/commands/index.js +259 -0
- package/src/agent/commands/queries.js +347 -0
- package/src/agent/connection_handler.js +96 -0
- package/src/agent/conversation.js +353 -0
- package/src/agent/history.js +122 -0
- package/src/agent/library/full_state.js +89 -0
- package/src/agent/library/index.js +23 -0
- package/src/agent/library/lockdown.js +32 -0
- package/src/agent/library/skill_library.js +93 -0
- package/src/agent/library/skills.js +2093 -0
- package/src/agent/library/world.js +431 -0
- package/src/agent/memory_bank.js +25 -0
- package/src/agent/mindserver_proxy.js +136 -0
- package/src/agent/modes.js +446 -0
- package/src/agent/npc/build_goal.js +80 -0
- package/src/agent/npc/construction/dirt_shelter.json +38 -0
- package/src/agent/npc/construction/large_house.json +230 -0
- package/src/agent/npc/construction/small_stone_house.json +42 -0
- package/src/agent/npc/construction/small_wood_house.json +42 -0
- package/src/agent/npc/controller.js +261 -0
- package/src/agent/npc/data.js +50 -0
- package/src/agent/npc/item_goal.js +355 -0
- package/src/agent/npc/utils.js +126 -0
- package/src/agent/self_prompter.js +146 -0
- package/src/agent/settings.js +7 -0
- package/src/agent/speak.js +150 -0
- package/src/agent/tasks/construction_tasks.js +1104 -0
- package/src/agent/tasks/cooking_tasks.js +358 -0
- package/src/agent/tasks/tasks.js +594 -0
- package/src/agent/templates/execTemplate.js +6 -0
- package/src/agent/templates/lintTemplate.js +10 -0
- package/src/agent/vision/browser_viewer.js +8 -0
- package/src/agent/vision/camera.js +78 -0
- package/src/agent/vision/vision_interpreter.js +82 -0
- package/src/mindcraft/index.js +28 -0
- package/src/mindcraft/mcserver.js +154 -0
- package/src/mindcraft/mindcraft.js +111 -0
- package/src/mindcraft/mindserver.js +328 -0
- package/src/mindcraft/public/index.html +1253 -0
- package/src/mindcraft/public/settings_spec.json +145 -0
- package/src/mindcraft/userconfig.js +72 -0
- package/src/mindcraft-py/example.py +27 -0
- package/src/mindcraft-py/init-mindcraft.js +24 -0
- package/src/mindcraft-py/mindcraft.py +99 -0
- package/src/models/_model_map.js +89 -0
- package/src/models/azure.js +32 -0
- package/src/models/cerebras.js +61 -0
- package/src/models/claude.js +87 -0
- package/src/models/deepseek.js +59 -0
- package/src/models/gemini.js +176 -0
- package/src/models/glhf.js +71 -0
- package/src/models/gpt.js +147 -0
- package/src/models/grok.js +82 -0
- package/src/models/groq.js +95 -0
- package/src/models/huggingface.js +86 -0
- package/src/models/hyperbolic.js +114 -0
- package/src/models/lmstudio.js +74 -0
- package/src/models/mercury.js +95 -0
- package/src/models/mistral.js +94 -0
- package/src/models/novita.js +71 -0
- package/src/models/ollama.js +115 -0
- package/src/models/openrouter.js +77 -0
- package/src/models/prompter.js +366 -0
- package/src/models/qwen.js +80 -0
- package/src/models/replicate.js +60 -0
- package/src/models/vllm.js +81 -0
- package/src/process/agent_process.js +84 -0
- package/src/process/init_agent.js +54 -0
- package/src/utils/examples.js +83 -0
- package/src/utils/keys.js +34 -0
- package/src/utils/math.js +13 -0
- package/src/utils/mcdata.js +572 -0
- package/src/utils/text.js +78 -0
- package/src/utils/translator.js +30 -0
|
@@ -0,0 +1,572 @@
|
|
|
1
|
+
import minecraftData from 'minecraft-data';
|
|
2
|
+
import settings from '../agent/settings.js';
|
|
3
|
+
import { createBot } from 'mineflayer';
|
|
4
|
+
import prismarine_items from 'prismarine-item';
|
|
5
|
+
import { pathfinder } from 'mineflayer-pathfinder';
|
|
6
|
+
import { plugin as pvp } from 'mineflayer-pvp';
|
|
7
|
+
import { plugin as collectblock } from 'mineflayer-collectblock';
|
|
8
|
+
import { plugin as autoEat } from 'mineflayer-auto-eat';
|
|
9
|
+
import plugin from 'mineflayer-armor-manager';
|
|
10
|
+
const armorManager = plugin;
|
|
11
|
+
let mc_version = settings.minecraft_version;
|
|
12
|
+
let mcdata = null;
|
|
13
|
+
let Item = null;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @typedef {string} ItemName
|
|
17
|
+
* @typedef {string} BlockName
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
export const WOOD_TYPES = ['oak', 'spruce', 'birch', 'jungle', 'acacia', 'dark_oak', 'mangrove', 'cherry'];
|
|
21
|
+
export const MATCHING_WOOD_BLOCKS = [
|
|
22
|
+
'log',
|
|
23
|
+
'planks',
|
|
24
|
+
'sign',
|
|
25
|
+
'boat',
|
|
26
|
+
'fence_gate',
|
|
27
|
+
'door',
|
|
28
|
+
'fence',
|
|
29
|
+
'slab',
|
|
30
|
+
'stairs',
|
|
31
|
+
'button',
|
|
32
|
+
'pressure_plate',
|
|
33
|
+
'trapdoor'
|
|
34
|
+
]
|
|
35
|
+
export const WOOL_COLORS = [
|
|
36
|
+
'white',
|
|
37
|
+
'orange',
|
|
38
|
+
'magenta',
|
|
39
|
+
'light_blue',
|
|
40
|
+
'yellow',
|
|
41
|
+
'lime',
|
|
42
|
+
'pink',
|
|
43
|
+
'gray',
|
|
44
|
+
'light_gray',
|
|
45
|
+
'cyan',
|
|
46
|
+
'purple',
|
|
47
|
+
'blue',
|
|
48
|
+
'brown',
|
|
49
|
+
'green',
|
|
50
|
+
'red',
|
|
51
|
+
'black'
|
|
52
|
+
]
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
export function initBot(username) {
|
|
56
|
+
const options = {
|
|
57
|
+
username: username,
|
|
58
|
+
host: settings.host,
|
|
59
|
+
port: settings.port,
|
|
60
|
+
auth: settings.auth,
|
|
61
|
+
version: mc_version,
|
|
62
|
+
checkTimeoutInterval: 60000, // 60s keep-alive check (default 30s) — reduces disconnects on slow servers
|
|
63
|
+
}
|
|
64
|
+
if (!mc_version || mc_version === "auto") {
|
|
65
|
+
delete options.version;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const bot = createBot(options);
|
|
69
|
+
|
|
70
|
+
// Throttle position packets to avoid kicks on Paper/Spigot servers
|
|
71
|
+
// Paper enforces stricter packet rate limits than vanilla, causing ECONNRESET
|
|
72
|
+
// when mineflayer sends position updates faster than 50ms apart
|
|
73
|
+
let lastPositionUpdate = 0;
|
|
74
|
+
let pendingPositionPacket = null;
|
|
75
|
+
const POSITION_THROTTLE_MS = 50;
|
|
76
|
+
const originalWrite = bot._client.write.bind(bot._client);
|
|
77
|
+
bot._client.write = function(name, data) {
|
|
78
|
+
if (name === 'position' || name === 'position_look' || name === 'look') {
|
|
79
|
+
const now = Date.now();
|
|
80
|
+
if (now - lastPositionUpdate < POSITION_THROTTLE_MS) {
|
|
81
|
+
// Queue this packet so the last position update is never lost
|
|
82
|
+
if (!pendingPositionPacket) {
|
|
83
|
+
pendingPositionPacket = setTimeout(() => {
|
|
84
|
+
pendingPositionPacket = null;
|
|
85
|
+
lastPositionUpdate = Date.now();
|
|
86
|
+
originalWrite(name, data);
|
|
87
|
+
}, POSITION_THROTTLE_MS - (now - lastPositionUpdate));
|
|
88
|
+
}
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
lastPositionUpdate = now;
|
|
92
|
+
if (pendingPositionPacket) {
|
|
93
|
+
clearTimeout(pendingPositionPacket);
|
|
94
|
+
pendingPositionPacket = null;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return originalWrite(name, data);
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
// Suppress PartialReadError for non-critical packets
|
|
101
|
+
// Paper servers sometimes send packets that node-minecraft-protocol
|
|
102
|
+
// can't fully parse (scoreboard, resource_pack, custom_payload, etc.)
|
|
103
|
+
// These errors crash the bot but the packets aren't needed for gameplay
|
|
104
|
+
const originalEmit = bot._client.emit.bind(bot._client);
|
|
105
|
+
bot._client.emit = function(event, ...args) {
|
|
106
|
+
if (event === 'error' && args[0]) {
|
|
107
|
+
const err = args[0];
|
|
108
|
+
const errStr = err instanceof Error ? err.message : String(err);
|
|
109
|
+
if (errStr.includes('PartialReadError')) {
|
|
110
|
+
console.warn('[mcdata] Suppressed PartialReadError:', errStr.substring(0, 120));
|
|
111
|
+
return true; // Swallow the error
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return originalEmit(event, ...args);
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
bot.loadPlugin(pathfinder);
|
|
118
|
+
bot.loadPlugin(pvp);
|
|
119
|
+
bot.loadPlugin(collectblock);
|
|
120
|
+
bot.loadPlugin(autoEat);
|
|
121
|
+
bot.loadPlugin(armorManager); // auto equip armor
|
|
122
|
+
bot.once('resourcePack', () => {
|
|
123
|
+
bot.acceptResourcePack();
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
bot.once('login', () => {
|
|
127
|
+
mc_version = bot.version;
|
|
128
|
+
mcdata = minecraftData(mc_version);
|
|
129
|
+
Item = prismarine_items(mc_version);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
return bot;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export function isHuntable(mob) {
|
|
136
|
+
if (!mob || !mob.name) return false;
|
|
137
|
+
const animals = ['chicken', 'cow', 'llama', 'mooshroom', 'pig', 'rabbit', 'sheep'];
|
|
138
|
+
return animals.includes(mob.name.toLowerCase()) && !mob.metadata[16]; // metadata 16 is not baby
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function isHostile(mob) {
|
|
142
|
+
if (!mob || !mob.name) return false;
|
|
143
|
+
return (mob.type === 'mob' || mob.type === 'hostile') && mob.name !== 'iron_golem' && mob.name !== 'snow_golem';
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// blocks that don't work with collectBlock, need to be manually collected
|
|
147
|
+
export function mustCollectManually(blockName) {
|
|
148
|
+
// all crops (that aren't normal blocks), torches, buttons, levers, redstone,
|
|
149
|
+
const full_names = ['wheat', 'carrots', 'potatoes', 'beetroots', 'nether_wart', 'cocoa', 'sugar_cane', 'kelp', 'short_grass', 'fern', 'tall_grass', 'bamboo',
|
|
150
|
+
'poppy', 'dandelion', 'blue_orchid', 'allium', 'azure_bluet', 'oxeye_daisy', 'cornflower', 'lilac', 'wither_rose', 'lily_of_the_valley', 'wither_rose',
|
|
151
|
+
'lever', 'redstone_wire', 'lantern']
|
|
152
|
+
const partial_names = ['sapling', 'torch', 'button', 'carpet', 'pressure_plate', 'mushroom', 'tulip', 'bush', 'vines', 'fern']
|
|
153
|
+
return full_names.includes(blockName.toLowerCase()) || partial_names.some(partial => blockName.toLowerCase().includes(partial));
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export function getItemId(itemName) {
|
|
157
|
+
let item = mcdata.itemsByName[itemName];
|
|
158
|
+
if (item) {
|
|
159
|
+
return item.id;
|
|
160
|
+
}
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export function getItemName(itemId) {
|
|
165
|
+
let item = mcdata.items[itemId]
|
|
166
|
+
if (item) {
|
|
167
|
+
return item.name;
|
|
168
|
+
}
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export function getBlockId(blockName) {
|
|
173
|
+
let block = mcdata.blocksByName[blockName];
|
|
174
|
+
if (block) {
|
|
175
|
+
return block.id;
|
|
176
|
+
}
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export function getBlockName(blockId) {
|
|
181
|
+
let block = mcdata.blocks[blockId]
|
|
182
|
+
if (block) {
|
|
183
|
+
return block.name;
|
|
184
|
+
}
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export function getEntityId(entityName) {
|
|
189
|
+
let entity = mcdata.entitiesByName[entityName];
|
|
190
|
+
if (entity) {
|
|
191
|
+
return entity.id;
|
|
192
|
+
}
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export function getAllItems(ignore) {
|
|
197
|
+
if (!ignore) {
|
|
198
|
+
ignore = [];
|
|
199
|
+
}
|
|
200
|
+
let items = []
|
|
201
|
+
for (const itemId in mcdata.items) {
|
|
202
|
+
const item = mcdata.items[itemId];
|
|
203
|
+
if (!ignore.includes(item.name)) {
|
|
204
|
+
items.push(item);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
return items;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export function getAllItemIds(ignore) {
|
|
211
|
+
const items = getAllItems(ignore);
|
|
212
|
+
let itemIds = [];
|
|
213
|
+
for (const item of items) {
|
|
214
|
+
itemIds.push(item.id);
|
|
215
|
+
}
|
|
216
|
+
return itemIds;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
export function getAllBlocks(ignore) {
|
|
220
|
+
if (!ignore) {
|
|
221
|
+
ignore = [];
|
|
222
|
+
}
|
|
223
|
+
let blocks = []
|
|
224
|
+
for (const blockId in mcdata.blocks) {
|
|
225
|
+
const block = mcdata.blocks[blockId];
|
|
226
|
+
if (!ignore.includes(block.name)) {
|
|
227
|
+
blocks.push(block);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
return blocks;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
export function getAllBlockIds(ignore) {
|
|
234
|
+
const blocks = getAllBlocks(ignore);
|
|
235
|
+
let blockIds = [];
|
|
236
|
+
for (const block of blocks) {
|
|
237
|
+
blockIds.push(block.id);
|
|
238
|
+
}
|
|
239
|
+
return blockIds;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
export function getAllBiomes() {
|
|
243
|
+
return mcdata.biomes;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
export function getItemCraftingRecipes(itemName) {
|
|
247
|
+
let itemId = getItemId(itemName);
|
|
248
|
+
if (!mcdata.recipes[itemId]) {
|
|
249
|
+
return null;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
let recipes = [];
|
|
253
|
+
for (let r of mcdata.recipes[itemId]) {
|
|
254
|
+
let recipe = {};
|
|
255
|
+
let ingredients = [];
|
|
256
|
+
if (r.ingredients) {
|
|
257
|
+
ingredients = r.ingredients;
|
|
258
|
+
} else if (r.inShape) {
|
|
259
|
+
ingredients = r.inShape.flat();
|
|
260
|
+
}
|
|
261
|
+
for (let ingredient of ingredients) {
|
|
262
|
+
let ingredientName = getItemName(ingredient);
|
|
263
|
+
if (ingredientName === null) continue;
|
|
264
|
+
if (!recipe[ingredientName])
|
|
265
|
+
recipe[ingredientName] = 0;
|
|
266
|
+
recipe[ingredientName]++;
|
|
267
|
+
}
|
|
268
|
+
recipes.push([
|
|
269
|
+
recipe,
|
|
270
|
+
{craftedCount : r.result.count}
|
|
271
|
+
]);
|
|
272
|
+
}
|
|
273
|
+
// sort recipes by if their ingredients include common items
|
|
274
|
+
const commonItems = ['oak_planks', 'oak_log', 'coal', 'cobblestone'];
|
|
275
|
+
recipes.sort((a, b) => {
|
|
276
|
+
let commonCountA = Object.keys(a[0]).filter(key => commonItems.includes(key)).reduce((acc, key) => acc + a[0][key], 0);
|
|
277
|
+
let commonCountB = Object.keys(b[0]).filter(key => commonItems.includes(key)).reduce((acc, key) => acc + b[0][key], 0);
|
|
278
|
+
return commonCountB - commonCountA;
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
return recipes;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
export function isSmeltable(itemName) {
|
|
285
|
+
const misc_smeltables = ['beef', 'chicken', 'cod', 'mutton', 'porkchop', 'rabbit', 'salmon', 'tropical_fish', 'potato', 'kelp', 'sand', 'cobblestone', 'clay_ball'];
|
|
286
|
+
return itemName.includes('raw') || itemName.includes('log') || misc_smeltables.includes(itemName);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
export function getSmeltingFuel(bot) {
|
|
290
|
+
let fuel = bot.inventory.items().find(i => i.name === 'coal' || i.name === 'charcoal' || i.name === 'blaze_rod')
|
|
291
|
+
if (fuel)
|
|
292
|
+
return fuel;
|
|
293
|
+
fuel = bot.inventory.items().find(i => i.name.includes('log') || i.name.includes('planks'))
|
|
294
|
+
if (fuel)
|
|
295
|
+
return fuel;
|
|
296
|
+
return bot.inventory.items().find(i => i.name === 'coal_block' || i.name === 'lava_bucket');
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
export function getFuelSmeltOutput(fuelName) {
|
|
300
|
+
if (fuelName === 'coal' || fuelName === 'charcoal')
|
|
301
|
+
return 8;
|
|
302
|
+
if (fuelName === 'blaze_rod')
|
|
303
|
+
return 12;
|
|
304
|
+
if (fuelName.includes('log') || fuelName.includes('planks'))
|
|
305
|
+
return 1.5
|
|
306
|
+
if (fuelName === 'coal_block')
|
|
307
|
+
return 80;
|
|
308
|
+
if (fuelName === 'lava_bucket')
|
|
309
|
+
return 100;
|
|
310
|
+
return 0;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
export function getItemSmeltingIngredient(itemName) {
|
|
314
|
+
return {
|
|
315
|
+
baked_potato: 'potato',
|
|
316
|
+
steak: 'raw_beef',
|
|
317
|
+
cooked_chicken: 'raw_chicken',
|
|
318
|
+
cooked_cod: 'raw_cod',
|
|
319
|
+
cooked_mutton: 'raw_mutton',
|
|
320
|
+
cooked_porkchop: 'raw_porkchop',
|
|
321
|
+
cooked_rabbit: 'raw_rabbit',
|
|
322
|
+
cooked_salmon: 'raw_salmon',
|
|
323
|
+
dried_kelp: 'kelp',
|
|
324
|
+
iron_ingot: 'raw_iron',
|
|
325
|
+
gold_ingot: 'raw_gold',
|
|
326
|
+
copper_ingot: 'raw_copper',
|
|
327
|
+
glass: 'sand'
|
|
328
|
+
}[itemName];
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
export function getItemBlockSources(itemName) {
|
|
332
|
+
let itemId = getItemId(itemName);
|
|
333
|
+
let sources = [];
|
|
334
|
+
for (let block of getAllBlocks()) {
|
|
335
|
+
if (block.drops.includes(itemId)) {
|
|
336
|
+
sources.push(block.name);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
return sources;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
export function getItemAnimalSource(itemName) {
|
|
343
|
+
return {
|
|
344
|
+
raw_beef: 'cow',
|
|
345
|
+
raw_chicken: 'chicken',
|
|
346
|
+
raw_cod: 'cod',
|
|
347
|
+
raw_mutton: 'sheep',
|
|
348
|
+
raw_porkchop: 'pig',
|
|
349
|
+
raw_rabbit: 'rabbit',
|
|
350
|
+
raw_salmon: 'salmon',
|
|
351
|
+
leather: 'cow',
|
|
352
|
+
wool: 'sheep'
|
|
353
|
+
}[itemName];
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
export function getBlockTool(blockName) {
|
|
357
|
+
let block = mcdata.blocksByName[blockName];
|
|
358
|
+
if (!block || !block.harvestTools) {
|
|
359
|
+
return null;
|
|
360
|
+
}
|
|
361
|
+
return getItemName(Object.keys(block.harvestTools)[0]); // Double check first tool is always simplest
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
export function makeItem(name, amount=1) {
|
|
365
|
+
return new Item(getItemId(name), amount);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Returns the number of ingredients required to use the recipe once.
|
|
370
|
+
*
|
|
371
|
+
* @param {Recipe} recipe
|
|
372
|
+
* @returns {Object<mc.ItemName, number>} an object describing the number of each ingredient.
|
|
373
|
+
*/
|
|
374
|
+
export function ingredientsFromPrismarineRecipe(recipe) {
|
|
375
|
+
let requiredIngedients = {};
|
|
376
|
+
if (recipe.inShape)
|
|
377
|
+
for (const ingredient of recipe.inShape.flat()) {
|
|
378
|
+
if(ingredient.id<0) continue; //prismarine-recipe uses id -1 as an empty crafting slot
|
|
379
|
+
const ingredientName = getItemName(ingredient.id);
|
|
380
|
+
requiredIngedients[ingredientName] ??=0;
|
|
381
|
+
requiredIngedients[ingredientName] += ingredient.count;
|
|
382
|
+
}
|
|
383
|
+
if (recipe.ingredients)
|
|
384
|
+
for (const ingredient of recipe.ingredients) {
|
|
385
|
+
if(ingredient.id<0) continue;
|
|
386
|
+
const ingredientName = getItemName(ingredient.id);
|
|
387
|
+
requiredIngedients[ingredientName] ??=0;
|
|
388
|
+
requiredIngedients[ingredientName] -= ingredient.count;
|
|
389
|
+
//Yes, the `-=` is intended.
|
|
390
|
+
//prismarine-recipe uses positive numbers for the shaped ingredients but negative for unshaped.
|
|
391
|
+
//Why this is the case is beyond my understanding.
|
|
392
|
+
}
|
|
393
|
+
return requiredIngedients;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* Calculates the number of times an action, such as a crafing recipe, can be completed before running out of resources.
|
|
398
|
+
* @template T - doesn't have to be an item. This could be any resource.
|
|
399
|
+
* @param {Object.<T, number>} availableItems - The resources available; e.g, `{'cobble_stone': 7, 'stick': 10}`
|
|
400
|
+
* @param {Object.<T, number>} requiredItems - The resources required to complete the action once; e.g, `{'cobble_stone': 3, 'stick': 2}`
|
|
401
|
+
* @param {boolean} discrete - Is the action discrete?
|
|
402
|
+
* @returns {{num: number, limitingResource: (T | null)}} the number of times the action can be completed and the limmiting resource; e.g `{num: 2, limitingResource: 'cobble_stone'}`
|
|
403
|
+
*/
|
|
404
|
+
export function calculateLimitingResource(availableItems, requiredItems, discrete=true) {
|
|
405
|
+
let limitingResource = null;
|
|
406
|
+
let num = Infinity;
|
|
407
|
+
for (const itemType in requiredItems) {
|
|
408
|
+
if (availableItems[itemType] < requiredItems[itemType] * num) {
|
|
409
|
+
limitingResource = itemType;
|
|
410
|
+
num = availableItems[itemType] / requiredItems[itemType];
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
if(discrete) num = Math.floor(num);
|
|
414
|
+
return {num, limitingResource}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
let loopingItems = new Set();
|
|
418
|
+
|
|
419
|
+
export function initializeLoopingItems() {
|
|
420
|
+
|
|
421
|
+
loopingItems = new Set(['coal',
|
|
422
|
+
'wheat',
|
|
423
|
+
'bone_meal',
|
|
424
|
+
'diamond',
|
|
425
|
+
'emerald',
|
|
426
|
+
'raw_iron',
|
|
427
|
+
'raw_gold',
|
|
428
|
+
'redstone',
|
|
429
|
+
'blue_wool',
|
|
430
|
+
'packed_mud',
|
|
431
|
+
'raw_copper',
|
|
432
|
+
'iron_ingot',
|
|
433
|
+
'dried_kelp',
|
|
434
|
+
'gold_ingot',
|
|
435
|
+
'slime_ball',
|
|
436
|
+
'black_wool',
|
|
437
|
+
'quartz_slab',
|
|
438
|
+
'copper_ingot',
|
|
439
|
+
'lapis_lazuli',
|
|
440
|
+
'honey_bottle',
|
|
441
|
+
'rib_armor_trim_smithing_template',
|
|
442
|
+
'eye_armor_trim_smithing_template',
|
|
443
|
+
'vex_armor_trim_smithing_template',
|
|
444
|
+
'dune_armor_trim_smithing_template',
|
|
445
|
+
'host_armor_trim_smithing_template',
|
|
446
|
+
'tide_armor_trim_smithing_template',
|
|
447
|
+
'wild_armor_trim_smithing_template',
|
|
448
|
+
'ward_armor_trim_smithing_template',
|
|
449
|
+
'coast_armor_trim_smithing_template',
|
|
450
|
+
'spire_armor_trim_smithing_template',
|
|
451
|
+
'snout_armor_trim_smithing_template',
|
|
452
|
+
'shaper_armor_trim_smithing_template',
|
|
453
|
+
'netherite_upgrade_smithing_template',
|
|
454
|
+
'raiser_armor_trim_smithing_template',
|
|
455
|
+
'sentry_armor_trim_smithing_template',
|
|
456
|
+
'silence_armor_trim_smithing_template',
|
|
457
|
+
'wayfinder_armor_trim_smithing_template']);
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* Gets a detailed plan for crafting an item considering current inventory
|
|
463
|
+
*/
|
|
464
|
+
export function getDetailedCraftingPlan(targetItem, count = 1, current_inventory = {}) {
|
|
465
|
+
initializeLoopingItems();
|
|
466
|
+
if (!targetItem || count <= 0 || !getItemId(targetItem)) {
|
|
467
|
+
return "Invalid input. Please provide a valid item name and positive count.";
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
if (isBaseItem(targetItem)) {
|
|
471
|
+
const available = current_inventory[targetItem] || 0;
|
|
472
|
+
if (available >= count) return "You have all required items already in your inventory!";
|
|
473
|
+
return `${targetItem} is a base item, you need to find ${count - available} more in the world`;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
const inventory = { ...current_inventory };
|
|
477
|
+
const leftovers = {};
|
|
478
|
+
const plan = craftItem(targetItem, count, inventory, leftovers);
|
|
479
|
+
return formatPlan(targetItem, plan);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
function isBaseItem(item) {
|
|
483
|
+
return loopingItems.has(item) || getItemCraftingRecipes(item) === null;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
function craftItem(item, count, inventory, leftovers, crafted = { required: {}, steps: [], leftovers: {} }) {
|
|
487
|
+
// Check available inventory and leftovers first
|
|
488
|
+
const availableInv = inventory[item] || 0;
|
|
489
|
+
const availableLeft = leftovers[item] || 0;
|
|
490
|
+
const totalAvailable = availableInv + availableLeft;
|
|
491
|
+
|
|
492
|
+
if (totalAvailable >= count) {
|
|
493
|
+
// Use leftovers first, then inventory
|
|
494
|
+
const useFromLeft = Math.min(availableLeft, count);
|
|
495
|
+
leftovers[item] = availableLeft - useFromLeft;
|
|
496
|
+
|
|
497
|
+
const remainingNeeded = count - useFromLeft;
|
|
498
|
+
if (remainingNeeded > 0) {
|
|
499
|
+
inventory[item] = availableInv - remainingNeeded;
|
|
500
|
+
}
|
|
501
|
+
return crafted;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
// Use whatever is available
|
|
505
|
+
const stillNeeded = count - totalAvailable;
|
|
506
|
+
if (availableLeft > 0) leftovers[item] = 0;
|
|
507
|
+
if (availableInv > 0) inventory[item] = 0;
|
|
508
|
+
|
|
509
|
+
if (isBaseItem(item)) {
|
|
510
|
+
crafted.required[item] = (crafted.required[item] || 0) + stillNeeded;
|
|
511
|
+
return crafted;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
const recipe = getItemCraftingRecipes(item)?.[0];
|
|
515
|
+
if (!recipe) {
|
|
516
|
+
crafted.required[item] = stillNeeded;
|
|
517
|
+
return crafted;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
const [ingredients, result] = recipe;
|
|
521
|
+
const craftedPerRecipe = result.craftedCount;
|
|
522
|
+
const batchCount = Math.ceil(stillNeeded / craftedPerRecipe);
|
|
523
|
+
const totalProduced = batchCount * craftedPerRecipe;
|
|
524
|
+
|
|
525
|
+
// Add excess to leftovers
|
|
526
|
+
if (totalProduced > stillNeeded) {
|
|
527
|
+
leftovers[item] = (leftovers[item] || 0) + (totalProduced - stillNeeded);
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
// Process each ingredient
|
|
531
|
+
for (const [ingredientName, ingredientCount] of Object.entries(ingredients)) {
|
|
532
|
+
const totalIngredientNeeded = ingredientCount * batchCount;
|
|
533
|
+
craftItem(ingredientName, totalIngredientNeeded, inventory, leftovers, crafted);
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
// Add crafting step
|
|
537
|
+
const stepIngredients = Object.entries(ingredients)
|
|
538
|
+
.map(([name, amount]) => `${amount * batchCount} ${name}`)
|
|
539
|
+
.join(' + ');
|
|
540
|
+
crafted.steps.push(`Craft ${stepIngredients} -> ${totalProduced} ${item}`);
|
|
541
|
+
|
|
542
|
+
return crafted;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
function formatPlan(targetItem, { required, steps, leftovers }) {
|
|
546
|
+
const lines = [];
|
|
547
|
+
|
|
548
|
+
if (Object.keys(required).length > 0) {
|
|
549
|
+
lines.push('You are missing the following items:');
|
|
550
|
+
Object.entries(required).forEach(([item, count]) =>
|
|
551
|
+
lines.push(`- ${count} ${item}`));
|
|
552
|
+
lines.push('\nOnce you have these items, here\'s your crafting plan:');
|
|
553
|
+
} else {
|
|
554
|
+
lines.push('You have all items required to craft this item!');
|
|
555
|
+
lines.push('Here\'s your crafting plan:');
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
lines.push('');
|
|
559
|
+
lines.push(...steps);
|
|
560
|
+
|
|
561
|
+
if (Object.keys(required).some(item => item.includes('oak')) && !targetItem.includes('oak')) {
|
|
562
|
+
lines.push('Note: Any varient of wood can be used for this recipe.');
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
if (Object.keys(leftovers).length > 0) {
|
|
566
|
+
lines.push('\nYou will have leftover:');
|
|
567
|
+
Object.entries(leftovers).forEach(([item, count]) =>
|
|
568
|
+
lines.push(`- ${count} ${item}`));
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
return lines.join('\n');
|
|
572
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
export function stringifyTurns(turns) {
|
|
2
|
+
let res = '';
|
|
3
|
+
for (let turn of turns) {
|
|
4
|
+
if (turn.role === 'assistant') {
|
|
5
|
+
res += `\nYour output:\n${turn.content}`;
|
|
6
|
+
} else if (turn.role === 'system') {
|
|
7
|
+
res += `\nSystem output: ${turn.content}`;
|
|
8
|
+
} else {
|
|
9
|
+
res += `\nUser input: ${turn.content}`;
|
|
10
|
+
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
return res.trim();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function toSinglePrompt(turns, system=null, stop_seq='***', model_nickname='assistant') {
|
|
17
|
+
let prompt = system ? `${system}${stop_seq}` : '';
|
|
18
|
+
let role = '';
|
|
19
|
+
turns.forEach((message) => {
|
|
20
|
+
role = message.role;
|
|
21
|
+
if (role === 'assistant') role = model_nickname;
|
|
22
|
+
prompt += `${role}: ${message.content}${stop_seq}`;
|
|
23
|
+
});
|
|
24
|
+
if (role !== model_nickname) // if the last message was from the user/system, add a prompt for the model. otherwise, pretend we are extending the model's own message
|
|
25
|
+
prompt += model_nickname + ": ";
|
|
26
|
+
return prompt;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function _getWords(text) {
|
|
30
|
+
return text.replace(/[^a-zA-Z ]/g, '').toLowerCase().split(' ');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function wordOverlapScore(text1, text2) {
|
|
34
|
+
const words1 = _getWords(text1);
|
|
35
|
+
const words2 = _getWords(text2);
|
|
36
|
+
const intersection = words1.filter(word => words2.includes(word));
|
|
37
|
+
return intersection.length / (words1.length + words2.length - intersection.length);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// ensures stricter turn order and roles:
|
|
41
|
+
// - system messages are treated as user messages and prefixed with SYSTEM:
|
|
42
|
+
// - combines repeated messages from users
|
|
43
|
+
// - separates repeat assistant messages with filler user messages
|
|
44
|
+
export function strictFormat(turns) {
|
|
45
|
+
let prev_role = null;
|
|
46
|
+
let messages = [];
|
|
47
|
+
let filler = {role: 'user', content: '_'};
|
|
48
|
+
for (let msg of turns) {
|
|
49
|
+
if (typeof msg.content === 'string') {
|
|
50
|
+
msg.content = msg.content.trim();
|
|
51
|
+
}
|
|
52
|
+
if (msg.role === 'system') {
|
|
53
|
+
msg.role = 'user';
|
|
54
|
+
msg.content = 'SYSTEM: ' + msg.content;
|
|
55
|
+
}
|
|
56
|
+
if (msg.role === prev_role && msg.role === 'assistant') {
|
|
57
|
+
// insert empty user message to separate assistant messages
|
|
58
|
+
messages.push(filler);
|
|
59
|
+
messages.push(msg);
|
|
60
|
+
}
|
|
61
|
+
else if (msg.role === prev_role) {
|
|
62
|
+
// combine new message with previous message instead of adding a new one
|
|
63
|
+
messages[messages.length-1].content += '\n' + msg.content;
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
messages.push(msg);
|
|
67
|
+
}
|
|
68
|
+
prev_role = msg.role;
|
|
69
|
+
|
|
70
|
+
}
|
|
71
|
+
if (messages.length > 0 && messages[0].role !== 'user') {
|
|
72
|
+
messages.unshift(filler); // anthropic requires user message to start
|
|
73
|
+
}
|
|
74
|
+
if (messages.length === 0) {
|
|
75
|
+
messages.push(filler);
|
|
76
|
+
}
|
|
77
|
+
return messages;
|
|
78
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import translate from 'google-translate-api-x';
|
|
2
|
+
import settings from '../agent/settings.js';
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
export async function handleTranslation(message) {
|
|
7
|
+
let preferred_lang = String(settings.language);
|
|
8
|
+
if (!preferred_lang || preferred_lang.toLowerCase() === 'en' || preferred_lang.toLowerCase() === 'english')
|
|
9
|
+
return message;
|
|
10
|
+
try {
|
|
11
|
+
const translation = await translate(message, { to: preferred_lang });
|
|
12
|
+
return translation.text || message;
|
|
13
|
+
} catch (error) {
|
|
14
|
+
console.error('Error translating message:', error);
|
|
15
|
+
return message;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export async function handleEnglishTranslation(message) {
|
|
20
|
+
let preferred_lang = String(settings.language);
|
|
21
|
+
if (!preferred_lang || preferred_lang.toLowerCase() === 'en' || preferred_lang.toLowerCase() === 'english')
|
|
22
|
+
return message;
|
|
23
|
+
try {
|
|
24
|
+
const translation = await translate(message, { to: 'english' });
|
|
25
|
+
return translation.text || message;
|
|
26
|
+
} catch (error) {
|
|
27
|
+
console.error('Error translating message:', error);
|
|
28
|
+
return message;
|
|
29
|
+
}
|
|
30
|
+
}
|