Files
LLM-Connect/init.lua

227 lines
8.2 KiB
Lua
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
-- ===========================================================================
-- LLM Connect Init v0.9.0-dev
-- author: H5N3RG
-- license: LGPL-3.0-or-later
-- ===========================================================================
local core = core
local mod_dir = core.get_modpath("llm_connect")
-- === HTTP API ===
local http = core.request_http_api()
if not http then
core.log("error", "[llm_connect] HTTP API not available! Add 'llm_connect' to secure.http_mods in minetest.conf!")
return
end
-- === Privileges ===
core.register_privilege("llm", {
description = "LLM Connect: /llm chat interface (chat mode only)",
give_to_singleplayer = true,
give_to_admin = true,
})
core.register_privilege("llm_dev", {
description = "LLM Connect: Smart Lua IDE + sandbox code execution (whitelist limited)",
give_to_singleplayer = false,
give_to_admin = false,
})
core.register_privilege("llm_worldedit", {
description = "LLM Connect: WorldEdit agency (WE Single + WE Loop + material picker)",
give_to_singleplayer = false,
give_to_admin = false,
})
core.register_privilege("llm_root", {
description = "LLM Connect: Full access (implies llm + llm_dev + llm_worldedit). Config, unrestricted execution, persistent code.",
give_to_singleplayer = false,
give_to_admin = true,
})
-- === Load central LLM API module ===
local llm_api_ok, llm_api = pcall(dofile, mod_dir .. "/llm_api.lua")
if not llm_api_ok or not llm_api then
core.log("error", "[llm_connect] Failed to load llm_api.lua: " .. tostring(llm_api))
return
end
if not llm_api.init(http) then
core.log("error", "[llm_connect] Failed to initialize llm_api")
return
end
-- === Load code executor ===
local executor_ok, executor = pcall(dofile, mod_dir .. "/code_executor.lua")
if not executor_ok or not executor then
core.log("error", "[llm_connect] Failed to load code_executor.lua: " .. tostring(executor))
return
end
-- === Load GUI modules ===
local chat_gui_ok, chat_gui = pcall(dofile, mod_dir .. "/chat_gui.lua")
if not chat_gui_ok then
core.log("error", "[llm_connect] Failed to load chat_gui.lua: " .. tostring(chat_gui))
return
end
local ide_gui_ok, ide_gui = pcall(dofile, mod_dir .. "/ide_gui.lua")
if not ide_gui_ok then
core.log("error", "[llm_connect] Failed to load ide_gui.lua: " .. tostring(ide_gui))
return
end
local config_gui_ok, config_gui = pcall(dofile, mod_dir .. "/config_gui.lua")
if not config_gui_ok then
core.log("error", "[llm_connect] Failed to load config_gui.lua: " .. tostring(config_gui))
return
end
-- === Load helpers ===
local chat_context_ok, chat_context = pcall(dofile, mod_dir .. "/chat_context.lua")
if not chat_context_ok then
core.log("warning", "[llm_connect] chat_context.lua not loaded: " .. tostring(chat_context))
chat_context = nil
end
-- === Load WorldEdit agency module (optional dependency) ===
local we_agency_ok, we_agency = pcall(dofile, mod_dir .. "/llm_worldedit.lua")
if not we_agency_ok then
core.log("warning", "[llm_connect] llm_worldedit.lua failed to load: " .. tostring(we_agency))
we_agency = nil
elseif not we_agency.is_available() then
core.log("warning", "[llm_connect] WorldEdit not detected at load time agency mode disabled")
core.log("warning", "[llm_connect] worldedit global type: " .. type(worldedit))
-- NOTE: we_agency is still set as global we_available() checks at runtime
-- so WE buttons may still appear if worldedit loads later (should not happen with optional_depends)
end
-- === Load material picker ===
local picker_ok, material_picker = pcall(dofile, mod_dir .. "/material_picker.lua")
if not picker_ok then
core.log("warning", "[llm_connect] material_picker.lua not loaded: " .. tostring(material_picker))
material_picker = nil
end
-- === Make modules globally available ===
_G.chat_gui = chat_gui
_G.llm_api = llm_api
_G.executor = executor
_G.we_agency = we_agency
_G.material_picker = material_picker
_G.ide_gui = ide_gui
_G.config_gui = config_gui
-- === Startup code loader ===
local startup_file = core.get_worldpath() .. "/llm_startup.lua"
local function load_startup_code()
local f = io.open(startup_file, "r")
if f then
f:close()
core.log("action", "[llm_connect] Loading startup code from " .. startup_file)
local ok, err = pcall(dofile, startup_file)
if not ok then
core.log("error", "[llm_connect] Startup code error: " .. tostring(err))
core.log("error", "[llm_connect] Fix the error in llm_startup.lua and restart the server")
else
core.log("action", "[llm_connect] Startup code loaded successfully")
end
else
core.log("action", "[llm_connect] No llm_startup.lua found (this is normal on first run)")
end
end
load_startup_code()
-- === Chat Commands ===
core.register_chatcommand("llm", {
description = "Opens the LLM chat interface",
privs = {llm = true},
func = function(name)
chat_gui.show(name)
return true, "Opening LLM chat..."
end,
})
core.register_chatcommand("llm_msg", {
params = "<message>",
description = "Send a direct message to the LLM (text-only, no GUI)",
privs = {llm = true},
func = function(name, param)
if not param or param == "" then
return false, "Usage: /llm_msg <your question>"
end
local messages = {{role = "user", content = param}}
llm_api.request(messages, function(result)
if result.success then
core.chat_send_player(name, "[LLM] " .. (result.content or "(no response)"))
else
core.chat_send_player(name, "[LLM] Error: " .. (result.error or "unknown error"))
end
end, {timeout = llm_api.get_timeout("chat")})
return true, "Request sent..."
end,
})
core.register_chatcommand("llm_undo", {
description = "Undo the last WorldEdit agency operation",
privs = {llm = true},
func = function(name)
if not _G.we_agency then
return false, "WorldEdit agency module not loaded"
end
local res = _G.we_agency.undo(name)
return res.ok, "[LLM] " .. res.message
end,
})
core.register_chatcommand("llm_reload_startup", {
description = "Reload llm_startup.lua (WARNING: Cannot register new items!)",
privs = {llm_root = true},
func = function(name)
core.log("action", "[llm_connect] Manual startup reload triggered by " .. name)
core.chat_send_player(name, "[LLM] WARNING: Reloading startup code at runtime")
core.chat_send_player(name, "[LLM] New registrations will FAIL. Restart server for registrations.")
local f = io.open(startup_file, "r")
if f then
f:close()
local ok, err = pcall(dofile, startup_file)
if not ok then
core.chat_send_player(name, "[LLM] x Reload failed: " .. tostring(err))
return false, "Reload failed"
else
core.chat_send_player(name, "[LLM] Reloaded (restart needed for registrations)")
return true, "Code reloaded"
end
else
core.chat_send_player(name, "[LLM] x No llm_startup.lua found")
return false, "File not found"
end
end,
})
-- === Central formspec handler ===
core.register_on_player_receive_fields(function(player, formname, fields)
if not player then return false end
local name = player:get_player_name()
if formname:match("^llm_connect:chat") or formname:match("^llm_connect:material_picker") then
return chat_gui.handle_fields(name, formname, fields)
elseif formname:match("^llm_connect:ide") then
return ide_gui.handle_fields(name, formname, fields)
elseif formname:match("^llm_connect:config") then
return config_gui.handle_fields(name, formname, fields)
end
return false
end)
-- === Logging ===
core.log("action", "[llm_connect] LLM Connect v0.9.0 loaded")
if llm_api.is_configured() then
core.log("action", "[llm_connect] LLM API ready - model: " .. tostring(llm_api.config.model))
else
core.log("warning", "[llm_connect] LLM API not configured yet - use /llm and open Config button")
end