kbot cpp port 1/3
This commit is contained in:
parent
f0aee9ce27
commit
04e056d395
1
packages/kbot/cpp/.gitignore
vendored
1
packages/kbot/cpp/.gitignore
vendored
@ -29,3 +29,4 @@ Thumbs.db
|
||||
cache/
|
||||
config/postgres.toml
|
||||
dist
|
||||
src/cmd_grid*.cpp
|
||||
|
||||
@ -79,6 +79,7 @@ add_subdirectory(packages/gadm_reader)
|
||||
add_subdirectory(packages/grid)
|
||||
add_subdirectory(packages/search)
|
||||
add_subdirectory(packages/enrichers)
|
||||
add_subdirectory(packages/kbot)
|
||||
|
||||
# ── Sources ──────────────────────────────────────────────────────────────────
|
||||
add_executable(${PROJECT_NAME}
|
||||
@ -87,11 +88,12 @@ add_executable(${PROJECT_NAME}
|
||||
src/cmd_gridsearch-filters.cpp
|
||||
src/cmd_gridsearch-uds.cpp
|
||||
src/cmd_gridsearch-postgres.cpp
|
||||
src/cmd_kbot.cpp
|
||||
src/gridsearch_serialize.cpp
|
||||
src/sys_metrics.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE CLI11::CLI11 tomlplusplus::tomlplusplus logger html postgres http json polymech ipc geo gadm_reader grid search enrichers)
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE CLI11::CLI11 tomlplusplus::tomlplusplus logger html postgres http json polymech ipc geo gadm_reader grid search enrichers kbot)
|
||||
|
||||
target_include_directories(${PROJECT_NAME} PRIVATE
|
||||
${asio_SOURCE_DIR}/asio/include
|
||||
|
||||
17
packages/kbot/cpp/packages/kbot/CMakeLists.txt
Normal file
17
packages/kbot/cpp/packages/kbot/CMakeLists.txt
Normal file
@ -0,0 +1,17 @@
|
||||
cmake_minimum_required(VERSION 3.20)
|
||||
|
||||
project(kbot CXX)
|
||||
|
||||
add_library(kbot STATIC
|
||||
kbot.cpp
|
||||
)
|
||||
|
||||
target_include_directories(kbot PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
${taskflow_SOURCE_DIR}
|
||||
)
|
||||
|
||||
target_link_libraries(kbot PUBLIC
|
||||
logger
|
||||
json
|
||||
)
|
||||
55
packages/kbot/cpp/packages/kbot/kbot.cpp
Normal file
55
packages/kbot/cpp/packages/kbot/kbot.cpp
Normal file
@ -0,0 +1,55 @@
|
||||
#include "kbot.h"
|
||||
#include <taskflow/taskflow.hpp>
|
||||
#include <iostream>
|
||||
#include "logger/logger.h"
|
||||
|
||||
namespace polymech {
|
||||
namespace kbot {
|
||||
|
||||
int run_kbot_ai_pipeline(const KBotOptions& opts, const KBotCallbacks& cb) {
|
||||
logger::info("Starting kbot ai pipeline (stub)");
|
||||
if (opts.dry_run) {
|
||||
logger::info("Dry run triggered for kbot ai");
|
||||
}
|
||||
|
||||
// Scaffolding multithreaded AI tasks
|
||||
tf::Executor executor(4);
|
||||
tf::Taskflow taskflow;
|
||||
|
||||
taskflow.emplace([opts, cb](){
|
||||
logger::info("Executing kbot ai tasks via Taskflow -> emit events...");
|
||||
if (cb.onEvent) {
|
||||
cb.onEvent("ai_progress", "{\"message\":\"Task stub completed\"}");
|
||||
}
|
||||
});
|
||||
|
||||
executor.run(taskflow).wait();
|
||||
|
||||
if (cb.onEvent) {
|
||||
cb.onEvent("job_result", "{\"status\":\"success\",\"mode\":\"ai\"}");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int run_kbot_run_pipeline(const KBotRunOptions& opts, const KBotCallbacks& cb) {
|
||||
logger::info("Starting kbot run pipeline (stub) for config: " + opts.config);
|
||||
if (opts.dry) {
|
||||
logger::info("Dry run triggered for kbot run");
|
||||
}
|
||||
if (opts.list) {
|
||||
logger::info("List configs mode enabled");
|
||||
}
|
||||
|
||||
// Stub std::system call execution (simulating child_process.execFileSync from TypeScript)
|
||||
if (!opts.dry && !opts.list) {
|
||||
logger::info("Simulating launching: .vscode/launch.json targeting " + opts.config);
|
||||
}
|
||||
|
||||
if (cb.onEvent) {
|
||||
cb.onEvent("job_result", "{\"status\":\"success\",\"mode\":\"run\"}");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace kbot
|
||||
} // namespace polymech
|
||||
70
packages/kbot/cpp/packages/kbot/kbot.h
Normal file
70
packages/kbot/cpp/packages/kbot/kbot.h
Normal file
@ -0,0 +1,70 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
|
||||
namespace polymech {
|
||||
namespace kbot {
|
||||
|
||||
struct KBotOptions {
|
||||
std::string path = ".";
|
||||
std::string prompt;
|
||||
std::string output;
|
||||
std::string dst;
|
||||
std::string append = "concat";
|
||||
std::string wrap = "none";
|
||||
std::string each;
|
||||
std::vector<std::string> disable;
|
||||
std::vector<std::string> disable_tools;
|
||||
std::vector<std::string> tools;
|
||||
std::vector<std::string> include_globs;
|
||||
std::vector<std::string> exclude_globs;
|
||||
std::string glob_extension;
|
||||
std::string api_key;
|
||||
std::string model;
|
||||
std::string router = "openrouter";
|
||||
std::string mode = "tools";
|
||||
int log_level = 4;
|
||||
std::string profile;
|
||||
std::string base_url;
|
||||
std::string config_path;
|
||||
std::string dump;
|
||||
std::string preferences;
|
||||
std::string logs;
|
||||
bool stream = false;
|
||||
bool alt = false;
|
||||
std::string env = "default";
|
||||
std::string filters;
|
||||
std::string query;
|
||||
bool dry_run = false;
|
||||
std::string format;
|
||||
|
||||
// Internal
|
||||
std::string job_id;
|
||||
std::shared_ptr<std::atomic<bool>> cancel_token;
|
||||
};
|
||||
|
||||
struct KBotRunOptions {
|
||||
std::string config = "default";
|
||||
bool dry = false;
|
||||
bool list = false;
|
||||
std::string project_path;
|
||||
std::string log_file_path;
|
||||
|
||||
// Internal
|
||||
std::string job_id;
|
||||
std::shared_ptr<std::atomic<bool>> cancel_token;
|
||||
};
|
||||
|
||||
struct KBotCallbacks {
|
||||
std::function<void(const std::string& type, const std::string& json)> onEvent;
|
||||
};
|
||||
|
||||
int run_kbot_ai_pipeline(const KBotOptions& opts, const KBotCallbacks& cb);
|
||||
int run_kbot_run_pipeline(const KBotRunOptions& opts, const KBotCallbacks& cb);
|
||||
|
||||
} // namespace kbot
|
||||
} // namespace polymech
|
||||
93
packages/kbot/cpp/src/cmd_kbot.cpp
Normal file
93
packages/kbot/cpp/src/cmd_kbot.cpp
Normal file
@ -0,0 +1,93 @@
|
||||
#include "cmd_kbot.h"
|
||||
#include "logger/logger.h"
|
||||
#include <CLI/CLI.hpp>
|
||||
#include <rapidjson/document.h>
|
||||
#include <iostream>
|
||||
|
||||
namespace polymech {
|
||||
|
||||
// Global states for CLI mode
|
||||
static kbot::KBotOptions g_kbot_opts;
|
||||
static kbot::KBotRunOptions g_run_opts;
|
||||
|
||||
static CLI::App* ai_cmd = nullptr;
|
||||
static CLI::App* run_cmd = nullptr;
|
||||
|
||||
CLI::App* setup_cmd_kbot(CLI::App& app) {
|
||||
auto* kbot_cmd = app.add_subcommand("kbot", "KBot AI & Task runner");
|
||||
|
||||
// ── AI Subcommand ──
|
||||
ai_cmd = kbot_cmd->add_subcommand("ai", "Run KBot AI tasks");
|
||||
ai_cmd->add_option("-p,--path", g_kbot_opts.path, "Target directory")->default_val(".");
|
||||
ai_cmd->add_option("--prompt", g_kbot_opts.prompt, "The prompt. Supports file paths and vars.");
|
||||
ai_cmd->add_option("--output", g_kbot_opts.output, "Optional output path for modified files");
|
||||
ai_cmd->add_option("--dst", g_kbot_opts.dst, "Optional destination path for the result");
|
||||
ai_cmd->add_option("--append", g_kbot_opts.append, "How to handle output if --dst exists: concat|merge|replace")->default_val("concat");
|
||||
ai_cmd->add_option("--wrap", g_kbot_opts.wrap, "Specify how to wrap output: meta|none")->default_val("none");
|
||||
ai_cmd->add_option("--each", g_kbot_opts.each, "Iterate over items (GLOB, JSON, array)");
|
||||
ai_cmd->add_option("--disable", g_kbot_opts.disable, "Disable tools categories");
|
||||
ai_cmd->add_option("--disableTools", g_kbot_opts.disable_tools, "Specific tools to disable");
|
||||
ai_cmd->add_option("--tools", g_kbot_opts.tools, "Tools to use");
|
||||
ai_cmd->add_option("--include", g_kbot_opts.include_globs, "Glob patterns or paths to include");
|
||||
ai_cmd->add_option("--exclude", g_kbot_opts.exclude_globs, "Glob patterns or paths to exclude");
|
||||
ai_cmd->add_option("--globExtension", g_kbot_opts.glob_extension, "Glob extension behavior (e.g. match-cpp)");
|
||||
ai_cmd->add_option("--api_key", g_kbot_opts.api_key, "Explicit API key to use");
|
||||
ai_cmd->add_option("--model", g_kbot_opts.model, "AI model to use");
|
||||
ai_cmd->add_option("--router", g_kbot_opts.router, "Router to use (openai, openrouter, deepseek)")->default_val("openrouter");
|
||||
ai_cmd->add_option("--mode", g_kbot_opts.mode, "Chat completion mode")->default_val("tools");
|
||||
ai_cmd->add_flag("--dry", g_kbot_opts.dry_run, "Dry run");
|
||||
|
||||
// ── Run Subcommand ──
|
||||
run_cmd = kbot_cmd->add_subcommand("run", "Run a .vscode/launch.json configuration");
|
||||
run_cmd->add_option("-c,--config", g_run_opts.config, "Config name")->default_val("default");
|
||||
run_cmd->add_flag("--dry", g_run_opts.dry, "Dry run");
|
||||
run_cmd->add_flag("--list", g_run_opts.list, "List available configs");
|
||||
run_cmd->add_option("--projectPath", g_run_opts.project_path, "Project path")->default_val(".");
|
||||
run_cmd->add_option("--logFilePath", g_run_opts.log_file_path, "Log file path");
|
||||
|
||||
return kbot_cmd;
|
||||
}
|
||||
|
||||
bool is_kbot_ai_parsed() { return ai_cmd != nullptr && ai_cmd->parsed(); }
|
||||
bool is_kbot_run_parsed() { return run_cmd != nullptr && run_cmd->parsed(); }
|
||||
|
||||
int run_cmd_kbot_ai() {
|
||||
return kbot::run_kbot_ai_pipeline(g_kbot_opts, kbot::KBotCallbacks{});
|
||||
}
|
||||
|
||||
int run_cmd_kbot_run() {
|
||||
return kbot::run_kbot_run_pipeline(g_run_opts, kbot::KBotCallbacks{});
|
||||
}
|
||||
|
||||
int run_kbot_ai_ipc(const std::string& payload, const std::string& jobId, const kbot::KBotCallbacks& cb) {
|
||||
kbot::KBotOptions opts;
|
||||
opts.job_id = jobId;
|
||||
|
||||
// Optional: Parse JSON from payload to overwrite opts variables using rapidjson
|
||||
rapidjson::Document doc;
|
||||
doc.Parse(payload.c_str());
|
||||
if (!doc.HasParseError() && doc.IsObject()) {
|
||||
if (doc.HasMember("prompt") && doc["prompt"].IsString()) opts.prompt = doc["prompt"].GetString();
|
||||
if (doc.HasMember("dry_run") && doc["dry_run"].IsBool()) opts.dry_run = doc["dry_run"].GetBool();
|
||||
}
|
||||
|
||||
logger::info("Receiving AI task over IPC... job: " + jobId);
|
||||
return kbot::run_kbot_ai_pipeline(opts, cb);
|
||||
}
|
||||
|
||||
int run_kbot_run_ipc(const std::string& payload, const std::string& jobId, const kbot::KBotCallbacks& cb) {
|
||||
kbot::KBotRunOptions opts;
|
||||
opts.job_id = jobId;
|
||||
|
||||
rapidjson::Document doc;
|
||||
doc.Parse(payload.c_str());
|
||||
if (!doc.HasParseError() && doc.IsObject()) {
|
||||
if (doc.HasMember("config") && doc["config"].IsString()) opts.config = doc["config"].GetString();
|
||||
if (doc.HasMember("dry") && doc["dry"].IsBool()) opts.dry = doc["dry"].GetBool();
|
||||
}
|
||||
|
||||
logger::info("Receiving run task over IPC... job: " + jobId);
|
||||
return kbot::run_kbot_run_pipeline(opts, cb);
|
||||
}
|
||||
|
||||
} // namespace polymech
|
||||
25
packages/kbot/cpp/src/cmd_kbot.h
Normal file
25
packages/kbot/cpp/src/cmd_kbot.h
Normal file
@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
#include <CLI/CLI.hpp>
|
||||
#include <string>
|
||||
#include <functional>
|
||||
#include "kbot.h"
|
||||
|
||||
namespace polymech {
|
||||
|
||||
/// Attach kbot subcommands to the main app
|
||||
CLI::App* setup_cmd_kbot(CLI::App& app);
|
||||
|
||||
/// CLI Entry points
|
||||
int run_cmd_kbot_ai();
|
||||
int run_cmd_kbot_run();
|
||||
|
||||
/// IPC / UDS Entry points
|
||||
int run_kbot_ai_ipc(const std::string& payload, const std::string& jobId, const kbot::KBotCallbacks& cb);
|
||||
int run_kbot_run_ipc(const std::string& payload, const std::string& jobId, const kbot::KBotCallbacks& cb);
|
||||
|
||||
/// Helper to check parsed state
|
||||
bool is_kbot_ai_parsed();
|
||||
bool is_kbot_run_parsed();
|
||||
|
||||
} // namespace polymech
|
||||
351
packages/kbot/cpp/src/gridsearch_serialize.cpp
Normal file
351
packages/kbot/cpp/src/gridsearch_serialize.cpp
Normal file
@ -0,0 +1,351 @@
|
||||
#include "gridsearch_serialize.h"
|
||||
|
||||
#include <rapidjson/stringbuffer.h>
|
||||
#include <rapidjson/writer.h>
|
||||
#include "cmd_gridsearch.h"
|
||||
|
||||
namespace polymech::serialize {
|
||||
|
||||
// ── grid-ready ──────────────────────────────────────────────────────────────
|
||||
|
||||
std::string grid_ready(const std::vector<grid::Waypoint>& waypoints) {
|
||||
rapidjson::StringBuffer sb;
|
||||
rapidjson::Writer<rapidjson::StringBuffer> w(sb);
|
||||
w.StartObject();
|
||||
w.Key("areas"); w.StartArray();
|
||||
for (size_t i = 0; i < waypoints.size(); ++i) {
|
||||
const auto& wp = waypoints[i];
|
||||
w.StartObject();
|
||||
w.Key("name"); w.String(("Waypoint " + std::to_string(wp.step)).c_str());
|
||||
w.Key("gid"); w.String(("wp-" + std::to_string(wp.step)).c_str());
|
||||
w.Key("lat"); w.Double(wp.lat);
|
||||
w.Key("lon"); w.Double(wp.lng);
|
||||
w.Key("radius_km"); w.Double(wp.radius_km);
|
||||
w.Key("area_gid"); w.String(wp.area_gid.c_str());
|
||||
w.Key("area_name"); w.String(wp.area_name.c_str());
|
||||
w.Key("index"); w.Int(static_cast<int>(i));
|
||||
w.EndObject();
|
||||
}
|
||||
w.EndArray();
|
||||
w.Key("total"); w.Int(static_cast<int>(waypoints.size()));
|
||||
w.EndObject();
|
||||
return sb.GetString();
|
||||
}
|
||||
|
||||
// ── waypoint-start ──────────────────────────────────────────────────────────
|
||||
|
||||
std::string waypoint_start(const grid::Waypoint& wp, int index, int total) {
|
||||
rapidjson::StringBuffer sb;
|
||||
rapidjson::Writer<rapidjson::StringBuffer> w(sb);
|
||||
w.StartObject();
|
||||
w.Key("name"); w.String(("Waypoint " + std::to_string(wp.step)).c_str());
|
||||
w.Key("gid"); w.String(("wp-" + std::to_string(wp.step)).c_str());
|
||||
w.Key("lat"); w.Double(wp.lat);
|
||||
w.Key("lon"); w.Double(wp.lng);
|
||||
w.Key("radius_km"); w.Double(wp.radius_km);
|
||||
w.Key("area_gid"); w.String(wp.area_gid.c_str());
|
||||
w.Key("area_name"); w.String(wp.area_name.c_str());
|
||||
w.Key("index"); w.Int(index);
|
||||
w.Key("total"); w.Int(total);
|
||||
w.EndObject();
|
||||
return sb.GetString();
|
||||
}
|
||||
|
||||
// ── location (per search result) ────────────────────────────────────────────
|
||||
|
||||
std::string location(const search::MapResult& r, int step) {
|
||||
rapidjson::StringBuffer sb;
|
||||
rapidjson::Writer<rapidjson::StringBuffer> w(sb);
|
||||
w.StartObject();
|
||||
w.Key("location"); w.StartObject();
|
||||
w.Key("title"); w.String(r.title.c_str());
|
||||
w.Key("place_id"); w.String(r.place_id.c_str());
|
||||
w.Key("address"); w.String(r.address.c_str());
|
||||
w.Key("website"); w.String(r.website.c_str());
|
||||
w.Key("type"); w.String(r.type.c_str());
|
||||
w.Key("phone"); w.String(r.phone.c_str());
|
||||
w.Key("rating"); w.Double(r.rating);
|
||||
w.Key("reviews"); w.Int(r.reviews);
|
||||
w.Key("lat"); w.Double(r.gps.lat);
|
||||
w.Key("lng"); w.Double(r.gps.lng);
|
||||
w.Key("types"); w.StartArray();
|
||||
for (const auto& t : r.types) w.String(t.c_str());
|
||||
w.EndArray();
|
||||
w.EndObject();
|
||||
w.Key("areaName"); w.String(("Waypoint " + std::to_string(step)).c_str());
|
||||
w.EndObject();
|
||||
return sb.GetString();
|
||||
}
|
||||
|
||||
// ── area_start ──────────────────────────────────────────────────────────────
|
||||
|
||||
std::string area_start(const std::string& area_gid, const std::string& area_name) {
|
||||
rapidjson::StringBuffer sb;
|
||||
rapidjson::Writer<rapidjson::StringBuffer> w(sb);
|
||||
w.StartObject();
|
||||
w.Key("gid"); w.String(area_gid.c_str());
|
||||
w.Key("name"); w.String(area_name.c_str());
|
||||
w.EndObject();
|
||||
return sb.GetString();
|
||||
}
|
||||
|
||||
// ── area_finish ─────────────────────────────────────────────────────────────
|
||||
|
||||
std::string area_finish(const std::string& area_gid) {
|
||||
rapidjson::StringBuffer sb;
|
||||
rapidjson::Writer<rapidjson::StringBuffer> w(sb);
|
||||
w.StartObject();
|
||||
w.Key("gid"); w.String(area_gid.c_str());
|
||||
w.EndObject();
|
||||
return sb.GetString();
|
||||
}
|
||||
|
||||
// ── waypoint_finish ─────────────────────────────────────────────────────────
|
||||
|
||||
std::string waypoint_finish(const grid::Waypoint& wp, int results, int apiCalls) {
|
||||
rapidjson::StringBuffer sb;
|
||||
rapidjson::Writer<rapidjson::StringBuffer> w(sb);
|
||||
w.StartObject();
|
||||
w.Key("name"); w.String(("Waypoint " + std::to_string(wp.step)).c_str());
|
||||
w.Key("gid"); w.String(("wp-" + std::to_string(wp.step)).c_str());
|
||||
w.Key("results"); w.Int(results);
|
||||
w.Key("apiCalls"); w.Int(apiCalls);
|
||||
w.EndObject();
|
||||
return sb.GetString();
|
||||
}
|
||||
|
||||
// ── enrich-start ────────────────────────────────────────────────────────────
|
||||
|
||||
std::string enrich_start(int locationCount) {
|
||||
rapidjson::StringBuffer sb;
|
||||
rapidjson::Writer<rapidjson::StringBuffer> w(sb);
|
||||
w.StartObject();
|
||||
w.Key("locationCount"); w.Int(locationCount);
|
||||
w.EndObject();
|
||||
return sb.GetString();
|
||||
}
|
||||
|
||||
// ── nodePage (per page error) ───────────────────────────────────────────────
|
||||
|
||||
std::string node_page(const enrichers::PageError& pe, const std::string& placeId) {
|
||||
rapidjson::StringBuffer sb;
|
||||
rapidjson::Writer<rapidjson::StringBuffer> w(sb);
|
||||
w.StartObject();
|
||||
w.Key("location"); w.String(placeId.c_str());
|
||||
w.Key("url"); w.String(pe.url.c_str());
|
||||
w.Key("status"); w.String(pe.status.c_str());
|
||||
w.Key("error"); w.String(pe.error.c_str());
|
||||
w.EndObject();
|
||||
return sb.GetString();
|
||||
}
|
||||
|
||||
// ── node-error ──────────────────────────────────────────────────────────────
|
||||
|
||||
std::string node_error(const enrichers::EnrichedNode& node) {
|
||||
rapidjson::StringBuffer sb;
|
||||
rapidjson::Writer<rapidjson::StringBuffer> w(sb);
|
||||
w.StartObject();
|
||||
w.Key("node"); w.StartObject();
|
||||
w.Key("title"); w.String(node.title.c_str());
|
||||
w.Key("placeId"); w.String(node.place_id.c_str());
|
||||
w.EndObject();
|
||||
w.Key("error"); w.String(node.error.c_str());
|
||||
w.EndObject();
|
||||
return sb.GetString();
|
||||
}
|
||||
|
||||
// ── node (enriched location) ────────────────────────────────────────────────
|
||||
|
||||
std::string node(const enrichers::EnrichedNode& n) {
|
||||
rapidjson::StringBuffer sb;
|
||||
rapidjson::Writer<rapidjson::StringBuffer> w(sb);
|
||||
w.StartObject();
|
||||
w.Key("idx"); w.Int(n.idx);
|
||||
w.Key("title"); w.String(n.title.c_str());
|
||||
w.Key("placeId"); w.String(n.place_id.c_str());
|
||||
w.Key("website"); w.String(n.website.c_str());
|
||||
w.Key("address"); w.String(n.address.c_str());
|
||||
w.Key("type"); w.String(n.type.c_str());
|
||||
w.Key("status"); w.String(enrichers::status_string(n.status));
|
||||
w.Key("emails"); w.StartArray();
|
||||
for (const auto& e : n.emails) w.String(e.c_str());
|
||||
w.EndArray();
|
||||
|
||||
w.Key("social"); w.StartArray();
|
||||
for (const auto& s : n.socials) {
|
||||
w.StartObject();
|
||||
w.Key("url"); w.String(s.url.c_str());
|
||||
w.Key("platform"); w.String(s.platform.c_str());
|
||||
w.EndObject();
|
||||
}
|
||||
w.EndArray();
|
||||
|
||||
w.Key("sites"); w.StartArray();
|
||||
for (const auto& s : n.sites) {
|
||||
w.StartObject();
|
||||
w.Key("url"); w.String(s.first.c_str());
|
||||
w.Key("name"); w.String("home");
|
||||
w.Key("content"); w.String(s.second.c_str());
|
||||
w.EndObject();
|
||||
}
|
||||
w.EndArray();
|
||||
|
||||
w.Key("pagesFound"); w.Int(n.pages_found);
|
||||
w.Key("pagesScraped"); w.Int(n.pages_scraped);
|
||||
w.Key("metaMs"); w.Int(n.meta_ms);
|
||||
w.Key("emailMs"); w.Int(n.email_ms);
|
||||
w.Key("totalMs"); w.Int(n.total_ms);
|
||||
w.Key("gridArea"); w.String(n.grid_area.c_str());
|
||||
w.Key("gridGid"); w.String(n.grid_gid.c_str());
|
||||
w.EndObject();
|
||||
return sb.GetString();
|
||||
}
|
||||
|
||||
// ── write_options helper ────────────────────────────────────────────────────
|
||||
|
||||
static void write_options(rapidjson::Writer<rapidjson::StringBuffer>& w, const polymech::PipelineOptions& opts) {
|
||||
w.Key("options");
|
||||
w.StartObject();
|
||||
w.Key("jobId"); w.String(opts.job_id.c_str());
|
||||
w.Key("searchQuery"); w.String(opts.search_query.c_str());
|
||||
w.Key("searchDomain"); w.String(opts.search_domain.c_str());
|
||||
w.Key("searchLanguage"); w.String(opts.search_language.c_str());
|
||||
w.Key("searchCountry"); w.String(opts.search_country.c_str());
|
||||
w.Key("searchLimit"); w.Int(opts.search_limit);
|
||||
w.Key("searchZoom"); w.Int(opts.search_zoom);
|
||||
w.Key("dryRun"); w.Bool(opts.dry_run);
|
||||
w.Key("enrich"); w.Bool(opts.enrich);
|
||||
|
||||
w.Key("grid");
|
||||
w.StartObject();
|
||||
w.Key("gridMode"); w.String(opts.grid_opts.gridMode.c_str());
|
||||
w.Key("cellSize"); w.Double(opts.grid_opts.cellSize);
|
||||
w.Key("cellOverlap"); w.Double(opts.grid_opts.cellOverlap);
|
||||
w.Key("centroidOverlap"); w.Double(opts.grid_opts.centroidOverlap);
|
||||
w.Key("maxCellsLimit"); w.Int(opts.grid_opts.maxCellsLimit);
|
||||
w.Key("maxElevation"); w.Double(opts.grid_opts.maxElevation);
|
||||
w.Key("minDensity"); w.Double(opts.grid_opts.minDensity);
|
||||
w.Key("minGhsPop"); w.Double(opts.grid_opts.minGhsPop);
|
||||
w.Key("minGhsBuilt"); w.Double(opts.grid_opts.minGhsBuilt);
|
||||
w.Key("ghsFilterMode"); w.String(opts.grid_opts.ghsFilterMode.c_str());
|
||||
w.Key("allowMissingGhs"); w.Bool(opts.grid_opts.allowMissingGhs);
|
||||
w.Key("bypassFilters"); w.Bool(opts.grid_opts.bypassFilters);
|
||||
w.Key("pathOrder"); w.String(opts.grid_opts.pathOrder.c_str());
|
||||
w.Key("groupByRegion"); w.Bool(opts.grid_opts.groupByRegion);
|
||||
w.EndObject();
|
||||
|
||||
w.Key("areas");
|
||||
w.StartArray();
|
||||
for (const auto& a : opts.areas) {
|
||||
w.StartObject();
|
||||
w.Key("gid"); w.String(a.gid.c_str());
|
||||
w.Key("name"); w.String(a.name.c_str());
|
||||
w.Key("level"); w.Int(a.level);
|
||||
w.EndObject();
|
||||
}
|
||||
w.EndArray();
|
||||
w.EndObject();
|
||||
}
|
||||
|
||||
// ── job_result (with enrichment) ────────────────────────────────────────────
|
||||
|
||||
std::string job_result(const polymech::PipelineOptions& opts, int64_t enumMs, int64_t searchMs, int64_t enrichMs, int64_t totalMs,
|
||||
int totalEmails, int totalPagesScraped, int freshApiCalls,
|
||||
int waypointCount, int validCells, int skippedCells,
|
||||
int totalResults, const std::vector<std::string>& enrichResults,
|
||||
double totalScannedSqKm, double totalPopulation) {
|
||||
rapidjson::StringBuffer sb;
|
||||
rapidjson::Writer<rapidjson::StringBuffer> w(sb);
|
||||
w.StartObject();
|
||||
write_options(w, opts);
|
||||
|
||||
w.Key("enumMs"); w.Int64(enumMs);
|
||||
w.Key("searchMs"); w.Int64(searchMs);
|
||||
w.Key("enrichMs"); w.Int64(enrichMs);
|
||||
w.Key("totalMs"); w.Int64(totalMs);
|
||||
|
||||
w.Key("gridStats");
|
||||
w.StartObject();
|
||||
w.Key("validCells"); w.Int(validCells);
|
||||
w.Key("skippedCells"); w.Int(skippedCells);
|
||||
w.Key("totalWaypoints"); w.Int(waypointCount);
|
||||
w.EndObject();
|
||||
|
||||
w.Key("searchStats");
|
||||
w.StartObject();
|
||||
w.Key("apiCalls"); w.Int(freshApiCalls);
|
||||
w.Key("filtered"); w.Int(0); // placeholder if needed
|
||||
w.Key("areaCount"); w.Int(waypointCount);
|
||||
w.Key("totalResults"); w.Int(totalResults);
|
||||
w.Key("totalScannedSqKm"); w.Double(totalScannedSqKm);
|
||||
w.Key("totalPopulation"); w.Double(totalPopulation);
|
||||
w.EndObject();
|
||||
|
||||
w.Key("totalEmails"); w.Int(totalEmails);
|
||||
|
||||
w.Key("enrichResults");
|
||||
w.StartArray();
|
||||
for (const auto& id : enrichResults) {
|
||||
w.String(id.c_str());
|
||||
}
|
||||
w.EndArray();
|
||||
|
||||
w.Key("freshApiCalls"); w.Int(freshApiCalls);
|
||||
w.Key("waypointCount"); w.Int(waypointCount);
|
||||
w.Key("totalPagesScraped"); w.Int(totalPagesScraped);
|
||||
|
||||
w.EndObject();
|
||||
return sb.GetString();
|
||||
}
|
||||
|
||||
// ── job_result (search only) ────────────────────────────────────────────────
|
||||
|
||||
std::string job_result_search_only(const polymech::PipelineOptions& opts, int64_t enumMs, int64_t searchMs, int64_t totalMs,
|
||||
int freshApiCalls, int waypointCount, int validCells,
|
||||
int skippedCells, int totalResults, const std::vector<std::string>& enrichResults,
|
||||
double totalScannedSqKm, double totalPopulation) {
|
||||
rapidjson::StringBuffer sb;
|
||||
rapidjson::Writer<rapidjson::StringBuffer> w(sb);
|
||||
w.StartObject();
|
||||
write_options(w, opts);
|
||||
|
||||
w.Key("enumMs"); w.Int64(enumMs);
|
||||
w.Key("searchMs"); w.Int64(searchMs);
|
||||
w.Key("enrichMs"); w.Int64(0);
|
||||
w.Key("totalMs"); w.Int64(totalMs);
|
||||
|
||||
w.Key("gridStats");
|
||||
w.StartObject();
|
||||
w.Key("validCells"); w.Int(validCells);
|
||||
w.Key("skippedCells"); w.Int(skippedCells);
|
||||
w.Key("totalWaypoints"); w.Int(waypointCount);
|
||||
w.EndObject();
|
||||
|
||||
w.Key("searchStats");
|
||||
w.StartObject();
|
||||
w.Key("apiCalls"); w.Int(freshApiCalls);
|
||||
w.Key("filtered"); w.Int(0);
|
||||
w.Key("areaCount"); w.Int(waypointCount);
|
||||
w.Key("totalResults"); w.Int(totalResults);
|
||||
w.Key("totalScannedSqKm"); w.Double(totalScannedSqKm);
|
||||
w.Key("totalPopulation"); w.Double(totalPopulation);
|
||||
w.EndObject();
|
||||
|
||||
w.Key("totalEmails"); w.Int(0);
|
||||
|
||||
w.Key("enrichResults");
|
||||
w.StartArray();
|
||||
for (const auto& id : enrichResults) {
|
||||
w.String(id.c_str());
|
||||
}
|
||||
w.EndArray();
|
||||
|
||||
w.Key("freshApiCalls"); w.Int(freshApiCalls);
|
||||
w.Key("waypointCount"); w.Int(waypointCount);
|
||||
w.Key("totalPagesScraped"); w.Int(0);
|
||||
|
||||
w.EndObject();
|
||||
return sb.GetString();
|
||||
}
|
||||
|
||||
} // namespace polymech::serialize
|
||||
@ -22,6 +22,7 @@
|
||||
#include "search/search.h"
|
||||
#include "enrichers/enrichers.h"
|
||||
#include "cmd_gridsearch.h"
|
||||
#include "cmd_kbot.h"
|
||||
|
||||
#ifndef PROJECT_VERSION
|
||||
#define PROJECT_VERSION "0.1.0"
|
||||
@ -91,6 +92,9 @@ int main(int argc, char *argv[]) {
|
||||
// Subcommand: gridsearch — Run a full gridsearch pipeline
|
||||
auto* gs_cmd = polymech::setup_cmd_gridsearch(app);
|
||||
|
||||
// Subcommand: kbot — AI workflows & task configurations
|
||||
auto* kbot_cmd = polymech::setup_cmd_kbot(app);
|
||||
|
||||
CLI11_PARSE(app, argc, argv);
|
||||
|
||||
// Worker mode uses stderr for logs to keep stdout clean for IPC frames
|
||||
@ -163,6 +167,38 @@ int main(int argc, char *argv[]) {
|
||||
// Stub: echo the payload back as job_result
|
||||
ipc::write_message({req.id, "job_result", req.payload});
|
||||
|
||||
} else if (req.type == "kbot-ai") {
|
||||
logger::info("Worker: kbot-ai job received");
|
||||
std::string req_id = req.id;
|
||||
polymech::kbot::KBotCallbacks cb;
|
||||
cb.onEvent = [&req_id](const std::string& type, const std::string& json) {
|
||||
if (type == "job_result") {
|
||||
ipc::write_message({req_id, "job_result", json});
|
||||
} else {
|
||||
ipc::write_message({"0", type, json});
|
||||
}
|
||||
};
|
||||
int rc = polymech::run_kbot_ai_ipc(req.payload, req.id, cb);
|
||||
if (rc != 0) {
|
||||
ipc::write_message({req.id, "error", "{\"message\":\"kbot ai pipeline failed\"}"});
|
||||
}
|
||||
|
||||
} else if (req.type == "kbot-run") {
|
||||
logger::info("Worker: kbot-run job received");
|
||||
std::string req_id = req.id;
|
||||
polymech::kbot::KBotCallbacks cb;
|
||||
cb.onEvent = [&req_id](const std::string& type, const std::string& json) {
|
||||
if (type == "job_result") {
|
||||
ipc::write_message({req_id, "job_result", json});
|
||||
} else {
|
||||
ipc::write_message({"0", type, json});
|
||||
}
|
||||
};
|
||||
int rc = polymech::run_kbot_run_ipc(req.payload, req.id, cb);
|
||||
if (rc != 0) {
|
||||
ipc::write_message({req.id, "error", "{\"message\":\"kbot run pipeline failed\"}"});
|
||||
}
|
||||
|
||||
} else if (req.type == "shutdown") {
|
||||
ipc::write_message({req.id, "shutdown_ack", "{}"});
|
||||
logger::info("Worker: shutdown requested, exiting");
|
||||
@ -263,6 +299,14 @@ int main(int argc, char *argv[]) {
|
||||
return polymech::run_cmd_gridsearch();
|
||||
}
|
||||
|
||||
// ── kbot subcommand ──────────────────────────────────────────────────
|
||||
if (polymech::is_kbot_ai_parsed()) {
|
||||
return polymech::run_cmd_kbot_ai();
|
||||
}
|
||||
if (polymech::is_kbot_run_parsed()) {
|
||||
return polymech::run_cmd_kbot_run();
|
||||
}
|
||||
|
||||
// No subcommand — show help
|
||||
std::cout << app.help() << "\n";
|
||||
return 0;
|
||||
|
||||
36
packages/kbot/cpp/src/sys_metrics.cpp
Normal file
36
packages/kbot/cpp/src/sys_metrics.cpp
Normal file
@ -0,0 +1,36 @@
|
||||
#include "sys_metrics.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#define NOMINMAX
|
||||
#include <windows.h>
|
||||
#include <psapi.h>
|
||||
#pragma comment(lib, "psapi.lib")
|
||||
|
||||
namespace polymech {
|
||||
size_t get_current_rss_mb() {
|
||||
PROCESS_MEMORY_COUNTERS info;
|
||||
if (GetProcessMemoryInfo(GetCurrentProcess(), &info, sizeof(info))) {
|
||||
return (size_t)(info.WorkingSetSize) / (1024 * 1024);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint64_t get_cpu_time_ms() {
|
||||
FILETIME creationTime, exitTime, kernelTime, userTime;
|
||||
if (GetProcessTimes(GetCurrentProcess(), &creationTime, &exitTime, &kernelTime, &userTime)) {
|
||||
ULARGE_INTEGER kernel, user;
|
||||
kernel.LowPart = kernelTime.dwLowDateTime;
|
||||
kernel.HighPart = kernelTime.dwHighDateTime;
|
||||
user.LowPart = userTime.dwLowDateTime;
|
||||
user.HighPart = userTime.dwHighDateTime;
|
||||
return (kernel.QuadPart + user.QuadPart) / 10000;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
#else
|
||||
namespace polymech {
|
||||
size_t get_current_rss_mb() { return 0; }
|
||||
uint64_t get_cpu_time_ms() { return 0; }
|
||||
}
|
||||
#endif
|
||||
@ -44,6 +44,10 @@ add_executable(test_polymech unit/test_polymech.cpp)
|
||||
target_link_libraries(test_polymech PRIVATE Catch2::Catch2WithMain polymech postgres Threads::Threads)
|
||||
catch_discover_tests(test_polymech)
|
||||
|
||||
add_executable(test_cmd_kbot unit/test_cmd_kbot.cpp ../src/cmd_kbot.cpp)
|
||||
target_link_libraries(test_cmd_kbot PRIVATE Catch2::Catch2WithMain CLI11::CLI11 logger tomlplusplus::tomlplusplus kbot Threads::Threads)
|
||||
catch_discover_tests(test_cmd_kbot)
|
||||
|
||||
# E2E test — polymech fetch_pages from live Supabase
|
||||
add_executable(test_polymech_e2e e2e/test_polymech_e2e.cpp)
|
||||
target_link_libraries(test_polymech_e2e PRIVATE Catch2::Catch2WithMain tomlplusplus::tomlplusplus logger postgres polymech json Threads::Threads)
|
||||
|
||||
60
packages/kbot/cpp/tests/unit/test_cmd_kbot.cpp
Normal file
60
packages/kbot/cpp/tests/unit/test_cmd_kbot.cpp
Normal file
@ -0,0 +1,60 @@
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <CLI/CLI.hpp>
|
||||
#include "../../src/cmd_kbot.h"
|
||||
|
||||
using namespace polymech;
|
||||
|
||||
TEST_CASE("KBot CLI AI Command Parsing", "[kbot]") {
|
||||
CLI::App app{"polymech-cli"};
|
||||
auto* kbot_cmd = setup_cmd_kbot(app);
|
||||
REQUIRE(kbot_cmd != nullptr);
|
||||
|
||||
SECTION("Default values for kbot ai") {
|
||||
int argc = 3;
|
||||
const char* argv[] = {"polymech-cli", "kbot", "ai"};
|
||||
REQUIRE_NOTHROW(app.parse(argc, argv));
|
||||
|
||||
REQUIRE(is_kbot_ai_parsed() == true);
|
||||
REQUIRE(is_kbot_run_parsed() == false);
|
||||
// We can't access g_kbot_opts easily since it's static in cmd_kbot.cpp,
|
||||
// but testing that it doesn't throw is a good start.
|
||||
// In a real app we might pass options structs around instead of globals.
|
||||
}
|
||||
|
||||
SECTION("Arguments for kbot ai") {
|
||||
int argc = 7;
|
||||
const char* argv[] = {
|
||||
"polymech-cli", "kbot", "ai",
|
||||
"--prompt", "hello world",
|
||||
"--mode", "chat"
|
||||
};
|
||||
REQUIRE_NOTHROW(app.parse(argc, argv));
|
||||
REQUIRE(is_kbot_ai_parsed() == true);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("KBot CLI Run Command Parsing", "[kbot]") {
|
||||
CLI::App app{"polymech-cli"};
|
||||
auto* kbot_cmd = setup_cmd_kbot(app);
|
||||
REQUIRE(kbot_cmd != nullptr);
|
||||
|
||||
SECTION("Default values for kbot run") {
|
||||
int argc = 3;
|
||||
const char* argv[] = {"polymech-cli", "kbot", "run"};
|
||||
REQUIRE_NOTHROW(app.parse(argc, argv));
|
||||
|
||||
REQUIRE(is_kbot_run_parsed() == true);
|
||||
REQUIRE(is_kbot_ai_parsed() == false);
|
||||
}
|
||||
|
||||
SECTION("Arguments for kbot run") {
|
||||
int argc = 6;
|
||||
const char* argv[] = {
|
||||
"polymech-cli", "kbot", "run",
|
||||
"-c", "frontend-dev",
|
||||
"--dry"
|
||||
};
|
||||
REQUIRE_NOTHROW(app.parse(argc, argv));
|
||||
REQUIRE(is_kbot_run_parsed() == true);
|
||||
}
|
||||
}
|
||||
69
packages/kbot/docs/cpp-todos.md
Normal file
69
packages/kbot/docs/cpp-todos.md
Normal file
@ -0,0 +1,69 @@
|
||||
# KBot C++ Port Plan (WIP Scaffolding)
|
||||
|
||||
This document outlines the scaffolding steps to port the TypeScript `kbot` implementation (both AI tools and the project runner) over to the C++ `polymech-cli` application.
|
||||
|
||||
## 1. CLI Scaffolding (`main.cpp` & `cmd_kbot`)
|
||||
|
||||
The C++ port will introduce a new `kbot` subcommand tree loosely mimicking the existing TypeScript entry points (`zod_schema.ts`).
|
||||
|
||||
- **Target Files**:
|
||||
- `src/main.cpp` (Register the command)
|
||||
- `src/cmd_kbot.h` (Declarations & Options Structs)
|
||||
- `src/cmd_kbot.cpp` (Implementation)
|
||||
|
||||
### Subcommand `ai`
|
||||
This command replaces the standard `OptionsSchema` from `zod_schema.ts`.
|
||||
- Using `CLI::App* ai_cmd = kbot_cmd->add_subcommand("ai", "Run KBot AI workflows");`
|
||||
- **Arguments to Map** via `CLI11`:
|
||||
- `--path` (default `.`)
|
||||
- `--prompt` (string)
|
||||
- `--output` (string)
|
||||
- `--dst` (string)
|
||||
- `--append` (enum: `concat`, `merge`, `replace`)
|
||||
- `--wrap` (enum: `meta`, `none`)
|
||||
- `--each` (Glob pattern / list / JSON)
|
||||
- `--disable`, `--disableTools`, `--tools`
|
||||
- `--include`, `--exclude`, `--globExtension`
|
||||
- `--model`, `--router`, `--mode` (enum: `completion`, `tools`, `assistant`, `responses`, `custom`)
|
||||
- Flags: `--stream`, `--dry`, `--alt`
|
||||
- Advanced: `--baseURL`, `--config`, `--dump`, `--preferences`, `--logs`, `--env`
|
||||
|
||||
### Subcommand `run`
|
||||
This command replaces `commons/src/lib/run.ts` which spawns debug configurations.
|
||||
- Using `CLI::App* run_cmd = kbot_cmd->add_subcommand("run", "Run a launch.json configuration");`
|
||||
- **Arguments to Map**:
|
||||
- `--config` (default `default`)
|
||||
- `--dry` (flag)
|
||||
- `--list` (flag)
|
||||
- `--projectPath` (default `process.cwd()`)
|
||||
- `--logFilePath` (default `log-configuration.json`)
|
||||
|
||||
## 2. Multithreading & Execution Pattern
|
||||
|
||||
Referencing `cmd_gridsearch.h`, the port will leverage `tf::Taskflow` and `tf::Executor` along with `moodycamel::ConcurrentQueue` for processing parallel tasks (like running a prompt against multiple items via `--each`).
|
||||
|
||||
- **Architecture Details**:
|
||||
1. **Config Loading**: Read preferences/configs (using `tomlplusplus` or `rapidjson`).
|
||||
2. **Globbing / Resolution**: Resolve paths using `--include`/`--exclude`/`--each`.
|
||||
3. **Task Queueing**: For every item resolved by `--each`, queue a task.
|
||||
4. **Task Execution (Stubbed)**: The concurrent thread handles creating the LLM request.
|
||||
5. **Streaming / Output**: Results stream back (or are written to `--dst`), potentially emitting events over an IPC channel or to `stdout` depending on daemon mode setups.
|
||||
|
||||
## 3. Testing Setup
|
||||
|
||||
We'll replicate the testing approach found in `tests/` utilizing `Catch2` for BDD/TDD styled tests.
|
||||
|
||||
- **Target Files**:
|
||||
- `tests/test_cmd_kbot.cpp`
|
||||
- `tests/test_kbot_run.cpp`
|
||||
- **Cases to cover**:
|
||||
- Validation of CLI argument defaults against `zod_schema.ts`.
|
||||
- Behavior of `kbot run --list` correctly interpreting a mock `.vscode/launch.json`.
|
||||
- Dry run of the `--each` pipeline ensuring tasks get initialized properly.
|
||||
|
||||
## Next Steps (Scaffolding Phase)
|
||||
1. Add `cmd_kbot.h/cpp` with the CLI schema variables.
|
||||
2. Hook up the subcommands in `main.cpp`.
|
||||
3. Stub the execution functions (`run_cmd_kbot_ai` and `run_cmd_kbot_run`) just to print out the parsed JSON representing the state.
|
||||
4. Add the targets to `CMakeLists.txt` and verify the build passes.
|
||||
5. Create initial Catch2 tests just to ensure the flags parse correctly without crashing.
|
||||
Loading…
Reference in New Issue
Block a user