refine a bit

main
hkr04 2025-03-17 14:07:41 +08:00
parent bfe2430534
commit 8aa98cf515
15 changed files with 344 additions and 127 deletions

View File

@ -78,12 +78,12 @@ if(Python3_FOUND)
endif() endif()
# #
add_executable(humanus_simple main_simple.cpp logger.cpp schema.cpp) # add_executable(humanus_simple main_simple.cpp logger.cpp schema.cpp)
target_link_libraries(humanus_simple PRIVATE Threads::Threads ${OPENSSL_LIBRARIES}) # target_link_libraries(humanus_simple PRIVATE Threads::Threads ${OPENSSL_LIBRARIES})
if(Python3_FOUND) # if(Python3_FOUND)
target_link_libraries(humanus_simple PRIVATE ${Python3_LIBRARIES}) # target_link_libraries(humanus_simple PRIVATE ${Python3_LIBRARIES})
endif() # endif()
# #
install(TARGETS humanus_cpp DESTINATION bin) install(TARGETS humanus_cpp DESTINATION bin)
install(TARGETS humanus_simple DESTINATION bin) # install(TARGETS humanus_simple DESTINATION bin)

View File

@ -85,7 +85,7 @@ struct BaseAgent : std::enable_shared_from_this<BaseAgent> {
// Execute the agent's main loop asynchronously // Execute the agent's main loop asynchronously
virtual std::string run(const std::string& request = "") { virtual std::string run(const std::string& request = "") {
if (state != AgentState::IDLE) { if (state != AgentState::IDLE) {
throw std::runtime_error("Cannot run agent from state" + agent_state_map[state]); throw std::runtime_error("Cannot run agent from state " + agent_state_map[state]);
} }
if (!request.empty()) { if (!request.empty()) {
@ -97,7 +97,15 @@ struct BaseAgent : std::enable_shared_from_this<BaseAgent> {
while (current_step < max_steps && state == AgentState::RUNNING) { while (current_step < max_steps && state == AgentState::RUNNING) {
current_step++; current_step++;
logger->info("Executing step " + std::to_string(current_step) + "/" + std::to_string(max_steps)); logger->info("Executing step " + std::to_string(current_step) + "/" + std::to_string(max_steps));
std::string step_result = step(); std::string step_result;
try {
step_result = step();
} catch (const std::exception& e) {
logger->error("Error executing step " + std::to_string(current_step) + ": " + std::string(e.what()));
state = AgentState::ERROR;
break;
}
if (is_stuck()) { if (is_stuck()) {
this->handle_stuck_state(); this->handle_stuck_state();
@ -108,7 +116,11 @@ struct BaseAgent : std::enable_shared_from_this<BaseAgent> {
if (current_step >= max_steps) { if (current_step >= max_steps) {
results.push_back("Terminated: Reached max steps (" + std::to_string(max_steps) + ")"); results.push_back("Terminated: Reached max steps (" + std::to_string(max_steps) + ")");
} }
state = AgentState::IDLE; // RUNNING -> IDLE if (state == AgentState::ERROR) {
results.push_back("Terminated: Agent state is " + agent_state_map[state]);
} else {
state = AgentState::IDLE; // FINISHED -> IDLE
}
std::string result_str = ""; std::string result_str = "";

View File

@ -116,7 +116,7 @@ void PlanningAgent::update_plan_status(const std::string& tool_call_id) {
try { try {
// Mark the step as completed // Mark the step as completed
available_tools.execute( ToolResult result = available_tools.execute(
"planning", "planning",
{ {
{"command", "mark_step"}, {"command", "mark_step"},
@ -127,6 +127,7 @@ void PlanningAgent::update_plan_status(const std::string& tool_call_id) {
); );
logger->info( logger->info(
"Marked step " + std::to_string(step_index) + " as completed in plan " + active_plan_id "Marked step " + std::to_string(step_index) + " as completed in plan " + active_plan_id
+ "\n\n" + result.to_string() + "\n\n"
); );
} catch (const std::exception& e) { } catch (const std::exception& e) {
logger->warn("Failed to update plan status: " + std::string(e.what())); logger->warn("Failed to update plan status: " + std::string(e.what()));
@ -195,7 +196,7 @@ int PlanningAgent::_get_current_step_index() {
// Create an initial plan based on the request. // Create an initial plan based on the request.
void PlanningAgent::create_initial_plan(const std::string& request) { void PlanningAgent::create_initial_plan(const std::string& request) {
logger->info("Creating initial plan with ID: " + request); logger->info("Creating initial plan with ID: " + active_plan_id);
std::vector<Message> messages = { std::vector<Message> messages = {
Message::user_message( Message::user_message(

View File

@ -20,7 +20,7 @@ bool ToolCallAgent::think() {
tool_calls = ToolCall::from_json_list(response["tool_calls"]); tool_calls = ToolCall::from_json_list(response["tool_calls"]);
// Log response info // Log response info
logger->info("" + name + "'s thoughts:" + response["content"].dump()); logger->info("" + name + "'s thoughts: " + response["content"].dump());
logger->info( logger->info(
"🛠️ " + name + " selected " + std::to_string(tool_calls.size()) + " tool(s) to use" "🛠️ " + name + " selected " + std::to_string(tool_calls.size()) + " tool(s) to use"
); );
@ -114,7 +114,11 @@ std::string ToolCallAgent::execute_tool(ToolCall tool_call) {
std::string name = tool_call.function.name; std::string name = tool_call.function.name;
if (available_tools.tools_map.find(name) == available_tools.tools_map.end()) { if (available_tools.tools_map.find(name) == available_tools.tools_map.end()) {
return "Error: Unknown tool '" + name + "'"; return "Error: Unknown tool '" + name + "'. Please use one of the following tools: " +
std::accumulate(available_tools.tools_map.begin(), available_tools.tools_map.end(), std::string(),
[](const std::string& a, const auto& b) {
return a + (a.empty() ? "" : ", ") + b.first;
});
} }
try { try {

View File

@ -1,6 +1,6 @@
[llm] [llm]
model = "deepseek-chat" model = "anthropic/claude-3.7-sonnet"
base_url = "https://api.deepseek.com" base_url = "https://openrouter.ai"
end_point = "/v1/chat/completions" end_point = "/api/v1/chat/completions"
api_key = "sk-93c5bfcb920c4a8aa345791d429b8536" api_key = "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"
max_tokens = 8192 max_tokens = 8192

View File

@ -3,4 +3,11 @@ model = "anthropic/claude-3.7-sonnet"
base_url = "https://openrouter.ai" base_url = "https://openrouter.ai"
end_point = "/api/v1/chat/completions" end_point = "/api/v1/chat/completions"
api_key = "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" api_key = "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"
max_tokens = 8196 max_tokens = 8192
[llm]
model = "deepseek-chat"
base_url = "https://api.deepseek.com"
end_point = "/v1/chat/completions"
api_key = "sk-93c5bfcb920c4a8aa345791d429b8536"
max_tokens = 8192

View File

@ -48,6 +48,8 @@ struct BaseFlow {
} }
} }
virtual ~BaseFlow() = default;
// Get the primary agent for the flow // Get the primary agent for the flow
std::shared_ptr<BaseAgent> primary_agent() const { std::shared_ptr<BaseAgent> primary_agent() const {
return agents.at(primary_agent_key); return agents.at(primary_agent_key);

View File

@ -58,7 +58,7 @@ std::string PlanningFlow::execute(const std::string& input) {
result += step_result + "\n"; result += step_result + "\n";
// Check if agent wants to terminate // Check if agent wants to terminate
if (executor->state == AgentState::FINISHED) { if (executor->state == AgentState::FINISHED || executor->state == AgentState::ERROR) {
break; break;
} }
} }
@ -197,7 +197,6 @@ void PlanningFlow::_get_current_step_info(int& current_step_index, json& step_in
} }
current_step_index = i; current_step_index = i;
step_info = step;
return; return;
} }
current_step_index = -1; current_step_index = -1;
@ -245,7 +244,7 @@ void PlanningFlow::_mark_step_completed() {
try { try {
// Mark the step as completed // Mark the step as completed
planning_tool->execute({ ToolResult result = planning_tool->execute({
{"command", "mark_step"}, {"command", "mark_step"},
{"plan_id", active_plan_id}, {"plan_id", active_plan_id},
{"step_index", current_step_index}, {"step_index", current_step_index},
@ -253,6 +252,7 @@ void PlanningFlow::_mark_step_completed() {
}); });
logger->info( logger->info(
"Marked step " + std::to_string(current_step_index) + " as completed in plan " + active_plan_id "Marked step " + std::to_string(current_step_index) + " as completed in plan " + active_plan_id
+ "\n\n" + result.to_string() + "\n\n"
); );
} catch (const std::exception& e) { } catch (const std::exception& e) {
logger->warn("Failed to update plan status: " + std::string(e.what())); logger->warn("Failed to update plan status: " + std::string(e.what()));

View File

@ -33,11 +33,14 @@ struct PlanningFlow : public BaseFlow {
executor_keys(executor_keys), executor_keys(executor_keys),
active_plan_id(active_plan_id) { active_plan_id(active_plan_id) {
if (!llm) { if (!llm) {
this->llm = LLM::get_instance(); this->llm = LLM::get_instance("default");
} }
if (!planning_tool) { if (!planning_tool) {
this->planning_tool = std::make_shared<PlanningTool>(); this->planning_tool = std::make_shared<PlanningTool>();
} }
if (active_plan_id.empty()) {
this->active_plan_id = "plan_" + std::to_string(std::chrono::system_clock::now().time_since_epoch().count());
}
if (executor_keys.empty()) { if (executor_keys.empty()) {
for (const auto& [key, agent] : agents) { for (const auto& [key, agent] : agents) {
this->executor_keys.push_back(key); this->executor_keys.push_back(key);

View File

@ -1,6 +1,7 @@
#include "agent/manus.h" #include "agent/manus.h"
#include "logger.h" #include "logger.h"
#include "prompt.h" #include "prompt.h"
#include "flow/flow_factory.h"
#if defined (__unix__) || (defined (__APPLE__) && defined (__MACH__)) #if defined (__unix__) || (defined (__APPLE__) && defined (__MACH__))
#include <signal.h> #include <signal.h>
@ -42,16 +43,52 @@ int main() {
SetConsoleCtrlHandler(reinterpret_cast<PHANDLER_ROUTINE>(console_ctrl_handler), true); SetConsoleCtrlHandler(reinterpret_cast<PHANDLER_ROUTINE>(console_ctrl_handler), true);
#endif #endif
} }
Manus agent = Manus(); // Manus agent = Manus();
// while (true) {
// std::string prompt;
// std::cout << "Enter your prompt (or 'exit' to quit): ";
// std::getline(std::cin, prompt);
// if (prompt == "exit") {
// logger->info("Goodbye!");
// break;
// }
// logger->info("Processing your request...");
// agent.run(prompt);
// }
std::shared_ptr<BaseAgent> agent_ptr = std::make_shared<Manus>();
std::map<std::string, std::shared_ptr<BaseAgent>> agents;
agents["default"] = agent_ptr;
auto flow = FlowFactory::create_flow(
FlowType::PLANNING,
nullptr, // llm
nullptr, // planning_tool
std::vector<std::string>{}, // executor_keys
"", // active_plan_id
agents, // agents
std::vector<std::shared_ptr<BaseTool>>{}, // tools
"default" // primary_agent_key
);
while (true) { while (true) {
std::string prompt; if (agent_ptr->current_step == agent_ptr->max_steps) {
std::cout << "Program automatically paused after " << agent_ptr->current_step << " steps." << std::endl;
std::cout << "Enter your prompt (like 'continue' to continue or 'exit' to quit): ";
agent_ptr->current_step = 0;
} else {
std::cout << "Enter your prompt (or 'exit' to quit): "; std::cout << "Enter your prompt (or 'exit' to quit): ";
}
std::string prompt;
std::getline(std::cin, prompt); std::getline(std::cin, prompt);
if (prompt == "exit") { if (prompt == "exit") {
logger->info("Goodbye!"); logger->info("Goodbye!");
break; break;
} }
logger->info("Processing your request...");
agent.run(prompt); std::cout << "Processing your request..." << std::endl;
auto result = flow->execute(prompt);
std::cout << result << std::endl;
} }
} }

View File

@ -13,6 +13,7 @@
#include <string> #include <string>
#include <memory> #include <memory>
#include <stdexcept> #include <stdexcept>
#include <mutex>
// 检查是否找到Python // 检查是否找到Python
#ifdef PYTHON_FOUND #ifdef PYTHON_FOUND
@ -24,13 +25,31 @@
* @brief PythonPython * @brief PythonPython
*/ */
class python_interpreter { class python_interpreter {
private:
// 互斥锁确保Python解释器的线程安全
mutable std::mutex py_mutex;
bool is_initialized;
public: public:
/** /**
* @brief Python * @brief Python
*/ */
python_interpreter() { python_interpreter() : is_initialized(false) {
#ifdef PYTHON_FOUND #ifdef PYTHON_FOUND
try {
Py_Initialize(); Py_Initialize();
if (Py_IsInitialized()) {
is_initialized = true;
// 初始化线程支持
PyEval_InitThreads();
// 释放GIL允许其他线程获取
PyThreadState *_save = PyEval_SaveThread();
} else {
std::cerr << "Python解释器初始化失败" << std::endl;
}
} catch (const std::exception& e) {
std::cerr << "Python解释器初始化异常: " << e.what() << std::endl;
}
#endif #endif
} }
@ -39,7 +58,11 @@ public:
*/ */
~python_interpreter() { ~python_interpreter() {
#ifdef PYTHON_FOUND #ifdef PYTHON_FOUND
if (is_initialized) {
std::lock_guard<std::mutex> lock(py_mutex);
Py_Finalize(); Py_Finalize();
is_initialized = false;
}
#endif #endif
} }
@ -50,50 +73,144 @@ public:
*/ */
mcp::json forward(const mcp::json& input) const { mcp::json forward(const mcp::json& input) const {
#ifdef PYTHON_FOUND #ifdef PYTHON_FOUND
if (!is_initialized) {
return mcp::json{{"error", "Python解释器未正确初始化"}};
}
// 获取GIL锁
std::lock_guard<std::mutex> lock(py_mutex);
PyGILState_STATE gstate = PyGILState_Ensure();
mcp::json result_json;
try {
if (input.contains("code") && input["code"].is_string()) { if (input.contains("code") && input["code"].is_string()) {
std::string code = input["code"].get<std::string>(); std::string code = input["code"].get<std::string>();
// Create scope to manage Python objects automatically // 获取主模块和字典
PyObject *main_module = PyImport_AddModule("__main__"); PyObject *main_module = PyImport_AddModule("__main__");
if (!main_module) {
PyGILState_Release(gstate);
return mcp::json{{"error", "无法获取Python主模块"}};
}
PyObject *main_dict = PyModule_GetDict(main_module); PyObject *main_dict = PyModule_GetDict(main_module);
if (!main_dict) {
PyGILState_Release(gstate);
return mcp::json{{"error", "无法获取Python主模块字典"}};
}
// 导入sys和io模块
PyObject *sys_module = PyImport_ImportModule("sys"); PyObject *sys_module = PyImport_ImportModule("sys");
if (!sys_module) {
PyErr_Print();
PyGILState_Release(gstate);
return mcp::json{{"error", "无法导入sys模块"}};
}
PyObject *io_module = PyImport_ImportModule("io"); PyObject *io_module = PyImport_ImportModule("io");
if (!io_module) {
Py_DECREF(sys_module);
PyErr_Print();
PyGILState_Release(gstate);
return mcp::json{{"error", "无法导入io模块"}};
}
// 获取StringIO类
PyObject *string_io = PyObject_GetAttrString(io_module, "StringIO"); PyObject *string_io = PyObject_GetAttrString(io_module, "StringIO");
if (!string_io) {
Py_DECREF(io_module);
Py_DECREF(sys_module);
PyErr_Print();
PyGILState_Release(gstate);
return mcp::json{{"error", "无法获取StringIO类"}};
}
// 创建StringIO对象
PyObject *sys_stdout = PyObject_CallObject(string_io, nullptr); PyObject *sys_stdout = PyObject_CallObject(string_io, nullptr);
if (!sys_stdout) {
Py_DECREF(string_io);
Py_DECREF(io_module);
Py_DECREF(sys_module);
PyErr_Print();
PyGILState_Release(gstate);
return mcp::json{{"error", "无法创建stdout StringIO对象"}};
}
PyObject *sys_stderr = PyObject_CallObject(string_io, nullptr); PyObject *sys_stderr = PyObject_CallObject(string_io, nullptr);
if (!sys_stderr) {
Py_DECREF(sys_stdout);
Py_DECREF(string_io);
Py_DECREF(io_module);
Py_DECREF(sys_module);
PyErr_Print();
PyGILState_Release(gstate);
return mcp::json{{"error", "无法创建stderr StringIO对象"}};
}
// Replace sys.stdout and sys.stderr with our StringIO objects // 保存原始的stdout和stderr
PySys_SetObject("stdout", sys_stdout); PyObject *old_stdout = PySys_GetObject("stdout");
PySys_SetObject("stderr", sys_stderr); PyObject *old_stderr = PySys_GetObject("stderr");
// Execute the Python code if (old_stdout) Py_INCREF(old_stdout);
if (old_stderr) Py_INCREF(old_stderr);
// 替换sys.stdout和sys.stderr
if (PySys_SetObject("stdout", sys_stdout) != 0 ||
PySys_SetObject("stderr", sys_stderr) != 0) {
Py_DECREF(sys_stderr);
Py_DECREF(sys_stdout);
Py_DECREF(string_io);
Py_DECREF(io_module);
Py_DECREF(sys_module);
PyErr_Print();
PyGILState_Release(gstate);
return mcp::json{{"error", "无法设置stdout/stderr重定向"}};
}
// 执行Python代码
PyObject *result = PyRun_String(code.c_str(), Py_file_input, main_dict, main_dict); PyObject *result = PyRun_String(code.c_str(), Py_file_input, main_dict, main_dict);
if (!result) { if (!result) {
PyErr_Print(); PyErr_Print();
} }
Py_XDECREF(result); Py_XDECREF(result);
// Fetch the output and error from the StringIO object // 获取输出和错误
PyObject *out_value = PyObject_CallMethod(sys_stdout, "getvalue", nullptr); PyObject *out_value = PyObject_CallMethod(sys_stdout, "getvalue", nullptr);
PyObject *err_value = PyObject_CallMethod(sys_stderr, "getvalue", nullptr); PyObject *err_value = PyObject_CallMethod(sys_stderr, "getvalue", nullptr);
// Convert Python string to C++ string std::string output, error;
std::string output = PyUnicode_AsUTF8(out_value);
std::string error = PyUnicode_AsUTF8(err_value);
// Restore the original sys.stdout and sys.stderr // 安全地转换Python字符串到C++字符串
PySys_SetObject("stdout", PySys_GetObject("stdout")); if (out_value && PyUnicode_Check(out_value)) {
PySys_SetObject("stderr", PySys_GetObject("stderr")); output = PyUnicode_AsUTF8(out_value);
}
// Clean up if (err_value && PyUnicode_Check(err_value)) {
error = PyUnicode_AsUTF8(err_value);
}
// 恢复原始的stdout和stderr
if (old_stdout) {
PySys_SetObject("stdout", old_stdout);
Py_DECREF(old_stdout);
}
if (old_stderr) {
PySys_SetObject("stderr", old_stderr);
Py_DECREF(old_stderr);
}
// 清理
Py_XDECREF(out_value);
Py_XDECREF(err_value);
Py_DECREF(sys_stdout); Py_DECREF(sys_stdout);
Py_DECREF(sys_stderr); Py_DECREF(sys_stderr);
Py_DECREF(string_io); Py_DECREF(string_io);
Py_DECREF(io_module); Py_DECREF(io_module);
Py_DECREF(sys_module); Py_DECREF(sys_module);
// Prepare JSON output // 准备JSON输出
mcp::json result_json;
if (!output.empty()) { if (!output.empty()) {
result_json["output"] = output; result_json["output"] = output;
} }
@ -114,13 +231,18 @@ public:
last_line = last_line.substr(pos); last_line = last_line.substr(pos);
} }
return mcp::json{{"warning", "No output. Maybe try with print(" + last_line + ")?"}}; result_json["warning"] = "No output. Maybe try with print(" + last_line + ")?";
}
} else {
result_json["error"] = "Invalid parameters or code not provided";
}
} catch (const std::exception& e) {
result_json["error"] = std::string("Python执行异常: ") + e.what();
} }
// 释放GIL
PyGILState_Release(gstate);
return result_json; return result_json;
} else {
return mcp::json{{"error", "Invalid parameters or code not provided"}};
}
#else #else
return mcp::json{{"error", "Python interpreter not available"}}; return mcp::json{{"error", "Python interpreter not available"}};
#endif #endif
@ -133,7 +255,6 @@ static python_interpreter interpreter;
// Python执行工具处理函数 // Python执行工具处理函数
mcp::json python_execute_handler(const mcp::json& args) { mcp::json python_execute_handler(const mcp::json& args) {
if (!args.contains("code")) { if (!args.contains("code")) {
std::cout << args.dump() << std::endl;
throw mcp::mcp_exception(mcp::error_code::invalid_params, "缺少'code'参数"); throw mcp::mcp_exception(mcp::error_code::invalid_params, "缺少'code'参数");
} }
@ -141,25 +262,9 @@ mcp::json python_execute_handler(const mcp::json& args) {
// 使用Python解释器执行代码 // 使用Python解释器执行代码
mcp::json result = interpreter.forward(args); mcp::json result = interpreter.forward(args);
// 格式化输出结果
std::string output;
if (result.contains("output")) {
output += result["output"].get<std::string>();
}
if (result.contains("error")) {
output += result["error"].get<std::string>();
}
if (result.contains("warning")) {
output += result["warning"].get<std::string>();
}
if (output.empty()) {
output = "Executed successfully but w/o output. Maybe you should print more information.";
}
return {{ return {{
{"type", "text"}, {"type", "text"},
{"text", output} {"text", result.dump(2)}
}}; }};
} catch (const std::exception& e) { } catch (const std::exception& e) {
throw mcp::mcp_exception(mcp::error_code::internal_error, throw mcp::mcp_exception(mcp::error_code::internal_error,
@ -167,12 +272,11 @@ mcp::json python_execute_handler(const mcp::json& args) {
} }
} }
// 注册Python执行工具的函数 // Register the PythonExecute tool
void register_python_execute_tool(mcp::server& server) { void register_python_execute_tool(mcp::server& server) {
// 注册PythonExecute工具 mcp::tool python_tool = mcp::tool_builder("python_execute")
mcp::tool python_tool = mcp::tool_builder("PythonExecute") .with_description("Execute Python code and return the result")
.with_description("执行Python代码并返回结果") .with_string_param("code", "The Python code to execute", true)
.with_string_param("code", "要执行的Python代码", true)
.build(); .build();
server.register_tool(python_tool, python_execute_handler); server.register_tool(python_tool, python_execute_handler);

View File

@ -149,8 +149,16 @@ struct ToolResult {
}; };
} }
static std::string parse_json_content(const json& content) {
if (content.is_string()) {
return content.get<std::string>();
} else {
return content.dump(2);
}
}
std::string to_string() const { std::string to_string() const {
return !error.empty() ? "Error: " + error.dump() : output.dump(); return !error.empty() ? "Error: " + parse_json_content(error) : parse_json_content(output);
} }
}; };
@ -161,7 +169,7 @@ struct ToolError : ToolResult {
// Execute the tool with given parameters. // Execute the tool with given parameters.
struct BaseTool { struct BaseTool {
inline static std::set<std::string> special_tool_name = {"terminate"}; inline static std::set<std::string> special_tool_name = {"terminate", "planning"};
std::string name; std::string name;
std::string description; std::string description;

View File

@ -62,6 +62,19 @@ struct FileSystem : BaseTool {
"required": ["tool"] "required": ["tool"]
})json"); })json");
inline static std::set<std::string> allowed_tools = {
"read_file",
"read_multiple_files",
"write_file",
"edit_file",
"create_directory",
"list_directory",
"move_file",
"search_files",
"get_file_info",
"list_allowed_directories"
};
FileSystem() : BaseTool(name_, description_, parameters_) {} FileSystem() : BaseTool(name_, description_, parameters_) {}
ToolResult execute(const json& args) override { ToolResult execute(const json& args) override {
@ -82,6 +95,14 @@ struct FileSystem : BaseTool {
return ToolError("Tool is required"); return ToolError("Tool is required");
} }
if (allowed_tools.find(tool) == allowed_tools.end()) {
return ToolError("Unknown tool '" + tool + "'. Please use one of the following tools: " +
std::accumulate(allowed_tools.begin(), allowed_tools.end(), std::string(),
[](const std::string& a, const std::string& b) {
return a + (a.empty() ? "" : ", ") + b;
}));
}
json result = _client->call_tool(tool, args); json result = _client->call_tool(tool, args);
bool is_error = result.value("isError", false); bool is_error = result.value("isError", false);

View File

@ -254,7 +254,7 @@ ToolResult PlanningTool::_delete_plan(const std::string& plan_id) {
// Format a plan for display. // Format a plan for display.
std::string PlanningTool::_format_plan(const json& plan) { std::string PlanningTool::_format_plan(const json& plan) {
std::stringstream output_ss; std::stringstream output_ss;
output_ss << "Plan ID: " << plan["plan_id"].get<std::string>() << "\n"; output_ss << "Plan: " << plan.value("title", "Unknown Plan") << " (ID: " << plan["plan_id"].get<std::string>() << ")\n";
int current_length = output_ss.str().length(); int current_length = output_ss.str().length();
for (int i = 0; i < current_length; i++) { for (int i = 0; i < current_length; i++) {

View File

@ -58,6 +58,16 @@ struct Puppeteer : BaseTool {
"required": ["tool"] "required": ["tool"]
})json"); })json");
inline static std::set<std::string> allowed_tools = {
"navigate",
"screenshot",
"click",
"hover",
"fill",
"select",
"evaluate"
};
Puppeteer() : BaseTool(name_, description_, parameters_) {} Puppeteer() : BaseTool(name_, description_, parameters_) {}
ToolResult execute(const json& args) override { ToolResult execute(const json& args) override {
@ -78,6 +88,14 @@ struct Puppeteer : BaseTool {
return ToolError("Tool is required"); return ToolError("Tool is required");
} }
if (allowed_tools.find(tool) == allowed_tools.end()) {
return ToolError("Unknown tool '" + tool + "'. Please use one of the following tools: " +
std::accumulate(allowed_tools.begin(), allowed_tools.end(), std::string(),
[](const std::string& a, const std::string& b) {
return a + (a.empty() ? "" : ", ") + b;
}));
}
json result = _client->call_tool("puppeteer_" + tool, args); json result = _client->call_tool("puppeteer_" + tool, args);
bool is_error = result.value("isError", false); bool is_error = result.value("isError", false);