diff --git a/.gitignore b/.gitignore index 76b4bce0..0642e2f4 100644 --- a/.gitignore +++ b/.gitignore @@ -17,4 +17,32 @@ src/app/emu/cpu/internal/old_cpu.cc build-windows src/lib/libpng src/lib/zlib -assets/layouts/ow_toolset.zeml \ No newline at end of file +assets/layouts/ow_toolset.zeml +build_minimal/ +build_test/ +src/lib/cmake/glew/CopyImportedTargetProperties.cmake +src/lib/cmake/glew/glew-config.cmake +src/lib/cmake/glew/glew-targets-release.cmake +src/lib/cmake/glew/glew-targets.cmake +src/lib/cmake/SDL2/sdl2-config-version.cmake +src/lib/cmake/SDL2/sdl2-config.cmake +src/lib/GL/glew.h +src/lib/GL/glxew.h +src/lib/GL/wglew.h +src/lib/libGLEW.2.2.0.dylib +src/lib/libGLEW.2.2.dylib +src/lib/libGLEW.a +src/lib/libGLEW.dylib +src/lib/libSDL2_test.a +src/lib/libSDL2-2.0.0.dylib +src/lib/libSDL2.a +src/lib/libSDL2.dylib +src/lib/libSDL2main.a +src/app/gui/modules/component.h +src/ios/macOS/Info-macOS.plist +src/ios/macOS/MainMenu.storyboard +yaze_log.txt +build_test/ +compile_commands.json +build*/ +*.sfc \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 6398e879..76fa7d11 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -55,9 +55,17 @@ option(YAZE_ENABLE_EXPERIMENTAL_TESTS "Enable experimental/unstable tests" ON) option(YAZE_ENABLE_UI_TESTS "Enable ImGui Test Engine UI testing" ON) option(YAZE_MINIMAL_BUILD "Minimal build for CI (disable optional features)" OFF) +# Optional JSON support (required for Gemini and structured agent responses) +option(YAZE_WITH_JSON "Enable JSON support for AI integrations" OFF) + # Optional gRPC support for ImGuiTestHarness (z3ed agent mode) option(YAZE_WITH_GRPC "Enable gRPC-based ImGuiTestHarness for automated GUI testing (experimental)" OFF) +if(YAZE_WITH_GRPC AND NOT YAZE_WITH_JSON) + message(STATUS "Enabling JSON support because gRPC is enabled") + set(YAZE_WITH_JSON ON CACHE BOOL "Enable JSON support" FORCE) +endif() + # Configure minimal builds for CI/CD if(YAZE_MINIMAL_BUILD) set(YAZE_ENABLE_UI_TESTS OFF CACHE BOOL "Disabled for minimal build" FORCE) @@ -120,6 +128,12 @@ endif() # Create a common interface target for shared settings add_library(yaze_common INTERFACE) target_compile_features(yaze_common INTERFACE cxx_std_23) +target_include_directories(yaze_common INTERFACE ${CMAKE_SOURCE_DIR}/third_party/httplib) + +if(YAZE_WITH_JSON) + set(JSON_BuildTests OFF CACHE INTERNAL "Disable nlohmann_json tests") + add_subdirectory(${CMAKE_SOURCE_DIR}/third_party/json ${CMAKE_BINARY_DIR}/third_party/json EXCLUDE_FROM_ALL) +endif() # Platform-specific configurations if(YAZE_PLATFORM_LINUX) @@ -180,7 +194,6 @@ else() target_compile_definitions(yaze_common INTERFACE _SILENCE_CXX23_DEPRECATION_WARNING _SILENCE_ALL_CXX23_DEPRECATION_WARNINGS - ABSL_HAVE_INTRINSIC_INT128=1 # Enable intrinsic int128 support ) endif() diff --git a/docs/z3ed/AGENT-ROADMAP.md b/docs/z3ed/AGENT-ROADMAP.md index 89669451..efc187a7 100644 --- a/docs/z3ed/AGENT-ROADMAP.md +++ b/docs/z3ed/AGENT-ROADMAP.md @@ -97,3 +97,24 @@ This vision will be realized through a shared interface available in both the `z 6. **Performance and Cost-Saving**: - Implement a response cache to reduce latency and API costs. - Add token usage tracking and reporting. + +## Current Status & Next Steps (As of Oct 3, Session 2) + +We have made significant progress in laying the foundation for the conversational agent. + +### ✅ Completed +- **Initial `ConversationalAgentService`**: The basic service is in place. +- **TUI Chat Stub**: A functional `agent chat` command exists. +- **GUI Chat Widget Stub**: An `AgentChatWidget` is integrated into the main GUI. +- **Initial Agent "Tools"**: `resource-list` and `dungeon-list-sprites` commands are implemented. +- **Tool Use Foundation**: The `ToolDispatcher` is implemented, and the AI services are aware of the new tool call format. + +### ⚠️ Current Blocker: Build Configuration +We are currently facing a linker error when building the main `yaze` application with gRPC support. The `ToolDispatcher` is unable to find the definitions for the `HandleResourceListCommand` and `HandleDungeonListSpritesCommand` functions. + +**Root Cause**: These handler functions are only compiled as part of the `z3ed` target, not the `yaze` target. The `ToolDispatcher`, which is now included in the `yaze` build, depends on them. + +### 🚀 Next Steps +1. **Resolve the Build Issue**: The immediate next step is to fix the linker error. This will likely involve a thoughtful refactoring of our CMake configuration to better share sources between the `yaze` and `z3ed` targets. +2. **Simplify CMake Structure**: As discussed, the current structure of including `.cmake` files from various subdirectories is becoming difficult to manage. We should consider flattening this into a more centralized source list in the main `src/CMakeLists.txt`. +3. **Continue with Tool Integration**: Once the build is fixed, we can proceed with integrating the tool execution results back into the conversational loop. diff --git a/docs/z3ed/README.md b/docs/z3ed/README.md index baeb408a..73e6e786 100644 --- a/docs/z3ed/README.md +++ b/docs/z3ed/README.md @@ -116,15 +116,6 @@ Here are some example prompts you can try with either Ollama or Gemini: 2. **[E6-z3ed-cli-design.md](E6-z3ed-cli-design.md)** - Detailed architecture and design philosophy. 3. **[E6-z3ed-reference.md](E6-z3ed-reference.md)** - Complete command reference and API documentation. -### Quick References -- **[QUICK_REFERENCE.md](QUICK_REFERENCE.md)** - Condensed command cheatsheet. -- **[QUICK-START-GEMINI.md](QUICK-START-GEMINI.md)** - Gemini API setup and testing guide. - -### Implementation Guides -- **[LLM-INTEGRATION-PLAN.md](LLM-INTEGRATION-PLAN.md)** - (Archive) Original LLM integration roadmap. -- **[IT-05-IMPLEMENTATION-GUIDE.md](IT-05-IMPLEMENTATION-GUIDE.md)** - Test introspection API (complete ✅). -- **[IT-08-IMPLEMENTATION-GUIDE.md](IT-08-IMPLEMENTATION-GUIDE.md)** - Enhanced error reporting (complete ✅). - ## Current Status (October 2025) The project is currently focused on implementing a conversational AI agent. See [AGENT-ROADMAP.md](AGENT-ROADMAP.md) for a detailed breakdown of what's complete, in progress, and planned. diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 3cada243..ce971178 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -83,6 +83,10 @@ else() message(STATUS "NFD disabled for minimal build") endif() +if(YAZE_BUILD_APP OR YAZE_BUILD_Z3ED) + include(cli/agent.cmake) +endif() + if (YAZE_BUILD_APP) include(app/app.cmake) endif() @@ -171,9 +175,6 @@ if (YAZE_BUILD_LIB) ${YAZE_APP_EMU_SRC} ${YAZE_GUI_SRC} ${YAZE_UTIL_SRC} - # CLI service sources (needed for ProposalDrawer) - cli/service/planning/proposal_registry.cc - cli/service/rom/rom_sandbox_manager.cc # cli/service/gui_automation_client.cc # Moved to yaze_c cli/service/testing/test_workflow_generator.cc ) @@ -225,6 +226,7 @@ if (YAZE_BUILD_LIB) target_link_libraries( yaze_core PUBLIC asar-static + yaze_agent ${ABSL_TARGETS} ${SDL_TARGETS} ${CMAKE_DL_LIBS} diff --git a/src/app/app.cmake b/src/app/app.cmake index cc72c10f..9abf26c2 100644 --- a/src/app/app.cmake +++ b/src/app/app.cmake @@ -18,10 +18,6 @@ if (APPLE) ${YAZE_UTIL_SRC} ${YAZE_GUI_SRC} ${IMGUI_SRC} - # CLI service sources (needed for ProposalDrawer) - cli/service/planning/proposal_registry.cc - cli/service/rom/rom_sandbox_manager.cc - cli/service/planning/policy_evaluator.cc # Bundled Resources ${YAZE_RESOURCE_FILES} ) @@ -57,10 +53,6 @@ else() ${YAZE_UTIL_SRC} ${YAZE_GUI_SRC} ${IMGUI_SRC} - # CLI service sources (needed for ProposalDrawer) - cli/service/planning/proposal_registry.cc - cli/service/rom/rom_sandbox_manager.cc - cli/service/planning/policy_evaluator.cc ) # Add asset files for Windows/Linux builds @@ -133,6 +125,8 @@ target_link_libraries( ImGui ) +target_link_libraries(yaze PRIVATE yaze_agent) + # Enable policy framework in main yaze target target_compile_definitions(yaze PRIVATE YAZE_ENABLE_POLICY_FRAMEWORK=1) @@ -274,20 +268,6 @@ if(YAZE_WITH_GRPC) ${CMAKE_SOURCE_DIR}/src/app/core/testing/test_script_parser.cc ${CMAKE_SOURCE_DIR}/src/app/core/testing/test_script_parser.h) - # Add AI agent sources - target_sources(yaze PRIVATE - ${CMAKE_SOURCE_DIR}/src/cli/service/agent/conversational_agent_service.cc - ${CMAKE_SOURCE_DIR}/src/cli/service/ai/service_factory.cc - ${CMAKE_SOURCE_DIR}/src/cli/service/ai/ai_service.cc - ${CMAKE_SOURCE_DIR}/src/cli/service/ai/ollama_ai_service.cc - ${CMAKE_SOURCE_DIR}/src/cli/service/ai/gemini_ai_service.cc - ${CMAKE_SOURCE_DIR}/src/cli/service/ai/prompt_builder.cc - ${CMAKE_SOURCE_DIR}/src/cli/service/planning/tile16_proposal_generator.cc - ${CMAKE_SOURCE_DIR}/src/cli/service/resources/resource_context_builder.cc - ${CMAKE_SOURCE_DIR}/src/cli/service/resources/resource_catalog.cc - ${CMAKE_SOURCE_DIR}/src/cli/service/agent/tool_dispatcher.cc - ) - # Link gRPC libraries target_link_libraries(yaze PRIVATE grpc++ diff --git a/src/cli/agent.cmake b/src/cli/agent.cmake new file mode 100644 index 00000000..e5855fb4 --- /dev/null +++ b/src/cli/agent.cmake @@ -0,0 +1,46 @@ +set(YAZE_AGENT_SOURCES + cli/handlers/agent/tool_commands.cc + cli/service/agent/conversational_agent_service.cc + cli/service/agent/tool_dispatcher.cc + cli/service/ai/ai_service.cc + cli/service/ai/ollama_ai_service.cc + cli/service/ai/prompt_builder.cc + cli/service/ai/service_factory.cc + cli/service/planning/policy_evaluator.cc + cli/service/planning/proposal_registry.cc + cli/service/planning/tile16_proposal_generator.cc + cli/service/resources/resource_catalog.cc + cli/service/resources/resource_context_builder.cc + cli/service/rom/rom_sandbox_manager.cc +) + +if(YAZE_WITH_JSON) + list(APPEND YAZE_AGENT_SOURCES cli/service/ai/gemini_ai_service.cc) +endif() + +add_library(yaze_agent STATIC ${YAZE_AGENT_SOURCES}) + +target_link_libraries(yaze_agent + PUBLIC + yaze_common + ${ABSL_TARGETS} +) + +target_include_directories(yaze_agent + PUBLIC + ${CMAKE_SOURCE_DIR}/src + ${CMAKE_SOURCE_DIR}/incl + ${CMAKE_SOURCE_DIR}/third_party/httplib + ${CMAKE_SOURCE_DIR}/src/lib +) + +if(SDL2_INCLUDE_DIR) + target_include_directories(yaze_agent PUBLIC ${SDL2_INCLUDE_DIR}) +endif() + +if(YAZE_WITH_JSON) + target_link_libraries(yaze_agent PUBLIC nlohmann_json::nlohmann_json) + target_compile_definitions(yaze_agent PUBLIC YAZE_WITH_JSON) +endif() + +set_target_properties(yaze_agent PROPERTIES POSITION_INDEPENDENT_CODE ON) diff --git a/src/cli/handlers/agent/general_commands.cc b/src/cli/handlers/agent/general_commands.cc index 5741bf9b..39eecee9 100644 --- a/src/cli/handlers/agent/general_commands.cc +++ b/src/cli/handlers/agent/general_commands.cc @@ -470,160 +470,6 @@ absl::Status HandleDescribeCommand(const std::vector& arg_vec) { return absl::OkStatus(); } -absl::Status HandleResourceListCommand( - const std::vector& arg_vec) { - std::string type; - std::string format = "table"; - - for (size_t i = 0; i < arg_vec.size(); ++i) { - const std::string& token = arg_vec[i]; - if (token == "--type") { - if (i + 1 < arg_vec.size()) { - type = arg_vec[++i]; - } else { - return absl::InvalidArgumentError("--type requires a value."); - } - } else if (absl::StartsWith(token, "--type=")) { - type = token.substr(7); - } else if (token == "--format") { - if (i + 1 < arg_vec.size()) { - format = arg_vec[++i]; - } else { - return absl::InvalidArgumentError("--format requires a value."); - } - } else if (absl::StartsWith(token, "--format=")) { - format = token.substr(9); - } - } - - if (type.empty()) { - return absl::InvalidArgumentError( - "Usage: agent resource-list --type [--format ]"); - } - - // 1. Load the ROM - std::string rom_path = absl::GetFlag(FLAGS_rom); - if (rom_path.empty()) { - return absl::FailedPreconditionError( - "No ROM loaded. Use --rom= to specify ROM file."); - } - Rom rom; - auto status = rom.LoadFromFile(rom_path); - if (!status.ok()) { - return absl::FailedPreconditionError(absl::StrFormat( - "Failed to load ROM from '%s': %s", rom_path, status.message())); - } - - // 2. Get labels using ResourceContextBuilder - ResourceContextBuilder context_builder(&rom); - auto labels_or = context_builder.GetLabels(type); - if (!labels_or.ok()) { - return labels_or.status(); - } - auto labels = labels_or.value(); - - // 3. Format and print output - if (format == "json") { - std::cout << "{\n"; - bool first = true; - for (const auto& [key, value] : labels) { - if (!first) { - std::cout << ",\n"; - } - std::cout << " \"" << key << "\": \"" << value << "\""; - first = false; - } - std::cout << "\n}\n"; - } else { // Table format - std::cout << "=== " << absl::AsciiStrToUpper(type) << " Labels ===\n"; - for (const auto& [key, value] : labels) { - std::cout << absl::StrFormat(" %-10s : %s\n", key, value); - } - } - - return absl::OkStatus(); -} - -absl::Status HandleDungeonListSpritesCommand( - const std::vector& arg_vec) { - std::string room_id_str; - std::string format = "table"; - - for (size_t i = 0; i < arg_vec.size(); ++i) { - const std::string& token = arg_vec[i]; - if (token == "--room") { - if (i + 1 < arg_vec.size()) { - room_id_str = arg_vec[++i]; - } else { - return absl::InvalidArgumentError("--room requires a value."); - } - } else if (absl::StartsWith(token, "--room=")) { - room_id_str = token.substr(7); - } else if (token == "--format") { - if (i + 1 < arg_vec.size()) { - format = arg_vec[++i]; - } else { - return absl::InvalidArgumentError("--format requires a value."); - } - } else if (absl::StartsWith(token, "--format=")) { - format = token.substr(9); - } - } - - if (room_id_str.empty()) { - return absl::InvalidArgumentError( - "Usage: agent dungeon-list-sprites --room [--format " - "]"); - } - - int room_id; - if (!absl::SimpleHexAtoi(room_id_str, &room_id)) { - return absl::InvalidArgumentError("Invalid room ID format. Must be hex."); - } - - // 1. Load the ROM - std::string rom_path = absl::GetFlag(FLAGS_rom); - if (rom_path.empty()) { - return absl::FailedPreconditionError( - "No ROM loaded. Use --rom= to specify ROM file."); - } - Rom rom; - auto status = rom.LoadFromFile(rom_path); - if (!status.ok()) { - return absl::FailedPreconditionError(absl::StrFormat( - "Failed to load ROM from '%s': %s", rom_path, status.message())); - } - - // 2. Load dungeon room and get sprites - auto room = zelda3::LoadRoomFromRom(&rom, room_id); - const auto& sprites = room.GetSprites(); - - // 3. Format and print output - if (format == "json") { - std::cout << "[\n"; - for (size_t i = 0; i < sprites.size(); ++i) { - const auto& sprite = sprites[i]; - std::cout << " {\n"; - std::cout << " \"id\": " << sprite.id() << ",\n"; - std::cout << " \"x\": " << sprite.x() << ",\n"; - std::cout << " \"y\": " << sprite.y() << "\n"; - std::cout << " }" << (i == sprites.size() - 1 ? "" : ","); - std::cout << "\n"; - } - std::cout << "]\n"; - } else { // Table format - std::cout << "=== Sprites in Room " << room_id_str << " ===\n"; - std::cout << absl::StrFormat("%-10s %-5s %-5s\n", "ID (Hex)", "X", "Y"); - std::cout << std::string(22, '-') << "\n"; - for (const auto& sprite : sprites) { - std::cout << absl::StrFormat("0x%-8X %-5d %-5d\n", sprite.id(), - sprite.x(), sprite.y()); - } - } - - return absl::OkStatus(); -} - absl::Status HandleChatCommand() { tui::ChatTUI chat_tui; chat_tui.Run(); diff --git a/src/cli/handlers/agent/tool_commands.cc b/src/cli/handlers/agent/tool_commands.cc new file mode 100644 index 00000000..98b9d6a7 --- /dev/null +++ b/src/cli/handlers/agent/tool_commands.cc @@ -0,0 +1,180 @@ +#include "cli/handlers/agent/commands.h" + +#include +#include +#include + +#include "absl/flags/declare.h" +#include "absl/flags/flag.h" +#include "absl/status/status.h" +#include "absl/status/statusor.h" +#include "absl/strings/ascii.h" +#include "absl/strings/match.h" +#include "absl/strings/numbers.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/str_format.h" +#include "app/rom.h" +#include "app/zelda3/dungeon/room.h" +#include "cli/service/resources/resource_context_builder.h" + +ABSL_DECLARE_FLAG(std::string, rom); + +namespace yaze { +namespace cli { +namespace agent { + +namespace { + +absl::StatusOr LoadRomFromFlag() { + std::string rom_path = absl::GetFlag(FLAGS_rom); + if (rom_path.empty()) { + return absl::FailedPreconditionError( + "No ROM loaded. Use --rom= to specify ROM file."); + } + + Rom rom; + auto status = rom.LoadFromFile(rom_path); + if (!status.ok()) { + return absl::FailedPreconditionError(absl::StrFormat( + "Failed to load ROM from '%s': %s", rom_path, status.message())); + } + + return rom; +} + +} // namespace + +absl::Status HandleResourceListCommand( + const std::vector& arg_vec) { + std::string type; + std::string format = "table"; + + for (size_t i = 0; i < arg_vec.size(); ++i) { + const std::string& token = arg_vec[i]; + if (token == "--type") { + if (i + 1 >= arg_vec.size()) { + return absl::InvalidArgumentError("--type requires a value."); + } + type = arg_vec[++i]; + } else if (absl::StartsWith(token, "--type=")) { + type = token.substr(7); + } else if (token == "--format") { + if (i + 1 >= arg_vec.size()) { + return absl::InvalidArgumentError("--format requires a value."); + } + format = arg_vec[++i]; + } else if (absl::StartsWith(token, "--format=")) { + format = token.substr(9); + } + } + + if (type.empty()) { + return absl::InvalidArgumentError( + "Usage: agent resource-list --type [--format ]"); + } + + auto rom_or = LoadRomFromFlag(); + if (!rom_or.ok()) { + return rom_or.status(); + } + Rom rom = std::move(rom_or.value()); + + ResourceContextBuilder context_builder(&rom); + auto labels_or = context_builder.GetLabels(type); + if (!labels_or.ok()) { + return labels_or.status(); + } + auto labels = std::move(labels_or.value()); + + if (format == "json") { + std::cout << "{\n"; + bool first = true; + for (const auto& [key, value] : labels) { + if (!first) { + std::cout << ",\n"; + } + std::cout << " \"" << key << "\": \"" << value << "\""; + first = false; + } + std::cout << "\n}\n"; + } else { + std::cout << "=== " << absl::AsciiStrToUpper(type) << " Labels ===\n"; + for (const auto& [key, value] : labels) { + std::cout << absl::StrFormat(" %-10s : %s\n", key, value); + } + } + + return absl::OkStatus(); +} + +absl::Status HandleDungeonListSpritesCommand( + const std::vector& arg_vec) { + std::string room_id_str; + std::string format = "table"; + + for (size_t i = 0; i < arg_vec.size(); ++i) { + const std::string& token = arg_vec[i]; + if (token == "--room") { + if (i + 1 >= arg_vec.size()) { + return absl::InvalidArgumentError("--room requires a value."); + } + room_id_str = arg_vec[++i]; + } else if (absl::StartsWith(token, "--room=")) { + room_id_str = token.substr(7); + } else if (token == "--format") { + if (i + 1 >= arg_vec.size()) { + return absl::InvalidArgumentError("--format requires a value."); + } + format = arg_vec[++i]; + } else if (absl::StartsWith(token, "--format=")) { + format = token.substr(9); + } + } + + if (room_id_str.empty()) { + return absl::InvalidArgumentError( + "Usage: agent dungeon-list-sprites --room [--format ]"); + } + + int room_id; + if (!absl::SimpleHexAtoi(room_id_str, &room_id)) { + return absl::InvalidArgumentError( + "Invalid room ID format. Must be hex."); + } + + auto rom_or = LoadRomFromFlag(); + if (!rom_or.ok()) { + return rom_or.status(); + } + Rom rom = std::move(rom_or.value()); + + auto room = zelda3::LoadRoomFromRom(&rom, room_id); + const auto& sprites = room.GetSprites(); + + if (format == "json") { + std::cout << "[\n"; + for (size_t i = 0; i < sprites.size(); ++i) { + const auto& sprite = sprites[i]; + std::cout << " {\n"; + std::cout << " \"id\": " << sprite.id() << ",\n"; + std::cout << " \"x\": " << sprite.x() << ",\n"; + std::cout << " \"y\": " << sprite.y() << "\n"; + std::cout << " }" << (i + 1 == sprites.size() ? "" : ",") << "\n"; + } + std::cout << "]\n"; + } else { + std::cout << "=== Sprites in Room " << room_id_str << " ===\n"; + std::cout << absl::StrFormat("%-10s %-5s %-5s\n", "ID (Hex)", "X", "Y"); + std::cout << std::string(22, '-') << "\n"; + for (const auto& sprite : sprites) { + std::cout << absl::StrFormat("0x%-8X %-5d %-5d\n", sprite.id(), + sprite.x(), sprite.y()); + } + } + + return absl::OkStatus(); +} + +} // namespace agent +} // namespace cli +} // namespace yaze diff --git a/src/cli/service/agent/tool_dispatcher.cc b/src/cli/service/agent/tool_dispatcher.cc index 11767037..ec8ccba3 100644 --- a/src/cli/service/agent/tool_dispatcher.cc +++ b/src/cli/service/agent/tool_dispatcher.cc @@ -1,5 +1,8 @@ #include "cli/service/agent/tool_dispatcher.h" +#include +#include + #include "absl/strings/str_format.h" #include "cli/handlers/agent/commands.h" @@ -15,24 +18,29 @@ absl::StatusOr ToolDispatcher::Dispatch( args.push_back(value); } + // Capture stdout + std::stringstream buffer; + auto old_cout_buf = std::cout.rdbuf(); + std::cout.rdbuf(buffer.rdbuf()); + + absl::Status status; if (tool_call.tool_name == "resource-list") { - // Note: This is a simplified approach for now. A more robust solution - // would capture stdout instead of relying on the handler to return a string. - auto status = HandleResourceListCommand(args); - if (!status.ok()) { - return status; - } - return "Successfully listed resources."; + status = HandleResourceListCommand(args); } else if (tool_call.tool_name == "dungeon-list-sprites") { - auto status = HandleDungeonListSpritesCommand(args); - if (!status.ok()) { - return status; - } - return "Successfully listed sprites."; + status = HandleDungeonListSpritesCommand(args); + } else { + status = absl::UnimplementedError( + absl::StrFormat("Unknown tool: %s", tool_call.tool_name)); } - return absl::UnimplementedError( - absl::StrFormat("Unknown tool: %s", tool_call.tool_name)); + // Restore stdout + std::cout.rdbuf(old_cout_buf); + + if (!status.ok()) { + return status; + } + + return buffer.str(); } } // namespace agent diff --git a/src/cli/service/ai/service_factory.cc b/src/cli/service/ai/service_factory.cc index 69e87a1f..cd76a527 100644 --- a/src/cli/service/ai/service_factory.cc +++ b/src/cli/service/ai/service_factory.cc @@ -1,23 +1,32 @@ #include "cli/service/ai/service_factory.h" +#include #include #include "cli/service/ai/ai_service.h" -#include "cli/service/ai/gemini_ai_service.h" #include "cli/service/ai/ollama_ai_service.h" +#ifdef YAZE_WITH_JSON +#include "cli/service/ai/gemini_ai_service.h" +#endif + namespace yaze { namespace cli { std::unique_ptr CreateAIService() { // Priority: Ollama (local) > Gemini (remote) > Mock (testing) const char* provider_env = std::getenv("YAZE_AI_PROVIDER"); - const char* gemini_key = std::getenv("GEMINI_API_KEY"); const char* ollama_model = std::getenv("OLLAMA_MODEL"); + const std::string provider = provider_env ? provider_env : ""; + const bool gemini_requested = provider == "gemini"; + +#ifdef YAZE_WITH_JSON + const char* gemini_key = std::getenv("GEMINI_API_KEY"); const char* gemini_model = std::getenv("GEMINI_MODEL"); +#endif // Explicit provider selection - if (provider_env && std::string(provider_env) == "ollama") { + if (provider == "ollama") { OllamaConfig config; // Allow model override via env @@ -39,6 +48,7 @@ std::unique_ptr CreateAIService() { } // Gemini if API key provided +#ifdef YAZE_WITH_JSON if (gemini_key && std::strlen(gemini_key) > 0) { GeminiConfig config(gemini_key); @@ -59,6 +69,11 @@ std::unique_ptr CreateAIService() { std::cout << "🤖 Using Gemini AI with model: " << config.model << std::endl; return service; } +#else + if (gemini_requested || std::getenv("GEMINI_API_KEY")) { + std::cerr << "⚠️ Gemini support not available: rebuild with YAZE_WITH_JSON=ON" << std::endl; + } +#endif // Default: Mock service for testing std::cout << "🤖 Using MockAIService (no LLM configured)" << std::endl; diff --git a/src/cli/z3ed.cmake b/src/cli/z3ed.cmake index c7ceb913..66d72acd 100644 --- a/src/cli/z3ed.cmake +++ b/src/cli/z3ed.cmake @@ -48,13 +48,6 @@ add_executable( cli/handlers/agent/test_common.cc cli/handlers/agent/test_commands.cc cli/handlers/agent/gui_commands.cc - cli/service/ai/ai_service.cc - cli/service/ai/ollama_ai_service.cc - cli/service/ai/prompt_builder.cc - cli/service/planning/proposal_registry.cc - cli/service/resources/resource_catalog.cc - cli/service/rom/rom_sandbox_manager.cc - cli/service/planning/policy_evaluator.cc cli/service/testing/test_suite.h cli/service/testing/test_suite_loader.cc cli/service/testing/test_suite_loader.h @@ -62,17 +55,11 @@ add_executable( cli/service/testing/test_suite_reporter.h cli/service/testing/test_suite_writer.cc cli/service/testing/test_suite_writer.h - cli/service/ai/gemini_ai_service.cc cli/service/planning/tile16_proposal_generator.h - cli/service/planning/tile16_proposal_generator.cc cli/service/resources/resource_context_builder.h - cli/service/resources/resource_context_builder.cc cli/service/agent/conversational_agent_service.h - cli/service/agent/conversational_agent_service.cc cli/service/ai/service_factory.h - cli/service/ai/service_factory.cc cli/service/agent/tool_dispatcher.h - cli/service/agent/tool_dispatcher.cc app/rom.cc app/core/project.cc app/core/asar_wrapper.cc @@ -85,13 +72,8 @@ add_executable( ${IMGUI_SRC} ) -option(YAZE_WITH_JSON "Build with JSON support" OFF) if(YAZE_WITH_JSON) - add_subdirectory(${CMAKE_SOURCE_DIR}/third_party/json ${CMAKE_BINARY_DIR}/third_party/json) target_compile_definitions(z3ed PRIVATE YAZE_WITH_JSON) - target_link_libraries(z3ed PRIVATE nlohmann_json::nlohmann_json) - list(APPEND Z3ED_SRC_FILES cli/service/ai/gemini_ai_service.cc) - list(APPEND Z3ED_SRC_FILES cli/service/ai/prompt_builder.cc) endif() # ============================================================================ @@ -144,6 +126,7 @@ target_include_directories( target_link_libraries( z3ed PUBLIC asar-static + yaze_agent ftxui::component ftxui::screen ftxui::dom