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.
Files changed (116) hide show
  1. package/FAQ.md +38 -0
  2. package/LICENSE +21 -0
  3. package/README.md +255 -0
  4. package/andy.json +6 -0
  5. package/bin/mindcraft.js +80 -0
  6. package/keys.example.json +19 -0
  7. package/main.js +80 -0
  8. package/package.json +78 -0
  9. package/patches/minecraft-data+3.97.0.patch +13 -0
  10. package/patches/mineflayer+4.33.0.patch +54 -0
  11. package/patches/mineflayer-pathfinder+2.4.5.patch +265 -0
  12. package/patches/mineflayer-pvp+1.3.2.patch +13 -0
  13. package/patches/prismarine-viewer+1.33.0.patch +13 -0
  14. package/patches/protodef+1.19.0.patch +15 -0
  15. package/profiles/andy-4-reasoning.json +14 -0
  16. package/profiles/andy-4.json +7 -0
  17. package/profiles/azure.json +19 -0
  18. package/profiles/claude.json +7 -0
  19. package/profiles/claude_thinker.json +15 -0
  20. package/profiles/deepseek.json +7 -0
  21. package/profiles/defaults/_default.json +256 -0
  22. package/profiles/defaults/assistant.json +14 -0
  23. package/profiles/defaults/creative.json +14 -0
  24. package/profiles/defaults/god_mode.json +14 -0
  25. package/profiles/defaults/survival.json +14 -0
  26. package/profiles/freeguy.json +7 -0
  27. package/profiles/gemini.json +9 -0
  28. package/profiles/gpt.json +12 -0
  29. package/profiles/grok.json +7 -0
  30. package/profiles/llama.json +10 -0
  31. package/profiles/mercury.json +9 -0
  32. package/profiles/mistral.json +5 -0
  33. package/profiles/qwen.json +17 -0
  34. package/profiles/tasks/construction_profile.json +42 -0
  35. package/profiles/tasks/cooking_profile.json +11 -0
  36. package/profiles/tasks/crafting_profile.json +71 -0
  37. package/profiles/vllm.json +10 -0
  38. package/settings.js +64 -0
  39. package/src/agent/action_manager.js +177 -0
  40. package/src/agent/agent.js +561 -0
  41. package/src/agent/coder.js +229 -0
  42. package/src/agent/commands/actions.js +504 -0
  43. package/src/agent/commands/index.js +259 -0
  44. package/src/agent/commands/queries.js +347 -0
  45. package/src/agent/connection_handler.js +96 -0
  46. package/src/agent/conversation.js +353 -0
  47. package/src/agent/history.js +122 -0
  48. package/src/agent/library/full_state.js +89 -0
  49. package/src/agent/library/index.js +23 -0
  50. package/src/agent/library/lockdown.js +32 -0
  51. package/src/agent/library/skill_library.js +93 -0
  52. package/src/agent/library/skills.js +2093 -0
  53. package/src/agent/library/world.js +431 -0
  54. package/src/agent/memory_bank.js +25 -0
  55. package/src/agent/mindserver_proxy.js +136 -0
  56. package/src/agent/modes.js +446 -0
  57. package/src/agent/npc/build_goal.js +80 -0
  58. package/src/agent/npc/construction/dirt_shelter.json +38 -0
  59. package/src/agent/npc/construction/large_house.json +230 -0
  60. package/src/agent/npc/construction/small_stone_house.json +42 -0
  61. package/src/agent/npc/construction/small_wood_house.json +42 -0
  62. package/src/agent/npc/controller.js +261 -0
  63. package/src/agent/npc/data.js +50 -0
  64. package/src/agent/npc/item_goal.js +355 -0
  65. package/src/agent/npc/utils.js +126 -0
  66. package/src/agent/self_prompter.js +146 -0
  67. package/src/agent/settings.js +7 -0
  68. package/src/agent/speak.js +150 -0
  69. package/src/agent/tasks/construction_tasks.js +1104 -0
  70. package/src/agent/tasks/cooking_tasks.js +358 -0
  71. package/src/agent/tasks/tasks.js +594 -0
  72. package/src/agent/templates/execTemplate.js +6 -0
  73. package/src/agent/templates/lintTemplate.js +10 -0
  74. package/src/agent/vision/browser_viewer.js +8 -0
  75. package/src/agent/vision/camera.js +78 -0
  76. package/src/agent/vision/vision_interpreter.js +82 -0
  77. package/src/mindcraft/index.js +28 -0
  78. package/src/mindcraft/mcserver.js +154 -0
  79. package/src/mindcraft/mindcraft.js +111 -0
  80. package/src/mindcraft/mindserver.js +328 -0
  81. package/src/mindcraft/public/index.html +1253 -0
  82. package/src/mindcraft/public/settings_spec.json +145 -0
  83. package/src/mindcraft/userconfig.js +72 -0
  84. package/src/mindcraft-py/example.py +27 -0
  85. package/src/mindcraft-py/init-mindcraft.js +24 -0
  86. package/src/mindcraft-py/mindcraft.py +99 -0
  87. package/src/models/_model_map.js +89 -0
  88. package/src/models/azure.js +32 -0
  89. package/src/models/cerebras.js +61 -0
  90. package/src/models/claude.js +87 -0
  91. package/src/models/deepseek.js +59 -0
  92. package/src/models/gemini.js +176 -0
  93. package/src/models/glhf.js +71 -0
  94. package/src/models/gpt.js +147 -0
  95. package/src/models/grok.js +82 -0
  96. package/src/models/groq.js +95 -0
  97. package/src/models/huggingface.js +86 -0
  98. package/src/models/hyperbolic.js +114 -0
  99. package/src/models/lmstudio.js +74 -0
  100. package/src/models/mercury.js +95 -0
  101. package/src/models/mistral.js +94 -0
  102. package/src/models/novita.js +71 -0
  103. package/src/models/ollama.js +115 -0
  104. package/src/models/openrouter.js +77 -0
  105. package/src/models/prompter.js +366 -0
  106. package/src/models/qwen.js +80 -0
  107. package/src/models/replicate.js +60 -0
  108. package/src/models/vllm.js +81 -0
  109. package/src/process/agent_process.js +84 -0
  110. package/src/process/init_agent.js +54 -0
  111. package/src/utils/examples.js +83 -0
  112. package/src/utils/keys.js +34 -0
  113. package/src/utils/math.js +13 -0
  114. package/src/utils/mcdata.js +572 -0
  115. package/src/utils/text.js +78 -0
  116. 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
+ }