/cont logic
This commit is contained in:
@@ -1,14 +1,14 @@
|
|||||||
#!/usr/bin/env perl
|
#!/usr/bin/env perl
|
||||||
# MicroBantam (mb): the Bantam agent in <100 SLOC - readable, core modules only
|
# MicroBantam (mb): the Bantam agent in <100 SLOC - readable, core modules only
|
||||||
# Created by Luxferre in 2026, released into the public domain
|
# Created by Luxferre in 2026, released into the public domain
|
||||||
use strict; use warnings; use HTTP::Tiny; use JSON::PP; use POSIX qw(strftime); use File::Path qw(make_path);
|
use strict; use warnings; use HTTP::Tiny; use JSON::PP; use POSIX qw(strftime); use File::Path qw(make_path); use Digest::MD5 qw(md5_hex); use Cwd qw(abs_path);
|
||||||
$SIG{__WARN__} = sub { warn $_[0] unless $_[0] =~ /^Use of uninitialized value \$err in numeric eq \(==\) at .*IO\/Socket\/IP\.pm line \d+/ };
|
$SIG{__WARN__} = sub { warn $_[0] unless $_[0] =~ /^Use of uninitialized value \$err in numeric eq \(==\) at .*IO\/Socket\/IP\.pm line \d+/ };
|
||||||
binmode $_ => ':encoding(UTF-8)' for *STDIN, *STDOUT, *STDERR; $| = 1; # unbuffered output in UTF-8
|
binmode $_ => ':encoding(UTF-8)' for *STDIN, *STDOUT, *STDERR; $| = 1; # unbuffered output in UTF-8
|
||||||
my $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- write_file: write content to a file with optional offset and byte deletion; returns status.\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. 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 parentheses 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 contents in the project.";
|
my $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- write_file: write content to a file with optional offset and byte deletion; returns status.\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. 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 parentheses 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 contents in the project.";
|
||||||
my $SDIR = ($ENV{HOME} || $ENV{USERPROFILE} || '.') . '/.bantam/sessions';
|
my $SDIR = ($ENV{HOME} || $ENV{USERPROFILE} || '.') . '/.bantam/sessions';
|
||||||
sub cfg_file { -f '.bantam.cfg' ? '.bantam.cfg' : 'model.cfg' }
|
|
||||||
sub cfg { my %d = (endpoint=>'https://opencode.ai/zen/v1', model=>'big-pickle', temperature=>0.7, api_key=>'-', timeout=>300, shell_timeout=>120, max_al_iterations=>1000);
|
sub cfg { my %d = (endpoint=>'https://opencode.ai/zen/v1', model=>'big-pickle', temperature=>0.7, api_key=>'-', timeout=>300, shell_timeout=>120, max_al_iterations=>1000);
|
||||||
if (open my $f, '<:encoding(UTF-8)', cfg_file()) { while (<$f>) { /^([^\s=]+)\s*=\s*(.+)$/ and $d{$1} = $2; } }
|
my $cf = -f '.bantam.cfg' ? '.bantam.cfg' : 'model.cfg';
|
||||||
|
if (open my $f, '<:encoding(UTF-8)', $cf) { while (<$f>) { /^([^\s=]+)\s*=\s*(.+)$/ and $d{$1} = $2; } }
|
||||||
$d{api_key} = $ENV{OPENAI_API_KEY} if $d{api_key} eq '-' && $ENV{OPENAI_API_KEY}; \%d }
|
$d{api_key} = $ENV{OPENAI_API_KEY} if $d{api_key} eq '-' && $ENV{OPENAI_API_KEY}; \%d }
|
||||||
sub filter_text { my $s = shift // ''; $s =~ s/[^\x20\t\n\p{L}\p{N}\p{P}\p{S}\p{M}\p{Zs}]//g; $s }
|
sub filter_text { my $s = shift // ''; $s =~ s/[^\x20\t\n\p{L}\p{N}\p{P}\p{S}\p{M}\p{Zs}]//g; $s }
|
||||||
sub T { my ($n, $d, $p, $r) = @_; {type=>'function', function=>{name=>$n, description=>$d, parameters=>{type=>'object', properties=>$p, required=>$r || [keys %$p]}}} }
|
sub T { my ($n, $d, $p, $r) = @_; {type=>'function', function=>{name=>$n, description=>$d, parameters=>{type=>'object', properties=>$p, required=>$r || [keys %$p]}}} }
|
||||||
@@ -72,26 +72,26 @@ sub AL { my ($c, $msgs) = ($_[0], $_[1]);
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
$msgs; }
|
$msgs; }
|
||||||
sub sessions { my @s; for my $f (glob "$SDIR/*.json") { open my $fh, '<', $f or next; local $/; my $d = eval { decode_json(<$fh>) }; push @s, $d if $d; } sort { $b->{id} cmp $a->{id} } @s }
|
sub sessions { my @s; for my $f (glob "$SDIR/*.json") { open my $fh, '<', $f or next; local $/; my $d = eval { decode_json(<$fh>) }; if (ref $d eq 'HASH') { $d->{id} //= ($f =~ m{/([^/]+)\.json$} ? $1 : ''); push @s, $d; } } sort { ($b->{id}//'') cmp ($a->{id}//'') } @s }
|
||||||
sub sdir { make_path($SDIR) unless -d $SDIR; $SDIR }
|
sub sdir { make_path($SDIR) unless -d $SDIR; $SDIR } sub project_id { md5_hex(abs_path('.') || '.') }
|
||||||
sub save { sdir(); my ($id, $i) = (strftime('%Y%m%d-%H%M%S', localtime), 0); $id .= '-' . ++$i while -f "$SDIR/$id.json"; open my $f, '>', "$SDIR/$id.json" or die "cannot save: $!"; print $f JSON::PP->new->utf8->pretty->encode({id=>$id, messages=>$_[0]}); close $f; $id }
|
sub save { sdir(); my $id = ($_[1] && length $_[1]) ? $_[1] : project_id(); open my $f, '>', "$SDIR/$id.json" or die "cannot save: $!"; print $f JSON::PP->new->utf8->pretty->encode({id=>$id, messages=>$_[0]}); close $f; $id }
|
||||||
sub load { my ($hit) = grep { $_->{id} eq $_[0] } sessions(); die "no session: $_[0]\n" unless $hit; $hit->{messages} }
|
sub load { my $f = "$SDIR/$_[0].json"; if (-f $f) { open my $fh, '<', $f or die "cannot open $f: $!"; local $/; my $d = eval { decode_json(<$fh>) }; return $d->{messages} if $d && $d->{messages}; } my ($hit) = grep { ($_->{id}//'') eq $_[0] } sessions(); die "no session: $_[0]\n" unless $hit; $hit->{messages} }
|
||||||
sub autosave { sdir(); open my $f, '>', "$SDIR/autosave.json" or return; print $f JSON::PP->new->utf8->pretty->encode({id=>'autosave', messages=>$_[0]}); }
|
sub autosave { save($_[0], project_id()) } sub list_sessions { map { [$_->{id}, scalar @{$_->{messages} // []}] } sessions() }
|
||||||
sub list_sessions { map { [$_->{id}, scalar @{$_->{messages} // []}] } sessions() }
|
|
||||||
sub set_cfg { my ($k, $v, @ls, $f) = @_; if (open my $fh, '<:encoding(UTF-8)', '.bantam.cfg') { while (<$fh>) { push @ls, (!/^#/ && /^([^\s=]+)\s*=/ && $1 eq $k) ? ($f = 1, "$k=$v\n") : $_; } } push @ls, "$k=$v\n" unless $f; if (open my $fh, '>:encoding(UTF-8)', '.bantam.cfg') { print $fh @ls; close $fh; } }
|
sub set_cfg { my ($k, $v, @ls, $f) = @_; if (open my $fh, '<:encoding(UTF-8)', '.bantam.cfg') { while (<$fh>) { push @ls, (!/^#/ && /^([^\s=]+)\s*=/ && $1 eq $k) ? ($f = 1, "$k=$v\n") : $_; } } push @ls, "$k=$v\n" unless $f; if (open my $fh, '>:encoding(UTF-8)', '.bantam.cfg') { print $fh @ls; close $fh; } }
|
||||||
sub main {
|
sub main {
|
||||||
my ($c, $sp) = (cfg(), $DEF_SP); my $msgs = [{role=>'system', content=>$sp}];
|
my ($c, $sp) = (cfg(), $DEF_SP); my $msgs = [{role=>'system', content=>$sp}];
|
||||||
if (@ARGV) { open my $f, '<:encoding(UTF-8)', $ARGV[0] or die "cannot open $ARGV[0]: $!"; local $/; push @$msgs, {role=>'user', content=><$f>}; AL($c, $msgs); autosave($msgs); return; }
|
if (@ARGV) { open my $f, '<:encoding(UTF-8)', $ARGV[0] or die "cannot open $ARGV[0]: $!"; local $/; push @$msgs, {role=>'user', content=><$f>}; AL($c, $msgs); autosave($msgs); return; }
|
||||||
print "MicroBantam ready ($c->{model}). Commands: /quit /clear /save /list /load <id> /cfg <k> [v] /help\n";
|
print "MicroBantam ready ($c->{model}). Commands: /quit /clear /save [name] /continue /list /load <id> /cfg <k> [v] /help\n";
|
||||||
while (1) {
|
while (1) {
|
||||||
print "> "; my $u = <STDIN>; last unless defined $u; $u =~ s/^\s+|\s+$//g; next unless length $u;
|
print "> "; my $u = <STDIN>; last unless defined $u; $u =~ s/^\s+|\s+$//g; next unless length $u;
|
||||||
if ($u eq '/quit') { last; }
|
if ($u eq '/quit') { last; }
|
||||||
elsif ($u eq '/clear') { $msgs = [{role=>'system', content=>$sp}]; autosave($msgs); }
|
elsif ($u eq '/clear') { $msgs = [{role=>'system', content=>$sp}]; autosave($msgs); }
|
||||||
elsif ($u eq '/save') { print "session saved: ", save($msgs), "\n"; }
|
elsif ($u =~ /^\/save(?:\s+(.+))?$/) { print "session saved: ", save($msgs, $1), "\n"; }
|
||||||
elsif ($u eq '/list') { print "$_->[0] [$_->[1] msgs]\n" for list_sessions(); }
|
elsif ($u eq '/continue' || $u eq '/cont') { my $pid = project_id(); $msgs = eval { load($pid) }; $@ ? print("No session found for current project\n") : (autosave($msgs), print "continued: $pid\n"); }
|
||||||
|
elsif ($u eq '/list') { my $pid = project_id(); print "$_->[0]" . ($_->[0] eq $pid ? " (current project)" : "") . " [$_->[1] msgs]\n" for list_sessions(); }
|
||||||
elsif ($u =~ /^\/load(?:\s+(\S+))?$/) { if (defined $1) { $msgs = eval { load($1) }; $@ ? print($@) : (autosave($msgs), print "loaded: $1\n"); } else { print "usage: /load <session id>\n"; } }
|
elsif ($u =~ /^\/load(?:\s+(\S+))?$/) { if (defined $1) { $msgs = eval { load($1) }; $@ ? print($@) : (autosave($msgs), print "loaded: $1\n"); } else { print "usage: /load <session id>\n"; } }
|
||||||
elsif ($u =~ /^\/cfg(?:\s+(\S+)(?:\s+(.+))?)?$/) { if (defined $2) { set_cfg($1, $2); $c = cfg(); print "config: $1=$2\n"; } elsif (defined $1) { print exists $c->{$1} ? "$1=$c->{$1}\n" : "$1 not set\n"; } else { print "usage: /cfg <param> [val]\n"; } }
|
elsif ($u =~ /^\/cfg(?:\s+(\S+)(?:\s+(.+))?)?$/) { if (defined $2) { set_cfg($1, $2); $c = cfg(); print "config: $1=$2\n"; } elsif (defined $1) { print exists $c->{$1} ? "$1=$c->{$1}\n" : "$1 not set\n"; } else { print "usage: /cfg <param> [val]\n"; } }
|
||||||
elsif ($u eq '/help') { print "Commands: /quit /clear /save /list /load <id> /cfg <k> [v] /help\n"; }
|
elsif ($u eq '/help') { print "Commands: /quit /clear /save [name] /continue /list /load <id> /cfg <k> [v] /help\n"; }
|
||||||
else { push @$msgs, {role=>'user', content=>$u}; AL($c, $msgs); autosave($msgs); }
|
else { push @$msgs, {role=>'user', content=>$u}; AL($c, $msgs); autosave($msgs); }
|
||||||
}
|
}
|
||||||
autosave($msgs);
|
autosave($msgs);
|
||||||
|
|||||||
Reference in New Issue
Block a user