Refactor CLI Service Structure and Enhance AI Integration
- Restructured CLI service source files to improve organization, moving files into dedicated directories for better maintainability. - Introduced new AI service components, including `AIService`, `MockAIService`, and `GeminiAIService`, to facilitate natural language command generation. - Implemented `PolicyEvaluator` and `ProposalRegistry` for enhanced proposal management and policy enforcement in AI workflows. - Updated CMake configurations to reflect new file paths and ensure proper linking of the restructured components. - Enhanced test suite with new test workflow generation capabilities, improving the robustness of automated testing. This commit significantly advances the architecture of the z3ed system, laying the groundwork for more sophisticated AI-driven features and streamlined development processes.
This commit is contained in:
482
src/cli/service/resources/resource_catalog.cc
Normal file
482
src/cli/service/resources/resource_catalog.cc
Normal file
@@ -0,0 +1,482 @@
|
||||
#include "cli/service/resources/resource_catalog.h"
|
||||
|
||||
#include <sstream>
|
||||
|
||||
#include "absl/strings/str_cat.h"
|
||||
#include "absl/strings/str_join.h"
|
||||
#include "absl/strings/string_view.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace cli {
|
||||
|
||||
namespace {
|
||||
|
||||
ResourceSchema MakePaletteSchema() {
|
||||
ResourceSchema schema;
|
||||
schema.resource = "palette";
|
||||
schema.description =
|
||||
"Palette manipulation commands covering export, import, and color editing.";
|
||||
|
||||
ResourceAction export_action;
|
||||
export_action.name = "export";
|
||||
export_action.synopsis = "z3ed palette export --group <group> --id <id> --to <file>";
|
||||
export_action.stability = "experimental";
|
||||
export_action.arguments = {
|
||||
ResourceArgument{"--group", "integer", true, "Palette group id (0-31)."},
|
||||
ResourceArgument{"--id", "integer", true, "Palette index inside the group."},
|
||||
ResourceArgument{"--to", "path", true, "Destination file path for binary export."},
|
||||
};
|
||||
export_action.effects = {
|
||||
"Reads ROM palette buffer and writes binary palette data to disk."};
|
||||
|
||||
ResourceAction import_action;
|
||||
import_action.name = "import";
|
||||
import_action.synopsis =
|
||||
"z3ed palette import --group <group> --id <id> --from <file>";
|
||||
import_action.stability = "experimental";
|
||||
import_action.arguments = {
|
||||
ResourceArgument{"--group", "integer", true, "Palette group id (0-31)."},
|
||||
ResourceArgument{"--id", "integer", true, "Palette index inside the group."},
|
||||
ResourceArgument{"--from", "path", true, "Source binary palette file."},
|
||||
};
|
||||
import_action.effects = {
|
||||
"Writes imported palette bytes into ROM buffer and marks project dirty."};
|
||||
|
||||
schema.actions = {export_action, import_action};
|
||||
return schema;
|
||||
}
|
||||
|
||||
ResourceSchema MakeRomSchema() {
|
||||
ResourceSchema schema;
|
||||
schema.resource = "rom";
|
||||
schema.description = "ROM validation, diffing, and snapshot helpers.";
|
||||
|
||||
ResourceAction info_action;
|
||||
info_action.name = "info";
|
||||
info_action.synopsis = "z3ed rom info --rom <file>";
|
||||
info_action.stability = "stable";
|
||||
info_action.arguments = {
|
||||
ResourceArgument{"--rom", "path", true, "Path to ROM file configured via global flag."},
|
||||
};
|
||||
info_action.effects = {
|
||||
"Reads ROM from disk and displays basic information (title, size, filename)."};
|
||||
info_action.returns = {
|
||||
{"title", "string", "ROM internal title from header."},
|
||||
{"size", "integer", "ROM file size in bytes."},
|
||||
{"filename", "string", "Full path to the ROM file."}};
|
||||
|
||||
ResourceAction validate_action;
|
||||
validate_action.name = "validate";
|
||||
validate_action.synopsis = "z3ed rom validate --rom <file>";
|
||||
validate_action.stability = "stable";
|
||||
validate_action.arguments = {
|
||||
ResourceArgument{"--rom", "path", true, "Path to ROM file configured via global flag."},
|
||||
};
|
||||
validate_action.effects = {
|
||||
"Reads ROM from disk, verifies checksum, and reports header status."};
|
||||
validate_action.returns = {
|
||||
{"report", "object",
|
||||
"Structured validation summary with checksum and header results."}};
|
||||
|
||||
ResourceAction diff_action;
|
||||
diff_action.name = "diff";
|
||||
diff_action.synopsis = "z3ed rom diff <rom_a> <rom_b>";
|
||||
diff_action.stability = "beta";
|
||||
diff_action.arguments = {
|
||||
ResourceArgument{"rom_a", "path", true, "Reference ROM path."},
|
||||
ResourceArgument{"rom_b", "path", true, "Candidate ROM path."},
|
||||
};
|
||||
diff_action.effects = {
|
||||
"Reads two ROM images, compares bytes, and streams differences to stdout."};
|
||||
diff_action.returns = {
|
||||
{"differences", "integer", "Count of mismatched bytes between ROMs."}};
|
||||
|
||||
ResourceAction generate_action;
|
||||
generate_action.name = "generate-golden";
|
||||
generate_action.synopsis =
|
||||
"z3ed rom generate-golden <rom_file> <golden_file>";
|
||||
generate_action.stability = "experimental";
|
||||
generate_action.arguments = {
|
||||
ResourceArgument{"rom_file", "path", true, "Source ROM to snapshot."},
|
||||
ResourceArgument{"golden_file", "path", true, "Output path for golden image."},
|
||||
};
|
||||
generate_action.effects = {
|
||||
"Writes out exact ROM image for tooling baselines and diff workflows."};
|
||||
generate_action.returns = {
|
||||
{"artifact", "path", "Absolute path to the generated golden image."}};
|
||||
|
||||
schema.actions = {info_action, validate_action, diff_action, generate_action};
|
||||
return schema;
|
||||
}
|
||||
|
||||
ResourceSchema MakePatchSchema() {
|
||||
ResourceSchema schema;
|
||||
schema.resource = "patch";
|
||||
schema.description =
|
||||
"Patch authoring and application commands covering BPS and Asar flows.";
|
||||
|
||||
ResourceAction apply_action;
|
||||
apply_action.name = "apply";
|
||||
apply_action.synopsis = "z3ed patch apply <rom_file> <bps_patch>";
|
||||
apply_action.stability = "beta";
|
||||
apply_action.arguments = {
|
||||
ResourceArgument{"rom_file", "path", true,
|
||||
"Source ROM image that will receive the patch."},
|
||||
ResourceArgument{"bps_patch", "path", true,
|
||||
"BPS patch to apply to the ROM."},
|
||||
};
|
||||
apply_action.effects = {
|
||||
"Loads ROM from disk, applies a BPS patch, and writes `patched.sfc`."};
|
||||
apply_action.returns = {
|
||||
{"artifact", "path",
|
||||
"Absolute path to the patched ROM image produced on success."}};
|
||||
|
||||
ResourceAction asar_action;
|
||||
asar_action.name = "apply-asar";
|
||||
asar_action.synopsis = "z3ed patch apply-asar <patch.asm>";
|
||||
asar_action.stability = "prototype";
|
||||
asar_action.arguments = {
|
||||
ResourceArgument{"patch.asm", "path", true,
|
||||
"Assembly patch consumed by the bundled Asar runtime."},
|
||||
ResourceArgument{"--rom", "path", false,
|
||||
"ROM path supplied via global --rom flag."},
|
||||
};
|
||||
asar_action.effects = {
|
||||
"Invokes Asar against the active ROM buffer and applies assembled changes."};
|
||||
asar_action.returns = {
|
||||
{"log", "string", "Assembler diagnostics emitted during application."}};
|
||||
|
||||
ResourceAction create_action;
|
||||
create_action.name = "create";
|
||||
create_action.synopsis =
|
||||
"z3ed patch create --source <rom> --target <rom> --out <patch.bps>";
|
||||
create_action.stability = "experimental";
|
||||
create_action.arguments = {
|
||||
ResourceArgument{"--source", "path", true,
|
||||
"Baseline ROM used when computing the patch."},
|
||||
ResourceArgument{"--target", "path", true,
|
||||
"Modified ROM to diff against the baseline."},
|
||||
ResourceArgument{"--out", "path", true,
|
||||
"Output path for the generated BPS patch."},
|
||||
};
|
||||
create_action.effects = {
|
||||
"Compares source and target images to synthesize a distributable BPS patch."};
|
||||
create_action.returns = {
|
||||
{"artifact", "path", "File system path to the generated patch."}};
|
||||
|
||||
schema.actions = {apply_action, asar_action, create_action};
|
||||
return schema;
|
||||
}
|
||||
|
||||
ResourceSchema MakeOverworldSchema() {
|
||||
ResourceSchema schema;
|
||||
schema.resource = "overworld";
|
||||
schema.description = "Overworld tile inspection and manipulation commands.";
|
||||
|
||||
ResourceAction get_tile;
|
||||
get_tile.name = "get-tile";
|
||||
get_tile.synopsis = "z3ed overworld get-tile --map <map_id> --x <x> --y <y>";
|
||||
get_tile.stability = "stable";
|
||||
get_tile.arguments = {
|
||||
ResourceArgument{"--map", "integer", true, "Overworld map identifier (0-63)."},
|
||||
ResourceArgument{"--x", "integer", true, "Tile x coordinate."},
|
||||
ResourceArgument{"--y", "integer", true, "Tile y coordinate."},
|
||||
};
|
||||
get_tile.returns = {
|
||||
{"tile", "integer",
|
||||
"Tile id located at the supplied coordinates."}};
|
||||
|
||||
ResourceAction set_tile;
|
||||
set_tile.name = "set-tile";
|
||||
set_tile.synopsis =
|
||||
"z3ed overworld set-tile --map <map_id> --x <x> --y <y> --tile <tile_id>";
|
||||
set_tile.stability = "experimental";
|
||||
set_tile.arguments = {
|
||||
ResourceArgument{"--map", "integer", true, "Overworld map identifier (0-63)."},
|
||||
ResourceArgument{"--x", "integer", true, "Tile x coordinate."},
|
||||
ResourceArgument{"--y", "integer", true, "Tile y coordinate."},
|
||||
ResourceArgument{"--tile", "integer", true, "Tile id to write."},
|
||||
};
|
||||
set_tile.effects = {
|
||||
"Mutates overworld tile map and enqueues render invalidation."};
|
||||
|
||||
schema.actions = {get_tile, set_tile};
|
||||
return schema;
|
||||
}
|
||||
|
||||
ResourceSchema MakeDungeonSchema() {
|
||||
ResourceSchema schema;
|
||||
schema.resource = "dungeon";
|
||||
schema.description = "Dungeon room export and inspection utilities.";
|
||||
|
||||
ResourceAction export_action;
|
||||
export_action.name = "export";
|
||||
export_action.synopsis = "z3ed dungeon export <room_id>";
|
||||
export_action.stability = "prototype";
|
||||
export_action.arguments = {
|
||||
ResourceArgument{"room_id", "integer", true,
|
||||
"Dungeon room identifier to inspect."},
|
||||
};
|
||||
export_action.effects = {
|
||||
"Loads the active ROM via --rom and prints metadata for the requested room."};
|
||||
export_action.returns = {
|
||||
{"metadata", "object",
|
||||
"Structured room summary including blockset, spriteset, palette, and layout."}};
|
||||
|
||||
ResourceAction list_objects_action;
|
||||
list_objects_action.name = "list-objects";
|
||||
list_objects_action.synopsis = "z3ed dungeon list-objects <room_id>";
|
||||
list_objects_action.stability = "prototype";
|
||||
list_objects_action.arguments = {
|
||||
ResourceArgument{"room_id", "integer", true,
|
||||
"Dungeon room identifier whose objects should be listed."},
|
||||
};
|
||||
list_objects_action.effects = {
|
||||
"Streams parsed dungeon object records for the requested room to stdout."};
|
||||
list_objects_action.returns = {
|
||||
{"objects", "array",
|
||||
"Collection of tile object records with ids, coordinates, and layers."}};
|
||||
|
||||
schema.actions = {export_action, list_objects_action};
|
||||
return schema;
|
||||
}
|
||||
|
||||
ResourceSchema MakeAgentSchema() {
|
||||
ResourceSchema schema;
|
||||
schema.resource = "agent";
|
||||
schema.description =
|
||||
"Agent workflow helpers including planning, diffing, listing, and schema discovery.";
|
||||
|
||||
ResourceAction describe_action;
|
||||
describe_action.name = "describe";
|
||||
describe_action.synopsis = "z3ed agent describe --resource <name>";
|
||||
describe_action.stability = "prototype";
|
||||
describe_action.arguments = {
|
||||
ResourceArgument{"--resource", "string", false,
|
||||
"Optional resource name to filter results."},
|
||||
};
|
||||
describe_action.returns = {
|
||||
{"schema", "object",
|
||||
"JSON schema describing resource arguments and semantics."}};
|
||||
|
||||
ResourceAction list_action;
|
||||
list_action.name = "list";
|
||||
list_action.synopsis = "z3ed agent list";
|
||||
list_action.stability = "prototype";
|
||||
list_action.arguments = {};
|
||||
list_action.effects = {{"reads", "proposal_registry"}};
|
||||
list_action.returns = {
|
||||
{"proposals", "array",
|
||||
"List of all proposals with ID, status, prompt, and metadata."}};
|
||||
|
||||
ResourceAction diff_action;
|
||||
diff_action.name = "diff";
|
||||
diff_action.synopsis = "z3ed agent diff [--proposal-id <id>]";
|
||||
diff_action.stability = "prototype";
|
||||
diff_action.arguments = {
|
||||
ResourceArgument{"--proposal-id", "string", false,
|
||||
"Optional proposal ID to view specific proposal. Defaults to latest pending."},
|
||||
};
|
||||
diff_action.effects = {{"reads", "proposal_registry"}, {"reads", "sandbox"}};
|
||||
diff_action.returns = {
|
||||
{"diff", "string", "Unified diff showing changes to ROM."},
|
||||
{"log", "string", "Execution log of commands run."},
|
||||
{"metadata", "object", "Proposal metadata including status and timestamps."}};
|
||||
|
||||
schema.actions = {describe_action, list_action, diff_action};
|
||||
return schema;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
const ResourceCatalog& ResourceCatalog::Instance() {
|
||||
static ResourceCatalog* instance = new ResourceCatalog();
|
||||
return *instance;
|
||||
}
|
||||
|
||||
ResourceCatalog::ResourceCatalog()
|
||||
: resources_({MakeRomSchema(), MakePatchSchema(), MakePaletteSchema(),
|
||||
MakeOverworldSchema(), MakeDungeonSchema(), MakeAgentSchema()}) {}
|
||||
|
||||
absl::StatusOr<ResourceSchema> ResourceCatalog::GetResource(absl::string_view name) const {
|
||||
for (const auto& resource : resources_) {
|
||||
if (resource.resource == name) {
|
||||
return resource;
|
||||
}
|
||||
}
|
||||
return absl::NotFoundError(absl::StrCat("Resource not found: ", name));
|
||||
}
|
||||
|
||||
const std::vector<ResourceSchema>& ResourceCatalog::AllResources() const { return resources_; }
|
||||
|
||||
std::string ResourceCatalog::SerializeResource(const ResourceSchema& schema) const {
|
||||
return SerializeResources({schema});
|
||||
}
|
||||
|
||||
std::string ResourceCatalog::SerializeResources(const std::vector<ResourceSchema>& schemas) const {
|
||||
std::vector<std::string> entries;
|
||||
entries.reserve(schemas.size());
|
||||
for (const auto& resource : schemas) {
|
||||
std::vector<std::string> action_entries;
|
||||
action_entries.reserve(resource.actions.size());
|
||||
for (const auto& action : resource.actions) {
|
||||
std::vector<std::string> arg_entries;
|
||||
arg_entries.reserve(action.arguments.size());
|
||||
for (const auto& arg : action.arguments) {
|
||||
arg_entries.push_back(absl::StrCat(
|
||||
"{\"flag\":\"", EscapeJson(arg.flag),
|
||||
"\",\"type\":\"", EscapeJson(arg.type),
|
||||
"\",\"required\":", arg.required ? "true" : "false",
|
||||
",\"description\":\"", EscapeJson(arg.description), "\"}"));
|
||||
}
|
||||
std::vector<std::string> effect_entries;
|
||||
effect_entries.reserve(action.effects.size());
|
||||
for (const auto& effect : action.effects) {
|
||||
effect_entries.push_back(absl::StrCat("\"", EscapeJson(effect), "\""));
|
||||
}
|
||||
std::vector<std::string> return_entries;
|
||||
return_entries.reserve(action.returns.size());
|
||||
for (const auto& ret : action.returns) {
|
||||
return_entries.push_back(absl::StrCat(
|
||||
"{\"field\":\"", EscapeJson(ret.field),
|
||||
"\",\"type\":\"", EscapeJson(ret.type),
|
||||
"\",\"description\":\"", EscapeJson(ret.description), "\"}"));
|
||||
}
|
||||
action_entries.push_back(absl::StrCat(
|
||||
"{\"name\":\"", EscapeJson(action.name),
|
||||
"\",\"synopsis\":\"", EscapeJson(action.synopsis),
|
||||
"\",\"stability\":\"", EscapeJson(action.stability),
|
||||
"\",\"arguments\":[", absl::StrJoin(arg_entries, ","), "],",
|
||||
"\"effects\":[", absl::StrJoin(effect_entries, ","), "],",
|
||||
"\"returns\":[", absl::StrJoin(return_entries, ","), "]}"));
|
||||
}
|
||||
entries.push_back(absl::StrCat(
|
||||
"{\"resource\":\"", EscapeJson(resource.resource),
|
||||
"\",\"description\":\"", EscapeJson(resource.description),
|
||||
"\",\"actions\":[", absl::StrJoin(action_entries, ","), "]}"));
|
||||
}
|
||||
return absl::StrCat("{\"resources\":[", absl::StrJoin(entries, ","), "]}");
|
||||
}
|
||||
|
||||
std::string ResourceCatalog::EscapeJson(absl::string_view value) {
|
||||
std::string out;
|
||||
out.reserve(value.size());
|
||||
for (char c : value) {
|
||||
switch (c) {
|
||||
case '\\':
|
||||
out += "\\\\";
|
||||
break;
|
||||
case '\"':
|
||||
out += "\\\"";
|
||||
break;
|
||||
case '\n':
|
||||
out += "\\n";
|
||||
break;
|
||||
case '\r':
|
||||
out += "\\r";
|
||||
break;
|
||||
case '\t':
|
||||
out += "\\t";
|
||||
break;
|
||||
default:
|
||||
out += c;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
std::string ResourceCatalog::SerializeResourcesAsYaml(
|
||||
const std::vector<ResourceSchema>& schemas,
|
||||
absl::string_view version,
|
||||
absl::string_view last_updated) const {
|
||||
std::string out;
|
||||
absl::StrAppend(&out, "# Auto-generated resource catalogue\n");
|
||||
absl::StrAppend(&out, "version: ", EscapeYaml(version), "\n");
|
||||
absl::StrAppend(&out, "last_updated: ", EscapeYaml(last_updated), "\n");
|
||||
absl::StrAppend(&out, "resources:\n");
|
||||
|
||||
for (const auto& resource : schemas) {
|
||||
absl::StrAppend(&out, " - name: ", EscapeYaml(resource.resource), "\n");
|
||||
absl::StrAppend(&out, " description: ", EscapeYaml(resource.description), "\n");
|
||||
|
||||
if (resource.actions.empty()) {
|
||||
absl::StrAppend(&out, " actions: []\n");
|
||||
continue;
|
||||
}
|
||||
|
||||
absl::StrAppend(&out, " actions:\n");
|
||||
for (const auto& action : resource.actions) {
|
||||
absl::StrAppend(&out, " - name: ", EscapeYaml(action.name), "\n");
|
||||
absl::StrAppend(&out, " synopsis: ", EscapeYaml(action.synopsis), "\n");
|
||||
absl::StrAppend(&out, " stability: ", EscapeYaml(action.stability), "\n");
|
||||
|
||||
if (action.arguments.empty()) {
|
||||
absl::StrAppend(&out, " args: []\n");
|
||||
} else {
|
||||
absl::StrAppend(&out, " args:\n");
|
||||
for (const auto& arg : action.arguments) {
|
||||
absl::StrAppend(&out, " - flag: ", EscapeYaml(arg.flag), "\n");
|
||||
absl::StrAppend(&out, " type: ", EscapeYaml(arg.type), "\n");
|
||||
absl::StrAppend(&out, " required: ", arg.required ? "true\n" : "false\n");
|
||||
absl::StrAppend(&out, " description: ", EscapeYaml(arg.description), "\n");
|
||||
}
|
||||
}
|
||||
|
||||
if (action.effects.empty()) {
|
||||
absl::StrAppend(&out, " effects: []\n");
|
||||
} else {
|
||||
absl::StrAppend(&out, " effects:\n");
|
||||
for (const auto& effect : action.effects) {
|
||||
absl::StrAppend(&out, " - ", EscapeYaml(effect), "\n");
|
||||
}
|
||||
}
|
||||
|
||||
if (action.returns.empty()) {
|
||||
absl::StrAppend(&out, " returns: []\n");
|
||||
} else {
|
||||
absl::StrAppend(&out, " returns:\n");
|
||||
for (const auto& ret : action.returns) {
|
||||
absl::StrAppend(&out, " - field: ", EscapeYaml(ret.field), "\n");
|
||||
absl::StrAppend(&out, " type: ", EscapeYaml(ret.type), "\n");
|
||||
absl::StrAppend(&out, " description: ", EscapeYaml(ret.description), "\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
std::string ResourceCatalog::EscapeYaml(absl::string_view value) {
|
||||
std::string out;
|
||||
out.reserve(value.size() + 2);
|
||||
out.push_back('"');
|
||||
for (char c : value) {
|
||||
switch (c) {
|
||||
case '\\':
|
||||
out += "\\\\";
|
||||
break;
|
||||
case '"':
|
||||
out += "\\\"";
|
||||
break;
|
||||
case '\n':
|
||||
out += "\\n";
|
||||
break;
|
||||
case '\r':
|
||||
out += "\\r";
|
||||
break;
|
||||
case '\t':
|
||||
out += "\\t";
|
||||
break;
|
||||
default:
|
||||
out.push_back(c);
|
||||
break;
|
||||
}
|
||||
}
|
||||
out.push_back('"');
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace cli
|
||||
} // namespace yaze
|
||||
71
src/cli/service/resources/resource_catalog.h
Normal file
71
src/cli/service/resources/resource_catalog.h
Normal file
@@ -0,0 +1,71 @@
|
||||
#ifndef YAZE_SRC_CLI_SERVICE_RESOURCE_CATALOG_H_
|
||||
#define YAZE_SRC_CLI_SERVICE_RESOURCE_CATALOG_H_
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/status/status.h"
|
||||
#include "absl/status/statusor.h"
|
||||
#include "absl/strings/string_view.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace cli {
|
||||
|
||||
struct ResourceArgument {
|
||||
std::string flag;
|
||||
std::string type;
|
||||
bool required;
|
||||
std::string description;
|
||||
};
|
||||
|
||||
struct ResourceAction {
|
||||
std::string name;
|
||||
std::string synopsis;
|
||||
std::string stability;
|
||||
std::vector<ResourceArgument> arguments;
|
||||
std::vector<std::string> effects;
|
||||
struct ReturnValue {
|
||||
std::string field;
|
||||
std::string type;
|
||||
std::string description;
|
||||
};
|
||||
std::vector<ReturnValue> returns;
|
||||
};
|
||||
|
||||
struct ResourceSchema {
|
||||
std::string resource;
|
||||
std::string description;
|
||||
std::vector<ResourceAction> actions;
|
||||
};
|
||||
|
||||
// ResourceCatalog exposes a machine-readable description of CLI resources so that
|
||||
// both humans and AI agents can introspect capabilities at runtime.
|
||||
class ResourceCatalog {
|
||||
public:
|
||||
static const ResourceCatalog& Instance();
|
||||
|
||||
absl::StatusOr<ResourceSchema> GetResource(absl::string_view name) const;
|
||||
const std::vector<ResourceSchema>& AllResources() const;
|
||||
|
||||
// Serialize helpers for `z3ed agent describe`. These return compact JSON
|
||||
// strings so we avoid introducing a hard dependency on nlohmann::json.
|
||||
std::string SerializeResource(const ResourceSchema& schema) const;
|
||||
std::string SerializeResources(const std::vector<ResourceSchema>& schemas) const;
|
||||
std::string SerializeResourcesAsYaml(
|
||||
const std::vector<ResourceSchema>& schemas,
|
||||
absl::string_view version,
|
||||
absl::string_view last_updated) const;
|
||||
|
||||
private:
|
||||
ResourceCatalog();
|
||||
|
||||
static std::string EscapeJson(absl::string_view value);
|
||||
static std::string EscapeYaml(absl::string_view value);
|
||||
|
||||
std::vector<ResourceSchema> resources_;
|
||||
};
|
||||
|
||||
} // namespace cli
|
||||
} // namespace yaze
|
||||
|
||||
#endif // YAZE_SRC_CLI_SERVICE_RESOURCE_CATALOG_H_
|
||||
262
src/cli/service/resources/resource_context_builder.cc
Normal file
262
src/cli/service/resources/resource_context_builder.cc
Normal file
@@ -0,0 +1,262 @@
|
||||
#include "cli/service/resources/resource_context_builder.h"
|
||||
|
||||
#include <sstream>
|
||||
|
||||
namespace yaze {
|
||||
namespace cli {
|
||||
|
||||
std::string ResourceContextBuilder::GetCommonTile16Reference() {
|
||||
std::ostringstream oss;
|
||||
oss << "Common Tile16s:\n";
|
||||
oss << " - 0x020: Grass\n";
|
||||
oss << " - 0x022: Dirt\n";
|
||||
oss << " - 0x02E: Tree\n";
|
||||
oss << " - 0x003: Bush\n";
|
||||
oss << " - 0x004: Rock\n";
|
||||
oss << " - 0x021: Flower\n";
|
||||
oss << " - 0x023: Sand\n";
|
||||
oss << " - 0x14C: Water (top edge)\n";
|
||||
oss << " - 0x14D: Water (middle)\n";
|
||||
oss << " - 0x14E: Water (bottom edge)\n";
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
std::string ResourceContextBuilder::ExtractOverworldLabels() {
|
||||
if (!rom_ || !rom_->is_loaded()) {
|
||||
return "Overworld Maps: (ROM not loaded)\n";
|
||||
}
|
||||
|
||||
auto* label_mgr = rom_->resource_label();
|
||||
if (!label_mgr || !label_mgr->labels_loaded_) {
|
||||
return "Overworld Maps: (No labels file loaded)\n";
|
||||
}
|
||||
|
||||
std::ostringstream oss;
|
||||
oss << "Overworld Maps:\n";
|
||||
|
||||
// Check if "overworld" labels exist
|
||||
auto it = label_mgr->labels_.find("overworld");
|
||||
if (it != label_mgr->labels_.end()) {
|
||||
for (const auto& [key, value] : it->second) {
|
||||
oss << " - " << key << ": \"" << value << "\"\n";
|
||||
}
|
||||
} else {
|
||||
// Provide defaults
|
||||
oss << " - 0: \"Light World\"\n";
|
||||
oss << " - 1: \"Dark World\"\n";
|
||||
oss << " - 3: \"Desert\"\n";
|
||||
}
|
||||
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
std::string ResourceContextBuilder::ExtractDungeonLabels() {
|
||||
if (!rom_ || !rom_->is_loaded()) {
|
||||
return "Dungeons: (ROM not loaded)\n";
|
||||
}
|
||||
|
||||
auto* label_mgr = rom_->resource_label();
|
||||
if (!label_mgr || !label_mgr->labels_loaded_) {
|
||||
return "Dungeons: (No labels file loaded)\n";
|
||||
}
|
||||
|
||||
std::ostringstream oss;
|
||||
oss << "Dungeons:\n";
|
||||
|
||||
// Check if "dungeon" labels exist
|
||||
auto it = label_mgr->labels_.find("dungeon");
|
||||
if (it != label_mgr->labels_.end()) {
|
||||
for (const auto& [key, value] : it->second) {
|
||||
oss << " - " << key << ": \"" << value << "\"\n";
|
||||
}
|
||||
} else {
|
||||
// Provide vanilla defaults
|
||||
oss << " - 0x00: \"Hyrule Castle\"\n";
|
||||
oss << " - 0x02: \"Eastern Palace\"\n";
|
||||
oss << " - 0x04: \"Desert Palace\"\n";
|
||||
oss << " - 0x06: \"Tower of Hera\"\n";
|
||||
}
|
||||
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
std::string ResourceContextBuilder::ExtractEntranceLabels() {
|
||||
if (!rom_ || !rom_->is_loaded()) {
|
||||
return "Entrances: (ROM not loaded)\n";
|
||||
}
|
||||
|
||||
auto* label_mgr = rom_->resource_label();
|
||||
if (!label_mgr || !label_mgr->labels_loaded_) {
|
||||
return "Entrances: (No labels file loaded)\n";
|
||||
}
|
||||
|
||||
std::ostringstream oss;
|
||||
oss << "Entrances:\n";
|
||||
|
||||
// Check if "entrance" labels exist
|
||||
auto it = label_mgr->labels_.find("entrance");
|
||||
if (it != label_mgr->labels_.end()) {
|
||||
for (const auto& [key, value] : it->second) {
|
||||
oss << " - " << key << ": \"" << value << "\"\n";
|
||||
}
|
||||
} else {
|
||||
// Provide vanilla defaults
|
||||
oss << " - 0x00: \"Link's House\"\n";
|
||||
oss << " - 0x01: \"Sanctuary\"\n";
|
||||
}
|
||||
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
std::string ResourceContextBuilder::ExtractRoomLabels() {
|
||||
if (!rom_ || !rom_->is_loaded()) {
|
||||
return "Rooms: (ROM not loaded)\n";
|
||||
}
|
||||
|
||||
auto* label_mgr = rom_->resource_label();
|
||||
if (!label_mgr || !label_mgr->labels_loaded_) {
|
||||
return "Rooms: (No labels file loaded)\n";
|
||||
}
|
||||
|
||||
std::ostringstream oss;
|
||||
oss << "Rooms:\n";
|
||||
|
||||
// Check if "room" labels exist
|
||||
auto it = label_mgr->labels_.find("room");
|
||||
if (it != label_mgr->labels_.end()) {
|
||||
for (const auto& [key, value] : it->second) {
|
||||
oss << " - " << key << ": \"" << value << "\"\n";
|
||||
}
|
||||
} else {
|
||||
oss << " (No room labels defined)\n";
|
||||
}
|
||||
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
std::string ResourceContextBuilder::ExtractSpriteLabels() {
|
||||
if (!rom_ || !rom_->is_loaded()) {
|
||||
return "Sprites: (ROM not loaded)\n";
|
||||
}
|
||||
|
||||
auto* label_mgr = rom_->resource_label();
|
||||
if (!label_mgr || !label_mgr->labels_loaded_) {
|
||||
return "Sprites: (No labels file loaded)\n";
|
||||
}
|
||||
|
||||
std::ostringstream oss;
|
||||
oss << "Sprites:\n";
|
||||
|
||||
// Check if "sprite" labels exist
|
||||
auto it = label_mgr->labels_.find("sprite");
|
||||
if (it != label_mgr->labels_.end()) {
|
||||
for (const auto& [key, value] : it->second) {
|
||||
oss << " - " << key << ": \"" << value << "\"\n";
|
||||
}
|
||||
} else {
|
||||
// Provide vanilla defaults
|
||||
oss << " - 0x00: \"Soldier\"\n";
|
||||
oss << " - 0x01: \"Octorok\"\n";
|
||||
}
|
||||
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
absl::StatusOr<std::string> ResourceContextBuilder::BuildResourceContext() {
|
||||
if (!rom_) {
|
||||
return absl::InvalidArgumentError("ROM pointer is null");
|
||||
}
|
||||
|
||||
std::ostringstream context;
|
||||
|
||||
context << "=== AVAILABLE RESOURCES ===\n\n";
|
||||
|
||||
// Add overworld maps
|
||||
context << ExtractOverworldLabels() << "\n";
|
||||
|
||||
// Add dungeons
|
||||
context << ExtractDungeonLabels() << "\n";
|
||||
|
||||
// Add entrances
|
||||
context << ExtractEntranceLabels() << "\n";
|
||||
|
||||
// Add rooms (if any)
|
||||
context << ExtractRoomLabels() << "\n";
|
||||
|
||||
// Add sprites
|
||||
context << ExtractSpriteLabels() << "\n";
|
||||
|
||||
// Add common tile16 reference
|
||||
context << GetCommonTile16Reference() << "\n";
|
||||
|
||||
context << "=== INSTRUCTIONS ===\n";
|
||||
context << "1. Use the resource labels when they're available\n";
|
||||
context << "2. If a user refers to a custom name, check the labels above\n";
|
||||
context << "3. Always provide tile16 IDs as hex values (0x###)\n";
|
||||
context << "4. Explain which resources you're using in your reasoning\n";
|
||||
|
||||
return context.str();
|
||||
}
|
||||
|
||||
absl::StatusOr<std::map<std::string, std::string>>
|
||||
ResourceContextBuilder::GetLabels(const std::string& category) {
|
||||
if (!rom_ || !rom_->is_loaded()) {
|
||||
return absl::FailedPreconditionError("ROM not loaded");
|
||||
}
|
||||
|
||||
auto* label_mgr = rom_->resource_label();
|
||||
if (!label_mgr || !label_mgr->labels_loaded_) {
|
||||
return absl::FailedPreconditionError("No labels file loaded");
|
||||
}
|
||||
|
||||
std::map<std::string, std::string> result;
|
||||
|
||||
auto it = label_mgr->labels_.find(category);
|
||||
if (it != label_mgr->labels_.end()) {
|
||||
for (const auto& [key, value] : it->second) {
|
||||
result[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
absl::StatusOr<std::string> ResourceContextBuilder::ExportToJson() {
|
||||
if (!rom_ || !rom_->is_loaded()) {
|
||||
return absl::InvalidArgumentError("ROM not loaded");
|
||||
}
|
||||
|
||||
auto* label_mgr = rom_->resource_label();
|
||||
if (!label_mgr || !label_mgr->labels_loaded_) {
|
||||
return absl::InvalidArgumentError("No labels file loaded");
|
||||
}
|
||||
|
||||
std::ostringstream json;
|
||||
json << "{\n";
|
||||
|
||||
bool first_category = true;
|
||||
for (const auto& [category, labels] : label_mgr->labels_) {
|
||||
if (!first_category) json << ",\n";
|
||||
first_category = false;
|
||||
|
||||
json << " \"" << category << "\": {\n";
|
||||
|
||||
bool first_label = true;
|
||||
for (const auto& [key, value] : labels) {
|
||||
if (!first_label) json << ",\n";
|
||||
first_label = false;
|
||||
|
||||
json << " \"" << key << "\": \"" << value << "\"";
|
||||
}
|
||||
|
||||
json << "\n }";
|
||||
}
|
||||
|
||||
json << "\n}\n";
|
||||
|
||||
return json.str();
|
||||
}
|
||||
|
||||
} // namespace cli
|
||||
} // namespace yaze
|
||||
|
||||
163
src/cli/service/resources/resource_context_builder.h
Normal file
163
src/cli/service/resources/resource_context_builder.h
Normal file
@@ -0,0 +1,163 @@
|
||||
#ifndef YAZE_CLI_SERVICE_RESOURCE_CONTEXT_BUILDER_H_
|
||||
#define YAZE_CLI_SERVICE_RESOURCE_CONTEXT_BUILDER_H_
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
#include "absl/status/statusor.h"
|
||||
#include "app/rom.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace cli {
|
||||
|
||||
/**
|
||||
* @brief Builds contextual information from ROM resources for AI prompts.
|
||||
*
|
||||
* This class extracts user-defined labels from the ROM's ResourceLabelManager
|
||||
* and formats them into human-readable context that can be injected into
|
||||
* AI prompts. This enables AI to use meaningful names like "eastern_palace"
|
||||
* instead of opaque IDs like "0x02".
|
||||
*
|
||||
* Example usage:
|
||||
* ResourceContextBuilder builder(rom);
|
||||
* std::string context = builder.BuildResourceContext().value();
|
||||
* // Context contains formatted labels for all resource types
|
||||
*/
|
||||
class ResourceContextBuilder {
|
||||
public:
|
||||
explicit ResourceContextBuilder(Rom* rom) : rom_(rom) {}
|
||||
|
||||
/**
|
||||
* @brief Build a complete resource context string for AI prompts.
|
||||
*
|
||||
* Extracts all ResourceLabels from the ROM and formats them into
|
||||
* a structured text format suitable for AI consumption.
|
||||
*
|
||||
* Example output:
|
||||
* ```
|
||||
* === AVAILABLE RESOURCES ===
|
||||
*
|
||||
* Overworld Maps:
|
||||
* - 0: "Light World" (user: "hyrule_overworld")
|
||||
* - 1: "Dark World" (user: "dark_world")
|
||||
*
|
||||
* Dungeons:
|
||||
* - 0x00: "Hyrule Castle" (user: "castle")
|
||||
* - 0x02: "Eastern Palace" (user: "east_palace")
|
||||
*
|
||||
* Common Tile16s:
|
||||
* - 0x020: Grass
|
||||
* - 0x02E: Tree
|
||||
* - 0x14C: Water (top)
|
||||
* ```
|
||||
*
|
||||
* @return Formatted resource context string
|
||||
*/
|
||||
absl::StatusOr<std::string> BuildResourceContext();
|
||||
|
||||
/**
|
||||
* @brief Get labels for a specific resource category.
|
||||
*
|
||||
* @param category Resource type ("overworld", "dungeon", "entrance", etc.)
|
||||
* @return Map of ID -> label for that category
|
||||
*/
|
||||
absl::StatusOr<std::map<std::string, std::string>> GetLabels(
|
||||
const std::string& category);
|
||||
|
||||
/**
|
||||
* @brief Export all labels to JSON format.
|
||||
*
|
||||
* Creates a structured JSON representation of all resources
|
||||
* for potential use by AI services.
|
||||
*
|
||||
* @return JSON string with all resource labels
|
||||
*/
|
||||
absl::StatusOr<std::string> ExportToJson();
|
||||
|
||||
private:
|
||||
Rom* rom_;
|
||||
|
||||
/**
|
||||
* @brief Extract overworld map labels.
|
||||
*
|
||||
* Returns formatted string like:
|
||||
* ```
|
||||
* Overworld Maps:
|
||||
* - 0: "Light World" (user: "hyrule_overworld")
|
||||
* - 1: "Dark World" (user: "dark_world")
|
||||
* ```
|
||||
*/
|
||||
std::string ExtractOverworldLabels();
|
||||
|
||||
/**
|
||||
* @brief Extract dungeon labels.
|
||||
*
|
||||
* Returns formatted string like:
|
||||
* ```
|
||||
* Dungeons:
|
||||
* - 0x00: "Hyrule Castle" (user: "castle")
|
||||
* - 0x02: "Eastern Palace" (user: "east_palace")
|
||||
* ```
|
||||
*/
|
||||
std::string ExtractDungeonLabels();
|
||||
|
||||
/**
|
||||
* @brief Extract entrance labels.
|
||||
*
|
||||
* Returns formatted string like:
|
||||
* ```
|
||||
* Entrances:
|
||||
* - 0x00: "Link's House" (user: "starting_house")
|
||||
* - 0x01: "Sanctuary" (user: "church")
|
||||
* ```
|
||||
*/
|
||||
std::string ExtractEntranceLabels();
|
||||
|
||||
/**
|
||||
* @brief Extract room labels.
|
||||
*
|
||||
* Returns formatted string like:
|
||||
* ```
|
||||
* Rooms:
|
||||
* - 0x00_0x10: "Eastern Palace Boss Room"
|
||||
* - 0x04_0x05: "Desert Palace Treasure Room"
|
||||
* ```
|
||||
*/
|
||||
std::string ExtractRoomLabels();
|
||||
|
||||
/**
|
||||
* @brief Extract sprite labels.
|
||||
*
|
||||
* Returns formatted string like:
|
||||
* ```
|
||||
* Sprites:
|
||||
* - 0x00: "Soldier" (user: "green_soldier")
|
||||
* - 0x01: "Octorok" (user: "red_octorok")
|
||||
* ```
|
||||
*/
|
||||
std::string ExtractSpriteLabels();
|
||||
|
||||
/**
|
||||
* @brief Add common tile16 reference for AI.
|
||||
*
|
||||
* Provides a quick reference of common tile16 IDs that AI
|
||||
* can use without needing to search through the entire tileset.
|
||||
*
|
||||
* Returns formatted string like:
|
||||
* ```
|
||||
* Common Tile16s:
|
||||
* - 0x020: Grass
|
||||
* - 0x022: Dirt
|
||||
* - 0x02E: Tree
|
||||
* - 0x14C: Water (top edge)
|
||||
* - 0x14D: Water (middle)
|
||||
* ```
|
||||
*/
|
||||
std::string GetCommonTile16Reference();
|
||||
};
|
||||
|
||||
} // namespace cli
|
||||
} // namespace yaze
|
||||
|
||||
#endif // YAZE_CLI_SERVICE_RESOURCE_CONTEXT_BUILDER_H_
|
||||
|
||||
Reference in New Issue
Block a user