2026-03-27 10:07:11 +01:00
#include "server-tools.h"
#include <sheredom/subprocess.h>
#include <filesystem>
#include <fstream>
#include <regex>
#include <thread>
#include <chrono>
#include <atomic>
#include <cstring>
#include <climits>
2026-05-05 06:35:27 +03:00
#include <algorithm>
2026-03-27 10:07:11 +01:00
namespace fs = std :: filesystem ;
//
// internal helpers
//
static std :: vector < char *> to_cstr_vec ( const std :: vector < std :: string > & v ) {
std :: vector < char *> r ;
r . reserve ( v . size () + 1 );
for ( const auto & s : v ) {
r . push_back ( const_cast < char *> ( s . c_str ()));
}
r . push_back ( nullptr );
return r ;
}
struct run_proc_result {
std :: string output ;
int exit_code = - 1 ;
bool timed_out = false ;
};
static run_proc_result run_process (
const std :: vector < std :: string > & args ,
size_t max_output ,
int timeout_secs ) {
run_proc_result res ;
subprocess_s proc ;
auto argv = to_cstr_vec ( args );
int options = subprocess_option_no_window
| subprocess_option_combined_stdout_stderr
| subprocess_option_inherit_environment
| subprocess_option_search_user_path ;
if ( subprocess_create ( argv . data (), options , & proc ) != 0 ) {
res . output = "failed to spawn process" ;
return res ;
}
std :: atomic < bool > done { false };
std :: atomic < bool > timed_out { false };
std :: thread timeout_thread ([ & ]() {
auto deadline = std :: chrono :: steady_clock :: now () + std :: chrono :: seconds ( timeout_secs );
while ( ! done . load ()) {
if ( std :: chrono :: steady_clock :: now () >= deadline ) {
timed_out . store ( true );
subprocess_terminate ( & proc );
return ;
}
std :: this_thread :: sleep_for ( std :: chrono :: milliseconds ( 100 ));
}
});
FILE * f = subprocess_stdout ( & proc );
std :: string output ;
bool truncated = false ;
if ( f ) {
char buf [ 4096 ];
while ( fgets ( buf , sizeof ( buf ), f ) != nullptr ) {
if ( ! truncated ) {
size_t len = strlen ( buf );
if ( output . size () + len <= max_output ) {
output . append ( buf , len );
} else {
output . append ( buf , max_output - output . size ());
truncated = true ;
}
}
}
}
done . store ( true );
if ( timeout_thread . joinable ()) {
timeout_thread . join ();
}
subprocess_join ( & proc , & res . exit_code );
subprocess_destroy ( & proc );
res . output = output ;
res . timed_out = timed_out . load ();
if ( truncated ) {
res . output += " \n [output truncated]" ;
}
return res ;
}
json server_tool :: to_json () {
return {
{ "display_name" , display_name },
{ "tool" , name },
{ "type" , "builtin" },
{ "permissions" , json {
{ "write" , permission_write }
}},
{ "definition" , get_definition ()},
};
}
//
// read_file: read a file with optional line range and line-number prefix
//
static constexpr size_t SERVER_TOOL_READ_FILE_MAX_SIZE = 16 * 1024 ; // 16 KB
struct server_tool_read_file : server_tool {
server_tool_read_file () {
name = "read_file" ;
display_name = "Read file" ;
permission_write = false ;
}
json get_definition () override {
return {
{ "type" , "function" },
{ "function" , {
{ "name" , name },
{ "description" , "Read the contents of a file. Optionally specify a 1-based line range. "
"If append_loc is true, each line is prefixed with its line number (e.g. \" 1 \u2192 ... \" )." },
{ "parameters" , {
{ "type" , "object" },
{ "properties" , {
{ "path" , {{ "type" , "string" }, { "description" , "Path to the file" }}},
{ "start_line" , {{ "type" , "integer" }, { "description" , "First line to read, 1-based (default: 1)" }}},
{ "end_line" , {{ "type" , "integer" }, { "description" , "Last line to read, 1-based inclusive (default: end of file)" }}},
{ "append_loc" , {{ "type" , "boolean" }, { "description" , "Prefix each line with its line number" }}},
}},
{ "required" , json :: array ({ "path" })},
}},
}},
};
}
json invoke ( json params ) override {
std :: string path = params . at ( "path" ). get < std :: string > ();
int start_line = json_value ( params , "start_line" , 1 );
int end_line = json_value ( params , "end_line" , - 1 ); // -1 = no limit
bool append_loc = json_value ( params , "append_loc" , false );
std :: error_code ec ;
uintmax_t file_size = fs :: file_size ( path , ec );
if ( ec ) {
return {{ "error" , "cannot stat file: " + ec . message ()}};
}
if ( file_size > SERVER_TOOL_READ_FILE_MAX_SIZE && end_line == - 1 ) {
return {{ "error" , string_format (
"file too large (%zu bytes, max %zu). Use start_line/end_line to read a portion." ,
( size_t ) file_size , SERVER_TOOL_READ_FILE_MAX_SIZE )}};
}
std :: ifstream f ( path );
if ( ! f ) {
return {{ "error" , "failed to open file: " + path }};
}
std :: string result ;
std :: string line ;
int lineno = 0 ;
while ( std :: getline ( f , line )) {
lineno ++ ;
if ( lineno < start_line ) continue ;
if ( end_line != - 1 && lineno > end_line ) break ;
std :: string out_line ;
if ( append_loc ) {
out_line = std :: to_string ( lineno ) + " \u2192 " + line + " \n " ;
} else {
out_line = line + " \n " ;
}
if ( result . size () + out_line . size () > SERVER_TOOL_READ_FILE_MAX_SIZE ) {
result += "[output truncated]" ;
break ;
}
result += out_line ;
}
return {{ "plain_text_response" , result }};
}
};
//
// file_glob_search: find files matching a glob pattern under a base directory
//
static constexpr size_t SERVER_TOOL_FILE_SEARCH_MAX_RESULTS = 100 ;
struct server_tool_file_glob_search : server_tool {
server_tool_file_glob_search () {
name = "file_glob_search" ;
display_name = "File search" ;
permission_write = false ;
}
json get_definition () override {
return {
{ "type" , "function" },
{ "function" , {
{ "name" , name },
{ "description" , "Recursively search for files matching a glob pattern under a directory." },
{ "parameters" , {
{ "type" , "object" },
{ "properties" , {
{ "path" , {{ "type" , "string" }, { "description" , "Base directory to search in" }}},
{ "include" , {{ "type" , "string" }, { "description" , "Glob pattern for files to include (e.g. \" **/*.cpp \" ). Default: **" }}},
{ "exclude" , {{ "type" , "string" }, { "description" , "Glob pattern for files to exclude" }}},
}},
{ "required" , json :: array ({ "path" })},
}},
}},
};
}
json invoke ( json params ) override {
std :: string base = params . at ( "path" ). get < std :: string > ();
std :: string include = json_value ( params , "include" , std :: string ( "**" ));
std :: string exclude = json_value ( params , "exclude" , std :: string ( "" ));
std :: ostringstream output_text ;
size_t count = 0 ;
std :: error_code ec ;
for ( const auto & entry : fs :: recursive_directory_iterator ( base ,
fs :: directory_options :: skip_permission_denied , ec )) {
if ( ! entry . is_regular_file ()) continue ;
std :: string rel = fs :: relative ( entry . path (), base , ec ). string ();
if ( ec ) continue ;
std :: replace ( rel . begin (), rel . end (), '\\' , '/' );
if ( ! glob_match ( include , rel )) continue ;
if ( ! exclude . empty () && glob_match ( exclude , rel )) continue ;
output_text << entry . path (). string () << " \n " ;
if ( ++ count >= SERVER_TOOL_FILE_SEARCH_MAX_RESULTS ) {
break ;
}
}
output_text << " \n --- \n Total matches: " << count << " \n " ;
return {{ "plain_text_response" , output_text . str ()}};
}
};
//
// grep_search: search for a regex pattern in files
//
static constexpr size_t SERVER_TOOL_GREP_SEARCH_MAX_RESULTS = 100 ;
struct server_tool_grep_search : server_tool {
server_tool_grep_search () {
name = "grep_search" ;
display_name = "Grep search" ;
permission_write = false ;
}
json get_definition () override {
return {
{ "type" , "function" },
{ "function" , {
{ "name" , name },
{ "description" , "Search for a regex pattern in files under a path. Returns matching lines." },
{ "parameters" , {
{ "type" , "object" },
{ "properties" , {
{ "path" , {{ "type" , "string" }, { "description" , "File or directory to search in" }}},
{ "pattern" , {{ "type" , "string" }, { "description" , "Regular expression pattern to search for" }}},
{ "include" , {{ "type" , "string" }, { "description" , "Glob pattern to filter files (default: **)" }}},
{ "exclude" , {{ "type" , "string" }, { "description" , "Glob pattern to exclude files" }}},
{ "return_line_numbers" , {{ "type" , "boolean" }, { "description" , "If true, include line numbers in results" }}},
}},
{ "required" , json :: array ({ "path" , "pattern" })},
}},
}},
};
}
json invoke ( json params ) override {
std :: string path = params . at ( "path" ). get < std :: string > ();
std :: string pat_str = params . at ( "pattern" ). get < std :: string > ();
std :: string include = json_value ( params , "include" , std :: string ( "**" ));
std :: string exclude = json_value ( params , "exclude" , std :: string ( "" ));
bool show_lineno = json_value ( params , "return_line_numbers" , false );
std :: regex pattern ;
try {
pattern = std :: regex ( pat_str );
} catch ( const std :: regex_error & e ) {
return {{ "error" , std :: string ( "invalid regex: " ) + e . what ()}};
}
std :: ostringstream output_text ;
size_t total = 0 ;
auto search_file = [ & ]( const fs :: path & fpath ) {
std :: ifstream f ( fpath );
if ( ! f ) return ;
std :: string line ;
int lineno = 0 ;
while ( std :: getline ( f , line ) && total < SERVER_TOOL_GREP_SEARCH_MAX_RESULTS ) {
lineno ++ ;
if ( std :: regex_search ( line , pattern )) {
output_text << fpath . string () << ":" ;
if ( show_lineno ) {
output_text << lineno << ":" ;
}
output_text << line << " \n " ;
total ++ ;
}
}
};
std :: error_code ec ;
if ( fs :: is_regular_file ( path , ec )) {
search_file ( path );
} else if ( fs :: is_directory ( path , ec )) {
for ( const auto & entry : fs :: recursive_directory_iterator ( path ,
fs :: directory_options :: skip_permission_denied , ec )) {
if ( ! entry . is_regular_file ()) continue ;
if ( total >= SERVER_TOOL_GREP_SEARCH_MAX_RESULTS ) break ;
std :: string rel = fs :: relative ( entry . path (), path , ec ). string ();
if ( ec ) continue ;
std :: replace ( rel . begin (), rel . end (), '\\' , '/' );
if ( ! glob_match ( include , rel )) continue ;
if ( ! exclude . empty () && glob_match ( exclude , rel )) continue ;
search_file ( entry . path ());
}
} else {
return {{ "error" , "path does not exist: " + path }};
}
output_text << " \n\n --- \n Total matches: " << total << " \n " ;
return {{ "plain_text_response" , output_text . str ()}};
}
};
//
// exec_shell_command: run an arbitrary shell command
//
static constexpr size_t SERVER_TOOL_EXEC_SHELL_COMMAND_MAX_OUTPUT_SIZE = 16 * 1024 ; // 16 KB
static constexpr int SERVER_TOOL_EXEC_SHELL_COMMAND_MAX_TIMEOUT = 60 ; // seconds
struct server_tool_exec_shell_command : server_tool {
server_tool_exec_shell_command () {
name = "exec_shell_command" ;
display_name = "Execute shell command" ;
permission_write = true ;
}
json get_definition () override {
return {
{ "type" , "function" },
{ "function" , {
{ "name" , name },
{ "description" , "Execute a shell command and return its output (stdout and stderr combined)." },
{ "parameters" , {
{ "type" , "object" },
{ "properties" , {
{ "command" , {{ "type" , "string" }, { "description" , "Shell command to execute" }}},
{ "timeout" , {{ "type" , "integer" }, { "description" , string_format ( "Timeout in seconds (default 10, max %d)" , SERVER_TOOL_EXEC_SHELL_COMMAND_MAX_TIMEOUT )}}},
{ "max_output_size" , {{ "type" , "integer" }, { "description" , string_format ( "Maximum output size in bytes (default %zu)" , SERVER_TOOL_EXEC_SHELL_COMMAND_MAX_OUTPUT_SIZE )}}},
}},
{ "required" , json :: array ({ "command" })},
}},
}},
};
}
json invoke ( json params ) override {
std :: string command = params . at ( "command" ). get < std :: string > ();
int timeout = json_value ( params , "timeout" , 10 );
size_t max_output = ( size_t ) json_value ( params , "max_output_size" , ( int ) SERVER_TOOL_EXEC_SHELL_COMMAND_MAX_OUTPUT_SIZE );
timeout = std :: min ( timeout , SERVER_TOOL_EXEC_SHELL_COMMAND_MAX_TIMEOUT );
max_output = std :: min ( max_output , SERVER_TOOL_EXEC_SHELL_COMMAND_MAX_OUTPUT_SIZE );
#ifdef _WIN32
std :: vector < std :: string > args = { "cmd" , "/c" , command };
#else
std :: vector < std :: string > args = { "sh" , "-c" , command };
#endif
auto res = run_process ( args , max_output , timeout );
std :: string text_output = res . output ;
text_output += string_format ( " \n [exit code: %d]" , res . exit_code );
if ( res . timed_out ) {
text_output += " [exit due to timed out]" ;
}
return {{ "plain_text_response" , text_output }};
}
};
//
// write_file: create or overwrite a file
//
struct server_tool_write_file : server_tool {
server_tool_write_file () {
name = "write_file" ;
display_name = "Write file" ;
permission_write = true ;
}
json get_definition () override {
return {
{ "type" , "function" },
{ "function" , {
{ "name" , name },
{ "description" , "Write content to a file, creating it (including parent directories) if it does not exist. May use with edit_file for more complex edits." },
{ "parameters" , {
{ "type" , "object" },
{ "properties" , {
{ "path" , {{ "type" , "string" }, { "description" , "Path of the file to write" }}},
{ "content" , {{ "type" , "string" }, { "description" , "Content to write" }}},
}},
{ "required" , json :: array ({ "path" , "content" })},
}},
}},
};
}
json invoke ( json params ) override {
std :: string path = params . at ( "path" ). get < std :: string > ();
std :: string content = params . at ( "content" ). get < std :: string > ();
std :: error_code ec ;
fs :: path fpath ( path );
if ( fpath . has_parent_path ()) {
fs :: create_directories ( fpath . parent_path (), ec );
if ( ec ) {
return {{ "error" , "failed to create directories: " + ec . message ()}};
}
}
std :: ofstream f ( path , std :: ios :: binary );
if ( ! f ) {
return {{ "error" , "failed to open file for writing: " + path }};
}
f << content ;
if ( ! f ) {
return {{ "error" , "failed to write file: " + path }};
}
return {{ "result" , "file written successfully" }, { "path" , path }, { "bytes" , content . size ()}};
}
};
//
// edit_file: edit file content via line-based changes
//
struct server_tool_edit_file : server_tool {
server_tool_edit_file () {
name = "edit_file" ;
display_name = "Edit file" ;
permission_write = true ;
}
json get_definition () override {
return {
{ "type" , "function" },
{ "function" , {
{ "name" , name },
{ "description" ,
"Edit a file by applying a list of line-based changes. "
"Each change targets a 1-based inclusive line range and has a mode: "
" \" replace \" (replace lines with content), "
" \" delete \" (remove lines, content must be empty string), "
" \" append \" (insert content after line_end). "
"Set line_start to -1 to target the end of file (line_end is ignored in that case). "
"Changes must not overlap. They are applied in reverse line order automatically." },
{ "parameters" , {
{ "type" , "object" },
{ "properties" , {
{ "path" , {{ "type" , "string" }, { "description" , "Path to the file to edit" }}},
{ "changes" , {
{ "type" , "array" },
{ "description" , "List of changes to apply" },
{ "items" , {
{ "type" , "object" },
{ "properties" , {
{ "mode" , {{ "type" , "string" }, { "description" , " \" replace \" , \" delete \" , or \" append \" " }}},
{ "line_start" , {{ "type" , "integer" }, { "description" , "First line of the range (1-based); use -1 for end of file" }}},
{ "line_end" , {{ "type" , "integer" }, { "description" , "Last line of the range (1-based, inclusive); ignored when line_start is -1" }}},
{ "content" , {{ "type" , "string" }, { "description" , "Content to insert; must be empty string for delete mode" }}},
}},
{ "required" , json :: array ({ "mode" , "line_start" , "line_end" , "content" })},
}},
}},
}},
{ "required" , json :: array ({ "path" , "changes" })},
}},
}},
};
}
json invoke ( json params ) override {
std :: string path = params . at ( "path" ). get < std :: string > ();
const json & changes = params . at ( "changes" );
if ( ! changes . is_array ()) {
return {{ "error" , " \" changes \" must be an array" }};
}
// read file into lines
std :: ifstream fin ( path );
if ( ! fin ) {
return {{ "error" , "failed to open file: " + path }};
}
std :: vector < std :: string > lines ;
{
std :: string line ;
while ( std :: getline ( fin , line )) {
lines . push_back ( line );
}
}
fin . close ();
// validate and collect changes, then sort descending by line_start
struct change_entry {
std :: string mode ;
int line_start ; // 1-based
int line_end ; // 1-based inclusive
std :: string content ;
};
std :: vector < change_entry > entries ;
entries . reserve ( changes . size ());
for ( const auto & ch : changes ) {
change_entry e ;
e . mode = ch . at ( "mode" ). get < std :: string > ();
e . line_start = ch . at ( "line_start" ). get < int > ();
e . line_end = ch . at ( "line_end" ). get < int > ();
e . content = ch . at ( "content" ). get < std :: string > ();
if ( e . mode != "replace" && e . mode != "delete" && e . mode != "append" ) {
return {{ "error" , "invalid mode \" " + e . mode + " \" ; must be replace, delete, or append" }};
}
if ( e . mode == "delete" && ! e . content . empty ()) {
return {{ "error" , "content must be empty string for delete mode" }};
}
int n = ( int ) lines . size ();
if ( e . line_start == - 1 ) {
// -1 means end of file; line_end is ignored — normalize to point past last line
e . line_start = n + 1 ;
e . line_end = n + 1 ;
} else {
if ( e . line_start < 1 || e . line_end < e . line_start ) {
return {{ "error" , string_format ( "invalid line range [%d, %d]" , e . line_start , e . line_end )}};
}
if ( e . line_end > n ) {
return {{ "error" , string_format ( "line_end %d exceeds file length %d" , e . line_end , n )}};
}
}
entries . push_back ( std :: move ( e ));
}
// sort descending so earlier-indexed changes don't shift later ones
std :: sort ( entries . begin (), entries . end (), []( const change_entry & a , const change_entry & b ) {
return a . line_start > b . line_start ;
});
// apply changes (0-based indices internally)
for ( const auto & e : entries ) {
int idx_start = e . line_start - 1 ; // 0-based
int idx_end = e . line_end - 1 ; // 0-based inclusive
// split content into lines (preserve trailing newline awareness)
std :: vector < std :: string > new_lines ;
if ( ! e . content . empty ()) {
std :: istringstream ss ( e . content );
std :: string ln ;
while ( std :: getline ( ss , ln )) {
new_lines . push_back ( ln );
}
// if content ends with \n, getline consumed it — no extra empty line needed
// if content does NOT end with \n, last line is still captured correctly
}
if ( e . mode == "replace" ) {
// erase [idx_start, idx_end] and insert new_lines
lines . erase ( lines . begin () + idx_start , lines . begin () + idx_end + 1 );
lines . insert ( lines . begin () + idx_start , new_lines . begin (), new_lines . end ());
} else if ( e . mode == "delete" ) {
lines . erase ( lines . begin () + idx_start , lines . begin () + idx_end + 1 );
} else { // append
// idx_end + 1 may equal lines.size() when line_start == -1 (end of file)
lines . insert ( lines . begin () + idx_end + 1 , new_lines . begin (), new_lines . end ());
}
}
// write file back
std :: ofstream fout ( path , std :: ios :: binary );
if ( ! fout ) {
return {{ "error" , "failed to open file for writing: " + path }};
}
for ( size_t i = 0 ; i < lines . size (); i ++ ) {
fout << lines [ i ];
if ( i + 1 < lines . size ()) {
fout << " \n " ;
}
}
if ( ! lines . empty ()) {
fout << " \n " ;
}
if ( ! fout ) {
return {{ "error" , "failed to write file: " + path }};
}
return {{ "result" , "file edited successfully" }, { "path" , path }, { "lines" , ( int ) lines . size ()}};
}
};
//
// apply_diff: apply a unified diff via git apply
//
struct server_tool_apply_diff : server_tool {
server_tool_apply_diff () {
name = "apply_diff" ;
display_name = "Apply diff" ;
permission_write = true ;
}
json get_definition () override {
return {
{ "type" , "function" },
{ "function" , {
{ "name" , name },
{ "description" , "Apply a unified diff to edit one or more files using git apply. Use this instead of edit_file when the changes are complex." },
{ "parameters" , {
{ "type" , "object" },
{ "properties" , {
{ "diff" , {{ "type" , "string" }, { "description" , "Unified diff content in git diff format" }}},
}},
{ "required" , json :: array ({ "diff" })},
}},
}},
};
}
json invoke ( json params ) override {
std :: string diff = params . at ( "diff" ). get < std :: string > ();
// write diff to a temporary file
static std :: atomic < int > counter { 0 };
std :: string tmp_path = ( fs :: temp_directory_path () /
( "llama_patch_" + std :: to_string ( ++ counter ) + ".patch" )). string ();
{
std :: ofstream f ( tmp_path , std :: ios :: binary );
if ( ! f ) {
return {{ "error" , "failed to create temp patch file" }};
}
f << diff ;
}
auto res = run_process ({ "git" , "apply" , tmp_path }, 4096 , 10 );
std :: error_code ec ;
fs :: remove ( tmp_path , ec );
if ( res . exit_code != 0 ) {
return {{ "error" , "git apply failed (exit " + std :: to_string ( res . exit_code ) + "): " + res . output }};
}
return {{ "result" , "patch applied successfully" }};
}
};
2026-05-04 06:19:41 -04:00
//
// get_datetime: returns the current date and time
//
struct server_tool_get_datetime : server_tool {
server_tool_get_datetime () {
name = "get_datetime" ;
display_name = "Get Date & Time" ;
permission_write = false ;
}
json get_definition () override {
return {
{ "type" , "function" },
{ "function" , {
{ "name" , name },
{ "description" , "Returns the current date and time" },
}},
};
}
json invoke ( json ) override {
auto now = std :: chrono :: system_clock :: now ();
auto time = std :: chrono :: system_clock :: to_time_t ( now );
return {{ "result" , std :: ctime ( & time )}};
}
};
2026-03-27 10:07:11 +01:00
//
// public API
//
static std :: vector < std :: unique_ptr < server_tool >> build_tools () {
std :: vector < std :: unique_ptr < server_tool >> tools ;
tools . push_back ( std :: make_unique < server_tool_read_file > ());
tools . push_back ( std :: make_unique < server_tool_file_glob_search > ());
tools . push_back ( std :: make_unique < server_tool_grep_search > ());
tools . push_back ( std :: make_unique < server_tool_exec_shell_command > ());
tools . push_back ( std :: make_unique < server_tool_write_file > ());
tools . push_back ( std :: make_unique < server_tool_edit_file > ());
tools . push_back ( std :: make_unique < server_tool_apply_diff > ());
2026-05-04 06:19:41 -04:00
tools . push_back ( std :: make_unique < server_tool_get_datetime > ());
2026-03-27 10:07:11 +01:00
return tools ;
}
void server_tools :: setup ( const std :: vector < std :: string > & enabled_tools ) {
if ( ! enabled_tools . empty ()) {
std :: unordered_set < std :: string > enabled_set ( enabled_tools . begin (), enabled_tools . end ());
auto all_tools = build_tools ();
2026-05-05 06:35:27 +03:00
// collect all known tool names for validation
std :: vector < std :: string > known_names ;
known_names . reserve ( all_tools . size ());
for ( const auto & t : all_tools ) {
known_names . push_back ( t -> name );
}
// validate that every requested tool is known
for ( const auto & name : enabled_tools ) {
if ( name == "all" ) continue ;
if ( std :: find ( known_names . begin (), known_names . end (), name ) == known_names . end ()) {
throw std :: runtime_error ( string_format (
"unknown tool \" %s \" . available tools: %s" ,
name . c_str (),
string_join ( known_names , ", " ). c_str ()));
}
}
2026-03-27 10:07:11 +01:00
tools . clear ();
for ( auto & t : all_tools ) {
if ( enabled_set . count ( t -> name ) > 0 || enabled_set . count ( "all" ) > 0 ) {
tools . push_back ( std :: move ( t ));
}
}
}
handle_get = [ this ]( const server_http_req & ) -> server_http_res_ptr {
auto res = std :: make_unique < server_http_res > ();
try {
json result = json :: array ();
for ( const auto & t : tools ) {
result . push_back ( t -> to_json ());
}
res -> data = safe_json_to_str ( result );
} catch ( const std :: exception & e ) {
SRV_ERR ( "got exception: %s \n " , e . what ());
res -> status = 500 ;
res -> data = safe_json_to_str ( format_error_response ( e . what (), ERROR_TYPE_SERVER ));
}
return res ;
};
handle_post = [ this ]( const server_http_req & req ) -> server_http_res_ptr {
auto res = std :: make_unique < server_http_res > ();
try {
json body = json :: parse ( req . body );
std :: string tool_name = body . at ( "tool" ). get < std :: string > ();
json params = body . value ( "params" , json :: object ());
json result = invoke ( tool_name , params );
res -> data = safe_json_to_str ( result );
} catch ( const json :: exception & e ) {
res -> status = 400 ;
res -> data = safe_json_to_str ( format_error_response ( e . what (), ERROR_TYPE_INVALID_REQUEST ));
} catch ( const std :: exception & e ) {
SRV_ERR ( "got exception: %s \n " , e . what ());
res -> status = 500 ;
res -> data = safe_json_to_str ( format_error_response ( e . what (), ERROR_TYPE_SERVER ));
}
return res ;
};
}
json server_tools :: invoke ( const std :: string & name , const json & params ) {
for ( auto & t : tools ) {
if ( t -> name == name ) {
return t -> invoke ( params );
}
}
return {{ "error" , "unknown tool: " + name }};
}