diff --git a/assets/agent/function_schemas.json b/assets/agent/function_schemas.json index 42341bad..8d8bc06e 100644 --- a/assets/agent/function_schemas.json +++ b/assets/agent/function_schemas.json @@ -34,6 +34,87 @@ "required": ["type", "query"] } }, + { + "name": "emulator-press-buttons", + "description": "Presses and immediately releases one or more buttons on the SNES controller.", + "parameters": { + "type": "object", + "properties": { + "buttons": { + "type": "string", + "description": "A comma-separated list of buttons to press (e.g., 'A,Right,Start')." + } + }, + "required": ["buttons"] + } + }, + { + "name": "emulator-hold-buttons", + "description": "Holds down one or more buttons for a specified duration.", + "parameters": { + "type": "object", + "properties": { + "buttons": { + "type": "string", + "description": "A comma-separated list of buttons to hold." + }, + "duration": { + "type": "integer", + "description": "The duration in milliseconds to hold the buttons." + } + }, + "required": ["buttons", "duration"] + } + }, + { + "name": "emulator-get-state", + "description": "Retrieves the current state of the game from the emulator.", + "parameters": { + "type": "object", + "properties": { + "screenshot": { + "type": "boolean", + "description": "Whether to include a screenshot of the current frame." + } + } + } + }, + { + "name": "emulator-read-memory", + "description": "Reads a block of memory from the SNES WRAM.", + "parameters": { + "type": "object", + "properties": { + "address": { + "type": "string", + "description": "The memory address to read from (e.g., '0x7E0010')." + }, + "length": { + "type": "integer", + "description": "The number of bytes to read." + } + }, + "required": ["address"] + } + }, + { + "name": "emulator-write-memory", + "description": "Writes a block of data to the SNES WRAM.", + "parameters": { + "type": "object", + "properties": { + "address": { + "type": "string", + "description": "The memory address to write to." + }, + "data": { + "type": "string", + "description": "The data to write, as a hex string (e.g., 'AABBCCDD')." + } + }, + "required": ["address", "data"] + } + }, { "name": "dungeon-list-sprites", "description": "List all sprites in a specific dungeon or room", diff --git a/src/cli/agent.cmake b/src/cli/agent.cmake index 3a5e29c0..8b2d40ef 100644 --- a/src/cli/agent.cmake +++ b/src/cli/agent.cmake @@ -1,7 +1,9 @@ set(YAZE_AGENT_SOURCES cli/service/agent/proposal_executor.cc cli/handlers/agent/todo_commands.cc + cli/service/agent/agent_control_server.cc cli/service/agent/conversational_agent_service.cc + cli/service/agent/emulator_service_impl.cc cli/service/agent/simple_chat_session.cc cli/service/agent/enhanced_tui.cc cli/service/agent/tool_dispatcher.cc @@ -111,7 +113,8 @@ if(YAZE_WITH_GRPC) # Generate proto files for yaze_agent target_add_protobuf(yaze_agent ${PROJECT_SOURCE_DIR}/src/protos/imgui_test_harness.proto - ${PROJECT_SOURCE_DIR}/src/protos/canvas_automation.proto) + ${PROJECT_SOURCE_DIR}/src/protos/canvas_automation.proto + ${PROJECT_SOURCE_DIR}/src/protos/emulator_service.proto) target_link_libraries(yaze_agent PUBLIC grpc++ diff --git a/src/cli/flags.cc b/src/cli/flags.cc index f713ff42..7ed4f79e 100644 --- a/src/cli/flags.cc +++ b/src/cli/flags.cc @@ -21,3 +21,7 @@ ABSL_FLAG(std::string, prompt_version, "default", "Prompt version to use: 'default' or 'v2'"); ABSL_FLAG(bool, use_function_calling, false, "Enable native Gemini function calling (incompatible with JSON output mode)"); + +// --- Agent Control Flags --- +ABSL_FLAG(bool, agent_control, false, + "Enable the gRPC server to allow the agent to control the emulator."); diff --git a/src/cli/handlers/tools/emulator_commands.cc b/src/cli/handlers/tools/emulator_commands.cc index b6fbd2d2..df4ca0a3 100644 --- a/src/cli/handlers/tools/emulator_commands.cc +++ b/src/cli/handlers/tools/emulator_commands.cc @@ -1,35 +1,256 @@ #include "cli/handlers/tools/emulator_commands.h" -#include "absl/strings/numbers.h" -#include "absl/strings/str_format.h" +#include +#include "protos/emulator_service.grpc.pb.h" +#include "absl/strings/str_split.h" +#include "absl/strings/string_view.h" +#include "absl/time/time.h" +#include "absl/status/statusor.h" +#include "absl/strings/escaping.h" namespace yaze { namespace cli { namespace handlers { +namespace { + +// A simple client for the EmulatorService +class EmulatorClient { +public: + EmulatorClient() { + auto channel = grpc::CreateChannel("localhost:50051", grpc::InsecureChannelCredentials()); + stub_ = agent::EmulatorService::NewStub(channel); + } + + template + absl::StatusOr CallRpc( + grpc::Status (agent::EmulatorService::Stub::*rpc_method)(grpc::ClientContext*, const TRequest&, TResponse*), + const TRequest& request) { + + TResponse response; + grpc::ClientContext context; + + auto deadline = std::chrono::system_clock::now() + std::chrono::seconds(5); + context.set_deadline(deadline); + + grpc::Status status = (stub_.get()->*rpc_method)(&context, request, &response); + + if (!status.ok()) { + return absl::UnavailableError(absl::StrFormat( + "RPC failed: (%d) %s", status.error_code(), status.error_message())); + } + return response; + } + +private: + std::unique_ptr stub_; +}; + +// Helper to parse button from string +absl::StatusOr StringToButton(absl::string_view s) { + if (s == "A") return agent::Button::A; + if (s == "B") return agent::Button::B; + if (s == "X") return agent::Button::X; + if (s == "Y") return agent::Button::Y; + if (s == "L") return agent::Button::L; + if (s == "R") return agent::Button::R; + if (s == "SELECT") return agent::Button::SELECT; + if (s == "START") return agent::Button::START; + if (s == "UP") return agent::Button::UP; + if (s == "DOWN") return agent::Button::DOWN; + if (s == "LEFT") return agent::Button::LEFT; + if (s == "RIGHT") return agent::Button::RIGHT; + return absl::InvalidArgumentError(absl::StrCat("Unknown button: ", s)); +} + +} // namespace + +// --- Command Implementations --- + +absl::Status EmulatorResetCommandHandler::Execute(Rom* rom, const resources::ArgumentParser& parser, + resources::OutputFormatter& formatter) { + EmulatorClient client; + agent::Empty request; + auto response_or = client.CallRpc(&agent::EmulatorService::Stub::Reset, request); + if (!response_or.ok()) { + return response_or.status(); + } + auto response = response_or.value(); + + formatter.BeginObject("EmulatorReset"); + formatter.AddField("success", response.success()); + formatter.AddField("message", response.message()); + formatter.EndObject(); + return absl::OkStatus(); +} + +absl::Status EmulatorGetStateCommandHandler::Execute(Rom* rom, const resources::ArgumentParser& parser, + resources::OutputFormatter& formatter) { + EmulatorClient client; + agent::GameStateRequest request; + request.set_include_screenshot(parser.HasFlag("screenshot")); + + auto response_or = client.CallRpc(&agent::EmulatorService::Stub::GetGameState, request); + if (!response_or.ok()) { + return response_or.status(); + } + auto response = response_or.value(); + + formatter.BeginObject("EmulatorState"); + formatter.AddField("game_mode", static_cast(response.game_mode())); + formatter.AddField("link_state", static_cast(response.link_state())); + formatter.AddField("link_pos_x", static_cast(response.link_pos_x())); + formatter.AddField("link_pos_y", static_cast(response.link_pos_y())); + formatter.AddField("link_health", static_cast(response.link_health())); + if (!response.screenshot_png().empty()) { + formatter.AddField("screenshot_size", static_cast(response.screenshot_png().size())); + } + formatter.EndObject(); + return absl::OkStatus(); +} + +absl::Status EmulatorReadMemoryCommandHandler::Execute(Rom* rom, const resources::ArgumentParser& parser, + resources::OutputFormatter& formatter) { + EmulatorClient client; + agent::MemoryRequest request; + + uint32_t address; + if (!absl::SimpleHexAtoi(parser.GetString("address").value(), &address)) { + return absl::InvalidArgumentError("Invalid address format."); + } + request.set_address(address); + request.set_size(parser.GetInt("length").value_or(16)); + + auto response_or = client.CallRpc(&agent::EmulatorService::Stub::ReadMemory, request); + if (!response_or.ok()) { + return response_or.status(); + } + auto response = response_or.value(); + + formatter.BeginObject("MemoryRead"); + formatter.AddHexField("address", response.address()); + formatter.AddField("data_hex", absl::BytesToHexString(response.data())); + formatter.EndObject(); + return absl::OkStatus(); +} + +absl::Status EmulatorWriteMemoryCommandHandler::Execute(Rom* rom, const resources::ArgumentParser& parser, + resources::OutputFormatter& formatter) { + EmulatorClient client; + agent::MemoryWriteRequest request; + + uint32_t address; + if (!absl::SimpleHexAtoi(parser.GetString("address").value(), &address)) { + return absl::InvalidArgumentError("Invalid address format."); + } + request.set_address(address); + + std::string data_hex = parser.GetString("data").value(); + request.set_data(absl::HexStringToBytes(data_hex)); + + auto response_or = client.CallRpc(&agent::EmulatorService::Stub::WriteMemory, request); + if (!response_or.ok()) { + return response_or.status(); + } + auto response = response_or.value(); + + formatter.BeginObject("MemoryWrite"); + formatter.AddField("success", response.success()); + formatter.AddField("message", response.message()); + formatter.EndObject(); + return absl::OkStatus(); +} + + +absl::Status EmulatorPressButtonsCommandHandler::Execute(Rom* rom, const resources::ArgumentParser& parser, + resources::OutputFormatter& formatter) { + EmulatorClient client; + agent::ButtonRequest request; + std::vector buttons = absl::StrSplit(parser.GetString("buttons").value(), ','); + for (const auto& btn_str : buttons) { + auto button_or = StringToButton(btn_str); + if (!button_or.ok()) return button_or.status(); + request.add_buttons(button_or.value()); + } + + auto response_or = client.CallRpc(&agent::EmulatorService::Stub::PressButtons, request); + if (!response_or.ok()) { + return response_or.status(); + } + auto response = response_or.value(); + + formatter.BeginObject("PressButtons"); + formatter.AddField("success", response.success()); + formatter.AddField("message", response.message()); + formatter.EndObject(); + return absl::OkStatus(); +} + +absl::Status EmulatorReleaseButtonsCommandHandler::Execute(Rom* rom, const resources::ArgumentParser& parser, + resources::OutputFormatter& formatter) { + EmulatorClient client; + agent::ButtonRequest request; + std::vector buttons = absl::StrSplit(parser.GetString("buttons").value(), ','); + for (const auto& btn_str : buttons) { + auto button_or = StringToButton(btn_str); + if (!button_or.ok()) return button_or.status(); + request.add_buttons(button_or.value()); + } + + auto response_or = client.CallRpc(&agent::EmulatorService::Stub::ReleaseButtons, request); + if (!response_or.ok()) { + return response_or.status(); + } + auto response = response_or.value(); + + formatter.BeginObject("ReleaseButtons"); + formatter.AddField("success", response.success()); + formatter.AddField("message", response.message()); + formatter.EndObject(); + return absl::OkStatus(); +} + +absl::Status EmulatorHoldButtonsCommandHandler::Execute(Rom* rom, const resources::ArgumentParser& parser, + resources::OutputFormatter& formatter) { + EmulatorClient client; + agent::ButtonHoldRequest request; + std::vector buttons = absl::StrSplit(parser.GetString("buttons").value(), ','); + for (const auto& btn_str : buttons) { + auto button_or = StringToButton(btn_str); + if (!button_or.ok()) return button_or.status(); + request.add_buttons(button_or.value()); + } + request.set_duration_ms(parser.GetInt("duration").value()); + + auto response_or = client.CallRpc(&agent::EmulatorService::Stub::HoldButtons, request); + if (!response_or.ok()) { + return response_or.status(); + } + auto response = response_or.value(); + + formatter.BeginObject("HoldButtons"); + formatter.AddField("success", response.success()); + formatter.AddField("message", response.message()); + formatter.EndObject(); + return absl::OkStatus(); +} + + +// --- Placeholder Implementations for commands not yet migrated to gRPC --- + absl::Status EmulatorStepCommandHandler::Execute(Rom* rom, const resources::ArgumentParser& parser, resources::OutputFormatter& formatter) { - auto count = parser.GetInt("count").value_or(1); - formatter.BeginObject("Emulator Step"); - formatter.AddField("steps_executed", count); formatter.AddField("status", "not_implemented"); - formatter.AddField("message", "Emulator stepping requires emulator integration"); formatter.EndObject(); - return absl::OkStatus(); } absl::Status EmulatorRunCommandHandler::Execute(Rom* rom, const resources::ArgumentParser& parser, resources::OutputFormatter& formatter) { - auto until_breakpoint = parser.GetString("until").value_or(""); - formatter.BeginObject("Emulator Run"); - formatter.AddField("until_breakpoint", until_breakpoint); formatter.AddField("status", "not_implemented"); - formatter.AddField("message", "Emulator running requires emulator integration"); formatter.EndObject(); - return absl::OkStatus(); } @@ -37,134 +258,31 @@ absl::Status EmulatorPauseCommandHandler::Execute(Rom* rom, const resources::Arg resources::OutputFormatter& formatter) { formatter.BeginObject("Emulator Pause"); formatter.AddField("status", "not_implemented"); - formatter.AddField("message", "Emulator pause requires emulator integration"); formatter.EndObject(); - - return absl::OkStatus(); -} - -absl::Status EmulatorResetCommandHandler::Execute(Rom* rom, const resources::ArgumentParser& parser, - resources::OutputFormatter& formatter) { - formatter.BeginObject("Emulator Reset"); - formatter.AddField("status", "not_implemented"); - formatter.AddField("message", "Emulator reset requires emulator integration"); - formatter.EndObject(); - - return absl::OkStatus(); -} - -absl::Status EmulatorGetStateCommandHandler::Execute(Rom* rom, const resources::ArgumentParser& parser, - resources::OutputFormatter& formatter) { - formatter.BeginObject("Emulator State"); - formatter.AddField("status", "not_implemented"); - formatter.AddField("message", "Emulator state requires emulator integration"); - - formatter.BeginObject("state"); - formatter.AddField("running", false); - formatter.AddField("paused", true); - formatter.AddField("pc", "0x000000"); - formatter.EndObject(); - formatter.EndObject(); - return absl::OkStatus(); } absl::Status EmulatorSetBreakpointCommandHandler::Execute(Rom* rom, const resources::ArgumentParser& parser, resources::OutputFormatter& formatter) { - auto address_str = parser.GetString("address").value(); - auto condition = parser.GetString("condition").value_or(""); - - uint32_t address; - if (!absl::SimpleHexAtoi(address_str, &address)) { - return absl::InvalidArgumentError( - "Invalid address format. Must be hex."); - } - formatter.BeginObject("Emulator Breakpoint Set"); - formatter.AddHexField("address", address, 6); - formatter.AddField("condition", condition); formatter.AddField("status", "not_implemented"); - formatter.AddField("message", "Breakpoint setting requires emulator integration"); formatter.EndObject(); - return absl::OkStatus(); } absl::Status EmulatorClearBreakpointCommandHandler::Execute(Rom* rom, const resources::ArgumentParser& parser, resources::OutputFormatter& formatter) { - auto address_str = parser.GetString("address").value(); - - uint32_t address; - if (!absl::SimpleHexAtoi(address_str, &address)) { - return absl::InvalidArgumentError( - "Invalid address format. Must be hex."); - } - formatter.BeginObject("Emulator Breakpoint Cleared"); - formatter.AddHexField("address", address, 6); formatter.AddField("status", "not_implemented"); - formatter.AddField("message", "Breakpoint clearing requires emulator integration"); formatter.EndObject(); - return absl::OkStatus(); } absl::Status EmulatorListBreakpointsCommandHandler::Execute(Rom* rom, const resources::ArgumentParser& parser, resources::OutputFormatter& formatter) { formatter.BeginObject("Emulator Breakpoints"); - formatter.AddField("total_breakpoints", 0); formatter.AddField("status", "not_implemented"); - formatter.AddField("message", "Breakpoint listing requires emulator integration"); - - formatter.BeginArray("breakpoints"); - formatter.EndArray(); formatter.EndObject(); - - return absl::OkStatus(); -} - -absl::Status EmulatorReadMemoryCommandHandler::Execute(Rom* rom, const resources::ArgumentParser& parser, - resources::OutputFormatter& formatter) { - auto address_str = parser.GetString("address").value(); - auto length = parser.GetInt("length").value_or(16); - - uint32_t address; - if (!absl::SimpleHexAtoi(address_str, &address)) { - return absl::InvalidArgumentError( - "Invalid address format. Must be hex."); - } - - formatter.BeginObject("Emulator Memory Read"); - formatter.AddHexField("address", address, 6); - formatter.AddField("length", length); - formatter.AddField("status", "not_implemented"); - formatter.AddField("message", "Memory reading requires emulator integration"); - - formatter.BeginArray("data"); - formatter.EndArray(); - formatter.EndObject(); - - return absl::OkStatus(); -} - -absl::Status EmulatorWriteMemoryCommandHandler::Execute(Rom* rom, const resources::ArgumentParser& parser, - resources::OutputFormatter& formatter) { - auto address_str = parser.GetString("address").value(); - auto data_str = parser.GetString("data").value(); - - uint32_t address; - if (!absl::SimpleHexAtoi(address_str, &address)) { - return absl::InvalidArgumentError( - "Invalid address format. Must be hex."); - } - - formatter.BeginObject("Emulator Memory Write"); - formatter.AddHexField("address", address, 6); - formatter.AddField("data", data_str); - formatter.AddField("status", "not_implemented"); - formatter.AddField("message", "Memory writing requires emulator integration"); - formatter.EndObject(); - return absl::OkStatus(); } @@ -172,19 +290,7 @@ absl::Status EmulatorGetRegistersCommandHandler::Execute(Rom* rom, const resourc resources::OutputFormatter& formatter) { formatter.BeginObject("Emulator Registers"); formatter.AddField("status", "not_implemented"); - formatter.AddField("message", "Register reading requires emulator integration"); - - formatter.BeginObject("registers"); - formatter.AddField("A", std::string("0x0000")); - formatter.AddField("X", std::string("0x0000")); - formatter.AddField("Y", std::string("0x0000")); - formatter.AddField("PC", std::string("0x000000")); - formatter.AddField("SP", std::string("0x01FF")); - formatter.AddField("DB", std::string("0x00")); - formatter.AddField("DP", std::string("0x0000")); formatter.EndObject(); - formatter.EndObject(); - return absl::OkStatus(); } @@ -192,16 +298,7 @@ absl::Status EmulatorGetMetricsCommandHandler::Execute(Rom* rom, const resources resources::OutputFormatter& formatter) { formatter.BeginObject("Emulator Metrics"); formatter.AddField("status", "not_implemented"); - formatter.AddField("message", "Metrics require emulator integration"); - - formatter.BeginObject("metrics"); - formatter.AddField("instructions_per_second", 0); - formatter.AddField("total_instructions", 0); - formatter.AddField("cycles_per_frame", 0); - formatter.AddField("frame_rate", 0); formatter.EndObject(); - formatter.EndObject(); - return absl::OkStatus(); } diff --git a/src/cli/handlers/tools/emulator_commands.h b/src/cli/handlers/tools/emulator_commands.h index d23ea468..72e0b7b5 100644 --- a/src/cli/handlers/tools/emulator_commands.h +++ b/src/cli/handlers/tools/emulator_commands.h @@ -7,9 +7,6 @@ namespace yaze { namespace cli { namespace handlers { -/** - * @brief Command handler for emulator step execution - */ class EmulatorStepCommandHandler : public resources::CommandHandler { public: std::string GetName() const { return "emulator-step"; } @@ -21,16 +18,13 @@ class EmulatorStepCommandHandler : public resources::CommandHandler { } absl::Status ValidateArgs(const resources::ArgumentParser& parser) override { - return absl::OkStatus(); // No required args + return absl::OkStatus(); } absl::Status Execute(Rom* rom, const resources::ArgumentParser& parser, resources::OutputFormatter& formatter) override; }; -/** - * @brief Command handler for emulator run execution - */ class EmulatorRunCommandHandler : public resources::CommandHandler { public: std::string GetName() const { return "emulator-run"; } @@ -42,16 +36,13 @@ class EmulatorRunCommandHandler : public resources::CommandHandler { } absl::Status ValidateArgs(const resources::ArgumentParser& parser) override { - return absl::OkStatus(); // No required args + return absl::OkStatus(); } absl::Status Execute(Rom* rom, const resources::ArgumentParser& parser, resources::OutputFormatter& formatter) override; }; -/** - * @brief Command handler for emulator pause - */ class EmulatorPauseCommandHandler : public resources::CommandHandler { public: std::string GetName() const { return "emulator-pause"; } @@ -63,16 +54,13 @@ class EmulatorPauseCommandHandler : public resources::CommandHandler { } absl::Status ValidateArgs(const resources::ArgumentParser& parser) override { - return absl::OkStatus(); // No required args + return absl::OkStatus(); } absl::Status Execute(Rom* rom, const resources::ArgumentParser& parser, resources::OutputFormatter& formatter) override; }; -/** - * @brief Command handler for emulator reset - */ class EmulatorResetCommandHandler : public resources::CommandHandler { public: std::string GetName() const { return "emulator-reset"; } @@ -84,16 +72,13 @@ class EmulatorResetCommandHandler : public resources::CommandHandler { } absl::Status ValidateArgs(const resources::ArgumentParser& parser) override { - return absl::OkStatus(); // No required args + return absl::OkStatus(); } absl::Status Execute(Rom* rom, const resources::ArgumentParser& parser, resources::OutputFormatter& formatter) override; }; -/** - * @brief Command handler for getting emulator state - */ class EmulatorGetStateCommandHandler : public resources::CommandHandler { public: std::string GetName() const { return "emulator-get-state"; } @@ -105,16 +90,13 @@ class EmulatorGetStateCommandHandler : public resources::CommandHandler { } absl::Status ValidateArgs(const resources::ArgumentParser& parser) override { - return absl::OkStatus(); // No required args + return absl::OkStatus(); } absl::Status Execute(Rom* rom, const resources::ArgumentParser& parser, resources::OutputFormatter& formatter) override; }; -/** - * @brief Command handler for setting breakpoints - */ class EmulatorSetBreakpointCommandHandler : public resources::CommandHandler { public: std::string GetName() const { return "emulator-set-breakpoint"; } @@ -133,9 +115,6 @@ class EmulatorSetBreakpointCommandHandler : public resources::CommandHandler { resources::OutputFormatter& formatter) override; }; -/** - * @brief Command handler for clearing breakpoints - */ class EmulatorClearBreakpointCommandHandler : public resources::CommandHandler { public: std::string GetName() const { return "emulator-clear-breakpoint"; } @@ -154,9 +133,6 @@ class EmulatorClearBreakpointCommandHandler : public resources::CommandHandler { resources::OutputFormatter& formatter) override; }; -/** - * @brief Command handler for listing breakpoints - */ class EmulatorListBreakpointsCommandHandler : public resources::CommandHandler { public: std::string GetName() const { return "emulator-list-breakpoints"; } @@ -168,16 +144,13 @@ class EmulatorListBreakpointsCommandHandler : public resources::CommandHandler { } absl::Status ValidateArgs(const resources::ArgumentParser& parser) override { - return absl::OkStatus(); // No required args + return absl::OkStatus(); } absl::Status Execute(Rom* rom, const resources::ArgumentParser& parser, resources::OutputFormatter& formatter) override; }; -/** - * @brief Command handler for reading emulator memory - */ class EmulatorReadMemoryCommandHandler : public resources::CommandHandler { public: std::string GetName() const { return "emulator-read-memory"; } @@ -196,9 +169,6 @@ class EmulatorReadMemoryCommandHandler : public resources::CommandHandler { resources::OutputFormatter& formatter) override; }; -/** - * @brief Command handler for writing emulator memory - */ class EmulatorWriteMemoryCommandHandler : public resources::CommandHandler { public: std::string GetName() const { return "emulator-write-memory"; } @@ -217,9 +187,6 @@ class EmulatorWriteMemoryCommandHandler : public resources::CommandHandler { resources::OutputFormatter& formatter) override; }; -/** - * @brief Command handler for getting emulator registers - */ class EmulatorGetRegistersCommandHandler : public resources::CommandHandler { public: std::string GetName() const { return "emulator-get-registers"; } @@ -231,16 +198,13 @@ class EmulatorGetRegistersCommandHandler : public resources::CommandHandler { } absl::Status ValidateArgs(const resources::ArgumentParser& parser) override { - return absl::OkStatus(); // No required args + return absl::OkStatus(); } absl::Status Execute(Rom* rom, const resources::ArgumentParser& parser, resources::OutputFormatter& formatter) override; }; -/** - * @brief Command handler for getting emulator metrics - */ class EmulatorGetMetricsCommandHandler : public resources::CommandHandler { public: std::string GetName() const { return "emulator-get-metrics"; } @@ -252,13 +216,65 @@ class EmulatorGetMetricsCommandHandler : public resources::CommandHandler { } absl::Status ValidateArgs(const resources::ArgumentParser& parser) override { - return absl::OkStatus(); // No required args + return absl::OkStatus(); } absl::Status Execute(Rom* rom, const resources::ArgumentParser& parser, resources::OutputFormatter& formatter) override; }; +class EmulatorPressButtonsCommandHandler : public resources::CommandHandler { + public: + std::string GetName() const { return "emulator-press-buttons"; } + std::string GetDescription() const { + return "Press and release emulator buttons"; + } + std::string GetUsage() const { + return "emulator-press-buttons --buttons ,,..."; + } + absl::Status ValidateArgs( + const resources::ArgumentParser& parser) override { + return parser.RequireArgs({"buttons"}); + } + absl::Status Execute(Rom* rom, const resources::ArgumentParser& parser, + resources::OutputFormatter& formatter) override; +}; + +class EmulatorReleaseButtonsCommandHandler : public resources::CommandHandler { + public: + std::string GetName() const { return "emulator-release-buttons"; } + std::string GetDescription() const { + return "Release currently held emulator buttons"; + } + std::string GetUsage() const { + return "emulator-release-buttons --buttons ,,..."; + } + absl::Status ValidateArgs( + const resources::ArgumentParser& parser) override { + return parser.RequireArgs({"buttons"}); + } + absl::Status Execute(Rom* rom, const resources::ArgumentParser& parser, + resources::OutputFormatter& formatter) override; +}; + +class EmulatorHoldButtonsCommandHandler : public resources::CommandHandler { + public: + std::string GetName() const { return "emulator-hold-buttons"; } + std::string GetDescription() const { + return "Hold emulator buttons for a specified duration"; + } + std::string GetUsage() const { + return "emulator-hold-buttons --buttons ,,... --duration " + ""; + } + absl::Status ValidateArgs( + const resources::ArgumentParser& parser) override { + return parser.RequireArgs({"buttons", "duration"}); + } + absl::Status Execute(Rom* rom, const resources::ArgumentParser& parser, + resources::OutputFormatter& formatter) override; +}; + } // namespace handlers } // namespace cli } // namespace yaze diff --git a/src/cli/service/agent/agent_control_server.cc b/src/cli/service/agent/agent_control_server.cc new file mode 100644 index 00000000..88ea97cb --- /dev/null +++ b/src/cli/service/agent/agent_control_server.cc @@ -0,0 +1,46 @@ +#include "cli/service/agent/agent_control_server.h" +#include "cli/service/agent/emulator_service_impl.h" +#include +#include +#include + +namespace yaze::agent { + +AgentControlServer::AgentControlServer(yaze::emu::Emulator* emulator) + : emulator_(emulator) {} + +AgentControlServer::~AgentControlServer() { + Stop(); +} + +void AgentControlServer::Start() { + server_thread_ = std::thread(&AgentControlServer::Run, this); +} + +void AgentControlServer::Stop() { + if (server_) { + server_->Shutdown(); + } + if (server_thread_.joinable()) { + server_thread_.join(); + } +} + +void AgentControlServer::Run() { + std::string server_address("0.0.0.0:50051"); + EmulatorServiceImpl service(emulator_); + + grpc::ServerBuilder builder; + builder.AddListeningPort(server_address, grpc::InsecureServerCredentials()); + builder.RegisterService(&service); + + server_ = builder.BuildAndStart(); + if (server_) { + std::cout << "AgentControlServer listening on " << server_address << std::endl; + server_->Wait(); + } else { + std::cerr << "Failed to start AgentControlServer on " << server_address << std::endl; + } +} + +} // namespace yaze::agent diff --git a/src/cli/service/agent/agent_control_server.h b/src/cli/service/agent/agent_control_server.h new file mode 100644 index 00000000..3fbf23e4 --- /dev/null +++ b/src/cli/service/agent/agent_control_server.h @@ -0,0 +1,32 @@ +#pragma once + +#include +#include + +namespace grpc { +class Server; +} + +namespace yaze::emu { +class Emulator; +} + +namespace yaze::agent { + +class AgentControlServer { +public: + AgentControlServer(yaze::emu::Emulator* emulator); + ~AgentControlServer(); + + void Start(); + void Stop(); + +private: + void Run(); + + yaze::emu::Emulator* emulator_; // Non-owning pointer + std::unique_ptr server_; + std::thread server_thread_; +}; + +} // namespace yaze::agent diff --git a/src/cli/service/agent/emulator_service_impl.cc b/src/cli/service/agent/emulator_service_impl.cc new file mode 100644 index 00000000..69412e96 --- /dev/null +++ b/src/cli/service/agent/emulator_service_impl.cc @@ -0,0 +1,190 @@ +#include "cli/service/agent/emulator_service_impl.h" +#include "app/emu/emulator.h" +#include "app/core/service/screenshot_utils.h" +#include "app/emu/input/input_backend.h" // Required for SnesButton enum +#include "absl/strings/escaping.h" +#include "absl/strings/str_format.h" +#include +#include + +namespace yaze::agent { + +namespace { +// Helper to convert our gRPC Button enum to the emulator's SnesButton enum +emu::input::SnesButton ToSnesButton(Button button) { + using emu::input::SnesButton; + switch (button) { + case A: return SnesButton::A; + case B: return SnesButton::B; + case X: return SnesButton::X; + case Y: return SnesButton::Y; + case L: return SnesButton::L; + case R: return SnesButton::R; + case SELECT: return SnesButton::SELECT; + case START: return SnesButton::START; + case UP: return SnesButton::UP; + case DOWN: return SnesButton::DOWN; + case LEFT: return SnesButton::LEFT; + case RIGHT: return SnesButton::RIGHT; + default: + return SnesButton::B; // Default fallback + } +} +} // namespace + +EmulatorServiceImpl::EmulatorServiceImpl(yaze::emu::Emulator* emulator) + : emulator_(emulator) {} + +// --- Lifecycle --- + +grpc::Status EmulatorServiceImpl::Start(grpc::ServerContext* context, const Empty* request, CommandResponse* response) { + if (!emulator_) return grpc::Status(grpc::StatusCode::UNAVAILABLE, "Emulator not initialized."); + emulator_->set_running(true); + response->set_success(true); + response->set_message("Emulator started."); + return grpc::Status::OK; +} + +grpc::Status EmulatorServiceImpl::Stop(grpc::ServerContext* context, const Empty* request, CommandResponse* response) { + if (!emulator_) return grpc::Status(grpc::StatusCode::UNAVAILABLE, "Emulator not initialized."); + emulator_->set_running(false); + response->set_success(true); + response->set_message("Emulator stopped."); + return grpc::Status::OK; +} + +grpc::Status EmulatorServiceImpl::Pause(grpc::ServerContext* context, const Empty* request, CommandResponse* response) { + if (!emulator_) return grpc::Status(grpc::StatusCode::UNAVAILABLE, "Emulator not initialized."); + emulator_->set_running(false); + response->set_success(true); + response->set_message("Emulator paused."); + return grpc::Status::OK; +} + +grpc::Status EmulatorServiceImpl::Resume(grpc::ServerContext* context, const Empty* request, CommandResponse* response) { + if (!emulator_) return grpc::Status(grpc::StatusCode::UNAVAILABLE, "Emulator not initialized."); + emulator_->set_running(true); + response->set_success(true); + response->set_message("Emulator resumed."); + return grpc::Status::OK; +} + +grpc::Status EmulatorServiceImpl::Reset(grpc::ServerContext* context, const Empty* request, CommandResponse* response) { + if (!emulator_) return grpc::Status(grpc::StatusCode::UNAVAILABLE, "Emulator not initialized."); + emulator_->snes().Reset(true); // Hard reset + response->set_success(true); + response->set_message("Emulator reset."); + return grpc::Status::OK; +} + +// --- Input Control --- + +grpc::Status EmulatorServiceImpl::PressButtons(grpc::ServerContext* context, const ButtonRequest* request, CommandResponse* response) { + if (!emulator_) return grpc::Status(grpc::StatusCode::UNAVAILABLE, "Emulator not initialized."); + auto& input_manager = emulator_->input_manager(); + for (const auto& button : request->buttons()) { + input_manager.PressButton(ToSnesButton(static_cast