yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
tool_registry.cc
Go to the documentation of this file.
2
3#include <algorithm>
4#include <string>
5#include <string_view>
6
7#include "absl/container/flat_hash_set.h"
8#include "absl/strings/str_cat.h"
9#include "absl/strings/str_split.h"
11
12namespace yaze {
13namespace cli {
14namespace agent {
15
16namespace {
17
18std::string StripTokenDelimiters(std::string token) {
19 while (!token.empty() && (token.front() == '[' || token.front() == '(' ||
20 token.front() == ',')) {
21 token.erase(token.begin());
22 }
23 while (!token.empty() &&
24 (token.back() == ']' || token.back() == ')' || token.back() == ',')) {
25 token.pop_back();
26 }
27 return token;
28}
29
30std::vector<std::string> SplitUsageTokens(std::string_view usage) {
31 std::vector<std::string> tokens;
32 // Older Abseil (e.g. distro packages on Ubuntu 22.04 CI) does not accept
33 // absl::SkipEmpty with string_view + char overload; split an owned string.
34 const std::string usage_owned(usage);
35 for (absl::string_view token :
36 absl::StrSplit(usage_owned, ' ', absl::SkipEmpty())) {
37 tokens.emplace_back(token);
38 }
39 return tokens;
40}
41
42std::vector<std::string> InferRequiredArgsFromUsage(const std::string& usage) {
43 std::vector<std::string> required_args;
44 absl::flat_hash_set<std::string> seen;
45 const auto tokens = SplitUsageTokens(usage);
46
47 int optional_depth = 0;
48 for (size_t i = 0; i < tokens.size(); ++i) {
49 const std::string& token = tokens[i];
50 optional_depth +=
51 static_cast<int>(std::count(token.begin(), token.end(), '['));
52 const bool is_optional = optional_depth > 0;
53
54 const std::string cleaned = StripTokenDelimiters(token);
55 if (!is_optional && cleaned.rfind("--", 0) == 0) {
56 std::string arg_name = cleaned.substr(2);
57 const size_t equals_pos = arg_name.find('=');
58 if (equals_pos != std::string::npos) {
59 arg_name = arg_name.substr(0, equals_pos);
60 } else if (i + 1 < tokens.size()) {
61 const std::string next = StripTokenDelimiters(tokens[i + 1]);
62 if (next.empty() || next.front() != '<') {
63 arg_name.clear();
64 }
65 } else {
66 arg_name.clear();
67 }
68
69 if (!arg_name.empty() && seen.insert(arg_name).second) {
70 required_args.push_back(std::move(arg_name));
71 }
72 }
73
74 optional_depth -=
75 static_cast<int>(std::count(token.begin(), token.end(), ']'));
76 }
77
78 return required_args;
79}
80
81std::vector<std::string> InferFlagArgsFromUsage(const std::string& usage) {
82 std::vector<std::string> flag_args;
83 absl::flat_hash_set<std::string> seen;
84 const auto tokens = SplitUsageTokens(usage);
85
86 for (size_t i = 0; i < tokens.size(); ++i) {
87 const std::string cleaned = StripTokenDelimiters(tokens[i]);
88 if (cleaned.rfind("--", 0) != 0) {
89 continue;
90 }
91
92 std::string arg_name = cleaned.substr(2);
93 if (arg_name.find('=') != std::string::npos) {
94 continue;
95 }
96
97 const bool has_value_token =
98 i + 1 < tokens.size() && !StripTokenDelimiters(tokens[i + 1]).empty() &&
99 StripTokenDelimiters(tokens[i + 1]).front() == '<';
100 if (has_value_token) {
101 continue;
102 }
103
104 if (seen.insert(arg_name).second) {
105 flag_args.push_back(std::move(arg_name));
106 }
107 }
108
109 return flag_args;
110}
111
112ToolAccess InferToolAccess(const std::string& tool_name) {
113 static const absl::flat_hash_set<std::string> kMutatingTools = {
114 "build-compile",
115 "build-configure",
116 "build-test",
117 "dungeon-import-custom-collision-json",
118 "dungeon-import-water-fill-json",
119 "dungeon-set-palette-color",
120 "dungeon-set-room-property",
121 "emulator-clear-breakpoint",
122 "emulator-pause",
123 "emulator-reset",
124 "emulator-run",
125 "emulator-set-breakpoint",
126 "emulator-step",
127 "emulator-write-memory",
128 "gui-click",
129 "gui-place-tile",
130 "gui-type",
131 "message-source-sync",
132 "overworld-set-tile",
133 "project-export",
134 "project-import",
135 "project-restore",
136 "project-snapshot",
137 "tools-extract-golden",
138 "tools-patch-v3",
139 };
140
141 return kMutatingTools.contains(tool_name) ? ToolAccess::kMutating
143}
144
146 if (def.access == ToolAccess::kReadOnly) {
147 def.access = InferToolAccess(def.name);
148 }
149 if (def.required_args.empty()) {
151 }
152 if (def.flag_args.empty()) {
154 }
155 return def;
156}
157
158} // namespace
159
161 static ToolRegistry instance;
162 instance.EnsureInitialized();
163 return instance;
164}
165
167 std::call_once(init_once_, [this]() {
168#ifndef __EMSCRIPTEN__
169 // The builtin tool set (tool_registration.cc) pulls in the full CLI
170 // command-handler layer, which is intentionally excluded from the minimal
171 // WASM agent library (see cli/agent.cmake). Skipping it here keeps the
172 // Emscripten link from requiring RegisterBuiltinAgentTools/tool_registration.cc.
174#else
175 (void)this;
176#endif
177 });
178}
179
181 HandlerFactory factory) {
182 ToolDefinition normalized = NormalizeDefinition(def);
183 const std::string tool_name = normalized.name;
184 tools_[tool_name] = {std::move(normalized), std::move(factory)};
185}
186
187std::vector<ToolDefinition> ToolRegistry::GetAllTools() const {
188 std::vector<ToolDefinition> defs;
189 defs.reserve(tools_.size());
190 for (const auto& [name, entry] : tools_) {
191 defs.push_back(entry.def);
192 }
193 return defs;
194}
195
196std::optional<ToolDefinition> ToolRegistry::GetToolDefinition(
197 const std::string& name) const {
198 auto it = tools_.find(name);
199 if (it != tools_.end()) {
200 return it->second.def;
201 }
202 return std::nullopt;
203}
204
205std::vector<ToolDefinition> ToolRegistry::GetToolsByCategory(
206 const std::string& category) const {
207 std::vector<ToolDefinition> defs;
208 for (const auto& [name, entry] : tools_) {
209 if (entry.def.category == category) {
210 defs.push_back(entry.def);
211 }
212 }
213 return defs;
214}
215
216absl::StatusOr<std::unique_ptr<resources::CommandHandler>>
217ToolRegistry::CreateHandler(const std::string& tool_name) const {
218 auto it = tools_.find(tool_name);
219 if (it == tools_.end()) {
220 return absl::NotFoundError(absl::StrCat("Tool not found: ", tool_name));
221 }
222 return it->second.factory();
223}
224
225} // namespace agent
226} // namespace cli
227} // namespace yaze
Centralized registry for all agent tools.
void RegisterTool(const ToolDefinition &def, HandlerFactory factory)
std::function< std::unique_ptr< resources::CommandHandler >()> HandlerFactory
std::optional< ToolDefinition > GetToolDefinition(const std::string &name) const
std::map< std::string, ToolEntry > tools_
static ToolRegistry & Get()
std::vector< ToolDefinition > GetToolsByCategory(const std::string &category) const
std::vector< ToolDefinition > GetAllTools() const
absl::StatusOr< std::unique_ptr< resources::CommandHandler > > CreateHandler(const std::string &tool_name) const
std::vector< std::string > InferRequiredArgsFromUsage(const std::string &usage)
ToolDefinition NormalizeDefinition(ToolDefinition def)
std::vector< std::string > SplitUsageTokens(std::string_view usage)
ToolAccess InferToolAccess(const std::string &tool_name)
std::vector< std::string > InferFlagArgsFromUsage(const std::string &usage)
void RegisterBuiltinAgentTools(ToolRegistry &registry)
Metadata describing a tool for the LLM.
std::vector< std::string > required_args
std::vector< std::string > flag_args