added some guards
This commit is contained in:
@@ -113,6 +113,8 @@ Using these rules, everyone can build their own copy of Bantam from scratch in l
|
||||
- Loop back to step 1.
|
||||
4. If no pending tool calls (or if `max_al_iterations` is reached), stop and return `messages`.
|
||||
|
||||
If the API rejects the request with an `Invalid assistant message: content or tool_calls must be set` error (usually caused by a previously cut-off stream that left an empty assistant message in the session), all implementations strip the last `assistant`-role message from the session and retry the call.
|
||||
|
||||
### Model configuration parameters
|
||||
|
||||
(shared by all implementations; `model.cfg` is plain `key=value` with `#` comments)
|
||||
@@ -156,6 +158,7 @@ MicroBantam (`mb`) is a compressed Perl 5 reference implementation of the same a
|
||||
- Session management: `/save`, `/list`, `/load <id>` (exact id only, no prefix matching), and auto-save to `~/.bantam/sessions/autosave.json` after every turn and on exit; session ids get `-1`, `-2`, ... suffixes on same-second collisions
|
||||
- Interactive mode (`/quit`, `/clear`, `/save`, `/list`, `/load <id>`, `/help`) and file input mode
|
||||
- Same built-in default system prompt and `OPENAI_API_KEY` fallback as the full implementation
|
||||
- Automatic recovery from `Invalid assistant message: content or tool_calls must be set` API errors: strips the last assistant message and retries
|
||||
|
||||
### What it drops
|
||||
|
||||
@@ -165,6 +168,15 @@ MicroBantam (`mb`) is a compressed Perl 5 reference implementation of the same a
|
||||
- Fibonacci backoff network retries (a failed request aborts with an `API error` message)
|
||||
- `/compact` context summarization
|
||||
|
||||
### Jim Tcl port
|
||||
|
||||
`mb.tcl` is a Jim Tcl port of the same agent (under 100 SLOC) with the same feature set as the Perl `mb`. It differs in three ways: it ships its own minimal HTTP client (raw sockets with chunked-transfer decoding) instead of `HTTP::Tiny`; it retries failed requests up to 3 times with a 2-second backoff instead of aborting on the first failure; and it relies on the external `timeout` command for `shell_exec` timeouts instead of `SIGALRM`. Run it the same way:
|
||||
|
||||
```bash
|
||||
./mb.tcl # interactive (or: jimsh mb.tcl)
|
||||
./mb.tcl prompt.txt # file input mode
|
||||
```
|
||||
|
||||
### Running
|
||||
|
||||
```bash
|
||||
@@ -178,6 +190,7 @@ MicroBantam (`mb`) is a compressed Perl 5 reference implementation of the same a
|
||||
- `main.go`, `term_linux.go`, `term_darwin.go`, `term_windows.go`, `term_other.go` — Go implementation (stdlib only, module `code.luxferre.top/luxferre/bantam`)
|
||||
- `bantam.pl` — Perl 5 implementation (core modules only)
|
||||
- `mb` — MicroBantam, compressed Perl 5 implementation (core modules only, under 100 SLOC)
|
||||
- `mb.tcl` — MicroBantam, Jim Tcl port (requires `jimsh` with the `json` and `ssl` extensions; under 100 SLOC)
|
||||
- `model.cfg`, `system.txt` — shared configuration and system prompt
|
||||
- `README.md` — this document
|
||||
|
||||
|
||||
@@ -134,7 +134,9 @@ sub llm {
|
||||
return $d->{choices}[0]{message} if $d && $d->{choices} && @{$d->{choices}};
|
||||
}
|
||||
my $status = $res->{status} // 0;
|
||||
my $err = $res->{reason} || $res->{content} || "HTTP status $status";
|
||||
my $body = $res->{content} // '';
|
||||
$body = substr($body, 0, 500) if length($body) > 500;
|
||||
my $err = length($body) ? "$body (HTTP $status)" : ($res->{reason} || "HTTP status $status");
|
||||
print "\r\e[K" if $_col; STDOUT->flush();
|
||||
die "[HTTP error: $err]\n" if $status >= 400 && $status < 500 && $status != 408 && $status != 429;
|
||||
if ($i <= $#fib) { print c("[network error: $err, retrying in ${dly}s...]", 31), "\n"; sleep($dly); next; }
|
||||
@@ -192,7 +194,9 @@ sub llm {
|
||||
return \%msg;
|
||||
}
|
||||
my $status = $res->{status} // 0;
|
||||
my $err = $res->{reason} || $res->{content} || "HTTP status $status";
|
||||
my $body = $res->{content} // '';
|
||||
$body = substr($body, 0, 500) if length($body) > 500;
|
||||
my $err = length($body) ? "$body (HTTP $status)" : ($res->{reason} || "HTTP status $status");
|
||||
print "\r\e[K" if $_col; STDOUT->flush();
|
||||
die "[HTTP error: $err]\n" if $status >= 400 && $status < 500 && $status != 408 && $status != 429;
|
||||
if ($i <= $#fib) { print c("[network error: $err, retrying in ${dly}s...]", 31), "\n"; sleep($dly); next; }
|
||||
@@ -210,7 +214,21 @@ sub AL {
|
||||
|
||||
for my $i (1 .. $mx) {
|
||||
my $m = eval { llm($cfg, $msgs, \@tools) };
|
||||
if ($@) { print c($@, 31); return $msgs; }
|
||||
if ($@) {
|
||||
if ($@ =~ /Invalid assistant message|content or tool_calls must be set/) {
|
||||
my $stripped = 0;
|
||||
for (my $j = @$msgs - 1; $j >= 0; $j--) {
|
||||
if (($msgs->[$j]{role} // '') eq 'assistant') {
|
||||
print c("[stripped malformed assistant message]", 33), "\n";
|
||||
splice @$msgs, $j, 1;
|
||||
$stripped = 1;
|
||||
last;
|
||||
}
|
||||
}
|
||||
redo if $stripped;
|
||||
}
|
||||
print c($@, 31); return $msgs;
|
||||
}
|
||||
push @$msgs, $m;
|
||||
if (!$st) {
|
||||
my $reas = $m->{reasoning_content} // $m->{reasoning};
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# Bantam agent: tiny, powerful, DIY
|
||||
# Created by Luxferre in 2026, released into the public domain
|
||||
|
||||
import sys, os, json, time, subprocess, urllib.request
|
||||
import sys, os, json, re, time, subprocess, urllib.request
|
||||
try: import readline
|
||||
except ImportError: readline = None
|
||||
|
||||
@@ -86,6 +86,9 @@ def sanitize_msgs(msgs):
|
||||
except Exception:
|
||||
fn["arguments"] = json.dumps({"invalid_raw": astr} if astr else {})
|
||||
|
||||
def is_invalid_assistant_err(e):
|
||||
return bool(re.search(r"Invalid assistant message|content or tool_calls must be set", str(e)))
|
||||
|
||||
fib = [1, 1, 2, 3, 5, 8, 13, 21, 34]
|
||||
|
||||
def llm(cfg, msgs, tools):
|
||||
@@ -155,7 +158,20 @@ MAX_DEPTH = 5
|
||||
def AL(cfg, msgs, sp, depth=0):
|
||||
stime, mx, st = num(cfg, "shell_timeout", 120), num(cfg, "max_al_iterations", 1000), cfg.get("stream", "true").lower() in ("true", "1", "yes")
|
||||
for _ in range(mx):
|
||||
m = llm(cfg, msgs, TOOLS); msgs.append(m)
|
||||
try:
|
||||
m = llm(cfg, msgs, TOOLS)
|
||||
except RuntimeError as e:
|
||||
if is_invalid_assistant_err(e):
|
||||
for i in range(len(msgs) - 1, -1, -1):
|
||||
if msgs[i].get("role") == "assistant":
|
||||
print(c("[stripped malformed assistant message]", 33))
|
||||
del msgs[i]
|
||||
break
|
||||
else:
|
||||
raise
|
||||
continue
|
||||
raise
|
||||
msgs.append(m)
|
||||
if not st:
|
||||
reas = m.get("reasoning_content") or m.get("reasoning")
|
||||
if reas: print(c("--- reasoning start ---", 36) + "\n" + c(reas, 2) + "\n" + c("--- reasoning end ---", 36) + "\n")
|
||||
|
||||
@@ -173,6 +173,11 @@ func sanitizeMessages(msgs []Message) {
|
||||
}
|
||||
}
|
||||
|
||||
func isInvalidAssistantErr(err error) bool {
|
||||
s := err.Error()
|
||||
return strings.Contains(s, "Invalid assistant message") || strings.Contains(s, "content or tool_calls must be set")
|
||||
}
|
||||
|
||||
func llm(cfg *Cfg, msgs []Message, tools []map[string]any) (Message, error) {
|
||||
sanitizeMessages(msgs)
|
||||
p := map[string]any{"model": cfg.Model, "temperature": cfg.Temperature, "messages": msgs, "stream": cfg.Stream}
|
||||
@@ -324,7 +329,21 @@ func AL(cfg *Cfg, msgs []Message, sp string, depth int) ([]Message, error) {
|
||||
done := false
|
||||
for i := 0; i < cfg.MaxALIterations && !done; i++ {
|
||||
m, err := llm(cfg, msgs, TOOLS)
|
||||
if err != nil { return msgs, err }
|
||||
if err != nil {
|
||||
if isInvalidAssistantErr(err) {
|
||||
stripped := false
|
||||
for j := len(msgs) - 1; j >= 0; j-- {
|
||||
if msgs[j].Role == "assistant" {
|
||||
fmt.Println(c("[stripped malformed assistant message]", 33))
|
||||
msgs = append(msgs[:j], msgs[j+1:]...)
|
||||
stripped = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if stripped { continue }
|
||||
}
|
||||
return msgs, err
|
||||
}
|
||||
for j := range m.ToolCalls {
|
||||
tc := &m.ToolCalls[j]
|
||||
astr := tc.Function.Arguments
|
||||
|
||||
@@ -41,7 +41,9 @@ sub llm { my ($c, $msgs) = @_; # one non-streaming chat completion
|
||||
print "\r\e[K" if $tty; # clear the spinner line
|
||||
my $d = $r->{success} ? eval { decode_json($r->{content}) } : undef;
|
||||
return $d->{choices}[0]{message} if $d && $d->{choices} && @{$d->{choices}};
|
||||
die "API error: " . ($r->{reason} || "HTTP $r->{status}") . "\n"; }
|
||||
my $rb = $r->{content} // '';
|
||||
$rb = substr($rb, 0, 500) if length($rb) > 500;
|
||||
die "API error: " . (length($rb) ? "$rb (HTTP $r->{status})" : ($r->{reason} || "HTTP $r->{status}")) . "\n"; }
|
||||
|
||||
sub shell_exec { my ($cmd, $t) = @_; # run a command under a hard timeout
|
||||
my $out = '';
|
||||
@@ -56,6 +58,10 @@ sub AL { my ($c, $msgs, $sp, $depth) = @_; # the agentic loop: LLM <-> tools unt
|
||||
$depth ||= 0;
|
||||
for (1 .. $c->{max_al_iterations}) {
|
||||
my $m = eval { llm($c, $msgs) };
|
||||
if ($@ && $@ =~ /Invalid assistant message|content or tool_calls must be set/ && grep({ $_->{role} eq 'assistant' } @$msgs)) {
|
||||
for (my $j = @$msgs - 1; $j >= 0; $j--) { if ($msgs->[$j]{role} eq 'assistant') { splice @$msgs, $j, 1; last; } }
|
||||
print "[stripped malformed assistant message]\n"; redo;
|
||||
}
|
||||
if ($@) { print $@; return $msgs; }
|
||||
push @$msgs, $m;
|
||||
print $m->{content}, "\n" if defined $m->{content} && length $m->{content};
|
||||
|
||||
@@ -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 }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user