2026-10-14 00:01:03 +03:00
|
|
|
/* Bantam: tiny, powerful, DIY AI agent — modern C++20 port
|
|
|
|
|
* Created by Luxferre in 2026, released into the public domain.
|
|
|
|
|
*
|
|
|
|
|
* This is a C++20 rewrite of the pure-C port (bantam-c). It talks to any
|
|
|
|
|
* OpenAI-compatible /chat/completions API and implements the same agentic
|
|
|
|
|
* loop, tools, session management, markdown terminal rendering and
|
|
|
|
|
* raw-terminal line editor.
|
|
|
|
|
*
|
|
|
|
|
* Design goals relative to the C port:
|
|
|
|
|
* - Use standard, safe C++ containers (std::string, std::vector,
|
|
|
|
|
* std::map, std::span) instead of raw memory areas.
|
|
|
|
|
* - Eliminate hand-rolled allocation/string code; rely on RAII so there
|
|
|
|
|
* is far less room for memory-allocation related vulnerabilities.
|
2026-10-14 08:36:39 +03:00
|
|
|
* - Depend only on libcurl (HTTP) and GNU readline (interactive line
|
|
|
|
|
* editing); JSON parsing/serialization and Markdown rendering are
|
|
|
|
|
* implemented natively in modern C++20 with zero external bloat.
|
2026-10-14 00:01:03 +03:00
|
|
|
*
|
|
|
|
|
* Feature macros: we rely on POSIX.1-2008 interfaces. */
|
|
|
|
|
#ifndef BANTAM_HPP
|
|
|
|
|
#define BANTAM_HPP
|
|
|
|
|
|
|
|
|
|
#include <cstdarg>
|
2026-10-14 07:10:03 +00:00
|
|
|
#include <cstdio>
|
2026-10-14 08:36:39 +03:00
|
|
|
#include <cstddef>
|
2026-10-14 00:01:03 +03:00
|
|
|
#include <cstdint>
|
|
|
|
|
#include <map>
|
|
|
|
|
#include <memory>
|
|
|
|
|
#include <span>
|
|
|
|
|
#include <string>
|
|
|
|
|
#include <string_view>
|
2026-10-14 08:36:39 +03:00
|
|
|
#include <utility>
|
2026-10-14 00:01:03 +03:00
|
|
|
#include <vector>
|
|
|
|
|
|
2026-10-14 08:36:39 +03:00
|
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
|
/* Minimal RFC 8259 JSON value, parser and serializer (json.cpp). */
|
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
|
enum class JType { Null, Bool, Int, Float, Str, Arr, Obj };
|
|
|
|
|
|
|
|
|
|
struct Json {
|
|
|
|
|
JType type = JType::Null;
|
|
|
|
|
bool bval = false;
|
|
|
|
|
int64_t ival = 0;
|
|
|
|
|
double dval = 0.0;
|
|
|
|
|
std::string sval;
|
|
|
|
|
std::vector<Json> aval;
|
|
|
|
|
std::vector<std::pair<std::string, Json>> oval;
|
|
|
|
|
|
|
|
|
|
Json();
|
|
|
|
|
~Json();
|
|
|
|
|
Json(const Json &);
|
|
|
|
|
Json(Json &&) noexcept;
|
|
|
|
|
Json &operator=(const Json &);
|
|
|
|
|
Json &operator=(Json &&) noexcept;
|
|
|
|
|
|
|
|
|
|
Json(std::nullptr_t);
|
|
|
|
|
Json(bool b);
|
|
|
|
|
Json(int n);
|
|
|
|
|
Json(int64_t n);
|
|
|
|
|
Json(double d);
|
|
|
|
|
Json(const char *s);
|
|
|
|
|
Json(std::string s);
|
|
|
|
|
Json(std::string_view s);
|
|
|
|
|
|
|
|
|
|
static Json object();
|
|
|
|
|
static Json array();
|
|
|
|
|
|
|
|
|
|
bool is_null() const { return type == JType::Null; }
|
|
|
|
|
bool is_bool() const { return type == JType::Bool; }
|
|
|
|
|
bool is_number_integer() const { return type == JType::Int; }
|
|
|
|
|
bool is_number() const { return type == JType::Int || type == JType::Float; }
|
|
|
|
|
bool is_string() const { return type == JType::Str; }
|
|
|
|
|
bool is_array() const { return type == JType::Arr; }
|
|
|
|
|
bool is_object() const { return type == JType::Obj; }
|
|
|
|
|
|
|
|
|
|
bool empty() const;
|
|
|
|
|
size_t size() const;
|
|
|
|
|
bool contains(std::string_view k) const;
|
|
|
|
|
Json &operator[](std::string_view k);
|
|
|
|
|
const Json &operator[](std::string_view k) const;
|
|
|
|
|
Json &at(size_t i);
|
|
|
|
|
const Json &at(size_t i) const;
|
|
|
|
|
void push_back(Json v);
|
|
|
|
|
|
|
|
|
|
std::string str(std::string_view def = "") const;
|
|
|
|
|
int64_t as_int64(int64_t def = 0) const;
|
|
|
|
|
int as_int(int def = 0) const;
|
|
|
|
|
|
|
|
|
|
std::string dump(int indent = -1) const;
|
|
|
|
|
static bool parse(std::string_view src, Json &out);
|
|
|
|
|
};
|
|
|
|
|
|
2026-10-14 00:01:03 +03:00
|
|
|
/* Markdown renderer state (code-fence tracking). */
|
|
|
|
|
struct MdState {
|
|
|
|
|
bool in_code = false;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
|
/* Tool call / message / usage / config structures. */
|
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
|
struct ToolCall {
|
|
|
|
|
std::string id;
|
|
|
|
|
std::string name;
|
|
|
|
|
std::string arguments; /* raw JSON text of the arguments object */
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct Message {
|
|
|
|
|
std::string role;
|
|
|
|
|
std::string content; /* empty when absent */
|
|
|
|
|
std::string reasoning; /* empty when absent */
|
|
|
|
|
std::vector<ToolCall> calls;
|
|
|
|
|
std::string tool_call_id; /* empty when absent (tool role only) */
|
|
|
|
|
int calls_shown = 0;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct Messages {
|
|
|
|
|
std::vector<Message> items;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct Usage {
|
|
|
|
|
int prompt_tokens = 0;
|
|
|
|
|
int completion_tokens = 0;
|
|
|
|
|
int total_tokens = 0;
|
|
|
|
|
int cached_tokens = 0;
|
|
|
|
|
int context_tokens = 0;
|
|
|
|
|
std::string model;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct Cfg {
|
|
|
|
|
std::string endpoint;
|
|
|
|
|
std::string model;
|
|
|
|
|
std::string api_key;
|
|
|
|
|
std::string color;
|
|
|
|
|
double temperature = 0.7;
|
|
|
|
|
int timeout = 300;
|
|
|
|
|
int shell_timeout = 120;
|
|
|
|
|
int max_al_iterations = 1000;
|
|
|
|
|
int stream = 1; /* 0/1 */
|
|
|
|
|
int force_stream = -1; /* -1 = auto, 0/1 = force (compaction) */
|
|
|
|
|
int context_window = 262144;
|
|
|
|
|
int max_tool_res = 65536;
|
|
|
|
|
std::map<std::string, std::string> raw; /* raw key/value map */
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct Session {
|
|
|
|
|
std::string id;
|
|
|
|
|
std::string created;
|
|
|
|
|
std::string summary;
|
|
|
|
|
Messages messages;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
|
/* Globals. */
|
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
|
extern int g_col; /* color enabled (0/1) */
|
|
|
|
|
extern volatile int g_interrupted; /* SIGINT flag */
|
|
|
|
|
|
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
|
/* util.cpp */
|
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
|
void setup_signal_handler();
|
|
|
|
|
int is_terminal(int fd);
|
|
|
|
|
int to_int_val(std::string_view s, int def);
|
2026-10-14 08:36:39 +03:00
|
|
|
bool read_file_str(std::string_view path, std::string &out);
|
|
|
|
|
bool write_file_str(std::string_view path, std::string_view data);
|
2026-10-14 00:01:03 +03:00
|
|
|
|
|
|
|
|
std::string sfmt(const char *fmt, ...);
|
|
|
|
|
void str_appendf(std::string &s, const char *fmt, ...);
|
|
|
|
|
|
|
|
|
|
/* UTF-8 helpers. */
|
|
|
|
|
int utf8_decode(const unsigned char *p, size_t avail, size_t *plen);
|
|
|
|
|
std::string_view utf8_truncate(std::string_view s, size_t max_bytes);
|
|
|
|
|
|
|
|
|
|
std::string filter_text(std::string_view s);
|
|
|
|
|
int color_enabled(const Cfg &cfg);
|
2026-10-14 07:10:03 +00:00
|
|
|
template<class R>
|
|
|
|
|
inline std::string cstr(std::string_view text, const R& codes) {
|
|
|
|
|
if (!g_col || codes.empty() || text.empty()) return std::string(text);
|
|
|
|
|
std::string s;
|
|
|
|
|
s.reserve(text.size() + 16);
|
|
|
|
|
s += "\033[";
|
|
|
|
|
for (size_t i = 0; i < codes.size(); i++) {
|
|
|
|
|
if (i) s += ';';
|
|
|
|
|
s += sfmt("%d", (int)codes[i]);
|
|
|
|
|
}
|
|
|
|
|
s += "m";
|
|
|
|
|
s += text;
|
|
|
|
|
s += "\033[0m";
|
|
|
|
|
return s;
|
|
|
|
|
}
|
|
|
|
|
template<class R>
|
|
|
|
|
inline void cprint(std::string_view text, const R& codes) {
|
|
|
|
|
std::string w = cstr(text, codes);
|
|
|
|
|
std::fputs(w.c_str(), stdout);
|
|
|
|
|
}
|
2026-10-14 00:01:03 +03:00
|
|
|
int visible_len(std::string_view s);
|
|
|
|
|
int rune_width(int cp);
|
|
|
|
|
|
|
|
|
|
void mkdir_p(std::string_view dir);
|
|
|
|
|
std::string project_id();
|
|
|
|
|
std::string bantam_tmp_dir();
|
|
|
|
|
std::string tool_res_dir();
|
|
|
|
|
std::string md5_hex(std::string_view data);
|
|
|
|
|
|
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
|
/* config.cpp */
|
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
|
const std::string *cfg_raw_get(const Cfg &c, std::string_view k);
|
|
|
|
|
bool internal_key(std::string_view k);
|
|
|
|
|
Cfg get_cfg(std::string_view path);
|
|
|
|
|
int set_cfg(std::string_view path, std::string_view key, std::string_view val);
|
|
|
|
|
std::string config_path();
|
|
|
|
|
int fetch_context_window(Cfg &cfg);
|
|
|
|
|
std::string list_models(Cfg &cfg, int *errout);
|
|
|
|
|
std::string tools_dir(const Cfg &c);
|
|
|
|
|
std::string skills_dir(const Cfg &c);
|
|
|
|
|
|
|
|
|
|
/* ------------------------------------------------------------------ */
|
2026-10-14 08:36:39 +03:00
|
|
|
/* json.cpp — JSON engine, request building & response parsing. */
|
2026-10-14 00:01:03 +03:00
|
|
|
/* ------------------------------------------------------------------ */
|
2026-10-14 08:36:39 +03:00
|
|
|
void serialize_message_json(Json &obj, const Message &m);
|
|
|
|
|
void parse_message_json(const Json &obj, Message &m);
|
2026-10-14 00:01:03 +03:00
|
|
|
std::string build_request_json(const Cfg &cfg, const Messages &msgs,
|
|
|
|
|
bool use_tools, bool stream);
|
|
|
|
|
bool parse_nonstream_response(std::string_view body, Message &out, Usage &usage);
|
|
|
|
|
|
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
|
/* llm.cpp — HTTP client (libcurl), headers, backoff, dispatch. */
|
2026-10-14 10:58:08 +03:00
|
|
|
/* -------------------------------------------------------------- */
|
2026-10-14 00:01:03 +03:00
|
|
|
int http_get(Cfg &cfg, std::string_view url, std::string &body, long &code,
|
|
|
|
|
std::string &errmsg);
|
|
|
|
|
int llm(Cfg &cfg, Messages &msgs, bool use_tools, Message &out, Usage &usage,
|
|
|
|
|
std::string &errmsg, volatile int *interrupted);
|
|
|
|
|
|
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
|
/* tools.cpp */
|
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
|
std::string shell_with_workdir(std::string_view cmd, std::string_view workdir,
|
|
|
|
|
int timeout, volatile int *interrupted);
|
|
|
|
|
std::string read_file_or_dir(std::string_view path, int offset, int limit,
|
|
|
|
|
int *style);
|
2026-10-14 10:58:08 +03:00
|
|
|
std::string write_file(std::string_view path, long line_offset, long byte_offset,
|
|
|
|
|
long del_lines, long del_bytes,
|
|
|
|
|
std::string_view content, int *err);
|
2026-10-14 00:01:03 +03:00
|
|
|
std::string offload_tool_result(std::string_view res, int max_res,
|
|
|
|
|
std::vector<std::string> &tmps);
|
|
|
|
|
void cleanup_temps(std::vector<std::string> &tmps);
|
|
|
|
|
|
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
|
/* session.cpp */
|
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
|
void save_session(const Messages &msgs, std::string_view sid,
|
|
|
|
|
std::string *out_id, std::string *out_summary);
|
|
|
|
|
std::vector<Session> sessions_load();
|
|
|
|
|
std::unique_ptr<Messages> load_session(std::string_view sid, std::string *err);
|
|
|
|
|
std::unique_ptr<Session> find_project_session();
|
|
|
|
|
void autosave(const Messages &msgs);
|
|
|
|
|
std::unique_ptr<Messages> compact(Cfg &cfg, const Messages &msgs,
|
|
|
|
|
std::string *summary, std::string *err,
|
|
|
|
|
volatile int *interrupted);
|
|
|
|
|
std::string summary_of(const Messages &msgs);
|
|
|
|
|
|
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
|
/* render.cpp — terminal markdown rendering. */
|
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
|
void render_md_print(std::string_view text);
|
|
|
|
|
void render_md_line_print(std::string_view line, MdState &st);
|
|
|
|
|
void render_table_print(const std::vector<std::string> &lines);
|
|
|
|
|
bool is_table_line(std::string_view line);
|
|
|
|
|
|
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
|
/* editor.cpp — GNU readline line editor + history (+ term helpers). */
|
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
|
void load_history();
|
|
|
|
|
void save_history();
|
|
|
|
|
void add_history(std::string_view s);
|
|
|
|
|
std::string read_line(std::string_view prompt, bool &ok);
|
|
|
|
|
std::string read_plain(std::string_view prompt, bool &ok);
|
|
|
|
|
int term_width();
|
|
|
|
|
|
|
|
|
|
#endif /* BANTAM_HPP */
|