Compare commits
30
Commits
ad1b6efcd5
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
676fc1013c | ||
|
|
4eee5d6ef9 | ||
|
|
3b5a8a5bfd | ||
|
|
e1efe70d8a | ||
|
|
ebc62b93bf | ||
|
|
d5e69c4a0a | ||
|
|
0cc2987dcb | ||
|
|
f23c9132ce | ||
|
|
0b2d9f2a90 | ||
|
|
1d8a19dd67 | ||
|
|
a02b388b1a | ||
|
|
9792cec64f | ||
|
|
b776644261 | ||
|
|
9ee04ddf32 | ||
|
|
69571704a1 | ||
|
|
ea1fb205bc | ||
|
|
f21dc0efde | ||
|
|
d9c7bb4407 | ||
|
|
b0a16e99e1 | ||
|
|
502a3f8c03 | ||
|
|
b3f2864f6a | ||
|
|
5b5853a2dd | ||
|
|
c0350f5d9b | ||
|
|
ed5eb88ba9 | ||
|
|
4ed12ed213 | ||
|
|
208d874cce | ||
|
|
e6472a77b7 | ||
|
|
5291415d4e | ||
|
|
c64e79b22f | ||
|
|
f17f15aea8 |
@@ -1,5 +1,7 @@
|
||||
# Bantam: tiny, powerful, DIY AI agent
|
||||
|
||||
**N.B. These Bantam versions have been officially deprecated.** Main development is being done on the [bantam-cpp](https://code.luxferre.top/luxferre/bantam-cpp) port, and it's recommended to switch to it whenever possible. This repo has been archived to preserve the codebase for historical purposes, and no further updates are expected here.
|
||||
|
||||
## About
|
||||
|
||||
Bantam is a minimalist, dependency-free AI agent specification with reference implementations in **Go** (`main.go` + `term_*.go`, module `code.luxferre.top/luxferre/bantam`) and **Perl 5** as **MicroBantam** (`mb`, under 100 SLOC). It provides an agentic loop capable of autonomous tool execution, direct shell interaction, file writing/editing, real-time response streaming, markdown terminal rendering with box-drawing tables, Fibonacci backoff network resilience, context window auto-discovery, token usage tracking with prompt cache breakdowns, and conversation compaction using any OpenAI-compatible completions API.
|
||||
@@ -7,9 +9,9 @@ Bantam is a minimalist, dependency-free AI agent specification with reference im
|
||||
The entire philosophy of Bantam is built upon two principles:
|
||||
|
||||
1. The structure must be as simple as possible for anyone to be able to reimplement the agent from a plain algorithm description.
|
||||
2. The agent only needs to provide two tools: a tool to call shell commands (`shell_exec`) and a tool to write/edit files (`write_file`). In theory, this should be sufficient to give LLMs the ability to handle tasks of any complexity.
|
||||
2. The agent only needs to provide a minimal set of tools: a tool to run shell commands (`bash`), a tool to inspect files and directories (`read`), and a tool to write/edit files (`write`). In theory, this should be sufficient to give LLMs the ability to handle tasks of any complexity.
|
||||
|
||||
Because of the second principle, Bantam itself was named after Victorinox Bantam Alox, a small and lightweight Swiss army knife with only two tools.
|
||||
Because of this minimal philosophy, Bantam itself was named after Victorinox Bantam Alox, a small and lightweight Swiss army knife with minimal tools.
|
||||
|
||||
## Usage
|
||||
|
||||
@@ -40,11 +42,13 @@ All implementations read `model.cfg` (or `.bantam.cfg`, which takes priority if
|
||||
1. Configure `model.cfg` (or `.bantam.cfg`, which takes priority) with your API settings:
|
||||
```ini
|
||||
endpoint=https://api.kilo.ai/api/openrouter
|
||||
model=kilo-auto/free
|
||||
model=openrouter/free
|
||||
temperature=0.7
|
||||
api_key=your_api_key_here
|
||||
stream=true
|
||||
context_window=200000
|
||||
max_tool_res=65536
|
||||
reasoning_effort=high
|
||||
```
|
||||
|
||||
2. Interactive mode:
|
||||
@@ -53,11 +57,11 @@ All implementations read `model.cfg` (or `.bantam.cfg`, which takes priority if
|
||||
./mb # MicroBantam (Perl 5)
|
||||
```
|
||||
|
||||
In interactive mode, prompts can span multiple lines: press **Ctrl+J** to insert a real line break (the cursor moves to the next line), then **Enter** to submit the whole multi-line prompt. The Go port ships its own raw-mode line editor (arrow keys move the cursor, Up/Down browse history, Backspace edits, Ctrl+C clears line / interrupts in-flight run, Ctrl+D exits), working everywhere without third-party dependencies.
|
||||
In interactive mode, prompts can span multiple lines: press **Ctrl+J** to insert a real line break (the cursor moves to the next line), then **Enter** to submit the whole multi-line prompt. Bracketed paste is enabled (`ESC[?2004h`) while editing, so a multi-line block pasted from the clipboard is inserted as literal line breaks and only the final **Enter** submits it. The Go port ships its own raw-mode line editor, working everywhere without third-party dependencies. It supports arrow keys and Up/Down for history, Backspace and Ctrl+D (non-empty line) to delete, and Emacs-style editing combos: **Ctrl+A** (start of line), **Ctrl+E** (end of line), **Ctrl+B** / **Ctrl+F** (move by character), **Ctrl+W** (delete previous word), **Ctrl+K** (kill to end of line), **Ctrl+U** (kill to start of line), **Ctrl+Left** / **Ctrl+Right** (move by word), and **Home** / **End** keys. **Ctrl+C** clears the line / interrupts an in-flight run, and **Ctrl+D** on an empty line exits.
|
||||
|
||||
After every interaction, Bantam displays token usage (prompt tokens, cached/uncached breakdown when supported by the provider, completion tokens, and context window utilization):
|
||||
```text
|
||||
[tokens: 1420 prompt (1000 cached, 420 uncached) + 85 completion | context: 1420/200000 (0.7%)]
|
||||
[openrouter/free: 1420 prompt (1000 cached, 420 uncached) + 85 completion | context: 1420/200000 (0.7%)]
|
||||
```
|
||||
|
||||
Sessions are saved under `~/.bantam/sessions/` and can be managed with these commands:
|
||||
@@ -67,8 +71,11 @@ All implementations read `model.cfg` (or `.bantam.cfg`, which takes priority if
|
||||
- `/load <id>` — load a saved session (exact id or unique prefix) and continue from there
|
||||
- `/compact` — compact context down to the system message and a concise summary using the LLM; the compaction prompt is appended to the conversation to derive the summary, then the conversation is reset to `[system, summary-user-message]` (a fresh prefix, so downstream prompt-cache hits depend on the provider and are not guaranteed)
|
||||
- `/cfg <param> [val]` — inspect or update a configuration parameter live (writes to `.bantam.cfg`)
|
||||
- `/model [val]` — alias for `/cfg model` (inspect or set the model)
|
||||
- `/endpoint [val]` — alias for `/cfg endpoint` (inspect or set the API endpoint)
|
||||
- `/skill` — with no argument, list every skill under the configured skills directory (or report that none are configured); with `<name> [prompt]`, load `<skills_dir>/<name>/SKILL.md` (or an absolute path when no skills dir is set) and send its contents prefixed to the optional `prompt` as the next user turn
|
||||
- `/models` — query the `/models` path on the current inference endpoint and print a plain list of supported model IDs, marking the currently configured model with a leading `* ` (Go port)
|
||||
- `!<cmd>` — execute a shell command directly through `shell_exec` without adding the result to the conversation context (Go port)
|
||||
- `!<cmd>` — execute a shell command directly through `bash` without adding the result to the conversation context (Go port)
|
||||
- `/help` — show all supported commands
|
||||
- `/clear` — reset the conversation to just the system prompt
|
||||
- `/quit` — exit
|
||||
@@ -95,13 +102,13 @@ Using these rules, everyone can build their own copy of Bantam from scratch in l
|
||||
### High-Level Overview
|
||||
|
||||
1. **Initialization**: Read the config file (`.bantam.cfg` if present, else `model.cfg`). Discover context window size from the `/models` endpoint (or fallback to `context_window` from `model.cfg` or 200,000 tokens). Prepare an array of messages starting with the built-in system prompt `{"role": "system", "content": system_prompt}`.
|
||||
2. **Input Processing**: Take user prompt (via command-line file parameter or interactive stdin). If prefixed with `!`, execute the command directly via `shell_exec` without appending to conversation context. Otherwise, append `{"role": "user", "content": prompt}`, and invoke `AL(cfg, messages)`.
|
||||
2. **Input Processing**: Take user prompt (via command-line file parameter or interactive stdin). If prefixed with `!`, execute the command directly via `bash` without appending to conversation context. Otherwise, append `{"role": "user", "content": prompt}`, and invoke `AL(cfg, messages)`.
|
||||
3. **Agentic Loop (`AL`)**:
|
||||
- Send `messages` and tool definitions (`shell_exec`, `write_file`) to the OpenAI-compatible `/chat/completions` API endpoint with custom `User-Agent` headers and `stream_options: {"include_usage": true}`.
|
||||
- Send `messages` and tool definitions (`bash`, `read`, `write`) to the OpenAI-compatible `/chat/completions` API endpoint with custom request headers (see below) and `stream_options: {"include_usage": true}`.
|
||||
- Support context cancellation (e.g. on `SIGINT` / Ctrl+C) to cleanly abort in-flight requests without appending incomplete messages.
|
||||
- On network or HTTP failure, retry using Fibonacci backoff delays (`1s, 1s, 2s, 3s, 5s, 8s, 13s, 21s, 34s`).
|
||||
- If `stream=true`, parse SSE data chunks (`data: {...}`) in real-time to stream reasoning content (`reasoning_content`) and response text directly to stdout, bracketing the reasoning block with `--- reasoning start ---` / `--- reasoning end ---` markers, rendering Markdown and tables constrained to terminal width.
|
||||
- Reconstruct the assistant message and track usage tokens (`prompt_tokens`, `completion_tokens`, cached tokens). If `tool_calls` exist, trace the call (`[tool call: name(args)]`), validate JSON arguments, execute the requested tool (`shell_exec` or `write_file`), trace the result (`[tool result: name]`), append the tool response `{"role": "tool", "tool_call_id": id, "content": result}`, and repeat the loop.
|
||||
- Reconstruct the assistant message and track usage tokens (`prompt_tokens`, `completion_tokens`, cached tokens). If `tool_calls` exist, trace the call (`[tool call: name(args)]`), validate JSON arguments, execute the requested tool (`bash`, `read`, or `write`; also accepting legacy `shell_exec` and `write_file`), trace the result (`[tool result: name]`), append the tool response `{"role": "tool", "tool_call_id": id, "content": result}`, and repeat the loop.
|
||||
- If no tool calls remain or `max_al_iterations` is reached, return the updated messages list and turn usage stats.
|
||||
4. **Post-Turn Reporting & Compaction**:
|
||||
- Display token usage and context window percentage.
|
||||
@@ -113,22 +120,23 @@ Using these rules, everyone can build their own copy of Bantam from scratch in l
|
||||
1. Initialize system prompt from built-in default.
|
||||
2. Read model parameters from the config file (`.bantam.cfg` if present, else `model.cfg`) in `key=value` format and discover context window size.
|
||||
3. Prepare a new message list with the system prompt (`role: "system"`).
|
||||
4. Read the first command-line parameter. If non-empty, read user prompt from the specified file. If prefixed with `!`, execute the shell command directly via `shell_exec` and exit. Otherwise, append to `messages` (`role: "user"`), run `AL(cfg, messages)`, display token usage, and exit.
|
||||
5. Read user prompt from standard input (with `readline` line editing and history in `~/.bantam_history`; **Ctrl+J** inserts a real newline into the line being edited). If equal to `/quit` or EOF, exit. If equal to `/clear`, reset `messages` to step 3 and return to step 5. If starting with `/save`, write the whole `messages` array to `~/.bantam/sessions/<name>.json` (or `<project-md5>.json` if no name is given) and return to step 5. If equal to `/continue` or `/cont`, load the session corresponding to the current project's MD5 hash and return to step 5. If equal to `/list`, print saved sessions and their summaries (marking current project session) and return to step 5. If starting with `/load`, replace `messages` with the saved session's messages (by exact id or unique prefix) and return to step 5. If equal to `/compact`, ask the LLM to summarize the conversation by appending the compaction prompt to derive the summary, replace `messages` with `[system, summary-user-message]`, and return to step 5. If starting with `/cfg`, display the current value (`/cfg <param>`) or update the config live by writing to `.bantam.cfg` (`/cfg <param> <val>`) and return to step 5. If equal to `/models`, query the `/models` path on the current inference endpoint and print a plain list of supported model IDs (the currently configured model marked with a leading `* `), then return to step 5. If starting with `!`, execute the command directly via `shell_exec` without adding the result to `messages` and return to step 5. If equal to `/help`, print the command list and return to step 5. After every user turn and on exit, auto-save `messages` to `~/.bantam/sessions/<project-md5>.json`.
|
||||
4. Read the first command-line parameter. If non-empty, read user prompt from the specified file. If prefixed with `!`, execute the shell command directly via `bash` and exit. Otherwise, append to `messages` (`role: "user"`), run `AL(cfg, messages)`, display token usage, and exit.
|
||||
5. Read user prompt from standard input (with `readline` line editing and history in `~/.bantam_history`; **Ctrl+J** inserts a real newline into the line being edited). If equal to `/quit` or EOF, exit. If equal to `/clear`, reset `messages` to step 3 and return to step 5. If starting with `/save`, write the whole `messages` array to `~/.bantam/sessions/<name>.json` (or `<project-md5>.json` if no name is given) and return to step 5. If equal to `/continue` or `/cont`, load the session corresponding to the current project's MD5 hash and return to step 5. If equal to `/list`, print saved sessions and their summaries (marking current project session) and return to step 5. If starting with `/load`, replace `messages` with the saved session's messages (by exact id or unique prefix) and return to step 5. If equal to `/compact`, ask the LLM to summarize the conversation by appending the compaction prompt to derive the summary, replace `messages` with `[system, summary-user-message]`, and return to step 5. If starting with `/cfg`, display the current value (`/cfg <param>`) or update the config live by writing to `.bantam.cfg` (`/cfg <param> <val>`) and return to step 5. `/model` and `/endpoint` are aliases for `/cfg model` and `/cfg endpoint` respectively and behave the same way. If equal to `/skill` (no argument), list every skill under the configured skills directory (or report that none is configured) and return to step 5. If starting with `/skill`, resolve the skill (a `<skills_dir>/<name>/SKILL.md` file, or an absolute path to a skill directory or `SKILL.md` file when no skills directory is configured), prefix its contents to the optional remaining text, and send the result as the next user turn (returning to step 5 after the response). If equal to `/models`, query the `/models` path on the current inference endpoint and print a plain list of supported model IDs (the currently configured model marked with a leading `* `), then return to step 5. If starting with `!`, execute the command directly via `bash` without adding the result to `messages` and return to step 5. If equal to `/help`, print the command list and return to step 5. After every user turn and on exit, auto-save `messages` to `~/.bantam/sessions/<project-md5>.json`.
|
||||
6. Append user prompt to `messages` (`role: "user"`), run `AL(cfg, messages)`, display token usage, check 60% context threshold for auto-compaction, and go to step 5.
|
||||
|
||||
### Agentic loop (`AL(cfg, messages)`) function
|
||||
|
||||
1. Call OpenAI-compatible Completions API (`POST {endpoint}/chat/completions`) forwarding relevant parameters from `cfg` (`model`, `temperature`, `stream`, `reasoning_effort`, etc., excluding internal agent configs like `endpoint`, `api_key`, `timeout`, `shell_timeout`, `max_al_iterations`, `color`, `context_window`) and optional `api_key` bearer header.
|
||||
- Set custom `User-Agent` header (`Mozilla/5.0 (compatible; Bantam/1.0)`) to avoid gateway 403 blocks.
|
||||
- Retry network/HTTP errors with Fibonacci backoff delays (`1s, 1s, 2s, 3s, 5s, 8s, 13s, 21s, 34s`).
|
||||
- If `stream=true`, parse SSE stream (`data: {...}`) for real-time reasoning and text output, bracketing reasoning with `--- reasoning start ---` / `--- reasoning end ---` markers.
|
||||
- Set custom request headers mimicking OpenCode for all endpoints: `User-Agent: opencode/1.18.31 ai-sdk/provider-utils/4.0.23 runtime/bun/1.3.14`, `x-opencode-client: cli`, `x-opencode-project: global`, `x-opencode-session: <ses_...>`, and a fresh `x-opencode-request: <msg_...>` per call (where the 12-hex timestamp prefix in `x-opencode-request` is the exact bitwise inverse of `x-opencode-session`). The `Authorization: Bearer <api_key>` header is added whenever `api_key` is set and not `-`.
|
||||
- Retry network/HTTP errors with Fibonacci backoff delays (`1s, 1s, 2s, 3s, 5s, 8s, 13s, 21s, 34s`).
|
||||
- If `stream=true`, parse SSE stream (`data: {...}`) for real-time reasoning and text output, bracketing reasoning with `--- reasoning start ---` / `--- reasoning end ---` markers.
|
||||
2. Append the assistant's response message object to `messages`. If non-streaming and response has reasoning tokens (`reasoning_content` or `reasoning`), output them wrapped in `--- reasoning start ---` / `--- reasoning end ---` markers.
|
||||
3. If there are pending `tool_calls` in the assistant response:
|
||||
- For each tool call, output a trace log (`[tool call: name(args)]`).
|
||||
- Sanitize tool arguments to filter out non-printable and space-like Unicode characters (protecting against indirect prompt injection), and validate JSON arguments. If invalid, format a tool-error response so the LLM can self-correct.
|
||||
- Execute tool action (`shell_exec` or `write_file`).
|
||||
- Execute tool action (`bash`, `read`, or `write`; also accepting legacy `shell_exec` and `write_file`).
|
||||
- Sanitize the tool result output to strip any non-printable and space-like Unicode characters (leaving only ASCII space, tab, newline, and printable Unicode characters).
|
||||
- If the sanitized result is larger than `max_tool_res` bytes, spill it to a unique file under `$TMPDIR/bantam/toolres` and replace the content appended to `messages` with a short instruction naming the file, its line count, and how to read it partially using the `read` tool (specifying `offset` and `limit`); every spilled file is deleted when the round ends (i.e. when the model emits a final response with no tool calls).
|
||||
- Output a trace log of the result (`[tool result: name]`).
|
||||
- Append tool result message (`role: "tool"`, `tool_call_id`, `content`: result string) to `messages`.
|
||||
- Loop back to step 1.
|
||||
@@ -138,20 +146,23 @@ If the API rejects the request with an `Invalid assistant message: content or to
|
||||
|
||||
### Model configuration parameters
|
||||
|
||||
(shared by all implementations; the config file — `.bantam.cfg` takes priority over `model.cfg` when both exist — is plain `key=value` with `#` comments)
|
||||
(shared by all implementations; `.bantam.cfg` takes priority over `model.cfg` when both exist — both are plain `key=value` with `#` comments. Values defined in `.bantam.cfg` override `BANTAM_*` environment variables; when `.bantam.cfg` is absent, `BANTAM_*` environment variables override `model.cfg` and built-in defaults.)
|
||||
|
||||
- `endpoint` (base OpenAI-compatible API URL, default `https://api.kilo.ai/api/openrouter`)
|
||||
- `model` (model name, default `kilo-auto/free`)
|
||||
- `temperature` (model temperature, default 0.7)
|
||||
- `api_key` (API key / Bearer token, optional; fall back to `OPENAI_API_KEY` env var)
|
||||
- `stream` (stream response tokens in real-time, default `true`)
|
||||
- `color` (ANSI coloring: `auto` (TTY-detected, default), `always`, or `never`; also disabled by `NO_COLOR`/`BANTAM_NO_COLOR` env vars)
|
||||
- `timeout` (HTTP timeout in seconds for LLM API calls, default 300; in the Go port it bounds connection setup and time-to-first-byte, so long streaming responses are not cut off mid-stream)
|
||||
- `shell_timeout` (timeout in seconds for `shell_exec` commands, default 120)
|
||||
- `max_al_iterations` (max tool-call loop iterations per `AL()` invocation, default 1000)
|
||||
- `context_window` (context window size in tokens, auto-discovered from `/models` API if available, fallback to this setting, default 200000)
|
||||
- `endpoint` (base OpenAI-compatible API URL, default `https://api.kilo.ai/api/openrouter`; falls back to `BANTAM_ENDPOINT` env var)
|
||||
- `model` (model name, default `openrouter/free`; falls back to `BANTAM_MODEL` env var)
|
||||
- `temperature` (model temperature, default 0.7; falls back to `BANTAM_TEMP` or `BANTAM_TEMPERATURE` env var)
|
||||
- `api_key` (API key / Bearer token, optional; falls back to `BANTAM_API_KEY` env var; set to `-` or left blank in config file to explicitly defer to `BANTAM_API_KEY`)
|
||||
- `stream` (stream response tokens in real-time, default `true`; falls back to `BANTAM_STREAM` env var)
|
||||
- `color` (ANSI coloring: `auto` (TTY-detected, default), `always`, or `never`; disabled by `NO_COLOR`/`BANTAM_NO_COLOR` env vars, falls back to `BANTAM_COLOR` env var)
|
||||
- `timeout` (HTTP timeout in seconds for LLM API calls, default 300; in the Go port it bounds connection setup and time-to-first-byte, so long streaming responses are not cut off mid-stream; falls back to `BANTAM_TIMEOUT` env var)
|
||||
- `shell_timeout` (timeout in seconds for `bash` shell commands, default 120; falls back to `BANTAM_SHELL_TIMEOUT` env var)
|
||||
- `max_al_iterations` (max tool-call loop iterations per `AL()` invocation, default 1000; falls back to `BANTAM_MAX_AL_ITERATIONS` env var)
|
||||
- `context_window` (context window size in tokens, auto-discovered from `/models` API if available, fallback to this setting, default 200000; falls back to `BANTAM_CONTEXT_WINDOW` env var)
|
||||
- `max_tool_res` (maximum number of bytes of a tool result that may be sent into the model context directly, default 65536; larger results are written to a unique file under `$TMPDIR/bantam/toolres` and replaced by a short instruction naming the file and its line count so it can be read in parts using the `read` tool, and the file is deleted once the round ends; falls back to `BANTAM_MAX_TOOL_RES` env var)
|
||||
- `reasoning_effort` (reasoning effort level, forwarded to chat completions API, default `high`; falls back to `BANTAM_REASONING_EFFORT` env var)
|
||||
|
||||
- `bantam_tools_dir` (optional path to a directory of extra shell tools; the Go port appends `"Extra shell tools can be found at <dir>"` to the system prompt at startup when set. The `BANTAM_TOOLS_DIR` environment variable overrides this and is checked first; if neither is set, nothing is appended. Note: this is an agent-internal hint, not forwarded to the API.)
|
||||
- `bantam_tools_dir` (optional path to a directory of extra shell tools; the Go port appends `"Extra shell tools can be found at <dir>"` to the system prompt at startup when set. When unset in the config file, it falls back to the `BANTAM_TOOLS_DIR` environment variable; if neither is set, nothing is appended. Note: this is an agent-internal hint, not forwarded to the API.)
|
||||
- `bantam_skills_dir` (optional path to a directory of reusable *skills*; each skill lives in its own subdirectory as `<skill_name>/SKILL.md`. When set, the Go port appends `"Skills may be discovered and invoked from <dir>"` to the system prompt at startup, and the `/skill <skill_name> [prompt]` command loads a skill's `SKILL.md` and runs it as a prompt. When unset in the config file, it falls back to the `BANTAM_SKILLS_DIR` environment variable; if neither is set, `/skill` accepts an absolute path to a skill directory or `SKILL.md` file instead. Note: this is an agent-internal hint, not forwarded to the API.)
|
||||
|
||||
The Go port also supports SOCKS5 proxying via the `SOCKS_PROXY` (or `socks_proxy`) environment variable (e.g. `SOCKS_PROXY=socks5://127.0.0.1:1080` or `SOCKS_PROXY=127.0.0.1:1080`), falling back to standard `HTTP_PROXY` / `HTTPS_PROXY` environment variables.
|
||||
|
||||
@@ -161,21 +172,32 @@ When enabled, the interactive console uses a subtle ANSI palette: the pending-re
|
||||
|
||||
### Tool call definitions
|
||||
|
||||
#### `write_file` tool
|
||||
#### `read` tool
|
||||
|
||||
- Parameters:
|
||||
- `path` (string, required): JSON-escaped file path to write to (must be created unless existing).
|
||||
- `offset` (integer, optional, default 0): byte offset to start writing from.
|
||||
- `del_bytes` (integer, optional, default 0): bytes to delete starting from the `offset` prior to writing.
|
||||
- `filePath` (string, required): path to the file to read or directory to inspect (also accepts `path`).
|
||||
- `offset` (integer, optional): line number to start reading from (1-indexed). Defaults to 1.
|
||||
- `limit` (integer, optional): maximum number of lines to read. Defaults to 2000.
|
||||
- Return value: string
|
||||
- Action: if the target is a regular file, returns numbered lines in `<line>: <content>` format. If the target is a directory, returns a list of directory entries (subdirectories include a trailing `/`).
|
||||
|
||||
#### `write` tool
|
||||
|
||||
- Parameters:
|
||||
- `filePath` (string, required): JSON-escaped file path to write to (also accepts `path`).
|
||||
- `offset` (integer, optional): byte offset to start writing from. If omitted together with `del_bytes`, the whole file is overwritten instead. Defaults to 0 (start of file).
|
||||
- `del_bytes` (integer, optional): bytes to delete starting at `offset`. If omitted together with `offset`, the whole file is overwritten instead.
|
||||
- `content` (string, required, may be empty): JSON-escaped content to write to the file.
|
||||
- Return value: string
|
||||
- Action: write `content` into the file at `path` starting at `offset` after deleting `del_bytes` bytes (creating the file and any necessary parent directories).
|
||||
- Action: if `offset` and `del_bytes` are both omitted, the entire file is overwritten with `content` (the file, and any necessary parent directories, are created if missing); otherwise `content` is written at `offset`, optionally deleting `del_bytes` bytes first (splicing prefix + content + suffix and never appending to the end of an existing file), creating the file and parent directories as needed. (Also accepted as legacy `write_file`.)
|
||||
|
||||
#### `shell_exec` tool
|
||||
#### `bash` tool
|
||||
|
||||
- Parameters: `command` (string)
|
||||
- Parameters:
|
||||
- `command` (string, required): shell command to run.
|
||||
- `workdir` (string, optional): working directory to execute the command in.
|
||||
- Return value: string
|
||||
- Action: run shell command specified in `command` subject to `shell_timeout` (default 120s) and return `output + '\n\nexit: ' + exit_code` string.
|
||||
- Action: run shell command specified in `command` via `bash -c` subject to `shell_timeout` (default 120s) and return `output + '\nexit: ' + exit_code` string. (Also accepted as legacy `shell_exec`.)
|
||||
|
||||
## MicroBantam
|
||||
|
||||
@@ -183,12 +205,12 @@ MicroBantam (`mb`) is a compressed Perl 5 reference implementation of the same a
|
||||
|
||||
### Features
|
||||
|
||||
- Full agentic loop: LLM calls, `shell_exec` / `write_file` tool execution with JSON argument validation (invalid args are fed back so the model can self-correct)
|
||||
- Full agentic loop: LLM calls, `bash` / `read` / `write` tool execution with JSON argument validation (invalid args are fed back so the model can self-correct)
|
||||
- Indirect prompt injection defense: sanitizes tool parameters and tool outputs by filtering non-printable and space-like Unicode characters, preserving standard space, tab, newline, and printable Unicode characters
|
||||
- A `...requesting...` in-flight indicator: in-place on a TTY (`\r` overwrite, erased on completion), a plain line when output is piped
|
||||
- Session management: `/save`, `/list`, `/load <id>` (exact id only, no prefix matching), `/cfg <param> [val]`, 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>`, `/cfg`, `/help`) and file input mode
|
||||
- Same built-in default system prompt and `OPENAI_API_KEY` fallback as the Go implementation
|
||||
- Same built-in default system prompt and `BANTAM_API_KEY` fallback as the Go implementation
|
||||
- Automatic recovery from `Invalid assistant message: content or tool_calls must be set` API errors: strips the last assistant message and retries (matches the Go port; note that a 4xx response other than this specific error aborts the run with an `API error` message, unlike the Go port which retries only on 5xx/408/429)
|
||||
|
||||
### What it drops
|
||||
@@ -198,6 +220,7 @@ MicroBantam (`mb`) is a compressed Perl 5 reference implementation of the same a
|
||||
- Line editing, Ctrl+J multi-line prompts and history (plain single-line prompts)
|
||||
- Fibonacci backoff network retries (a failed request aborts with an `API error` message)
|
||||
- `/compact` context summarization
|
||||
- `max_tool_res` tool-result spilling (oversized results are sent to the model verbatim)
|
||||
|
||||
### Running MicroBantam
|
||||
|
||||
@@ -215,9 +238,20 @@ MicroBantam (`mb`) is a compressed Perl 5 reference implementation of the same a
|
||||
|
||||
## Extra tools
|
||||
|
||||
The `extras/` directory contains small, dependency-light shell scripts that extend Bantam without changing its core. Because Bantam's built-in tools are `shell_exec` and `write_file`, these helpers can be invoked directly by the agent through `shell_exec` to give it real-world capabilities (web search, live weather) that the base model alone does not have. They are plain `/bin/sh` scripts depending only on `curl` (and `jq` where noted), so the agent can discover and run them just like any other command.
|
||||
The `extras/` directory contains small, dependency-light shell scripts that extend Bantam without changing its core. Because Bantam's built-in tools include `bash`, these helpers can be invoked directly by the agent through `bash` to give it real-world capabilities (web search, live weather) that the base model alone does not have. They are plain `/bin/sh` scripts depending only on `curl` (and `jq` where noted), so the agent can discover and run them just like any other command.
|
||||
|
||||
If you keep your own collection of helper scripts, point Bantam at them with the `BANTAM_TOOLS_DIR` environment variable or the `bantam_tools_dir` key in the config file (`.bantam.cfg` if present, else `model.cfg`). When either is defined (environment variable taking precedence over the config key), the Go port appends the line `Extra shell tools can be found at <dir>` to the system prompt at startup, so the agent is aware of where to look for them. The `extras/` scripts shipped here are just examples of what such a directory can contain.
|
||||
If you keep your own collection of helper scripts, point Bantam at them with the `bantam_tools_dir` key in the config file (`.bantam.cfg` if present, else `model.cfg`) or the `BANTAM_TOOLS_DIR` environment variable. When either is defined (the config file setting taking precedence over the environment variable), the Go port appends the line `Extra shell tools can be found at <dir>` to the system prompt at startup, so the agent is aware of where to look for them. The `extras/` scripts shipped here are just examples of what such a directory can contain.
|
||||
|
||||
## Skills
|
||||
|
||||
A *skill* is a reusable, self-contained instruction bundle the agent can load on demand. Each skill is a directory containing a `SKILL.md` file with the skill's prompt/instructions. Point Bantam at a skills directory with the `bantam_skills_dir` key in the config file (`.bantam.cfg` if present, else `model.cfg`) or the `BANTAM_SKILLS_DIR` environment variable; the config file setting takes precedence. When either is set, the Go port appends `Skills may be discovered and invoked from <dir>` to the system prompt at startup, and the agent can run a skill with:
|
||||
|
||||
```text
|
||||
/skill # list every skill under the configured skills directory
|
||||
/skill <name> [prompt] # loads <skills_dir>/<name>/SKILL.md and runs it as a prompt
|
||||
```
|
||||
|
||||
A bare `/skill` (no name) lists all skills found under the configured skills directory — a subdirectory is treated as a skill only if it contains a `SKILL.md` file — or reports that no skills directory is configured. The contents of `SKILL.md` are prefixed to the optional `prompt` and sent as the next user turn. If neither `BANTAM_SKILLS_DIR` nor `bantam_skills_dir` is set, `/skill <name>` accepts an absolute path instead: either a skill directory (e.g. `/abs/path/to/skill`, which resolves to `/abs/path/to/skill/SKILL.md`) or a direct path to a `SKILL.md` file.
|
||||
|
||||
### `extras/websearch`
|
||||
|
||||
@@ -303,11 +337,19 @@ The default system prompt instructs the agent to respect `AGENTS.md` contents in
|
||||
|
||||
### How do I tell Bantam about extra shell tools?
|
||||
|
||||
Set the `BANTAM_TOOLS_DIR` environment variable (or the `bantam_tools_dir` key in the config file — `.bantam.cfg` if present, else `model.cfg`) to a directory containing your helper scripts. When defined, the Go port appends `Extra shell tools can be found at <dir>` to the system prompt at startup, making the agent aware of them. The environment variable takes precedence over the config key; if neither is set, nothing is appended.
|
||||
Set the `bantam_tools_dir` key in the config file (`.bantam.cfg` if present, else `model.cfg`) or the `BANTAM_TOOLS_DIR` environment variable to a directory containing your helper scripts. When defined, the Go port appends `Extra shell tools can be found at <dir>` to the system prompt at startup, making the agent aware of them. The config file key takes precedence over the environment variable; if neither is set, nothing is appended.
|
||||
|
||||
### How do I give Bantam reusable skills?
|
||||
|
||||
Set the `bantam_skills_dir` key in the config file (`.bantam.cfg` if present, else `model.cfg`) or the `BANTAM_SKILLS_DIR` environment variable to a directory where each subdirectory is a skill containing a `SKILL.md` file. When defined, the Go port appends `Skills may be discovered and invoked from <dir>` to the system prompt at startup, and you (or the agent) can run a skill with `/skill <name> [prompt]`. The config file key takes precedence over the environment variable; if neither is set, `/skill` accepts an absolute path to a skill directory or `SKILL.md` file instead.
|
||||
|
||||
### What happens when a tool result is too large for the context?
|
||||
|
||||
Bantam never stuffs an arbitrarily large tool result into the context window. The `max_tool_res` configuration parameter (default `65536`, config-file key takes precedence over the `BANTAM_MAX_TOOL_RES` environment variable) sets the maximum number of bytes of a tool result that may be sent to the model directly. When a result exceeds that limit, the Go port writes the full output to a unique file under `$TMPDIR/bantam/toolres` and replaces the result in the conversation with a short note naming that file and stating its total line count. The agent is then expected to read the file with the `read` tool, paging through it with line offsets and limits (`offset` and `limit`) rather than loading everything at once. Every file spilled during a round is deleted as soon as the round ends, i.e. when the model emits its final response with no pending tool calls. The built-in system prompt also advertises this behaviour to the model at startup.
|
||||
|
||||
### Is there any common config place for Bantam?
|
||||
|
||||
No, loading the config file (`.bantam.cfg` if present, else `model.cfg`) is deliberately only supported from the current working directory. This allows natural separation of configs per project. In case there's no config file inside the project, Bantam will use the `kilo-auto/free` model from Kilo Code with the temperature 0.7.
|
||||
No, loading the config file (`.bantam.cfg` if present, else `model.cfg`) is deliberately only supported from the current working directory. This allows natural separation of configs per project. In case there's no config file inside the project, Bantam will use the `openrouter/free` model from Kilo Code with the temperature 0.7.
|
||||
|
||||
### Why no MCP support?
|
||||
|
||||
|
||||
+1346
-9
File diff suppressed because it is too large
Load Diff
@@ -4,12 +4,22 @@
|
||||
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+/ };
|
||||
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 three tools:\n- bash: run a bash command; returns its output and exit code.\n- read: read a file or directory from the local filesystem; returns numbered lines or directory entries.\n- write: write content to a file; if offset and del_bytes are both omitted it overwrites the entire file, otherwise it writes at the given byte offset (optionally deleting bytes first); 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.\nUse the target system's filesystem deliberately:\n- Use only \$TMPDIR/bantam as the temporary directory for intermediate or scratch files (logs, downloads, temporary build outputs, etc.); create it if it does not exist.\n- Create permanent artifacts in the current working directory unless the user explicitly instructs otherwise.\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';
|
||||
sub cfg { my %d = (endpoint=>'https://api.kilo.ai/api/openrouter', model=>'kilo-auto/free', temperature=>0.7, api_key=>'-', timeout=>300, shell_timeout=>120, max_al_iterations=>1000);
|
||||
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 }
|
||||
sub cfg {
|
||||
my %d = (endpoint=>'https://api.kilo.ai/api/openrouter', model=>'openrouter/free', temperature=>0.7, api_key=>'-', timeout=>300, shell_timeout=>120, max_al_iterations=>1000, reasoning_effort=>'high');
|
||||
if (-f 'model.cfg' && open my $mf, '<:encoding(UTF-8)', 'model.cfg') { while (<$mf>) { /^([^\s=]+)\s*=\s*(.+)$/ and $d{$1} = $2; } }
|
||||
$d{endpoint} = $ENV{BANTAM_ENDPOINT} if $ENV{BANTAM_ENDPOINT};
|
||||
$d{model} = $ENV{BANTAM_MODEL} if $ENV{BANTAM_MODEL};
|
||||
my $t = $ENV{BANTAM_TEMP} // $ENV{BANTAM_TEMPERATURE};
|
||||
$d{temperature} = $t if defined $t && $t =~ /^\d+(\.\d+)?$/;
|
||||
$d{timeout} = $ENV{BANTAM_TIMEOUT} if defined $ENV{BANTAM_TIMEOUT} && $ENV{BANTAM_TIMEOUT} =~ /^\d+$/;
|
||||
$d{shell_timeout} = $ENV{BANTAM_SHELL_TIMEOUT} if defined $ENV{BANTAM_SHELL_TIMEOUT} && $ENV{BANTAM_SHELL_TIMEOUT} =~ /^\d+$/;
|
||||
$d{max_al_iterations} = $ENV{BANTAM_MAX_AL_ITERATIONS} if defined $ENV{BANTAM_MAX_AL_ITERATIONS} && $ENV{BANTAM_MAX_AL_ITERATIONS} =~ /^\d+$/;
|
||||
$d{reasoning_effort} = $ENV{BANTAM_REASONING_EFFORT} if $ENV{BANTAM_REASONING_EFFORT};
|
||||
if (-f '.bantam.cfg' && open my $bf, '<:encoding(UTF-8)', '.bantam.cfg') { while (<$bf>) { /^([^\s=]+)\s*=\s*(.+)$/ and $d{$1} = $2; } }
|
||||
$d{api_key} = $ENV{BANTAM_API_KEY} if ($d{api_key} eq '-' || !$d{api_key}) && $ENV{BANTAM_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 T { my ($n, $d, $p, $r) = @_; {type=>'function', function=>{name=>$n, description=>$d, parameters=>{type=>'object', properties=>$p, required=>$r || [keys %$p]}}} }
|
||||
sub sanitize_msgs { for my $m (@{$_[0]}) { if (ref $m eq 'HASH') {
|
||||
@@ -20,9 +30,9 @@ sub sanitize_msgs { for my $m (@{$_[0]}) { if (ref $m eq 'HASH') {
|
||||
} } }
|
||||
sub llm { my ($c, $msgs) = @_; sanitize_msgs($msgs);
|
||||
my $ep = $c->{endpoint}; $ep =~ s{/+$}{};
|
||||
my $h = {'Content-Type'=>'application/json', 'User-Agent'=>'Mozilla/5.0 (compatible; MicroBantam/1.0)'};
|
||||
my $h = {'Content-Type'=>'application/json', 'User-Agent'=>'opencode/1.18.31 ai-sdk/provider-utils/4.0.23 runtime/bun/1.3.14', 'x-opencode-client'=>'cli', 'x-opencode-project'=>'global'};
|
||||
$h->{Authorization} = "Bearer $c->{api_key}" if $c->{api_key} ne '-';
|
||||
my %p = (messages=>$msgs, tools=>[T('shell_exec', 'Run a shell command, return output and exit code.', {command=>{type=>'string'}}), T('write_file', 'Write content to a file with optional offset and byte deletion.', {path=>{type=>'string'}, offset=>{type=>'integer'}, del_bytes=>{type=>'integer'}, content=>{type=>'string'}}, ['path', 'content'])], model=>$c->{model}, temperature=>0+$c->{temperature});
|
||||
my %p = (messages=>$msgs, tools=>[T('bash', 'Executes a given bash command, return output and exit code.', {command=>{type=>'string'}}), T('read', 'Read a file or directory from the local filesystem.', {filePath=>{type=>'string'}, offset=>{type=>'integer'}, limit=>{type=>'integer'}}, ['filePath']), T('write', 'Write content to a file. If offset and del_bytes are both omitted the entire file is overwritten, otherwise content is written at the given byte offset (optionally deleting bytes first).', {filePath=>{type=>'string'}, offset=>{type=>'integer'}, del_bytes=>{type=>'integer'}, content=>{type=>'string'}}, ['filePath', 'content'])], model=>$c->{model}, temperature=>0+$c->{temperature});
|
||||
for my $k (keys %$c) { next if $k =~ /^(endpoint|api_key|timeout|shell_timeout|max_al_iterations|stream|color)$/; my $val = eval { decode_json($c->{$k}) }; $p{$k} = defined $val ? $val : $c->{$k}; }
|
||||
my $tty = -t STDOUT; print $tty ? "\r...requesting..." : "...requesting...\n";
|
||||
my $r = HTTP::Tiny->new(timeout=>0+$c->{timeout})->post("$ep/chat/completions", {headers=>$h, content=>encode_json(\%p)});
|
||||
@@ -32,9 +42,16 @@ sub llm { my ($c, $msgs) = @_; sanitize_msgs($msgs);
|
||||
my $rb = substr($r->{content} // '', 0, 500);
|
||||
die "API error: " . (length($rb) ? "$rb (HTTP $r->{status})" : ($r->{reason} || "HTTP $r->{status}")) . "\n"; }
|
||||
sub shell_exec { my ($cmd, $t, $out) = (filter_text($_[0]), $_[1], '');
|
||||
eval { local $SIG{ALRM} = sub { alarm 0; die "timeout\n" }; alarm $t; $out = `$cmd 2>&1`; alarm 0; };
|
||||
eval { local $SIG{ALRM} = sub { alarm 0; die "timeout\n" }; alarm $t; $out = `bash -c \Q$cmd\E 2>&1`; alarm 0; };
|
||||
$out =~ s/\s+$//; utf8::decode($out); $out = filter_text($out);
|
||||
$@ ? "$out\n[timeout after ${t}s]\nexit: -1" : "$out\nexit: " . ($? >> 8); }
|
||||
sub read_file { my ($p, $off, $lim) = ($_[0] // '', $_[1] || 1, $_[2] || 2000);
|
||||
return "[read error: filePath required]" unless length $p;
|
||||
return "[read error: no such file or directory: $p]" unless -e $p;
|
||||
if (-d $p) { opendir my $dh, $p or return "[read error: cannot open dir $p: $!]"; my @e = sort grep { !/^\.\.?$/ } readdir $dh; closedir $dh; return join("\n", map { (-d "$p/$_" ? "$_/" : $_) } @e); }
|
||||
open my $fh, '<:encoding(UTF-8)', $p or return "[read error: cannot open $p: $!]"; my ($ln, $cnt, @r) = (0, 0);
|
||||
while (defined(my $line = <$fh>)) { $ln++; next if $ln < $off; chomp $line; $line = substr($line, 0, 2000) if length($line) > 2000; push @r, "$ln: $line"; last if ++$cnt >= $lim; }
|
||||
close $fh; join("\n", @r) }
|
||||
sub write_file { my ($p, $off, $del, $cnt) = ($_[0], $_[1] || 0, $_[2] || 0, $_[3] // '');
|
||||
return "[write_file error: path required]" unless defined $p && length $p;
|
||||
$off = 0 if $off < 0; $del = 0 if $del < 0;
|
||||
@@ -63,8 +80,22 @@ sub AL { my ($c, $msgs) = ($_[0], $_[1]);
|
||||
$tc->{function}{arguments} = filter_text($tc->{function}{arguments});
|
||||
my $a = eval { decode_json($tc->{function}{arguments} // '{}') };
|
||||
if (ref $a ne 'HASH') { $tc->{function}{arguments} = encode_json({invalid_raw => $tc->{function}{arguments} // ''}); $res = "bad JSON args for $fn: $tc->{function}{arguments}"; }
|
||||
elsif ($fn eq 'shell_exec') { $res = shell_exec($a->{command} // '', $c->{shell_timeout}); }
|
||||
elsif ($fn eq 'write_file') { $res = write_file($a->{path}, $a->{offset}, $a->{del_bytes}, $a->{content}); }
|
||||
elsif ($fn eq 'bash' || $fn eq 'shell_exec') { $res = shell_exec($a->{command} // '', $c->{shell_timeout}); }
|
||||
elsif ($fn eq 'read') { $res = read_file($a->{filePath} // $a->{path}, $a->{offset}, $a->{limit}); }
|
||||
elsif ($fn eq 'write' || $fn eq 'write_file') {
|
||||
my ($p, $cnt) = ($a->{filePath} // $a->{path}, defined $a->{content} ? $a->{content} : '');
|
||||
if (!(exists $a->{offset} && defined $a->{offset}) && !(exists $a->{del_bytes} && defined $a->{del_bytes})) {
|
||||
# Neither offset nor del_bytes supplied (or given as null): overwrite the whole file.
|
||||
if (!defined $p || $p eq '') { $res = "[write error: filePath required]"; }
|
||||
else {
|
||||
my $dir = $p =~ m{^(.*)/[^/]+$} ? $1 : ''; make_path($dir) if length($dir) && !-d $dir;
|
||||
open my $wfh, '>:raw', $p or $res = "[write error: cannot write $p: $!]";
|
||||
if (!$res) { print $wfh $cnt; close $wfh; $res = "Successfully wrote " . length($cnt) . " bytes to $p"; }
|
||||
}
|
||||
} else {
|
||||
$res = write_file($p, $a->{offset}, $a->{del_bytes}, $cnt);
|
||||
}
|
||||
}
|
||||
else { $res = "unknown tool: $fn"; }
|
||||
$res = filter_text($res);
|
||||
print "[tool] $fn: $res\n";
|
||||
@@ -79,7 +110,11 @@ sub load { my $f = "$SDIR/$_[0].json"; if (-f $f) { open my $fh, '<', $f or die
|
||||
sub autosave { save($_[0], project_id()) } 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 main {
|
||||
my ($c, $sp) = (cfg(), $DEF_SP); my $msgs = [{role=>'system', content=>$sp}];
|
||||
my ($c, $sp) = (cfg(), $DEF_SP);
|
||||
my $tmp = (defined $ENV{TMPDIR} && $ENV{TMPDIR} ne '') ? $ENV{TMPDIR} : '/tmp';
|
||||
$tmp =~ s{/+$}{};
|
||||
$sp =~ s{\$TMPDIR/bantam}{$tmp/bantam}g;
|
||||
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; }
|
||||
print "MicroBantam ready ($c->{model}). Commands: /quit /clear /save [name] /continue /list /load <id> /cfg <k> [v] /help\n";
|
||||
while (1) {
|
||||
|
||||
Reference in New Issue
Block a user