summaryrefslogtreecommitdiff
path: root/examples/simple-agent.lua
blob: baf00b5ab13589cbbb524aeac589c81e2f9dedaa (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
#!/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 <prompt>\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")