700 lines
24 KiB
C++
700 lines
24 KiB
C++
#include "harness.hpp"
|
|
#include "json_types.hpp"
|
|
#include "ollama_client.hpp"
|
|
#include "debug_client.hpp"
|
|
#include "editor.hpp"
|
|
#include "tool_registry.hpp"
|
|
#include "agent_profile.hpp"
|
|
#include "rebase_engine.hpp"
|
|
#include "utility.hpp"
|
|
#include "base64_util.hpp"
|
|
#if !defined(_WIN32)
|
|
#include "shell_tool.hpp"
|
|
#endif
|
|
#include "agent_tool.hpp"
|
|
#include "edit_tool.hpp"
|
|
#include "read_multi_modal.hpp"
|
|
#include "file_list_tool.hpp"
|
|
#include "file_read_tool.hpp"
|
|
#include "grep_tool.hpp"
|
|
#include "skill.hpp"
|
|
#include <nlohmann/json.hpp>
|
|
#include <algorithm>
|
|
#include <cstdlib>
|
|
#include <iostream>
|
|
#include <fstream>
|
|
#include <sstream>
|
|
#include <string>
|
|
#include <vector>
|
|
#include <filesystem>
|
|
#include <random>
|
|
#include <iomanip>
|
|
|
|
static const std::vector<AgentProfile> profiles = {
|
|
#if !defined(_WIN32)
|
|
{
|
|
.name = "shell_expert",
|
|
.description = "An agent that can run shell commands to inspect the system, "
|
|
"files, and processes. Delegate multi-step shell tasks here.",
|
|
.model = "gemma4:26b",
|
|
.tool_names = {"run_shell"}
|
|
},
|
|
#endif
|
|
// Add more agent profiles here
|
|
};
|
|
|
|
static const std::vector<std::string> default_tools =
|
|
{
|
|
};
|
|
|
|
// UUID v4 generation using <random> and <filesystem> only
|
|
static std::string generate_uuid() {
|
|
std::random_device rd;
|
|
std::mt19937 gen(rd());
|
|
std::uniform_int_distribution<> dis(0, 15);
|
|
std::uniform_int_distribution<> dis2(8, 11); // for variant bits
|
|
|
|
std::ostringstream ss;
|
|
for (int i = 0; i < 8; i++) {
|
|
ss << std::hex << dis(gen);
|
|
}
|
|
ss << "-";
|
|
for (int i = 0; i < 4; i++) {
|
|
ss << std::hex << dis(gen);
|
|
}
|
|
ss << "-4"; // version 4
|
|
for (int i = 0; i < 3; i++) {
|
|
ss << std::hex << dis(gen);
|
|
}
|
|
ss << "-";
|
|
ss << std::hex << dis2(gen);
|
|
for (int i = 0; i < 3; i++) {
|
|
ss << std::hex << dis(gen);
|
|
}
|
|
ss << "-";
|
|
for (int i = 0; i < 12; i++) {
|
|
ss << std::hex << dis(gen);
|
|
}
|
|
return ss.str();
|
|
}
|
|
|
|
// Helper function to read and deserialize HarnessState from a file
|
|
// Returns the parsed HarnessState, or exits on error
|
|
static HarnessState read_harness_state_or_exit(const char* file_path) {
|
|
std::ifstream file(file_path);
|
|
if (!file.is_open()) {
|
|
std::cerr << "Error: Cannot open file " << file_path << std::endl;
|
|
exit(1);
|
|
}
|
|
std::string file_content((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
|
|
auto data = expect_or_exit(parse_json_safely(file_content));
|
|
auto state_result = json_to<HarnessState>(data);
|
|
if (!state_result) {
|
|
std::cerr << "Error: Failed to parse chat file: " << state_result.error() << std::endl;
|
|
exit(1);
|
|
}
|
|
return state_result.value();
|
|
}
|
|
|
|
int main(int argc, char** argv) {
|
|
OllamaConfig cfg;
|
|
if (const char* m = std::getenv("OLLAMA_MODEL")) cfg.model = m;
|
|
if (const char* u = std::getenv("OLLAMA_URL")) cfg.base_url = u;
|
|
if (const char* a = std::getenv("OLLAMA_AUTHORIZATION_TOKEN")) cfg.auth_token = a;
|
|
|
|
bool use_debug = false;
|
|
bool debug_paths = false;
|
|
char const* debug_frontmatter = nullptr;
|
|
bool list_skills = false;
|
|
char const* test_json_parse = nullptr;
|
|
char const* test_expect_exit_success = nullptr;
|
|
char const* test_expect_exit_error = nullptr;
|
|
char const* test_type_required_type = nullptr;
|
|
char const* test_type_required_json = nullptr;
|
|
char const* test_rebase_history_size = nullptr;
|
|
char const* test_rebase_content = nullptr;
|
|
char const* test_build_rebase_json = nullptr;
|
|
char const* test_apply_rebase_lines = nullptr;
|
|
char const* test_apply_rebase_history = nullptr;
|
|
char const* test_edit_prompt_json = nullptr;
|
|
char const* test_edit_prompt_index = nullptr;
|
|
char const* test_edit_prompt_role = nullptr;
|
|
char const* test_base64_encode = nullptr;
|
|
char const* test_base64_decode = nullptr;
|
|
char const* test_mime_type = nullptr;
|
|
char const* test_data_uri_ext = nullptr;
|
|
char const* test_data_uri_b64 = nullptr;
|
|
char const* test_image_file = nullptr;
|
|
|
|
ToolRegistry master_registery;
|
|
ToolRegistry agent_tools;
|
|
expect_or_exit(master_registery.register_tool(make_edit_tool()));
|
|
expect_or_exit(master_registery.register_tool(make_read_multi_modal()));
|
|
expect_or_exit(master_registery.register_tool(make_file_list_tool()));
|
|
expect_or_exit(master_registery.register_tool(make_file_read_tool()));
|
|
expect_or_exit(master_registery.register_tool(make_grep_tool()));
|
|
#if !defined(_WIN32)
|
|
expect_or_exit(master_registery.register_tool(make_shell_tool(true))); // run_shell (safe)
|
|
expect_or_exit(master_registery.register_tool(make_shell_tool(false))); // run_dangerous_shell
|
|
#endif
|
|
// Add more base tools here:
|
|
|
|
for (const auto& profile : profiles)
|
|
expect_or_exit(master_registery.register_tool(make_agent_tool(profile, master_registery, cfg.base_url)));
|
|
|
|
auto consume_arg = [&](){
|
|
argc--;
|
|
argv++;
|
|
if (argc < 0)
|
|
{
|
|
std::cerr << "Error expected another argument" << std::endl;
|
|
exit(1);
|
|
}
|
|
|
|
return argv[-1];
|
|
};
|
|
|
|
char const* file_to_open = nullptr;
|
|
std::string auto_temp_file; // Path to auto-generated temp file (if any)
|
|
char const* prompt = nullptr;
|
|
char const* prompt_file = nullptr;
|
|
bool start_new = false;
|
|
bool list_models = false;
|
|
char const* print_file = nullptr;
|
|
std::vector<std::string> tools;
|
|
bool full_history = false;
|
|
bool edit_history = false;
|
|
std::vector<std::string> skill_filters;
|
|
std::vector<std::string> image_paths;
|
|
|
|
consume_arg(); // progname
|
|
|
|
while (argc > 0)
|
|
{
|
|
if (argv[0] == std::string("-F"))
|
|
{
|
|
consume_arg();
|
|
file_to_open = consume_arg();
|
|
}
|
|
else if (argv[0] == std::string("-p"))
|
|
{
|
|
consume_arg();
|
|
print_file = consume_arg();
|
|
}
|
|
else if (argv[0] == std::string("-t"))
|
|
{
|
|
consume_arg();
|
|
tools.push_back(consume_arg());
|
|
}
|
|
else if (argv[0] == std::string("-n"))
|
|
{
|
|
consume_arg();
|
|
start_new = true;
|
|
}
|
|
else if (argv[0] == std::string("-m"))
|
|
{
|
|
consume_arg();
|
|
prompt = consume_arg();
|
|
}
|
|
else if (argv[0] == std::string("-P"))
|
|
{
|
|
consume_arg();
|
|
prompt_file = consume_arg();
|
|
}
|
|
else if (argv[0] == std::string("-M"))
|
|
{
|
|
consume_arg();
|
|
cfg.model = consume_arg();
|
|
}
|
|
else if (argv[0] == std::string("-d"))
|
|
{
|
|
consume_arg();
|
|
use_debug = true;
|
|
}
|
|
else if (argv[0] == std::string("-D"))
|
|
{
|
|
consume_arg();
|
|
debug_paths = true;
|
|
}
|
|
else if (argv[0] == std::string("-y"))
|
|
{
|
|
consume_arg();
|
|
debug_frontmatter = consume_arg();
|
|
}
|
|
else if (argv[0] == std::string("-S"))
|
|
{
|
|
consume_arg();
|
|
list_skills = true;
|
|
}
|
|
else if (argv[0] == std::string("-J"))
|
|
{
|
|
consume_arg();
|
|
test_json_parse = consume_arg();
|
|
}
|
|
else if (argv[0] == std::string("-E"))
|
|
{
|
|
consume_arg();
|
|
test_expect_exit_success = consume_arg();
|
|
}
|
|
else if (argv[0] == std::string("-e"))
|
|
{
|
|
consume_arg();
|
|
test_expect_exit_error = consume_arg();
|
|
}
|
|
else if (argv[0] == std::string("-R"))
|
|
{
|
|
consume_arg();
|
|
test_type_required_type = consume_arg();
|
|
test_type_required_json = consume_arg();
|
|
}
|
|
else if (argv[0] == std::string("-P"))
|
|
{
|
|
consume_arg();
|
|
test_rebase_history_size = consume_arg();
|
|
test_rebase_content = consume_arg();
|
|
}
|
|
else if (argv[0] == std::string("-Z"))
|
|
{
|
|
consume_arg();
|
|
test_build_rebase_json = consume_arg();
|
|
}
|
|
else if (argv[0] == std::string("-A"))
|
|
{
|
|
consume_arg();
|
|
test_apply_rebase_lines = consume_arg();
|
|
test_apply_rebase_history = consume_arg();
|
|
}
|
|
else if (argv[0] == std::string("-B"))
|
|
{
|
|
consume_arg();
|
|
test_base64_encode = consume_arg();
|
|
}
|
|
else if (argv[0] == std::string("-K"))
|
|
{
|
|
consume_arg();
|
|
test_base64_decode = consume_arg();
|
|
}
|
|
else if (argv[0] == std::string("-W"))
|
|
{
|
|
consume_arg();
|
|
test_edit_prompt_json = consume_arg();
|
|
test_edit_prompt_index = consume_arg();
|
|
test_edit_prompt_role = consume_arg();
|
|
}
|
|
else if (argv[0] == std::string("-T"))
|
|
{
|
|
consume_arg();
|
|
test_mime_type = consume_arg();
|
|
}
|
|
else if (argv[0] == std::string("-U"))
|
|
{
|
|
consume_arg();
|
|
test_data_uri_ext = consume_arg();
|
|
test_data_uri_b64 = consume_arg();
|
|
}
|
|
else if (argv[0] == std::string("-L"))
|
|
{
|
|
consume_arg();
|
|
list_models = true;
|
|
}
|
|
else if (argv[0] == std::string("-l"))
|
|
{
|
|
for (auto const& x : master_registery.tool_names())
|
|
{
|
|
std::cout << x << std::endl;
|
|
}
|
|
exit(0);
|
|
}
|
|
else if (argv[0] == std::string("-f"))
|
|
{
|
|
consume_arg();
|
|
full_history = true;
|
|
}
|
|
else if (argv[0] == std::string("-s"))
|
|
{
|
|
consume_arg();
|
|
skill_filters.push_back(consume_arg());
|
|
}
|
|
else if (argv[0] == std::string("-b") || argv[0] == std::string("--edit-history"))
|
|
{
|
|
consume_arg();
|
|
edit_history = true;
|
|
}
|
|
else if (argv[0] == std::string("-i"))
|
|
{
|
|
consume_arg();
|
|
image_paths.push_back(consume_arg());
|
|
}
|
|
else if (argv[0] == std::string("-I"))
|
|
{
|
|
consume_arg();
|
|
test_image_file = consume_arg();
|
|
}
|
|
else if (argv[0] == std::string("-h"))
|
|
{
|
|
std::cout << "Usage: drharness [options]\n"
|
|
<< "Options:\n"
|
|
<< " -F <file> Chat file\n"
|
|
<< " -p <file> Pretty print a saved conversation JSON file\n"
|
|
<< " -t <tool> Tool name (can be specified multiple times)\n"
|
|
<< " -s <name> Skill name to enable (can be specified multiple times)\n"
|
|
<< " -i <path> Image file to attach (can be specified multiple times)\n"
|
|
<< " -n Start a new conversation (auto-generates temp file if -F not specified)\n"
|
|
<< " -m <prompt> Prompt message (required)\n"
|
|
<< " -P <file> Read prompt from file\n"
|
|
<< " -M <model> Ollama model name\n"
|
|
<< " -f Open editor with full history (requires -F)\n"
|
|
<< " -b Edit history (requires -F)\n"
|
|
<< " --edit-history\n"
|
|
<< " -l List available tools\n"
|
|
<< " -L List available models on server side\n"
|
|
<< " -D Debug: print all discovered skill paths and exit\n"
|
|
<< " -y <file> Debug: print YAML frontmatter from a SKILL.md file and exit\n"
|
|
<< " -S List discovered skills and exit\n"
|
|
<< " -J <json> Debug: test parse_json_safely and exit\n"
|
|
<< " -E <string> Debug: test expect_or_exit success case and exit\n"
|
|
<< " -e <string> Debug: test expect_or_exit error case and exit\n"
|
|
<< " -R <type> <json> Debug: test json_to<Type>() for required fields and exit\n"
|
|
<< " -P <size> <content> Debug: test rebase parser and exit\n"
|
|
<< " -Z <json> Debug: test build_rebase_content with JSON history and exit\n"
|
|
<< " -A <lines> <history> Debug: test apply_rebase with JSON and exit\n"
|
|
<< " -W <json> <index> <role> Debug: test build_edit_prompt and exit\n"
|
|
<< " -B <string> Debug: test base64 encode and exit\n"
|
|
<< " -K <string> Debug: test base64 decode and exit\n"
|
|
<< " -T <ext> Debug: test MIME type detection and exit\n"
|
|
<< " -U <ext> <b64> Debug: test data URI generation and exit\n"
|
|
<< " -I <file> Debug: test image file reading and exit\n"
|
|
<< " -d Use debug client (mock API via stdin)\n"
|
|
<< " -h Display this help menu\n";
|
|
exit(0);
|
|
}
|
|
else
|
|
{
|
|
std::cerr << "Unexpected argument: " << argv[0] << std::endl;
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
// Validate -b / --edit-history flag requirements
|
|
if (edit_history && !file_to_open)
|
|
{
|
|
std::cerr << "Error: -b/--edit-history requires -F <chat_file> to be specified" << std::endl;
|
|
return 1;
|
|
}
|
|
|
|
// Validate -b / --edit-history flag with empty history
|
|
if (edit_history && file_to_open)
|
|
{
|
|
HarnessState state_data = read_harness_state_or_exit(file_to_open);
|
|
|
|
if (state_data.history.empty()) {
|
|
std::cerr << "Error: Chat file has no history, cannot edit" << std::endl;
|
|
return 1;
|
|
}
|
|
|
|
// Run the interactive edit flow
|
|
HistoryEditor editor;
|
|
auto edit_result = editor.edit_history_interactive(state_data.history);
|
|
if (!edit_result) {
|
|
std::cerr << "Error: " << edit_result.error() << std::endl;
|
|
return 1;
|
|
}
|
|
|
|
// Save modified history to chat file
|
|
state_data.history = edit_result.value();
|
|
std::ofstream outfile(file_to_open);
|
|
if (!outfile.is_open()) {
|
|
std::cerr << "Error: Cannot write to file " << file_to_open << std::endl;
|
|
return 1;
|
|
}
|
|
outfile << nlohmann::json(state_data);
|
|
|
|
// Exit after successful edit
|
|
return 0;
|
|
}
|
|
|
|
// Helper function to create the client based on the debug flag
|
|
auto create_client = [&](bool debug) -> std::unique_ptr<Client> {
|
|
if (debug)
|
|
return std::make_unique<DebugClient>();
|
|
return std::make_unique<OllamaClient>(cfg);
|
|
};
|
|
|
|
// Scan for skills
|
|
auto all_skills_result = scan_skills();
|
|
if (!all_skills_result) {
|
|
std::cerr << "Fatal: " << all_skills_result.error() << std::endl;
|
|
return 1;
|
|
}
|
|
std::vector<SkillMetadata> all_skills = std::move(all_skills_result.value());
|
|
|
|
// Build list of discovered paths for debug output
|
|
std::vector<std::string> discovered_paths;
|
|
for (const auto& skill : all_skills) {
|
|
discovered_paths.push_back((std::filesystem::path(skill.directory) / "SKILL.md").string());
|
|
}
|
|
|
|
// Build enabled skills list based on filters
|
|
std::vector<SkillMetadata> enabled_skills;
|
|
if (skill_filters.empty()) {
|
|
enabled_skills = all_skills;
|
|
} else {
|
|
for (const auto& skill : all_skills) {
|
|
if (std::find(skill_filters.begin(), skill_filters.end(), skill.name) != skill_filters.end()) {
|
|
enabled_skills.push_back(skill);
|
|
}
|
|
}
|
|
}
|
|
|
|
expect_or_exit(master_registery.register_tool(make_skill_tool(enabled_skills)));
|
|
|
|
if (debug_paths) {
|
|
print_discovered_paths(discovered_paths);
|
|
return 0;
|
|
}
|
|
|
|
if (list_skills) {
|
|
print_skills(all_skills);
|
|
return 0;
|
|
}
|
|
|
|
if (debug_frontmatter) {
|
|
auto metadata = parse_skill_frontmatter(debug_frontmatter);
|
|
if (!metadata) {
|
|
std::cerr << "Error parsing frontmatter: " << metadata.error() << "\n";
|
|
return 1;
|
|
}
|
|
std::cout << "name: " << metadata->name << "\n";
|
|
if (metadata->description.has_value()) {
|
|
std::cout << "description: " << metadata->description.value() << "\n";
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
if (test_json_parse) {
|
|
auto result = parse_json_safely(test_json_parse);
|
|
std::cout << expect_or_exit(result).dump(2) << std::endl;
|
|
return 0;
|
|
}
|
|
|
|
if (test_expect_exit_success) {
|
|
auto result = std::expected<std::string, std::string>(test_expect_exit_success);
|
|
std::string value = expect_or_exit(result);
|
|
std::cout << value << std::endl;
|
|
return 0;
|
|
}
|
|
|
|
if (test_expect_exit_error) {
|
|
auto result = std::expected<std::string, std::string>(std::unexpected(test_expect_exit_error));
|
|
expect_or_exit(result);
|
|
return 0; // Should not reach here
|
|
}
|
|
|
|
if (test_type_required_type && test_type_required_json) {
|
|
auto j = expect_or_exit(parse_json_safely(test_type_required_json));
|
|
std::string type_str(test_type_required_type);
|
|
auto it = json_dispatch_map.find(type_str);
|
|
if (it != json_dispatch_map.end()) {
|
|
it->second(j);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
if (test_rebase_history_size && test_rebase_content) {
|
|
size_t history_size = expect_or_exit(string_toul(test_rebase_history_size));
|
|
RebaseEngine engine;
|
|
auto result = engine.parse_rebase_file(test_rebase_content, history_size);
|
|
nlohmann::json output = expect_or_exit(result);
|
|
std::cout << output.dump(2) << std::endl;
|
|
return 0;
|
|
}
|
|
|
|
if (test_build_rebase_json) {
|
|
auto j = expect_or_exit(parse_json_safely(test_build_rebase_json));
|
|
auto history_result = expect_or_exit(json_to<std::vector<Message>>(j));
|
|
HistoryEditor editor;
|
|
std::string content = editor.build_rebase_content(history_result);
|
|
std::cout << content;
|
|
return 0;
|
|
}
|
|
|
|
if (test_apply_rebase_lines && test_apply_rebase_history) {
|
|
auto lines_j = expect_or_exit(parse_json_safely(test_apply_rebase_lines));
|
|
auto lines_result = expect_or_exit(json_to<std::vector<RebaseLine>>(lines_j));
|
|
auto history_j = expect_or_exit(parse_json_safely(test_apply_rebase_history));
|
|
auto history_result = expect_or_exit(json_to<std::vector<Message>>(history_j));
|
|
RebaseEngine engine;
|
|
auto result = engine.apply_rebase(lines_result, history_result);
|
|
nlohmann::json output = expect_or_exit(result);
|
|
std::cout << output.dump(2) << std::endl;
|
|
return 0;
|
|
}
|
|
|
|
if (test_edit_prompt_json && test_edit_prompt_index && test_edit_prompt_role) {
|
|
auto j = expect_or_exit(parse_json_safely(test_edit_prompt_json));
|
|
auto history_result = expect_or_exit(json_to<std::vector<Message>>(j));
|
|
size_t edit_index = expect_or_exit(string_toul(test_edit_prompt_index));
|
|
const std::string role = test_edit_prompt_role;
|
|
HistoryEditor editor;
|
|
std::string prompt = editor.build_edit_prompt(history_result, edit_index, role);
|
|
std::cout << prompt;
|
|
return 0;
|
|
}
|
|
|
|
if (test_base64_encode) {
|
|
std::cout << base64_encode(test_base64_encode) << std::endl;
|
|
return 0;
|
|
}
|
|
|
|
if (test_base64_decode) {
|
|
auto result = base64_decode(test_base64_decode);
|
|
if (!result) {
|
|
std::cout << "error: " << result.error() << std::endl;
|
|
return 1;
|
|
}
|
|
std::cout << result.value() << std::endl;
|
|
return 0;
|
|
}
|
|
|
|
if (test_mime_type) {
|
|
std::cout << get_mime_type_from_extension(test_mime_type) << std::endl;
|
|
return 0;
|
|
}
|
|
|
|
if (test_data_uri_ext && test_data_uri_b64) {
|
|
std::cout << generate_data_uri(test_data_uri_ext, test_data_uri_b64) << std::endl;
|
|
return 0;
|
|
}
|
|
|
|
if (test_image_file) {
|
|
auto result = read_image_as_data_uri(test_image_file);
|
|
if (!result) {
|
|
std::cerr << "Error: " << result.error() << std::endl;
|
|
return 1;
|
|
}
|
|
std::cout << result.value() << std::endl;
|
|
return 0;
|
|
}
|
|
|
|
if (list_models)
|
|
{
|
|
auto models_result = create_client(use_debug)->list_models();
|
|
if (!models_result) {
|
|
std::cerr << "Error listing models: " << models_result.error() << std::endl;
|
|
return 1;
|
|
}
|
|
auto models = models_result.value();
|
|
|
|
for (auto const& x : models)
|
|
{
|
|
std::cout << x << std::endl;
|
|
}
|
|
exit(0);
|
|
}
|
|
|
|
if (print_file)
|
|
{
|
|
std::ifstream file(print_file);
|
|
if (!file.is_open()) {
|
|
std::cerr << "Error: Cannot open file " << print_file << "\n";
|
|
return 1;
|
|
}
|
|
|
|
std::string file_content((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
|
|
auto data = expect_or_exit(parse_json_safely(file_content));
|
|
print_history(std::cout, data);
|
|
return 0;
|
|
}
|
|
|
|
if (tools.empty())
|
|
agent_tools = expect_or_exit(master_registery.create_subregistry(default_tools));
|
|
else
|
|
agent_tools = expect_or_exit(master_registery.create_subregistry(tools));
|
|
|
|
// Auto-generate temp file if -n is specified without -F
|
|
if (start_new && !file_to_open) {
|
|
std::filesystem::path temp_dir = std::filesystem::temp_directory_path();
|
|
std::string uuid = generate_uuid();
|
|
auto temp_file_path = temp_dir / ("harness-" + uuid + ".json");
|
|
|
|
// Create empty temp file
|
|
std::ofstream temp_file(temp_file_path);
|
|
temp_file.close();
|
|
|
|
if (!std::filesystem::exists(temp_file_path)) {
|
|
std::cerr << "Error: Failed to create temp file" << std::endl;
|
|
exit(1);
|
|
}
|
|
|
|
auto_temp_file = temp_file_path.string();
|
|
file_to_open = auto_temp_file.c_str();
|
|
}
|
|
|
|
if (!file_to_open)
|
|
{
|
|
std::cerr << "Chat file is required (-F)" << std::endl;
|
|
exit(1);
|
|
}
|
|
|
|
if (start_new) {
|
|
std::ifstream file(file_to_open, std::ios::ate);
|
|
if (file.is_open()) {
|
|
std::streampos size = file.tellg();
|
|
if (size > 0) {
|
|
std::cerr << "Error: Cannot start a new conversation with an existing, non-empty file: " << file_to_open << "\n";
|
|
exit(1);
|
|
}
|
|
}
|
|
}
|
|
|
|
Harness harness(create_client(use_debug), agent_tools, {.max_tool_iterations = 1000, .verbose = true});
|
|
|
|
if (!start_new)
|
|
{
|
|
HarnessState state_data = read_harness_state_or_exit(file_to_open);
|
|
harness = Harness(create_client(use_debug), master_registery, state_data);
|
|
}
|
|
|
|
std::cerr << "drharness v0.3 | model: " << cfg.model
|
|
<< " | " << cfg.base_url << "\n\n";
|
|
|
|
std::string final_prompt;
|
|
|
|
if (prompt_file) {
|
|
std::ifstream pf(prompt_file);
|
|
if (!pf.is_open()) {
|
|
std::cerr << "Error: Cannot open prompt file " << prompt_file << "\n";
|
|
return 1;
|
|
}
|
|
final_prompt = std::string((std::istreambuf_iterator<char>(pf)), std::istreambuf_iterator<char>());
|
|
} else if (prompt) {
|
|
final_prompt = prompt;
|
|
} else {
|
|
final_prompt = expect_or_exit(run_editor(harness.serialize(), full_history));
|
|
}
|
|
|
|
std::cerr << "Prompt input:" << std::endl;
|
|
std::cerr << final_prompt << std::endl;
|
|
|
|
std::expected<std::string, std::string> reply;
|
|
|
|
if (!image_paths.empty()) {
|
|
reply = harness.chat(final_prompt, image_paths);
|
|
} else {
|
|
reply = harness.chat(final_prompt);
|
|
}
|
|
if (!reply) {
|
|
std::cerr << "Error: " << reply.error() << std::endl;
|
|
return 1;
|
|
}
|
|
|
|
std::ofstream harness_state(file_to_open);
|
|
std::cout << reply.value() << std::endl;
|
|
harness_state << nlohmann::json(harness.serialize());
|
|
|
|
// Print the auto-generated temp file path to stderr if it was auto-created
|
|
if (!auto_temp_file.empty()) {
|
|
std::cerr << auto_temp_file << std::endl;
|
|
}
|
|
}
|