#!/usr/bin/env lua -- A tiny standalone libpanto-lua app: one prompt in, streamed text out. -- -- Run with Lua 5.4 after building/installing libpanto-lua, for example: -- LUA_CPATH='./libpanto-lua/zig-out/lib/?.so;;' lua examples/simple-agent.lua "list the files" -- -- Required environment: -- PANTO_API_STYLE=openai_chat|anthropic_messages -- PANTO_API_KEY=... -- PANTO_BASE_URL=... -- PANTO_MODEL=... local panto = require("panto") local function getenv(name, default) local value = os.getenv(name) if value == nil or value == "" then return default end return value end local function require_env(name) local value = getenv(name) if value == nil then error("missing required env var: " .. name, 0) end return value end local function shell_quote(s) return "'" .. tostring(s):gsub("'", "'\\''") .. "'" end local function read_all(pipe) local chunks = {} while true do local chunk = pipe:read(8192) if chunk == nil then break end chunks[#chunks + 1] = chunk end return table.concat(chunks) end local function run_bash(command) local wrapped = "bash -lc " .. shell_quote(command) .. " 2>&1; printf '\\n__panto_exit_code:%s\\n' $?" local pipe = assert(io.popen(wrapped, "r")) local output = read_all(pipe) pipe:close() local body, code = output:match("^(.*)\n__panto_exit_code:(%-?%d+)\n$") return string.format("exit_code=%s\n%s", code or "unknown", body or output) end local prompt = table.concat(arg, " ") if prompt == "" then io.stderr:write("usage: lua examples/simple-agent.lua \n") os.exit(2) end local agent = panto.agent { api_style = require_env("PANTO_API_STYLE"), api_key = require_env("PANTO_API_KEY"), base_url = require_env("PANTO_BASE_URL"), model = require_env("PANTO_MODEL"), max_tokens = tonumber(getenv("PANTO_MAX_TOKENS", "4096")), } agent:set_system_prompt(table.concat({ "You are a very small coding agent.", "Use the bash tool when you need to inspect or change the local workspace.", "Keep final answers concise.", }, "\n")) agent:register_tool { name = "bash", description = "Run a shell command with bash -lc and return combined stdout/stderr plus the exit code.", schema = { type = "object", properties = { command = { type = "string", description = "The command to run.", }, }, required = { "command" }, }, handler = function(input) if type(input.command) ~= "string" or input.command == "" then error("bash.command must be a non-empty string") end return run_bash(input.command) end, } for ev in agent:run(prompt):events() do if ev.type == "content_delta" then io.write(ev.delta) io.flush() elseif ev.type == "tool_dispatch_start" then io.stderr:write(string.format("\n[bash: running %d tool call(s)]\n", ev.count)) elseif ev.type == "tool_dispatch_complete" then io.stderr:write("[bash: done]\n") end end io.write("\n")