From 8deb2656d5afd3a961b30052743d49c340376272 Mon Sep 17 00:00:00 2001 From: scawful Date: Sat, 4 Oct 2025 04:28:44 -0400 Subject: [PATCH] feat: Enhance AI service with verbose logging and command help - Added a `--verbose` flag to enable detailed debug output for the Gemini AI service. - Updated `GeminiAIService` constructor to log initialization details when verbose mode is enabled. - Modified `CreateAIService` to pass the verbose flag to the Gemini configuration. - Enhanced command help in `ModernCLI` to categorize commands and provide detailed descriptions. - Refactored `HandleSimpleChatCommand` to accept a pointer to `Rom` instead of a reference. - Updated `ShowCategoryHelp` to display command categories and examples. - Improved error handling and logging in `GeminiAIService` for better debugging. --- src/cli/handlers/agent.cc | 6 +- src/cli/handlers/agent/general_commands.cc | 9 +- src/cli/modern_cli.cc | 252 +++++++++++++++++++-- src/cli/modern_cli.h | 1 + src/cli/service/ai/gemini_ai_service.cc | 203 ++++++++++------- src/cli/service/ai/gemini_ai_service.h | 1 + src/cli/service/ai/service_factory.cc | 21 +- src/cli/service/ai/service_factory.h | 1 + 8 files changed, 374 insertions(+), 120 deletions(-) diff --git a/src/cli/handlers/agent.cc b/src/cli/handlers/agent.cc index 5abf155e..efce4487 100644 --- a/src/cli/handlers/agent.cc +++ b/src/cli/handlers/agent.cc @@ -4,8 +4,12 @@ #include #include +#include "absl/flags/declare.h" +#include "absl/flags/flag.h" #include "absl/status/status.h" +ABSL_DECLARE_FLAG(bool, quiet); + namespace yaze { namespace cli { namespace agent { @@ -133,7 +137,7 @@ absl::Status Agent::Run(const std::vector& arg_vec) { return agent::HandleChatCommand(rom_); } if (subcommand == "simple-chat") { - return agent::HandleSimpleChatCommand(subcommand_args, rom_, absl::GetFlag(FLAGS_quiet)); + return agent::HandleSimpleChatCommand(subcommand_args, &rom_, absl::GetFlag(FLAGS_quiet)); } return absl::InvalidArgumentError(std::string(agent::kUsage)); diff --git a/src/cli/handlers/agent/general_commands.cc b/src/cli/handlers/agent/general_commands.cc index 5178e7b9..645684fd 100644 --- a/src/cli/handlers/agent/general_commands.cc +++ b/src/cli/handlers/agent/general_commands.cc @@ -619,10 +619,10 @@ absl::Status HandleChatCommand(Rom& rom) { } absl::Status HandleSimpleChatCommand(const std::vector& arg_vec, - Rom& rom, bool quiet) { - RETURN_IF_ERROR(EnsureRomLoaded(rom, "agent simple-chat")); + Rom* rom, bool quiet) { + RETURN_IF_ERROR(EnsureRomLoaded(*rom, "agent simple-chat")); - auto _ = TryLoadProjectAndLabels(rom); + auto _ = TryLoadProjectAndLabels(*rom); std::optional batch_file; std::optional single_message; @@ -646,8 +646,7 @@ absl::Status HandleSimpleChatCommand(const std::vector& arg_vec, SimpleChatSession session; session.SetConfig(config); - session.SetRomContext(&rom); - session.SetQuietMode(quiet); + session.SetRomContext(rom); if (batch_file.has_value()) { std::ifstream file(*batch_file); diff --git a/src/cli/modern_cli.cc b/src/cli/modern_cli.cc index 5a31a2ac..d52ed78f 100644 --- a/src/cli/modern_cli.cc +++ b/src/cli/modern_cli.cc @@ -289,37 +289,243 @@ void ModernCLI::SetupCommands() { void ModernCLI::ShowHelp() { std::cout << GetColoredLogo() << std::endl; std::cout << std::endl; - std::cout << "USAGE:" << std::endl; - std::cout << " z3ed [--tui] [arguments]" << std::endl; + std::cout << "\033[1m\033[36mUSAGE:\033[0m" << std::endl; + std::cout << " z3ed [options] [arguments]" << std::endl; std::cout << std::endl; - std::cout << "GLOBAL FLAGS:" << std::endl; - std::cout << " --tui Launch Text User Interface" << std::endl; - std::cout << " --rom= Specify ROM file to use" << std::endl; + + std::cout << "\033[1m\033[36mGLOBAL OPTIONS:\033[0m" << std::endl; + std::cout << " --tui Launch interactive Text User Interface" << std::endl; + std::cout << " --rom= Specify ROM file path" << std::endl; + std::cout << " --verbose, -v Show detailed debug output" << std::endl; std::cout << " --version Show version information" << std::endl; - std::cout << " --help Show this help message" << std::endl; + std::cout << " --help, -h Show this help message" << std::endl; std::cout << std::endl; - std::cout << "COMMANDS:" << std::endl; - for (const auto& [name, info] : commands_) { - std::cout << absl::StrFormat(" %-25s %s", name, info.description) << std::endl; - } + // Categorize commands + std::cout << "\033[1m\033[36mCOMMANDS:\033[0m" << std::endl; + std::cout << std::endl; + std::cout << " \033[1mπŸ€– AI Agent\033[0m" << std::endl; + std::cout << " agent simple-chat Natural language ROM queries" << std::endl; + std::cout << " agent test-conversation Interactive testing mode" << std::endl; + std::cout << " \033[90mβ†’ z3ed help agent\033[0m" << std::endl; std::cout << std::endl; - std::cout << "EXAMPLES:" << std::endl; - std::cout << " z3ed --tui # Launch TUI" << std::endl; - std::cout << " z3ed patch apply-asar patch.asm --rom=zelda3.sfc # Apply Asar patch" << std::endl; - std::cout << " z3ed patch apply-bps changes.bps --rom=zelda3.sfc # Apply BPS patch" << std::endl; - std::cout << " z3ed patch extract-symbols patch.asm # Extract symbols" << std::endl; - std::cout << " z3ed rom info --rom=zelda3.sfc # Show ROM info" << std::endl; + + std::cout << " \033[1mπŸ”§ ROM Patching\033[0m" << std::endl; + std::cout << " patch apply-asar Apply Asar 65816 assembly patch" << std::endl; + std::cout << " patch apply-bps Apply BPS binary patch" << std::endl; + std::cout << " patch extract-symbols Extract symbols from assembly" << std::endl; + std::cout << " \033[90mβ†’ z3ed help patch\033[0m" << std::endl; std::cout << std::endl; - std::cout << "For more information on a specific command:" << std::endl; - std::cout << " z3ed help " << std::endl; + + std::cout << " \033[1mπŸ“¦ ROM Operations\033[0m" << std::endl; + std::cout << " rom info Display ROM information" << std::endl; + std::cout << " rom diff Compare two ROM files" << std::endl; + std::cout << " rom generate-golden Create golden test file" << std::endl; + std::cout << " \033[90mβ†’ z3ed help rom\033[0m" << std::endl; + std::cout << std::endl; + + std::cout << " \033[1mπŸ—ΊοΈ Overworld\033[0m" << std::endl; + std::cout << " overworld get-tile Get tile at coordinates" << std::endl; + std::cout << " overworld set-tile Place tile at coordinates" << std::endl; + std::cout << " overworld find-tile Search for tile occurrences" << std::endl; + std::cout << " overworld describe-map Show map metadata" << std::endl; + std::cout << " overworld list-warps List entrances and exits" << std::endl; + std::cout << " \033[90mβ†’ z3ed help overworld\033[0m" << std::endl; + std::cout << std::endl; + + std::cout << " \033[1m🏰 Dungeon\033[0m" << std::endl; + std::cout << " dungeon export Export dungeon data" << std::endl; + std::cout << " dungeon import Import dungeon data" << std::endl; + std::cout << " \033[90mβ†’ z3ed help dungeon\033[0m" << std::endl; + std::cout << std::endl; + + std::cout << " \033[1m🎨 Graphics\033[0m" << std::endl; + std::cout << " gfx export-sheet Export graphics sheet" << std::endl; + std::cout << " gfx import-sheet Import graphics sheet" << std::endl; + std::cout << " palette export Export palette data" << std::endl; + std::cout << " palette import Import palette data" << std::endl; + std::cout << " \033[90mβ†’ z3ed help gfx, z3ed help palette\033[0m" << std::endl; + std::cout << std::endl; + + std::cout << "\033[1m\033[36mQUICK START:\033[0m" << std::endl; + std::cout << " z3ed --tui" << std::endl; + std::cout << " z3ed agent simple-chat \"What is room 5?\" --rom=zelda3.sfc" << std::endl; + std::cout << " z3ed patch apply-asar patch.asm --rom=zelda3.sfc" << std::endl; + std::cout << std::endl; + + std::cout << "\033[90mFor detailed help: z3ed help \033[0m" << std::endl; } void ModernCLI::PrintTopLevelHelp() const { const_cast(this)->ShowHelp(); } +void ModernCLI::ShowCategoryHelp(const std::string& category) { + std::cout << GetColoredLogo() << std::endl; + std::cout << std::endl; + + if (category == "agent") { + std::cout << "\033[1m\033[36mπŸ€– AI AGENT COMMANDS\033[0m" << std::endl; + std::cout << std::endl; + std::cout << "\033[1mDESCRIPTION:\033[0m" << std::endl; + std::cout << " Natural language interface for ROM inspection using embedded labels." << std::endl; + std::cout << " Query rooms, sprites, entrances, and game data conversationally." << std::endl; + std::cout << std::endl; + std::cout << "\033[1mCOMMANDS:\033[0m" << std::endl; + std::cout << std::endl; + std::cout << " \033[1magent simple-chat\033[0m [\"\"]" << std::endl; + std::cout << " Single-shot or interactive chat mode" << std::endl; + std::cout << " Options: --rom=, --verbose" << std::endl; + std::cout << " Examples:" << std::endl; + std::cout << " z3ed agent simple-chat \"What sprites are in room 5?\" --rom=zelda3.sfc" << std::endl; + std::cout << " echo \"List all dungeons\" | z3ed agent simple-chat --rom=zelda3.sfc" << std::endl; + std::cout << std::endl; + std::cout << " \033[1magent test-conversation\033[0m" << std::endl; + std::cout << " Interactive testing mode with full context" << std::endl; + std::cout << " Options: --rom=, --verbose, --file=" << std::endl; + std::cout << std::endl; + std::cout << " \033[1magent chat\033[0m \"\"" << std::endl; + std::cout << " Advanced multi-turn conversation mode" << std::endl; + std::cout << " Options: --host=, --port=" << std::endl; + std::cout << std::endl; + std::cout << "\033[1mTIPS:\033[0m" << std::endl; + std::cout << " β€’ Use --verbose to see detailed API calls and responses" << std::endl; + std::cout << " β€’ Set GEMINI_API_KEY environment variable for Gemini" << std::endl; + std::cout << " β€’ Use --ai_provider=gemini or --ai_provider=ollama" << std::endl; + std::cout << std::endl; + + } else if (category == "patch") { + std::cout << "\033[1m\033[36mπŸ”§ ROM PATCHING COMMANDS\033[0m" << std::endl; + std::cout << std::endl; + std::cout << "\033[1mDESCRIPTION:\033[0m" << std::endl; + std::cout << " Apply patches and extract symbols from assembly files." << std::endl; + std::cout << std::endl; + std::cout << "\033[1mCOMMANDS:\033[0m" << std::endl; + std::cout << std::endl; + std::cout << " \033[1mpatch apply-asar\033[0m " << std::endl; + std::cout << " Apply Asar 65816 assembly patch to ROM" << std::endl; + std::cout << " Options: --rom=, --output=" << std::endl; + std::cout << " Example: z3ed patch apply-asar custom.asm --rom=zelda3.sfc" << std::endl; + std::cout << std::endl; + std::cout << " \033[1mpatch apply-bps\033[0m " << std::endl; + std::cout << " Apply BPS binary patch to ROM" << std::endl; + std::cout << " Options: --rom=, --output=" << std::endl; + std::cout << " Example: z3ed patch apply-bps hack.bps --rom=zelda3.sfc" << std::endl; + std::cout << std::endl; + std::cout << " \033[1mpatch extract-symbols\033[0m " << std::endl; + std::cout << " Extract symbol table from assembly file" << std::endl; + std::cout << " Example: z3ed patch extract-symbols code.asm" << std::endl; + std::cout << std::endl; + std::cout << "\033[1mRELATED:\033[0m" << std::endl; + std::cout << " z3ed help rom ROM operations and validation" << std::endl; + std::cout << std::endl; + + } else if (category == "rom") { + std::cout << "\033[1m\033[36mπŸ“¦ ROM OPERATIONS\033[0m" << std::endl; + std::cout << std::endl; + std::cout << "\033[1mCOMMANDS:\033[0m" << std::endl; + std::cout << std::endl; + std::cout << " \033[1mrom info\033[0m" << std::endl; + std::cout << " Display ROM header and metadata" << std::endl; + std::cout << " Example: z3ed rom info --rom=zelda3.sfc" << std::endl; + std::cout << std::endl; + std::cout << " \033[1mrom diff\033[0m" << std::endl; + std::cout << " Compare two ROM files byte-by-byte" << std::endl; + std::cout << " Example: z3ed rom diff --src=original.sfc --modified=hacked.sfc" << std::endl; + std::cout << std::endl; + std::cout << " \033[1mrom generate-golden\033[0m" << std::endl; + std::cout << " Create golden test reference file" << std::endl; + std::cout << " Example: z3ed rom generate-golden --rom=zelda3.sfc" << std::endl; + std::cout << std::endl; + std::cout << " \033[1mrom validate\033[0m" << std::endl; + std::cout << " Validate ROM checksum and structure" << std::endl; + std::cout << " Example: z3ed rom validate --rom=zelda3.sfc" << std::endl; + std::cout << std::endl; + + } else if (category == "overworld") { + std::cout << "\033[1m\033[36mπŸ—ΊοΈ OVERWORLD COMMANDS\033[0m" << std::endl; + std::cout << std::endl; + std::cout << "\033[1mDESCRIPTION:\033[0m" << std::endl; + std::cout << " Inspect and modify overworld map data, tiles, and warps." << std::endl; + std::cout << std::endl; + std::cout << "\033[1mCOMMANDS:\033[0m" << std::endl; + std::cout << std::endl; + std::cout << " \033[1moverworld get-tile\033[0m" << std::endl; + std::cout << " Get tile ID at specific coordinates" << std::endl; + std::cout << " Example: z3ed overworld get-tile --x=10 --y=20 --map=0 --rom=zelda3.sfc" << std::endl; + std::cout << std::endl; + std::cout << " \033[1moverworld set-tile\033[0m" << std::endl; + std::cout << " Place tile at coordinates" << std::endl; + std::cout << " Example: z3ed overworld set-tile --x=10 --y=20 --tile=0x42 --rom=zelda3.sfc" << std::endl; + std::cout << std::endl; + std::cout << " \033[1moverworld find-tile\033[0m" << std::endl; + std::cout << " Search for all occurrences of a tile" << std::endl; + std::cout << " Example: z3ed overworld find-tile --tile=0x42 --rom=zelda3.sfc" << std::endl; + std::cout << std::endl; + std::cout << " \033[1moverworld describe-map\033[0m" << std::endl; + std::cout << " Show map metadata and properties" << std::endl; + std::cout << " Example: z3ed overworld describe-map --map=0 --rom=zelda3.sfc" << std::endl; + std::cout << std::endl; + std::cout << " \033[1moverworld list-warps\033[0m" << std::endl; + std::cout << " List all entrances and exits" << std::endl; + std::cout << " Example: z3ed overworld list-warps --rom=zelda3.sfc" << std::endl; + std::cout << std::endl; + + } else if (category == "dungeon") { + std::cout << "\033[1m\033[36m🏰 DUNGEON COMMANDS\033[0m" << std::endl; + std::cout << std::endl; + std::cout << "\033[1mCOMMANDS:\033[0m" << std::endl; + std::cout << std::endl; + std::cout << " \033[1mdungeon export\033[0m" << std::endl; + std::cout << " Export dungeon room data to JSON" << std::endl; + std::cout << " Example: z3ed dungeon export --room=5 --rom=zelda3.sfc" << std::endl; + std::cout << std::endl; + std::cout << " \033[1mdungeon import\033[0m" << std::endl; + std::cout << " Import dungeon data from JSON" << std::endl; + std::cout << " Example: z3ed dungeon import --file=room5.json --rom=zelda3.sfc" << std::endl; + std::cout << std::endl; + + } else if (category == "gfx" || category == "graphics") { + std::cout << "\033[1m\033[36m🎨 GRAPHICS COMMANDS\033[0m" << std::endl; + std::cout << std::endl; + std::cout << "\033[1mCOMMANDS:\033[0m" << std::endl; + std::cout << std::endl; + std::cout << " \033[1mgfx export-sheet\033[0m" << std::endl; + std::cout << " Export graphics sheet to PNG" << std::endl; + std::cout << " Example: z3ed gfx export-sheet --sheet=0 --rom=zelda3.sfc" << std::endl; + std::cout << std::endl; + std::cout << " \033[1mgfx import-sheet\033[0m" << std::endl; + std::cout << " Import graphics from PNG" << std::endl; + std::cout << " Example: z3ed gfx import-sheet --file=custom.png --rom=zelda3.sfc" << std::endl; + std::cout << std::endl; + std::cout << "\033[1mRELATED:\033[0m" << std::endl; + std::cout << " z3ed help palette Palette manipulation commands" << std::endl; + std::cout << std::endl; + + } else if (category == "palette") { + std::cout << "\033[1m\033[36m🎨 PALETTE COMMANDS\033[0m" << std::endl; + std::cout << std::endl; + std::cout << "\033[1mCOMMANDS:\033[0m" << std::endl; + std::cout << std::endl; + std::cout << " \033[1mpalette export\033[0m" << std::endl; + std::cout << " Export palette data" << std::endl; + std::cout << " Example: z3ed palette export --palette=0 --rom=zelda3.sfc" << std::endl; + std::cout << std::endl; + std::cout << " \033[1mpalette import\033[0m" << std::endl; + std::cout << " Import palette data" << std::endl; + std::cout << " Example: z3ed palette import --file=colors.pal --rom=zelda3.sfc" << std::endl; + std::cout << std::endl; + + } else { + std::cout << "\033[1m\033[31mUnknown category: " << category << "\033[0m" << std::endl; + std::cout << std::endl; + std::cout << "Available categories: agent, patch, rom, overworld, dungeon, gfx, palette" << std::endl; + std::cout << std::endl; + std::cout << "Use 'z3ed --help' to see all commands." << std::endl; + } +} + absl::Status ModernCLI::Run(int argc, char* argv[]) { if (argc < 2) { ShowHelp(); @@ -332,6 +538,16 @@ absl::Status ModernCLI::Run(int argc, char* argv[]) { args.emplace_back(argv[i]); } + // Handle "help " command + if (args.size() >= 1 && args[0] == "help") { + if (args.size() == 1) { + ShowHelp(); + return absl::OkStatus(); + } + ShowCategoryHelp(args[1]); + return absl::OkStatus(); + } + const CommandInfo* command_info = nullptr; size_t consumed_tokens = 0; diff --git a/src/cli/modern_cli.h b/src/cli/modern_cli.h index b69d6f05..8a5bb12f 100644 --- a/src/cli/modern_cli.h +++ b/src/cli/modern_cli.h @@ -31,6 +31,7 @@ class ModernCLI { private: void SetupCommands(); void ShowHelp(); + void ShowCategoryHelp(const std::string& category); // Command Handlers absl::Status HandleAsarPatchCommand(const std::vector& args); diff --git a/src/cli/service/ai/gemini_ai_service.cc b/src/cli/service/ai/gemini_ai_service.cc index a07f3cad..8763e0b4 100644 --- a/src/cli/service/ai/gemini_ai_service.cc +++ b/src/cli/service/ai/gemini_ai_service.cc @@ -42,15 +42,20 @@ static void InitializeOpenSSL() { namespace yaze { namespace cli { -GeminiAIService::GeminiAIService(const GeminiConfig& config) +GeminiAIService::GeminiAIService(const GeminiConfig& config) : config_(config), function_calling_enabled_(config.use_function_calling) { - std::cerr << "πŸ”§ GeminiAIService constructor: start" << std::endl; - std::cerr << "πŸ”§ Function calling: " << (function_calling_enabled_ ? "enabled" : "disabled (JSON output mode)") << std::endl; - std::cerr << "πŸ”§ Prompt version: " << config_.prompt_version << std::endl; + if (config_.verbose) { + std::cerr << "[DEBUG] Initializing Gemini service..." << std::endl; + std::cerr << "[DEBUG] Function calling: " << (function_calling_enabled_ ? "enabled" : "disabled") << std::endl; + std::cerr << "[DEBUG] Prompt version: " << config_.prompt_version << std::endl; + } #ifdef CPPHTTPLIB_OPENSSL_SUPPORT // Initialize OpenSSL for HTTPS support InitializeOpenSSL(); + if (config_.verbose) { + std::cerr << "[DEBUG] OpenSSL initialized for HTTPS" << std::endl; + } #endif // Load command documentation into prompt builder with specified version @@ -62,10 +67,14 @@ GeminiAIService::GeminiAIService(const GeminiConfig& config) << status.message() << std::endl; } - std::cerr << "πŸ”§ GeminiAIService: loaded catalogue" << std::endl; + if (config_.verbose) { + std::cerr << "[DEBUG] Loaded prompt catalogue" << std::endl; + } if (config_.system_instruction.empty()) { - std::cerr << "πŸ”§ GeminiAIService: building system instruction" << std::endl; + if (config_.verbose) { + std::cerr << "[DEBUG] Building system instruction..." << std::endl; + } // Try to load version-specific system prompt file std::string prompt_file = config_.prompt_version == "v2" @@ -85,7 +94,9 @@ GeminiAIService::GeminiAIService(const GeminiConfig& config) std::stringstream buffer; buffer << file.rdbuf(); config_.system_instruction = buffer.str(); - std::cerr << "βœ“ Loaded prompt from: " << path << std::endl; + if (config_.verbose) { + std::cerr << "[DEBUG] Loaded prompt: " << path << std::endl; + } loaded = true; break; } @@ -99,10 +110,11 @@ GeminiAIService::GeminiAIService(const GeminiConfig& config) config_.system_instruction = BuildSystemInstruction(); } } - std::cerr << "πŸ”§ GeminiAIService: system instruction built" << std::endl; } - std::cerr << "πŸ”§ GeminiAIService constructor: complete" << std::endl; + if (config_.verbose) { + std::cerr << "[DEBUG] Gemini service initialized" << std::endl; + } } void GeminiAIService::EnableFunctionCalling(bool enable) { @@ -186,7 +198,9 @@ absl::Status GeminiAIService::CheckAvailability() { "Gemini AI service requires JSON support. Build with -DYAZE_WITH_JSON=ON"); #else try { - std::cerr << "πŸ”§ CheckAvailability: start" << std::endl; + if (config_.verbose) { + std::cerr << "[DEBUG] CheckAvailability: start" << std::endl; + } if (config_.api_key.empty()) { return absl::FailedPreconditionError( @@ -195,23 +209,33 @@ absl::Status GeminiAIService::CheckAvailability() { " Get your API key at: https://makersuite.google.com/app/apikey"); } - std::cerr << "πŸ”§ CheckAvailability: creating HTTPS client" << std::endl; + if (config_.verbose) { + std::cerr << "[DEBUG] CheckAvailability: creating HTTPS client" << std::endl; + } // Test API connectivity with a simple request httplib::Client cli("https://generativelanguage.googleapis.com"); - std::cerr << "πŸ”§ CheckAvailability: client created" << std::endl; + if (config_.verbose) { + std::cerr << "[DEBUG] CheckAvailability: client created" << std::endl; + } cli.set_connection_timeout(5, 0); // 5 seconds timeout - std::cerr << "πŸ”§ CheckAvailability: building endpoint" << std::endl; + if (config_.verbose) { + std::cerr << "[DEBUG] CheckAvailability: building endpoint" << std::endl; + } std::string test_endpoint = "/v1beta/models/" + config_.model; httplib::Headers headers = { {"x-goog-api-key", config_.api_key}, }; - std::cerr << "πŸ”§ CheckAvailability: making request to " << test_endpoint << std::endl; + if (config_.verbose) { + std::cerr << "[DEBUG] CheckAvailability: making request to " << test_endpoint << std::endl; + } auto res = cli.Get(test_endpoint.c_str(), headers); - std::cerr << "πŸ”§ CheckAvailability: got response" << std::endl; + if (config_.verbose) { + std::cerr << "[DEBUG] CheckAvailability: got response" << std::endl; + } if (!res) { return absl::UnavailableError( @@ -238,10 +262,14 @@ absl::Status GeminiAIService::CheckAvailability() { return absl::OkStatus(); } catch (const std::exception& e) { - std::cerr << "πŸ”§ CheckAvailability: EXCEPTION: " << e.what() << std::endl; + if (config_.verbose) { + std::cerr << "[DEBUG] CheckAvailability: EXCEPTION: " << e.what() << std::endl; + } return absl::InternalError(absl::StrCat("Exception during availability check: ", e.what())); } catch (...) { - std::cerr << "πŸ”§ CheckAvailability: UNKNOWN EXCEPTION" << std::endl; + if (config_.verbose) { + std::cerr << "[DEBUG] CheckAvailability: UNKNOWN EXCEPTION" << std::endl; + } return absl::InternalError("Unknown exception during availability check"); } #endif @@ -276,7 +304,9 @@ absl::StatusOr GeminiAIService::GenerateResponse( } try { - std::cerr << "πŸ”§ GenerateResponse: using curl for HTTPS request" << std::endl; + if (config_.verbose) { + std::cerr << "[DEBUG] Using curl for HTTPS request" << std::endl; + } // Build request with proper Gemini API v1beta format nlohmann::json request_body = { @@ -328,17 +358,19 @@ absl::StatusOr GeminiAIService::GenerateResponse( "-H 'x-goog-api-key: " + config_.api_key + "' " "-d @" + temp_file + " 2>&1"; - std::cerr << "πŸ”§ Executing curl request..." << std::endl; + if (config_.verbose) { + std::cerr << "[DEBUG] Executing API request..." << std::endl; + } FILE* pipe = popen(curl_cmd.c_str(), "r"); if (!pipe) { return absl::InternalError("Failed to execute curl command"); } - std::string response_body; + std::string response_str; char buffer[4096]; while (fgets(buffer, sizeof(buffer), pipe) != nullptr) { - response_body += buffer; + response_str += buffer; } int status = pclose(pipe); @@ -348,25 +380,30 @@ absl::StatusOr GeminiAIService::GenerateResponse( return absl::InternalError(absl::StrCat("Curl failed with status ", status)); } - if (response_body.empty()) { + if (response_str.empty()) { return absl::InternalError("Empty response from Gemini API"); } // Debug: print response - const char* verbose_env = std::getenv("Z3ED_VERBOSE"); - if (verbose_env && std::string(verbose_env) == "1") { + if (config_.verbose) { std::cout << "\n" << "\033[35m" << "πŸ” Raw Gemini API Response:" << "\033[0m" << "\n" - << "\033[2m" << response_body.substr(0, 500) << "\033[0m" << "\n\n"; + << "\033[2m" << response_str.substr(0, 500) << "\033[0m" << "\n\n"; } - std::cerr << "πŸ”§ Got response, parsing..." << std::endl; - return ParseGeminiResponse(response_body); + if (config_.verbose) { + std::cerr << "[DEBUG] Parsing response..." << std::endl; + } + return ParseGeminiResponse(response_str); } catch (const std::exception& e) { - std::cerr << "πŸ”§ GenerateResponse: EXCEPTION: " << e.what() << std::endl; + if (config_.verbose) { + std::cerr << "[ERROR] Exception: " << e.what() << std::endl; + } return absl::InternalError(absl::StrCat("Exception during generation: ", e.what())); } catch (...) { - std::cerr << "πŸ”§ GenerateResponse: UNKNOWN EXCEPTION" << std::endl; + if (config_.verbose) { + std::cerr << "[ERROR] Unknown exception" << std::endl; + } return absl::InternalError("Unknown exception during generation"); } #endif @@ -374,34 +411,34 @@ absl::StatusOr GeminiAIService::GenerateResponse( absl::StatusOr GeminiAIService::ParseGeminiResponse( const std::string& response_body) { -#ifdef YAZE_WITH_JSON +#ifndef YAZE_WITH_JSON + return absl::UnimplementedError("JSON support required"); +#else AgentResponse agent_response; - try { - nlohmann::json response_json = nlohmann::json::parse(response_body); - - // Navigate Gemini's response structure - if (!response_json.contains("candidates") || - response_json["candidates"].empty()) { - return absl::InternalError("❌ No candidates in Gemini response"); + auto response_json = nlohmann::json::parse(response_body, nullptr, false); + if (response_json.is_discarded()) { + return absl::InternalError("❌ Failed to parse Gemini response JSON"); + } + + // Navigate Gemini's response structure + if (!response_json.contains("candidates") || + response_json["candidates"].empty()) { + return absl::InternalError("❌ No candidates in Gemini response"); + } + + for (const auto& candidate : response_json["candidates"]) { + if (!candidate.contains("content") || + !candidate["content"].contains("parts")) { + continue; } - for (const auto& candidate : response_json["candidates"]) { - if (!candidate.contains("content") || - !candidate["content"].contains("parts")) { - continue; - } - - for (const auto& part : candidate["content"]["parts"]) { - if (!part.contains("text")) { - continue; - } - + for (const auto& part : candidate["content"]["parts"]) { + if (part.contains("text")) { std::string text_content = part["text"].get(); // Debug: Print raw LLM output when verbose mode is enabled - const char* verbose_env = std::getenv("Z3ED_VERBOSE"); - if (verbose_env && std::string(verbose_env) == "1") { + if (config_.verbose) { std::cout << "\n" << "\033[35m" << "πŸ” Raw LLM Response:" << "\033[0m" << "\n" << "\033[2m" << text_content << "\033[0m" << "\n\n"; } @@ -418,39 +455,22 @@ absl::StatusOr GeminiAIService::ParseGeminiResponse( } text_content = std::string(absl::StripAsciiWhitespace(text_content)); - // Parse as JSON object - try { - nlohmann::json response_json = nlohmann::json::parse(text_content); - if (response_json.contains("text_response") && - response_json["text_response"].is_string()) { + // Try to parse as JSON object + auto parsed_text = nlohmann::json::parse(text_content, nullptr, false); + if (!parsed_text.is_discarded()) { + if (parsed_text.contains("text_response") && + parsed_text["text_response"].is_string()) { agent_response.text_response = - response_json["text_response"].get(); + parsed_text["text_response"].get(); } - if (response_json.contains("reasoning") && - response_json["reasoning"].is_string()) { + if (parsed_text.contains("reasoning") && + parsed_text["reasoning"].is_string()) { agent_response.reasoning = - response_json["reasoning"].get(); + parsed_text["reasoning"].get(); } - if (response_json.contains("tool_calls") && - response_json["tool_calls"].is_array()) { - for (const auto& call : response_json["tool_calls"]) { - if (call.contains("tool_name") && call["tool_name"].is_string()) { - ToolCall tool_call; - tool_call.tool_name = call["tool_name"].get(); - if (call.contains("args") && call["args"].is_object()) { - for (auto& [key, value] : call["args"].items()) { - if (value.is_string()) { - tool_call.args[key] = value.get(); - } - } - } - agent_response.tool_calls.push_back(tool_call); - } - } - } - if (response_json.contains("commands") && - response_json["commands"].is_array()) { - for (const auto& cmd : response_json["commands"]) { + if (parsed_text.contains("commands") && + parsed_text["commands"].is_array()) { + for (const auto& cmd : parsed_text["commands"]) { if (cmd.is_string()) { std::string command = cmd.get(); if (absl::StartsWith(command, "z3ed ")) { @@ -460,8 +480,8 @@ absl::StatusOr GeminiAIService::ParseGeminiResponse( } } } - } catch (const nlohmann::json::exception& inner_e) { - // If parsing the full object fails, fallback to just commands + } else { + // If parsing the full object fails, fallback to extracting commands from text std::vector lines = absl::StrSplit(text_content, '\n'); for (const auto& line : lines) { std::string trimmed = std::string(absl::StripAsciiWhitespace(line)); @@ -478,11 +498,24 @@ absl::StatusOr GeminiAIService::ParseGeminiResponse( } } } + } else if (part.contains("functionCall")) { + const auto& call = part["functionCall"]; + if (call.contains("name") && call["name"].is_string()) { + ToolCall tool_call; + tool_call.tool_name = call["name"].get(); + if (call.contains("args") && call["args"].is_object()) { + for (auto& [key, value] : call["args"].items()) { + if (value.is_string()) { + tool_call.args[key] = value.get(); + } else if (value.is_number()) { + tool_call.args[key] = std::to_string(value.get()); + } + } + } + agent_response.tool_calls.push_back(tool_call); + } } } - } catch (const nlohmann::json::exception& e) { - return absl::InternalError( - absl::StrCat("❌ Failed to parse Gemini response: ", e.what())); } if (agent_response.text_response.empty() && @@ -495,8 +528,6 @@ absl::StatusOr GeminiAIService::ParseGeminiResponse( } return agent_response; -#else - return absl::UnimplementedError("JSON support required"); #endif } diff --git a/src/cli/service/ai/gemini_ai_service.h b/src/cli/service/ai/gemini_ai_service.h index 69e5f32b..4c869455 100644 --- a/src/cli/service/ai/gemini_ai_service.h +++ b/src/cli/service/ai/gemini_ai_service.h @@ -21,6 +21,7 @@ struct GeminiConfig { bool use_enhanced_prompting = true; // Enable few-shot examples bool use_function_calling = false; // Use native Gemini function calling std::string prompt_version = "default"; // Which prompt file to use (default, v2, etc.) + bool verbose = false; // Enable debug logging GeminiConfig() = default; explicit GeminiConfig(const std::string& key) : api_key(key) {} diff --git a/src/cli/service/ai/service_factory.cc b/src/cli/service/ai/service_factory.cc index 26efc588..a8d7b24a 100644 --- a/src/cli/service/ai/service_factory.cc +++ b/src/cli/service/ai/service_factory.cc @@ -71,29 +71,28 @@ std::unique_ptr CreateAIService(const AIServiceConfig& config) { // Gemini provider #ifdef YAZE_WITH_JSON if (config.provider == "gemini") { - std::cerr << "πŸ”§ Creating Gemini service..." << std::endl; - if (config.gemini_api_key.empty()) { std::cerr << "⚠️ Gemini API key not provided" << std::endl; - std::cerr << " Use --gemini_api_key= or set GEMINI_API_KEY environment variable" << std::endl; + std::cerr << " Use --gemini_api_key= or GEMINI_API_KEY environment variable" << std::endl; std::cerr << " Falling back to MockAIService" << std::endl; return std::make_unique(); } - std::cerr << "πŸ”§ Building Gemini config..." << std::endl; GeminiConfig gemini_config(config.gemini_api_key); if (!config.model.empty()) { gemini_config.model = config.model; } gemini_config.prompt_version = absl::GetFlag(FLAGS_prompt_version); gemini_config.use_function_calling = absl::GetFlag(FLAGS_use_function_calling); - std::cerr << "πŸ”§ Model: " << gemini_config.model << std::endl; - std::cerr << "πŸ”§ Prompt version: " << gemini_config.prompt_version << std::endl; + gemini_config.verbose = config.verbose; + + std::cerr << "πŸ€– AI Provider: gemini" << std::endl; + std::cerr << " Model: " << gemini_config.model << std::endl; + if (config.verbose) { + std::cerr << " Prompt: " << gemini_config.prompt_version << std::endl; + } - std::cerr << "πŸ”§ Creating Gemini service instance..." << std::endl; auto service = std::make_unique(gemini_config); - - std::cerr << "πŸ”§ Skipping availability check (causes segfault with SSL)" << std::endl; // Health check - DISABLED due to SSL issues // if (auto status = service->CheckAvailability(); !status.ok()) { // std::cerr << "⚠️ Gemini unavailable: " << status.message() << std::endl; @@ -102,7 +101,9 @@ std::unique_ptr CreateAIService(const AIServiceConfig& config) { // } std::cout << " Using model: " << gemini_config.model << std::endl; - std::cerr << "πŸ”§ Gemini service ready" << std::endl; + if (config.verbose) { + std::cerr << "[DEBUG] Gemini service ready" << std::endl; + } return service; } #else diff --git a/src/cli/service/ai/service_factory.h b/src/cli/service/ai/service_factory.h index cd94dc07..d5f2dac0 100644 --- a/src/cli/service/ai/service_factory.h +++ b/src/cli/service/ai/service_factory.h @@ -14,6 +14,7 @@ struct AIServiceConfig { std::string model; // Provider-specific model name std::string gemini_api_key; // For Gemini std::string ollama_host = "http://localhost:11434"; // For Ollama + bool verbose = false; // Enable debug logging }; // Create AI service using command-line flags