yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
agent_editor_profile.cc
Go to the documentation of this file.
1#include <filesystem>
2#include <fstream>
3#include <string>
4#include <system_error>
5#include <vector>
6
7#include "absl/status/status.h"
8#include "absl/status/statusor.h"
9#include "absl/strings/str_format.h"
10#include "absl/time/clock.h"
11#include "absl/time/time.h"
18#include "util/platform_paths.h"
19
20#if defined(YAZE_WITH_JSON)
21#include "nlohmann/json.hpp"
22#endif
23
24namespace yaze {
25namespace editor {
26
28 const std::filesystem::path& path) {
29#if defined(YAZE_WITH_JSON)
30 nlohmann::json json;
31 json["persona_notes"] = builder_state_.persona_notes;
32 json["goals"] = builder_state_.goals;
33 json["auto_run_tests"] = builder_state_.auto_run_tests;
34 json["auto_sync_rom"] = builder_state_.auto_sync_rom;
35 json["auto_focus_proposals"] = builder_state_.auto_focus_proposals;
36 json["ready_for_e2e"] = builder_state_.ready_for_e2e;
37 json["tools"] = {
38 {"resources", builder_state_.tools.resources},
39 {"dungeon", builder_state_.tools.dungeon},
40 {"overworld", builder_state_.tools.overworld},
41 {"dialogue", builder_state_.tools.dialogue},
42 {"gui", builder_state_.tools.gui},
43 {"music", builder_state_.tools.music},
44 {"sprite", builder_state_.tools.sprite},
45 {"emulator", builder_state_.tools.emulator},
46 {"memory_inspector", builder_state_.tools.memory_inspector},
47 };
48 json["stages"] = nlohmann::json::array();
49 for (const auto& stage : builder_state_.stages) {
50 json["stages"].push_back({{"name", stage.name},
51 {"summary", stage.summary},
52 {"completed", stage.completed}});
53 }
54
55 std::error_code ec;
56 std::filesystem::create_directories(path.parent_path(), ec);
57 std::ofstream file(path);
58 if (!file.is_open()) {
59 return absl::InternalError(
60 absl::StrFormat("Failed to open blueprint: %s", path.string()));
61 }
62 file << json.dump(2);
63 builder_state_.blueprint_path = path.string();
64 return absl::OkStatus();
65#else
66 (void)path;
67 return absl::UnimplementedError("Blueprint export requires JSON support");
68#endif
69}
70
72 const std::filesystem::path& path) {
73#if defined(YAZE_WITH_JSON)
74 std::ifstream file(path);
75 if (!file.is_open()) {
76 return absl::NotFoundError(
77 absl::StrFormat("Blueprint not found: %s", path.string()));
78 }
79
80 nlohmann::json json;
81 file >> json;
82
83 builder_state_.persona_notes = json.value("persona_notes", "");
84 builder_state_.goals.clear();
85 if (json.contains("goals") && json["goals"].is_array()) {
86 for (const auto& goal : json["goals"]) {
87 if (goal.is_string()) {
88 builder_state_.goals.push_back(goal.get<std::string>());
89 }
90 }
91 }
92 if (json.contains("tools") && json["tools"].is_object()) {
93 auto tools = json["tools"];
94 builder_state_.tools.resources = tools.value("resources", true);
95 builder_state_.tools.dungeon = tools.value("dungeon", true);
96 builder_state_.tools.overworld = tools.value("overworld", true);
97 builder_state_.tools.dialogue = tools.value("dialogue", true);
98 builder_state_.tools.gui = tools.value("gui", false);
99 builder_state_.tools.music = tools.value("music", false);
100 builder_state_.tools.sprite = tools.value("sprite", false);
101 builder_state_.tools.emulator = tools.value("emulator", false);
103 tools.value("memory_inspector", false);
104 }
105 builder_state_.auto_run_tests = json.value("auto_run_tests", false);
106 builder_state_.auto_sync_rom = json.value("auto_sync_rom", true);
108 json.value("auto_focus_proposals", true);
109 builder_state_.ready_for_e2e = json.value("ready_for_e2e", false);
110 if (json.contains("stages") && json["stages"].is_array()) {
111 builder_state_.stages.clear();
112 for (const auto& stage : json["stages"]) {
113 AgentBuilderState::Stage builder_stage;
114 builder_stage.name = stage.value("name", std::string{});
115 builder_stage.summary = stage.value("summary", std::string{});
116 builder_stage.completed = stage.value("completed", false);
117 builder_state_.stages.push_back(builder_stage);
118 }
119 }
120 builder_state_.blueprint_path = path.string();
121 return absl::OkStatus();
122#else
123 (void)path;
124 return absl::UnimplementedError("Blueprint import requires JSON support");
125#endif
126}
127
128absl::Status AgentEditor::SaveBotProfile(const BotProfile& profile) {
129#if defined(YAZE_WITH_JSON)
130 auto dir_status = EnsureProfilesDirectory();
131 if (!dir_status.ok())
132 return dir_status;
133
134 std::filesystem::path profile_path =
135 GetProfilesDirectory() / (profile.name + ".json");
136 std::ofstream file(profile_path);
137 if (!file.is_open()) {
138 return absl::InternalError("Failed to open profile file for writing");
139 }
140
141 file << ProfileToJson(profile);
142 file.close();
143 return Load();
144#else
145 return absl::UnimplementedError(
146 "JSON support required for profile management");
147#endif
148}
149
150absl::Status AgentEditor::LoadBotProfile(const std::string& name) {
151#if defined(YAZE_WITH_JSON)
152 std::filesystem::path profile_path =
153 GetProfilesDirectory() / (name + ".json");
154 if (!std::filesystem::exists(profile_path)) {
155 return absl::NotFoundError(absl::StrFormat("Profile '%s' not found", name));
156 }
157
158 std::ifstream file(profile_path);
159 if (!file.is_open()) {
160 return absl::InternalError("Failed to open profile file");
161 }
162
163 std::string json_content((std::istreambuf_iterator<char>(file)),
164 std::istreambuf_iterator<char>());
165
166 auto profile_or = JsonToProfile(json_content);
167 if (!profile_or.ok()) {
168 return profile_or.status();
169 }
170 current_profile_ = *profile_or;
172
187
190 return absl::OkStatus();
191#else
192 return absl::UnimplementedError(
193 "JSON support required for profile management");
194#endif
195}
196
197absl::Status AgentEditor::DeleteBotProfile(const std::string& name) {
198 std::filesystem::path profile_path =
199 GetProfilesDirectory() / (name + ".json");
200 if (!std::filesystem::exists(profile_path)) {
201 return absl::NotFoundError(absl::StrFormat("Profile '%s' not found", name));
202 }
203
204 std::filesystem::remove(profile_path);
205 return Load();
206}
207
208std::vector<AgentEditor::BotProfile> AgentEditor::GetAllProfiles() const {
209 return loaded_profiles_;
210}
211
233
234absl::Status AgentEditor::ExportProfile(const BotProfile& profile,
235 const std::filesystem::path& path) {
236#if defined(YAZE_WITH_JSON)
237 auto status = SaveBotProfile(profile);
238 if (!status.ok())
239 return status;
240
241 std::ofstream file(path);
242 if (!file.is_open()) {
243 return absl::InternalError("Failed to open file for export");
244 }
245 file << ProfileToJson(profile);
246 return absl::OkStatus();
247#else
248 (void)profile;
249 (void)path;
250 return absl::UnimplementedError("JSON support required");
251#endif
252}
253
254absl::Status AgentEditor::ImportProfile(const std::filesystem::path& path) {
255#if defined(YAZE_WITH_JSON)
256 if (!std::filesystem::exists(path)) {
257 return absl::NotFoundError("Import file not found");
258 }
259
260 std::ifstream file(path);
261 if (!file.is_open()) {
262 return absl::InternalError("Failed to open import file");
263 }
264
265 std::string json_content((std::istreambuf_iterator<char>(file)),
266 std::istreambuf_iterator<char>());
267
268 auto profile_or = JsonToProfile(json_content);
269 if (!profile_or.ok()) {
270 return profile_or.status();
271 }
272
273 return SaveBotProfile(*profile_or);
274#else
275 (void)path;
276 return absl::UnimplementedError("JSON support required");
277#endif
278}
279
280std::filesystem::path AgentEditor::GetProfilesDirectory() const {
282 if (agent_dir.ok()) {
283 return *agent_dir / "profiles";
284 }
286 if (temp_dir.ok()) {
287 return *temp_dir / "agent" / "profiles";
288 }
289 return std::filesystem::current_path() / "agent" / "profiles";
290}
291
293 auto dir = GetProfilesDirectory();
294 std::error_code ec;
295 std::filesystem::create_directories(dir, ec);
296 if (ec) {
297 return absl::InternalError(absl::StrFormat(
298 "Failed to create profiles directory: %s", ec.message()));
299 }
300 return absl::OkStatus();
301}
302
303std::string AgentEditor::ProfileToJson(const BotProfile& profile) const {
304#if defined(YAZE_WITH_JSON)
305 nlohmann::json json;
306 json["name"] = profile.name;
307 json["description"] = profile.description;
308 json["provider"] = profile.provider;
309 json["host_id"] = profile.host_id;
310 json["model"] = profile.model;
311 json["ollama_host"] = profile.ollama_host;
312 json["gemini_api_key"] = profile.gemini_api_key;
313 json["anthropic_api_key"] = profile.anthropic_api_key;
314 json["openai_api_key"] = profile.openai_api_key;
315 json["openai_base_url"] = profile.openai_base_url;
316 json["system_prompt"] = profile.system_prompt;
317 json["verbose"] = profile.verbose;
318 json["show_reasoning"] = profile.show_reasoning;
319 json["max_tool_iterations"] = profile.max_tool_iterations;
320 json["max_retry_attempts"] = profile.max_retry_attempts;
321 json["temperature"] = profile.temperature;
322 json["top_p"] = profile.top_p;
323 json["max_output_tokens"] = profile.max_output_tokens;
324 json["stream_responses"] = profile.stream_responses;
325 json["tags"] = profile.tags;
326 json["created_at"] = absl::FormatTime(absl::RFC3339_full, profile.created_at,
327 absl::UTCTimeZone());
328 json["modified_at"] = absl::FormatTime(
329 absl::RFC3339_full, profile.modified_at, absl::UTCTimeZone());
330
331 return json.dump(2);
332#else
333 (void)profile;
334 return "{}";
335#endif
336}
337
338absl::StatusOr<AgentEditor::BotProfile> AgentEditor::JsonToProfile(
339 const std::string& json_str) const {
340#if defined(YAZE_WITH_JSON)
341 try {
342 nlohmann::json json = nlohmann::json::parse(json_str);
343
344 BotProfile profile;
345 profile.name = json.value("name", "Unnamed Profile");
346 profile.description = json.value("description", "");
347 profile.provider = json.value("provider", std::string(cli::kProviderMock));
348 profile.host_id = json.value("host_id", "");
349 profile.model = json.value("model", "");
350 profile.ollama_host = json.value("ollama_host", "http://localhost:11434");
351 profile.gemini_api_key = json.value("gemini_api_key", "");
352 profile.anthropic_api_key = json.value("anthropic_api_key", "");
353 profile.openai_api_key = json.value("openai_api_key", "");
354 profile.openai_base_url =
355 json.value("openai_base_url", "https://api.openai.com");
356 profile.system_prompt = json.value("system_prompt", "");
357 profile.verbose = json.value("verbose", false);
358 profile.show_reasoning = json.value("show_reasoning", true);
359 profile.max_tool_iterations = json.value("max_tool_iterations", 4);
360 profile.max_retry_attempts = json.value("max_retry_attempts", 3);
361 profile.temperature = json.value("temperature", 0.25f);
362 profile.top_p = json.value("top_p", 0.95f);
363 profile.max_output_tokens = json.value("max_output_tokens", 2048);
364 profile.stream_responses = json.value("stream_responses", false);
365
366 if (json.contains("tags") && json["tags"].is_array()) {
367 for (const auto& tag : json["tags"]) {
368 profile.tags.push_back(tag.get<std::string>());
369 }
370 }
371
372 if (json.contains("created_at")) {
373 absl::Time created;
374 if (absl::ParseTime(absl::RFC3339_full,
375 json["created_at"].get<std::string>(), &created,
376 nullptr)) {
377 profile.created_at = created;
378 }
379 }
380
381 if (json.contains("modified_at")) {
382 absl::Time modified;
383 if (absl::ParseTime(absl::RFC3339_full,
384 json["modified_at"].get<std::string>(), &modified,
385 nullptr)) {
386 profile.modified_at = modified;
387 }
388 }
389
390 return profile;
391 } catch (const std::exception& e) {
392 return absl::InternalError(
393 absl::StrFormat("Failed to parse profile JSON: %s", e.what()));
394 }
395#else
396 (void)json_str;
397 return absl::UnimplementedError("JSON support required");
398#endif
399}
400
419
421 if (!context_) {
422 return;
423 }
424
425 auto& ctx_config = context_->agent_config();
426 ctx_config.ai_provider = current_profile_.provider.empty()
429 ctx_config.ai_model = current_profile_.model;
430 ctx_config.ollama_host = current_profile_.ollama_host.empty()
431 ? "http://localhost:11434"
433 ctx_config.gemini_api_key = current_profile_.gemini_api_key;
434 ctx_config.anthropic_api_key = current_profile_.anthropic_api_key;
435 ctx_config.openai_api_key = current_profile_.openai_api_key;
436 ctx_config.openai_base_url =
438 current_profile_.openai_base_url = ctx_config.openai_base_url;
439 ctx_config.host_id = current_profile_.host_id;
440 ctx_config.verbose = current_profile_.verbose;
441 ctx_config.show_reasoning = current_profile_.show_reasoning;
442 ctx_config.max_tool_iterations = current_profile_.max_tool_iterations;
443 ctx_config.max_retry_attempts = current_profile_.max_retry_attempts;
444 ctx_config.temperature = current_profile_.temperature;
445 ctx_config.top_p = current_profile_.top_p;
446 ctx_config.max_output_tokens = current_profile_.max_output_tokens;
447 ctx_config.stream_responses = current_profile_.stream_responses;
448
449 internal::CopyStringToBuffer(ctx_config.ai_provider,
450 ctx_config.provider_buffer);
451 internal::CopyStringToBuffer(ctx_config.ai_model, ctx_config.model_buffer);
452 internal::CopyStringToBuffer(ctx_config.ollama_host,
453 ctx_config.ollama_host_buffer);
454 internal::CopyStringToBuffer(ctx_config.gemini_api_key,
455 ctx_config.gemini_key_buffer);
456 internal::CopyStringToBuffer(ctx_config.anthropic_api_key,
457 ctx_config.anthropic_key_buffer);
458 internal::CopyStringToBuffer(ctx_config.openai_api_key,
459 ctx_config.openai_key_buffer);
460 internal::CopyStringToBuffer(ctx_config.openai_base_url,
461 ctx_config.openai_base_url_buffer);
462
464
466}
467
469 if (!context_) {
470 return;
471 }
472
473 auto& ctx_config = context_->agent_config();
474 const std::string prev_provider = ctx_config.ai_provider;
475 const std::string prev_openai_base = ctx_config.openai_base_url;
476 const std::string prev_ollama_host = ctx_config.ollama_host;
477 ctx_config.ai_provider =
478 config.ai_provider.empty() ? cli::kProviderMock : config.ai_provider;
479 ctx_config.ai_model = config.ai_model;
480 ctx_config.ollama_host = config.ollama_host.empty() ? "http://localhost:11434"
481 : config.ollama_host;
482 ctx_config.gemini_api_key = config.gemini_api_key;
483 ctx_config.anthropic_api_key = config.anthropic_api_key;
484 ctx_config.openai_api_key = config.openai_api_key;
485 ctx_config.openai_base_url =
487 ctx_config.host_id = config.host_id;
488 ctx_config.verbose = config.verbose;
489 ctx_config.show_reasoning = config.show_reasoning;
490 ctx_config.max_tool_iterations = config.max_tool_iterations;
491 ctx_config.max_retry_attempts = config.max_retry_attempts;
492 ctx_config.temperature = config.temperature;
493 ctx_config.top_p = config.top_p;
494 ctx_config.max_output_tokens = config.max_output_tokens;
495 ctx_config.stream_responses = config.stream_responses;
496 ctx_config.favorite_models = config.favorite_models;
497 ctx_config.model_chain = config.model_chain;
498 ctx_config.model_presets = config.model_presets;
499 ctx_config.chain_mode = config.chain_mode;
500 ctx_config.tool_config = config.tool_config;
501
502 if (prev_provider != ctx_config.ai_provider ||
503 prev_openai_base != ctx_config.openai_base_url ||
504 prev_ollama_host != ctx_config.ollama_host) {
505 auto& model_cache = context_->model_cache();
506 model_cache.available_models.clear();
507 model_cache.model_names.clear();
508 model_cache.last_refresh = absl::InfinitePast();
509 model_cache.auto_refresh_requested = false;
510 model_cache.last_provider = ctx_config.ai_provider;
511 model_cache.last_openai_base = ctx_config.openai_base_url;
512 model_cache.last_ollama_host = ctx_config.ollama_host;
513 }
514
515 internal::CopyStringToBuffer(ctx_config.ai_provider,
516 ctx_config.provider_buffer);
517 internal::CopyStringToBuffer(ctx_config.ai_model, ctx_config.model_buffer);
518 internal::CopyStringToBuffer(ctx_config.ollama_host,
519 ctx_config.ollama_host_buffer);
520 internal::CopyStringToBuffer(ctx_config.gemini_api_key,
521 ctx_config.gemini_key_buffer);
522 internal::CopyStringToBuffer(ctx_config.anthropic_api_key,
523 ctx_config.anthropic_key_buffer);
524 internal::CopyStringToBuffer(ctx_config.openai_api_key,
525 ctx_config.openai_key_buffer);
526 internal::CopyStringToBuffer(ctx_config.openai_base_url,
527 ctx_config.openai_base_url_buffer);
528
529 current_profile_.provider = ctx_config.ai_provider;
530 current_profile_.model = ctx_config.ai_model;
531 current_profile_.ollama_host = ctx_config.ollama_host;
532 current_profile_.gemini_api_key = ctx_config.gemini_api_key;
533 current_profile_.anthropic_api_key = ctx_config.anthropic_api_key;
534 current_profile_.openai_api_key = ctx_config.openai_api_key;
535 current_profile_.openai_base_url = ctx_config.openai_base_url;
536 current_profile_.host_id = ctx_config.host_id;
537 current_profile_.verbose = ctx_config.verbose;
538 current_profile_.show_reasoning = ctx_config.show_reasoning;
539 current_profile_.max_tool_iterations = ctx_config.max_tool_iterations;
540 current_profile_.max_retry_attempts = ctx_config.max_retry_attempts;
541 current_profile_.temperature = ctx_config.temperature;
542 current_profile_.top_p = ctx_config.top_p;
543 current_profile_.max_output_tokens = ctx_config.max_output_tokens;
544 current_profile_.stream_responses = ctx_config.stream_responses;
545 current_profile_.modified_at = absl::Now();
546
548
549 current_config_.provider = ctx_config.ai_provider;
550 current_config_.model = ctx_config.ai_model;
551 current_config_.ollama_host = ctx_config.ollama_host;
552 current_config_.gemini_api_key = ctx_config.gemini_api_key;
553 current_config_.openai_api_key = ctx_config.openai_api_key;
554 current_config_.openai_base_url = ctx_config.openai_base_url;
555 current_config_.verbose = ctx_config.verbose;
556 current_config_.show_reasoning = ctx_config.show_reasoning;
557 current_config_.max_tool_iterations = ctx_config.max_tool_iterations;
558 current_config_.max_retry_attempts = ctx_config.max_retry_attempts;
559 current_config_.temperature = ctx_config.temperature;
560 current_config_.top_p = ctx_config.top_p;
561 current_config_.max_output_tokens = ctx_config.max_output_tokens;
562 current_config_.stream_responses = ctx_config.stream_responses;
563
566}
567
569 if (!context_ || !agent_chat_) {
570 return;
571 }
572 auto* service = agent_chat_->GetAgentService();
573 if (!service) {
574 return;
575 }
576
577 const auto& tool_config = context_->agent_config().tool_config;
579 prefs.resources = tool_config.resources;
580 prefs.dungeon = tool_config.dungeon;
581 prefs.overworld = tool_config.overworld;
582 prefs.messages = tool_config.messages;
583 prefs.dialogue = tool_config.dialogue;
584 prefs.gui = tool_config.gui;
585 prefs.music = tool_config.music;
586 prefs.sprite = tool_config.sprite;
587#ifdef YAZE_WITH_GRPC
588 prefs.emulator = tool_config.emulator;
589#else
590 prefs.emulator = false;
591#endif
592 prefs.memory_inspector = tool_config.memory_inspector;
593 service->SetToolPreferences(prefs);
594}
595
596} // namespace editor
597} // namespace yaze
void SetCurrentProfile(const BotProfile &profile)
void ApplyConfig(const AgentConfig &config)
absl::Status SaveBuilderBlueprint(const std::filesystem::path &path)
absl::StatusOr< BotProfile > JsonToProfile(const std::string &json) const
absl::Status ImportProfile(const std::filesystem::path &path)
AgentBuilderState builder_state_
AgentUIContext * context_
absl::Status LoadBuilderBlueprint(const std::filesystem::path &path)
absl::Status ExportProfile(const BotProfile &profile, const std::filesystem::path &path)
absl::Status SaveBotProfile(const BotProfile &profile)
absl::Status DeleteBotProfile(const std::string &name)
std::filesystem::path GetProfilesDirectory() const
std::string ProfileToJson(const BotProfile &profile) const
absl::Status Load() override
absl::Status LoadBotProfile(const std::string &name)
std::vector< BotProfile > GetAllProfiles() const
void ApplyConfigFromContext(const AgentConfigState &config)
std::unique_ptr< AgentChat > agent_chat_
std::vector< BotProfile > loaded_profiles_
AgentConfigState & agent_config()
static absl::StatusOr< std::filesystem::path > GetTempDirectory()
Get a temporary directory for the application.
static absl::StatusOr< std::filesystem::path > GetAppDataSubdirectory(const std::string &subdir)
Get a subdirectory within the app data folder.
std::string NormalizeOpenAiBaseUrl(std::string base)
constexpr char kProviderMock[]
Definition provider_ids.h:7
void CopyStringToBuffer(const std::string &src, char(&dest)[N])
Agent configuration state.
std::vector< std::string > model_chain
std::vector< ModelPreset > model_presets
std::vector< std::string > favorite_models
struct yaze::editor::AgentEditor::AgentBuilderState::ToolPlan tools
std::vector< std::string > tags
std::vector< cli::ModelInfo > available_models