tool set refactor
This commit is contained in:
@@ -27,8 +27,6 @@ import (
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
const MAX_DEPTH = 5 // subagent recursion depth limit
|
||||
|
||||
var (
|
||||
COL bool // ANSI color enabled
|
||||
hist []string // line history (mirrors ~/.bantam_history)
|
||||
@@ -255,9 +253,9 @@ func configPath() string {
|
||||
}
|
||||
const defaultSystemPrompt = `You are Bantam, a tiny, powerful AI agent. Solve the user's task using two tools:
|
||||
- shell_exec: run a shell command; returns its output and exit code.
|
||||
- run_subagent: delegate a sub-task to a child agent; returns its reply.
|
||||
- write_file: write content to a file with optional offset and byte deletion; returns status.
|
||||
|
||||
Work 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.
|
||||
Work 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.
|
||||
|
||||
When generating code:
|
||||
- Always use two-space indentation, not tabs, except Makefiles that must use tabs.
|
||||
@@ -265,8 +263,7 @@ When generating code:
|
||||
- Write optimally and with as few third-party dependencies as possible.
|
||||
- Always test.
|
||||
- No emojis in code or documentation.
|
||||
- Respect AGENTS.md, GEMINI.md, CLAUDE.md contents in the project.`
|
||||
|
||||
- Respect AGENTS.md contents in the project.`
|
||||
|
||||
func toolsDir(cfg *Cfg) string {
|
||||
if v := strings.TrimSpace(os.Getenv("BANTAM_TOOLS_DIR")); v != "" {
|
||||
@@ -278,13 +275,6 @@ func toolsDir(cfg *Cfg) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func prompt(path string) string {
|
||||
if d, err := os.ReadFile(path); err == nil {
|
||||
return strings.TrimSpace(string(d))
|
||||
}
|
||||
return defaultSystemPrompt
|
||||
}
|
||||
|
||||
func c(t string, cs ...int) string {
|
||||
if !COL || len(cs) == 0 { return t }
|
||||
s := make([]string, len(cs))
|
||||
@@ -730,7 +720,7 @@ type ToolCall struct {
|
||||
|
||||
var TOOLS = []map[string]any{
|
||||
{"type": "function", "function": map[string]any{"name": "shell_exec", "description": "Run a shell command, return output and exit code.", "parameters": map[string]any{"type": "object", "properties": map[string]any{"command": map[string]any{"type": "string"}}, "required": []string{"command"}}}},
|
||||
{"type": "function", "function": map[string]any{"name": "run_subagent", "description": "Run a child agent with a prompt.", "parameters": map[string]any{"type": "object", "properties": map[string]any{"prompt": map[string]any{"type": "string"}}, "required": []string{"prompt"}}}},
|
||||
{"type": "function", "function": map[string]any{"name": "write_file", "description": "Write content to a file at a byte offset, optionally deleting bytes first.", "parameters": map[string]any{"type": "object", "properties": map[string]any{"path": map[string]any{"type": "string"}, "offset": map[string]any{"type": "integer"}, "del_bytes": map[string]any{"type": "integer"}, "content": map[string]any{"type": "string"}}, "required": []string{"path", "content"}}}},
|
||||
}
|
||||
|
||||
func strp(s string) *string { return &s }
|
||||
@@ -1113,7 +1103,54 @@ func lastRole(msgs []Message) string {
|
||||
return msgs[len(msgs)-1].Role
|
||||
}
|
||||
|
||||
func AL(ctx context.Context, cfg *Cfg, msgs []Message, sp string, depth int) ([]Message, Usage, error) {
|
||||
func writeFile(path string, offset, delBytes int, content string) (string, error) {
|
||||
path = strings.TrimSpace(path)
|
||||
if path == "" {
|
||||
return "", errors.New("path is required")
|
||||
}
|
||||
if offset < 0 {
|
||||
offset = 0
|
||||
}
|
||||
if delBytes < 0 {
|
||||
delBytes = 0
|
||||
}
|
||||
var data []byte
|
||||
if fileExists(path) {
|
||||
var err error
|
||||
data, err = os.ReadFile(path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
} else {
|
||||
dir := filepath.Dir(path)
|
||||
if dir != "" && dir != "." {
|
||||
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
}
|
||||
if offset > len(data) {
|
||||
padding := make([]byte, offset-len(data))
|
||||
data = append(data, padding...)
|
||||
}
|
||||
prefix := data[:offset]
|
||||
var suffix []byte
|
||||
endDel := offset + delBytes
|
||||
if endDel < len(data) {
|
||||
suffix = data[endDel:]
|
||||
}
|
||||
contentBytes := []byte(content)
|
||||
newData := make([]byte, 0, len(prefix)+len(contentBytes)+len(suffix))
|
||||
newData = append(newData, prefix...)
|
||||
newData = append(newData, contentBytes...)
|
||||
newData = append(newData, suffix...)
|
||||
if err := os.WriteFile(path, newData, 0644); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return fmt.Sprintf("Successfully wrote %d bytes to %s", len(contentBytes), path), nil
|
||||
}
|
||||
|
||||
func AL(ctx context.Context, cfg *Cfg, msgs []Message) ([]Message, Usage, error) {
|
||||
done := false
|
||||
var turnUsage Usage
|
||||
for i := 0; i < cfg.MaxALIterations && !done; i++ {
|
||||
@@ -1184,25 +1221,48 @@ func AL(ctx context.Context, cfg *Cfg, msgs []Message, sp string, depth int) ([]
|
||||
cmd = filterText(cmd)
|
||||
res = shell(ctx, cmd, cfg.ShellTimeout)
|
||||
if err := ctx.Err(); err != nil { return msgs, turnUsage, err }
|
||||
case "run_subagent":
|
||||
pr, _ := a["prompt"].(string)
|
||||
pr = filterText(pr)
|
||||
if depth >= MAX_DEPTH {
|
||||
res, sty = fmt.Sprintf("[subagent depth limit (%d) reached, child not spawned]", MAX_DEPTH), 31
|
||||
} else {
|
||||
subMsgs := []Message{
|
||||
{Role: "system", Content: strp(sp + "\n\nImportant: this is a child agent")},
|
||||
{Role: "user", Content: strp(pr)},
|
||||
case "write_file":
|
||||
path, _ := a["path"].(string)
|
||||
path = filterText(path)
|
||||
contentVal, hasContent := a["content"]
|
||||
var content string
|
||||
if hasContent && contentVal != nil {
|
||||
if s, ok := contentVal.(string); ok {
|
||||
content = s
|
||||
}
|
||||
if subr, subu, err := AL(ctx, cfg, subMsgs, sp, depth+1); err != nil {
|
||||
if errors.Is(err, context.Canceled) || ctx.Err() != nil {
|
||||
return msgs, turnUsage, err
|
||||
}
|
||||
res, sty = "[subagent error: "+err.Error()+"]", 31
|
||||
}
|
||||
offset := 0
|
||||
if v, ok := a["offset"]; ok {
|
||||
switch n := v.(type) {
|
||||
case float64:
|
||||
offset = int(n)
|
||||
case int:
|
||||
offset = n
|
||||
case string:
|
||||
offset = atoiD(n, 0)
|
||||
}
|
||||
}
|
||||
delBytes := 0
|
||||
if v, ok := a["del_bytes"]; ok {
|
||||
switch n := v.(type) {
|
||||
case float64:
|
||||
delBytes = int(n)
|
||||
case int:
|
||||
delBytes = n
|
||||
case string:
|
||||
delBytes = atoiD(n, 0)
|
||||
}
|
||||
}
|
||||
if strings.TrimSpace(path) == "" {
|
||||
res, sty = "[tool error: write_file requires 'path' parameter]", 31
|
||||
} else if !hasContent {
|
||||
res, sty = "[tool error: write_file requires 'content' parameter]", 31
|
||||
} else {
|
||||
out, err := writeFile(path, offset, delBytes, content)
|
||||
if err != nil {
|
||||
res, sty = fmt.Sprintf("[tool error: write_file %s: %v]", path, err), 31
|
||||
} else {
|
||||
turnUsage.CompletionTokens += subu.CompletionTokens
|
||||
turnUsage.TotalTokens += subu.TotalTokens
|
||||
res, sty = filterText(last(subr)), 2
|
||||
res, sty = out, 2
|
||||
}
|
||||
}
|
||||
default:
|
||||
@@ -1594,7 +1654,7 @@ func doCompact(cfg *Cfg, msgs []Message) []Message {
|
||||
}
|
||||
|
||||
func main() {
|
||||
sp := prompt(".bantamsys.txt")
|
||||
sp := defaultSystemPrompt
|
||||
cfg := getCfg(configPath())
|
||||
cfg.ContextWindow = fetchContextWindow(&cfg)
|
||||
if td := toolsDir(&cfg); td != "" {
|
||||
@@ -1618,11 +1678,11 @@ func main() {
|
||||
msgs = append(msgs, Message{Role: "user", Content: strp(u)})
|
||||
sigCtx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
|
||||
var usg Usage
|
||||
msgs, usg, err = AL(sigCtx, &cfg, msgs, sp, 0)
|
||||
msgs, usg, err = AL(sigCtx, &cfg, msgs)
|
||||
if lastRole(msgs) == "tool" {
|
||||
msgs = append(msgs, Message{Role: "user", Content: strp("continue")})
|
||||
fmt.Println(c("[auto continue: last message was a tool result]", 33))
|
||||
msgs, usg, err = AL(sigCtx, &cfg, msgs, sp, 0)
|
||||
msgs, usg, err = AL(sigCtx, &cfg, msgs)
|
||||
}
|
||||
interrupted := sigCtx.Err() != nil
|
||||
cancel()
|
||||
@@ -1739,11 +1799,11 @@ func main() {
|
||||
turnMsgs := append([]Message{}, msgs...)
|
||||
turnMsgs = append(turnMsgs, Message{Role: "user", Content: strp(u)})
|
||||
sigCtx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
|
||||
resMsgs, usg, err := AL(sigCtx, &cfg, turnMsgs, sp, 0)
|
||||
resMsgs, usg, err := AL(sigCtx, &cfg, turnMsgs)
|
||||
if lastRole(resMsgs) == "tool" {
|
||||
resMsgs = append(resMsgs, Message{Role: "user", Content: strp("continue")})
|
||||
fmt.Println(c("[auto continue: last message was a tool result]", 33))
|
||||
resMsgs, usg, err = AL(sigCtx, &cfg, resMsgs, sp, 0)
|
||||
resMsgs, usg, err = AL(sigCtx, &cfg, resMsgs)
|
||||
}
|
||||
interrupted := sigCtx.Err() != nil
|
||||
cancel()
|
||||
|
||||
Reference in New Issue
Block a user