yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
browser_agent.cc
Go to the documentation of this file.
1#include <vector>
2#include <string>
3#include <iostream>
4#include <thread>
5#include <functional>
6#include <mutex>
7
8#include "absl/status/status.h"
9#include "absl/strings/str_cat.h"
10#include "absl/strings/str_join.h"
11#include "cli/cli.h"
17#include "rom/rom.h"
18
19namespace yaze {
20namespace cli {
21
22// Accessor for the global AI service provided by wasm_terminal_bridge
23BrowserAIService* GetGlobalBrowserAIService();
24
25// Accessor for the global ROM from wasm_terminal_bridge.cc
26extern Rom* GetGlobalRom();
27
28namespace handlers {
29
30namespace {
31
32// Static history for the session
33std::vector<agent::ChatMessage> g_chat_history;
34std::mutex g_chat_mutex;
35
36// Simple in-memory storage for the last generated plan
37// In a full implementation, this would be a full ProposalRegistry
38std::string g_pending_plan;
39std::mutex g_plan_mutex;
40
41absl::Status HandleChatCommand(const std::vector<std::string>& args) {
42 auto* service = GetGlobalBrowserAIService();
43 if (!service) {
44 return absl::FailedPreconditionError(
45 "AI Service not initialized. Please set an API key using the settings menu.");
46 }
47
48 if (args.empty()) {
49 return absl::InvalidArgumentError("Please provide a message. Usage: agent chat <message>");
50 }
51
52 std::string prompt = absl::StrJoin(args, " ");
53
54 {
55 std::lock_guard<std::mutex> lock(g_chat_mutex);
56 // Add user message to history
57 agent::ChatMessage user_msg;
59 user_msg.message = prompt;
60 user_msg.timestamp = absl::Now();
61 g_chat_history.push_back(user_msg);
62 }
63
64 std::cout << "Thinking... (This may take a few seconds)" << std::endl;
65
66 // Run AI request in a background thread
67 std::thread([service]() {
68 std::vector<agent::ChatMessage> history_copy;
69 {
70 std::lock_guard<std::mutex> lock(g_chat_mutex);
71 history_copy = g_chat_history;
72 }
73
74 // Generate response using history
75 auto response_or = service->GenerateResponse(history_copy);
76
77 if (!response_or.ok()) {
78 std::cerr << "\nError: " << response_or.status().message() << "\n" << std::endl;
79 // Remove the failed user message so we don't get stuck in a bad state
80 std::lock_guard<std::mutex> lock(g_chat_mutex);
81 if (!g_chat_history.empty() &&
83 g_chat_history.pop_back();
84 }
85 return;
86 }
87
88 auto response = response_or.value();
89
90 {
91 std::lock_guard<std::mutex> lock(g_chat_mutex);
92 // Add agent response to history
93 agent::ChatMessage agent_msg;
95 agent_msg.message = response.text_response;
96 agent_msg.timestamp = absl::Now();
97 g_chat_history.push_back(agent_msg);
98 }
99
100 // Print response safely
101 // Note: In Emscripten, printf/cout from threads is proxied to main thread
102 std::cout << "\nAgent: " << response.text_response << "\n" << std::endl;
103 }).detach();
104
105 return absl::OkStatus();
106}
107
108absl::Status HandlePlanCommand(const std::vector<std::string>& args) {
109 auto* service = GetGlobalBrowserAIService();
110 if (!service) {
111 return absl::FailedPreconditionError("AI Service not initialized.");
112 }
113
114 if (args.empty()) {
115 return absl::InvalidArgumentError("Usage: agent plan <task description>");
116 }
117
118 std::string task = absl::StrJoin(args, " ");
119 std::string prompt = "Create a detailed step-by-step implementation plan for the following ROM hacking task. "
120 "Do not execute it yet, just plan it.\nTask: " + task;
121
122 std::cout << "Generating plan... (Check back in a moment)" << std::endl;
123
124 std::thread([service, prompt]() {
125 auto response_or = service->GenerateResponse(prompt);
126 if (!response_or.ok()) {
127 std::cerr << "\nPlan Error: " << response_or.status().message() << "\n" << std::endl;
128 return;
129 }
130
131 {
132 std::lock_guard<std::mutex> lock(g_plan_mutex);
133 g_pending_plan = response_or.value().text_response;
134 }
135
136 std::cout << "\nProposed Plan Ready:\n" << std::string(40, '-') << "\n";
137 std::cout << response_or.value().text_response << "\n" << std::string(40, '-') << "\n";
138 std::cout << "Run 'agent diff' to review this plan again.\n" << std::endl;
139 }).detach();
140
141 return absl::OkStatus();
142}
143
144absl::Status HandleDiffCommand(Rom&, const std::vector<std::string>&) {
145 std::lock_guard<std::mutex> lock(g_plan_mutex);
146 if (g_pending_plan.empty()) {
147 return absl::NotFoundError("No pending plan found. Run 'agent plan <task>' first.");
148 }
149
150 std::cout << "\nPending Plan (Conceptual Diff):\n" << std::string(40, '-') << "\n";
151 std::cout << g_pending_plan << "\n" << std::string(40, '-') << "\n";
152
153 return absl::OkStatus();
154}
155
156absl::Status HandleListCommand(const std::vector<std::string>& /*args*/) {
157 const auto& catalog = ResourceCatalog::Instance();
158 std::ostringstream oss;
159
160 oss << "Available Resources:\n";
161 for (const auto& resource : catalog.AllResources()) {
162 oss << " - " << resource.resource << ": " << resource.description << "\n";
163 }
164
165 std::cout << oss.str() << std::endl;
166 return absl::OkStatus();
167}
168
169absl::Status HandleDescribeCommand(const std::vector<std::string>& args) {
170 if (args.empty()) {
171 return absl::InvalidArgumentError("Usage: agent describe <resource_name>");
172 }
173
174 const std::string& name = args[0];
175 const auto& catalog = ResourceCatalog::Instance();
176 auto resource_or = catalog.GetResource(name);
177
178 if (!resource_or.ok()) {
179 return resource_or.status();
180 }
181
182 const auto& resource = resource_or.value();
183 std::string json_output = catalog.SerializeResource(resource);
184
185 std::cout << "Description of " << name << ":\n";
186 std::cout << json_output << std::endl;
187
188 return absl::OkStatus();
189}
190
191} // namespace
192
193// Forward declarations if needed
194namespace agent {
195 // Stubs for unsupported commands
196 absl::Status HandleRunCommand(const std::vector<std::string>&, Rom&) {
197 return absl::UnimplementedError("Agent run command not available in browser yet");
198 }
199 // HandlePlanCommand and HandleDiffCommand are now implemented in the anonymous namespace above
200 // and dispatched below.
201
202 absl::Status HandleCommitCommand(Rom&) {
203 return absl::UnimplementedError("Agent commit command not available in browser yet");
204 }
205 absl::Status HandleRevertCommand(Rom&) {
206 return absl::UnimplementedError("Agent revert command not available in browser yet");
207 }
208 absl::Status HandleAcceptCommand(const std::vector<std::string>&, Rom&) {
209 return absl::UnimplementedError("Agent accept command not available in browser yet");
210 }
211 absl::Status HandleTestCommand(const std::vector<std::string>&) {
212 return absl::UnimplementedError("Agent test command not available in browser");
213 }
214 absl::Status HandleTestConversationCommand(const std::vector<std::string>&) {
215 return absl::UnimplementedError("Agent test-conversation command not available in browser");
216 }
217 absl::Status HandleLearnCommand(const std::vector<std::string>&) {
218 return absl::UnimplementedError("Agent learn command not available in browser");
219 }
220}
221
222std::string GenerateAgentHelp() {
223 return "Available Agent Commands (Browser):\n"
224 " todo - Manage todo list\n"
225 " chat <message> - Chat with the AI agent\n"
226 " list - List available resources\n"
227 " describe <name> - Describe a specific resource\n"
228 " plan <task> - Generate an implementation plan\n"
229 " diff - View pending plan/changes\n";
230}
231
233 static Rom rom;
234 return rom;
235}
236
237absl::Status HandleAgentCommand(const std::vector<std::string>& arg_vec) {
238 if (arg_vec.empty()) {
239 std::cout << GenerateAgentHelp();
240 return absl::InvalidArgumentError("No subcommand specified");
241 }
242
243 const std::string& subcommand = arg_vec[0];
244 std::vector<std::string> subcommand_args(arg_vec.begin() + 1, arg_vec.end());
245
246 if (subcommand == "simple-chat" || subcommand == "chat") {
247 return HandleChatCommand(subcommand_args);
248 }
249
250 if (subcommand == "plan") {
251 return HandlePlanCommand(subcommand_args);
252 }
253
254 if (subcommand == "diff") {
255 // Diff usually takes args but we ignore them for this simple version
256 return HandleDiffCommand(AgentRom(), subcommand_args);
257 }
258
259 if (subcommand == "todo") {
260 return handlers::HandleTodoCommand(subcommand_args);
261 }
262
263 if (subcommand == "list") {
264 return HandleListCommand(subcommand_args);
265 }
266
267 if (subcommand == "describe") {
268 return HandleDescribeCommand(subcommand_args);
269 }
270
271 std::cout << GenerateAgentHelp();
272 return absl::InvalidArgumentError(absl::StrCat("Unknown subcommand: ", subcommand));
273}
274
275} // namespace handlers
276} // namespace cli
277} // namespace yaze
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
Definition rom.h:24
static const ResourceCatalog & Instance()
absl::Status HandleDescribeCommand(const std::vector< std::string > &args)
absl::Status HandleDiffCommand(Rom &rom, const std::vector< std::string > &args)
absl::Status HandleListCommand()
absl::Status HandleTestConversationCommand(const std::vector< std::string > &)
absl::Status HandleAcceptCommand(const std::vector< std::string > &, Rom &)
absl::Status HandleLearnCommand(const std::vector< std::string > &)
absl::Status HandleRunCommand(const std::vector< std::string > &, Rom &)
absl::Status HandleCommitCommand(Rom &)
absl::Status HandleRevertCommand(Rom &)
absl::Status HandleTestCommand(const std::vector< std::string > &)
absl::Status HandleChatCommand(const std::vector< std::string > &args)
std::string GenerateAgentHelp()
absl::Status HandleAgentCommand(const std::vector< std::string > &args)
Unified agent command handler using CommandRegistry.
Definition agent.cc:110
absl::Status HandleTodoCommand(const std::vector< std::string > &args)
Handle z3ed agent todo commands.
Rom * GetGlobalRom()
BrowserAIService * GetGlobalBrowserAIService()