/* 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. * - 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. * * Feature macros: we rely on POSIX.1-2008 interfaces. */ #ifndef BANTAM_HPP #define BANTAM_HPP #include #include #include #include #include #include #include #include #include #include #include /* ------------------------------------------------------------------ */ /* 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 aval; std::vector> 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); }; /* 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 calls; std::string tool_call_id; /* empty when absent (tool role only) */ int calls_shown = 0; }; struct Messages { std::vector 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; bool context_window_set = false; std::map 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); bool read_file_str(std::string_view path, std::string &out); bool write_file_str(std::string_view path, std::string_view data); 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); template 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 += ';'; int v = (int)codes[i]; if (v <= 0) { s += '0'; } else { char rev[12]; int rn = 0; while (v > 0 && rn < 12) { rev[rn++] = (char)('0' + (v % 10)); v /= 10; } while (rn > 0) s += rev[--rn]; } } s += 'm'; s += text; s += "\033[0m"; return s; } template inline void cprint(std::string_view text, const R& codes) { if (text.empty()) return; if (!g_col || codes.empty()) { std::fwrite(text.data(), 1, text.size(), stdout); return; } char seq[32]; size_t pos = 0; seq[pos++] = '\033'; seq[pos++] = '['; for (size_t i = 0; i < codes.size() && pos + 12 < sizeof(seq); i++) { if (i) seq[pos++] = ';'; int v = (int)codes[i]; if (v <= 0) { seq[pos++] = '0'; } else { char rev[12]; int rn = 0; while (v > 0 && rn < 12) { rev[rn++] = (char)('0' + (v % 10)); v /= 10; } while (rn > 0) seq[pos++] = rev[--rn]; } } seq[pos++] = 'm'; std::fwrite(seq, 1, pos, stdout); std::fwrite(text.data(), 1, text.size(), stdout); std::fwrite("\033[0m", 1, 4, stdout); } 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_view sess_id = ""); std::string tool_res_dir(std::string_view sess_id = ""); 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); /* ------------------------------------------------------------------ */ /* json.cpp — JSON engine, request building & response parsing. */ /* ------------------------------------------------------------------ */ void serialize_message_json(Json &obj, const Message &m); void parse_message_json(const Json &obj, Message &m); 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. */ /* ------------------------------------------------------------------ */ void http_cleanup(); 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); 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); std::string offload_tool_result(std::string_view res, int max_res, std::vector &tmps); void cleanup_temps(std::vector &tmps); /* ------------------------------------------------------------------ */ /* session.cpp */ /* ------------------------------------------------------------------ */ void save_session(const Messages &msgs, std::string_view sid, std::string *out_id, std::string *out_summary); std::vector sessions_load(); std::unique_ptr load_session(std::string_view sid, std::string *err); std::unique_ptr find_project_session(); void autosave(const Messages &msgs); std::unique_ptr 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 &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 */