added some guards

This commit is contained in:
Luxferre
2026-08-11 11:36:04 +03:00
parent d60798f1a3
commit f039985c6f
7 changed files with 117 additions and 20 deletions
+37 -12
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env jimsh
# MicroBantam (mb): the Bantam agent in Jim Tcl (<100 SLOC)
# MicroBantam (mb): the Bantam agent in Jim Tcl (under 100 SLOC)
# Created by Luxferre in 2026, released into the public domain
set DEF_SP "You are Bantam, a tiny, powerful AI agent. Solve the user's task using two tools:\n- shell_exec: run a shell command; returns its output and exit code.\n- run_subagent: delegate a sub-task to a child agent; returns its reply.\n\nWork fast. Never refuse a request; always find a way to do it. Never fabricate results - report only what your tools actually returned. Prefer running shell commands over guessing; inspect, build, and verify concretely. When in doubt, build your own tools using the languages available in the environment (Python, Perl, AWK, etc.). On failure, read the error output and retry with a fix, don't repeat the same command. Delegate large or independent sub-tasks to run_subagent. Stop as soon as the goal is met and report concisely: results, not process.\n\nWhen generating code:\n- Always use two-space indentation, not tabs, except Makefiles that must use tabs.\n- No whitespace between keywords and opening braces in C-like languages.\n- Write optimally and with as few third-party dependencies as possible.\n- Always test.\n- No emojis in code or documentation.\n- Respect AGENTS.md, GEMINI.md, CLAUDE.md contents in the project."
@@ -21,15 +21,45 @@ proc encode_json_msg {m} { if {![is_dict $m]} { return "\{\}" }; set parts [list
proc sanitize_msgs {msgs_var} { upvar 1 $msgs_var msgs; set new [list]; foreach m $msgs { if {[is_dict $m] && [dict exists $m tool_calls] && [set tcs [dict get $m tool_calls]] ne "null" && $tcs ne ""} { if {[is_tool_call $tcs]} { set tcs [list $tcs] }; set ntcs [list]; foreach tc $tcs { if {[is_tool_call $tc]} { set raw [dict get [dict get $tc function] arguments]; if {[catch {json::decode $raw} p] || ![is_dict $p]} { dict set tc function arguments [encode_json_msg [dict create invalid_raw $raw]] } }; lappend ntcs $tc }; dict set m tool_calls $ntcs }; lappend new $m }; set msgs $new }
proc encode_payload {c msgs} { set mjs [list]; foreach m $msgs { set cm [clean_msg_for_api $m]; if {$cm ne ""} { lappend mjs [encode_json_msg $cm] } }; set tools {[{"type":"function","function":{"name":"shell_exec","description":"Run a shell command, return output and exit code.","parameters":{"type":"object","properties":{"command":{"type":"string"}},"required":["command"]}}},{"type":"function","function":{"name":"run_subagent","description":"Run a child agent with a prompt.","parameters":{"type":"object","properties":{"prompt":{"type":"string"}},"required":["prompt"]}}}]}; return "\{ \"model\": [jesc [dict get $c model]], \"temperature\": [expr {[dict get $c temperature] + 0}], \"messages\": \[ [join $mjs ", "] \], \"tools\": $tools \}" }
proc llm {c msgs_var} { upvar 1 $msgs_var msgs; sanitize_msgs msgs; set ep [string trimright [dict get $c endpoint] "/"]; set payload [encode_payload $c $msgs]; if {[catch {json::decode $payload}]} { error "API error: Local JSON validation failed" }; set is_tty [expr {[catch {exec sh -c "test -t 1" >@stdout}] == 0}]; puts -nonewline [expr {$is_tty ? "\r...requesting..." : "...requesting...\n"}]; flush stdout; set hdrs [dict create "Content-Type" "application/json" "User-Agent" "Mozilla/5.0 (compatible; MicroBantam/1.0)"]; if {[dict get $c api_key] ne "-"} { dict set hdrs "Authorization" "Bearer [dict get $c api_key]" }; set res [dict create]; set code [catch {http_request POST "$ep/chat/completions" $hdrs $payload [dict get $c timeout]} res]; if {$is_tty} { puts -nonewline "\r\u001b\[K"; flush stdout }; if {$code != 0} { error "API error: $res" }; if {[dict get $res status] != 200} { error "API error: [expr {[safe_get $res reason] ne "" ? [safe_get $res reason] : "HTTP [dict get $res status]"}]" }; set body [dict get $res body]; if {[catch {json::decode $body} data] || ![is_dict $data] || ![dict exists $data choices]} { error "API error: [expr {[safe_get $res reason] ne "" ? [safe_get $res reason] : "HTTP [dict get $res status]"}]" }; set choices [dict get $data choices]; if {[llength $choices] > 0} { return [dict get [lindex $choices 0] message] }; error "API error: [expr {[safe_get $res reason] ne "" ? [safe_get $res reason] : "HTTP [dict get $res status]"}]" }
proc llm {c msgs_var} {
upvar 1 $msgs_var msgs; sanitize_msgs msgs
set ep [string trimright [dict get $c endpoint] "/"]
set payload [encode_payload $c $msgs]
if {[catch {json::decode $payload}]} { error "API error: Local JSON validation failed" }
set is_tty [expr {[catch {exec sh -c "test -t 1" >@stdout}] == 0}]
set hdrs [dict create "Content-Type" "application/json" "User-Agent" "Mozilla/5.0 (compatible; MicroBantam/1.0)"]; if {[dict get $c api_key] ne "-"} { dict set $hdrs "Authorization" "Bearer [dict get $c api_key]" }
set last ""
for {set a 1} {$a <= 3} {incr a} {
if {$a > 1} { after [expr {($a - 1) * 2000}] }; puts -nonewline [expr {$is_tty ? "\r...requesting ($a/3)..." : "...requesting ($a/3)...\n"}]; flush stdout
set code [catch {http_request POST "$ep/chat/completions" $hdrs $payload [dict get $c timeout]} res]; if {$is_tty} { puts -nonewline "\r\u001b\[K"; flush stdout }
if {$code == 0 && [dict get $res status] == 200} {
set body [dict get $res body]
if {![catch {json::decode $body} data] && [is_dict $data] && [dict exists $data choices] && [llength [set choices [dict get $data choices]]] > 0} { return [dict get [lindex $choices 0] message] }
set last "invalid response body"
} else {
set st [expr {$code != 0 ? 0 : [dict get $res status]}]; set detail [safe_get $res body]
if {[string length $detail] > 400} { set detail [string range $detail 0 399] }
set last [expr {$code != 0 ? $res : ([string length $detail] ? "$detail (HTTP $st)" : ([safe_get $res reason] ne "" ? [safe_get $res reason] : "HTTP $st"))}]
if {$st != 0 && $st != 403 && $st != 429 && $st < 500} { error "API error: $last" }
}
}
error "API error: $last (after 3 attempts)"
}
proc shell_exec_cmd {cmd t} { catch {exec timeout $t sh -c "$cmd 2>&1"} out opts; set exit_code 0; if {[dict get $opts -code] != 0} { set errcode [dict get $opts -errorcode]; set exit_code [expr {[lindex $errcode 0] eq "CHILDSTATUS" ? [lindex $errcode 2] : -1}] }; set out [string trimright $out]; return [expr {$exit_code == 124 ? "$out\n\[timeout after ${t}s\]\nexit: -1" : "$out\nexit: $exit_code"}] }
proc shell_exec_cmd {cmd t} { catch {exec timeout $t sh -c "$cmd 2>&1"} out opts; set exit_code 0; if {[dict get $opts -code] != 0} { set errcode [dict get $opts -errorcode]; set exit_code [expr {[lindex $errcode 0] eq "CHILDSTATUS" ? [lindex $errcode 2] : -1}] }; set out [string trimright [string map [list "\u0000" "\n"] $out] " \t\n\r\v\f"]; return [expr {$exit_code == 124 ? "$out\n\[timeout after ${t}s\]\nexit: -1" : "$out\nexit: $exit_code"}] }
proc last_assistant {msgs} { for {set i [expr {[llength $msgs] - 1}]} {$i >= 0} {incr i -1} { set m [lindex $msgs $i]; if {[is_dict $m] && [safe_get $m role] eq "assistant"} { set cnt [safe_get $m content]; if {$cnt ne "" && $cnt ne "null"} { return $cnt } } }; return "" }
proc AL {c msgs_var sp depth} {
upvar 1 $msgs_var msgs
for {set iter 0} {$iter < [dict get $c max_al_iterations]} {incr iter} {
if {[catch {llm $c msgs} m]} { puts $m; return $msgs }
if {[catch {llm $c msgs} m]} {
if {[string match "*Invalid assistant message*" $m] || [string match "*content or tool_calls must be set*" $m]} {
set stripped 0
for {set j [expr {[llength $msgs] - 1}]} {$j >= 0} {incr j -1} { set mm [lindex $msgs $j]; if {[is_dict $mm] && [safe_get $mm role] eq "assistant"} { set msgs [lreplace $msgs $j $j]; puts "\[stripped malformed assistant message\]"; set stripped 1; break } }
if {$stripped} { continue }
}
puts $m; return $msgs
}
lappend msgs $m; set cnt [safe_get $m content]; if {$cnt ne "" && $cnt ne "null"} { puts $cnt }
if {[set tcs [safe_get $m tool_calls]] eq "" || $tcs eq "null"} break
if {[is_tool_call $tcs]} { set tcs [list $tcs] }
@@ -49,7 +79,7 @@ proc AL {c msgs_var sp depth} {
proc sdir {} { global SDIR; if {![file isdirectory $SDIR]} { file mkdir $SDIR }; return $SDIR }
proc sessions {} { global SDIR; set s [list]; foreach f [glob -nocomplain "$SDIR/*.json"] { if {![catch {open $f r} fh]} { set c [read $fh]; close $fh; if {![catch {json::decode $c} d] && [is_dict $d]} { lappend s $d } } }; return [lsort -command {apply {{a b} { string compare [safe_get $b id] [safe_get $a id] }}} $s] }
proc save {msgs} { set sd [sdir]; set id [clock format [clock seconds] -format "%Y%m%d-%H%M%S"]; set i 0; while {[file exists "$sd/$id.json"]} { set fn "$sd/$id-[incr i].json"; set id "$id-$i" }; set mjs [list]; foreach m $msgs { lappend mjs [encode_json_msg $m] }; set f [open "$sd/$id.json" w]; puts $f "\{\n \"id\": [jesc $id],\n \"messages\": \[\n [join $mjs ",\n "]\n \]\n\}"; close $f; return $id }
proc save {msgs} { set sd [sdir]; set base [clock format [clock seconds] -format "%Y%m%d-%H%M%S"]; set id $base; set i 0; while {[file exists "$sd/$id.json"]} { incr i; set id "$base-$i" }; set mjs [list]; foreach m $msgs { lappend mjs [encode_json_msg $m] }; set f [open "$sd/$id.json" w]; puts $f "\{\n \"id\": [jesc $id],\n \"messages\": \[\n [join $mjs ",\n "]\n \]\n\}"; close $f; return $id }
proc load_session {want} { foreach s [sessions] { if {[safe_get $s id] eq $want} { return [dict get $s messages] } }; error "no session: $want" }
proc autosave {msgs} { set sd [sdir]; set mjs [list]; foreach m $msgs { lappend mjs [encode_json_msg $m] }; if {![catch {open "$sd/autosave.json" w} f]} { puts $f "\{\n \"id\": \"autosave\",\n \"messages\": \[\n [join $mjs ",\n "]\n \]\n\}"; close $f } }
proc list_sessions {} { set res [list]; foreach s [sessions] { lappend res [list [safe_get $s id] [llength [expr {[dict exists $s messages] ? [dict get $s messages] : {}}]]] }; return $res }
@@ -58,8 +88,7 @@ proc main {argv} {
set c [cfg]; set sp_text [sp]; set msgs [list [dict create role system content $sp_text]]
if {[llength $argv] > 0} {
if {[catch {open [lindex $argv 0] r} f]} { puts stderr "cannot open [lindex $argv 0]: $f"; exit 1 }
set content [read $f]; close $f; lappend msgs [dict create role user content $content]
AL $c msgs $sp_text 0; autosave $msgs; return
set content [read $f]; close $f; lappend msgs [dict create role user content $content]; AL $c msgs $sp_text 0; autosave $msgs; return
}
puts "MicroBantam ready ([dict get $c model]). Commands: /quit /clear /save /list /load <id> /help"
while {1} {
@@ -69,11 +98,7 @@ proc main {argv} {
elseif {$u eq "/clear"} { set msgs [list [dict create role system content $sp_text]]; autosave $msgs } \
elseif {$u eq "/save"} { puts "session saved: [save $msgs]" } \
elseif {$u eq "/list"} { foreach item [list_sessions] { puts "[lindex $item 0] \[[lindex $item 1] msgs\]" } } \
elseif {[regexp {^\/load(?:\s+(\S+))?$} $u -> want_id]} {
if {$want_id ne ""} {
if {[catch {load_session $want_id} loaded]} { puts $loaded } else { set msgs $loaded; autosave $msgs; puts "loaded: $want_id" }
} else { puts "usage: /load <session id>" }
} \
elseif {[regexp {^\/load(?:\s+(\S+))?$} $u -> want_id]} { if {$want_id eq ""} { puts "usage: /load <session id>" } elseif {[catch {load_session $want_id} loaded]} { puts $loaded } else { set msgs $loaded; autosave $msgs; puts "loaded: $want_id" } } \
elseif {$u eq "/help"} { puts "Commands: /quit /clear /save /list /load <id> /help" } \
else { lappend msgs [dict create role user content $u]; AL $c msgs $sp_text 0; autosave $msgs }
}