2026-02-07 00:37:36 +08:00
# include "tool_registry.h"
2026-02-26 09:26:26 +08:00
# include "mimi_config.h"
2026-02-07 00:37:36 +08:00
# include "tools/tool_web_search.h"
2026-02-07 13:05:02 +08:00
# include "tools/tool_get_time.h"
2026-02-07 17:54:52 +08:00
# include "tools/tool_files.h"
2026-02-09 01:02:33 +08:00
# include "tools/tool_cron.h"
2026-02-07 00:37:36 +08:00
# include <string.h>
# include "esp_log.h"
# include "cJSON.h"
static const char * TAG = " tools " ;
2026-02-09 01:02:33 +08:00
# define MAX_TOOLS 12
2026-02-07 00:37:36 +08:00
static mimi_tool_t s_tools [ MAX_TOOLS ] ;
static int s_tool_count = 0 ;
static char * s_tools_json = NULL ; /* cached JSON array string */
static void register_tool ( const mimi_tool_t * tool )
{
if ( s_tool_count > = MAX_TOOLS ) {
ESP_LOGE ( TAG , " Tool registry full " ) ;
return ;
}
s_tools [ s_tool_count + + ] = * tool ;
ESP_LOGI ( TAG , " Registered tool: %s " , tool - > name ) ;
}
static void build_tools_json ( void )
{
cJSON * arr = cJSON_CreateArray ( ) ;
for ( int i = 0 ; i < s_tool_count ; i + + ) {
cJSON * tool = cJSON_CreateObject ( ) ;
cJSON_AddStringToObject ( tool , " name " , s_tools [ i ] . name ) ;
cJSON_AddStringToObject ( tool , " description " , s_tools [ i ] . description ) ;
cJSON * schema = cJSON_Parse ( s_tools [ i ] . input_schema_json ) ;
if ( schema ) {
cJSON_AddItemToObject ( tool , " input_schema " , schema ) ;
}
cJSON_AddItemToArray ( arr , tool ) ;
}
free ( s_tools_json ) ;
s_tools_json = cJSON_PrintUnformatted ( arr ) ;
cJSON_Delete ( arr ) ;
ESP_LOGI ( TAG , " Tools JSON built (%d tools) " , s_tool_count ) ;
}
esp_err_t tool_registry_init ( void )
{
s_tool_count = 0 ;
/* Register web_search */
tool_web_search_init ( ) ;
mimi_tool_t ws = {
. name = " web_search " ,
2026-02-18 19:16:00 +08:00
. description = " Search the web for current information via Tavily (preferred) or Brave when configured. " ,
2026-02-07 00:37:36 +08:00
. input_schema_json =
" { \" type \" : \" object \" , "
" \" properties \" :{ \" query \" :{ \" type \" : \" string \" , \" description \" : \" The search query \" }}, "
" \" required \" :[ \" query \" ]} " ,
. execute = tool_web_search_execute ,
} ;
register_tool ( & ws ) ;
2026-02-07 13:05:02 +08:00
/* Register get_current_time */
mimi_tool_t gt = {
. name = " get_current_time " ,
. description = " Get the current date and time. Also sets the system clock. Call this when you need to know what time or date it is. " ,
. input_schema_json =
" { \" type \" : \" object \" , "
" \" properties \" :{}, "
" \" required \" :[]} " ,
. execute = tool_get_time_execute ,
} ;
register_tool ( & gt ) ;
2026-02-07 17:54:52 +08:00
/* Register read_file */
mimi_tool_t rf = {
. name = " read_file " ,
2026-02-26 09:26:26 +08:00
. description = " Read a file from SPIFFS storage. Path must start with " MIMI_SPIFFS_BASE " /. " ,
2026-02-07 17:54:52 +08:00
. input_schema_json =
" { \" type \" : \" object \" , "
2026-02-26 09:26:26 +08:00
" \" properties \" :{ \" path \" :{ \" type \" : \" string \" , \" description \" : \" Absolute path starting with " MIMI_SPIFFS_BASE " / \" }}, "
2026-02-07 17:54:52 +08:00
" \" required \" :[ \" path \" ]} " ,
. execute = tool_read_file_execute ,
} ;
register_tool ( & rf ) ;
/* Register write_file */
mimi_tool_t wf = {
. name = " write_file " ,
2026-02-26 09:26:26 +08:00
. description = " Write or overwrite a file on SPIFFS storage. Path must start with " MIMI_SPIFFS_BASE " /. " ,
2026-02-07 17:54:52 +08:00
. input_schema_json =
" { \" type \" : \" object \" , "
2026-02-26 09:26:26 +08:00
" \" properties \" :{ \" path \" :{ \" type \" : \" string \" , \" description \" : \" Absolute path starting with " MIMI_SPIFFS_BASE " / \" }, "
2026-02-07 17:54:52 +08:00
" \" content \" :{ \" type \" : \" string \" , \" description \" : \" File content to write \" }}, "
" \" required \" :[ \" path \" , \" content \" ]} " ,
. execute = tool_write_file_execute ,
} ;
register_tool ( & wf ) ;
/* Register edit_file */
mimi_tool_t ef = {
. name = " edit_file " ,
. description = " Find and replace text in a file on SPIFFS. Replaces first occurrence of old_string with new_string. " ,
. input_schema_json =
" { \" type \" : \" object \" , "
2026-02-26 09:26:26 +08:00
" \" properties \" :{ \" path \" :{ \" type \" : \" string \" , \" description \" : \" Absolute path starting with " MIMI_SPIFFS_BASE " / \" }, "
2026-02-07 17:54:52 +08:00
" \" old_string \" :{ \" type \" : \" string \" , \" description \" : \" Text to find \" }, "
" \" new_string \" :{ \" type \" : \" string \" , \" description \" : \" Replacement text \" }}, "
" \" required \" :[ \" path \" , \" old_string \" , \" new_string \" ]} " ,
. execute = tool_edit_file_execute ,
} ;
register_tool ( & ef ) ;
/* Register list_dir */
mimi_tool_t ld = {
. name = " list_dir " ,
. description = " List files on SPIFFS storage, optionally filtered by path prefix. " ,
. input_schema_json =
" { \" type \" : \" object \" , "
2026-02-26 09:26:26 +08:00
" \" properties \" :{ \" prefix \" :{ \" type \" : \" string \" , \" description \" : \" Optional path prefix filter, e.g. " MIMI_SPIFFS_BASE " /memory/ \" }}, "
2026-02-07 17:54:52 +08:00
" \" required \" :[]} " ,
. execute = tool_list_dir_execute ,
} ;
register_tool ( & ld ) ;
2026-02-09 01:02:33 +08:00
/* Register cron_add */
mimi_tool_t ca = {
. name = " cron_add " ,
. description = " Schedule a recurring or one-shot task. The message will trigger an agent turn when the job fires. " ,
. input_schema_json =
" { \" type \" : \" object \" , "
" \" properties \" :{ "
" \" name \" :{ \" type \" : \" string \" , \" description \" : \" Short name for the job \" }, "
" \" schedule_type \" :{ \" type \" : \" string \" , \" description \" : \" 'every' for recurring interval or 'at' for one-shot at a unix timestamp \" }, "
" \" interval_s \" :{ \" type \" : \" integer \" , \" description \" : \" Interval in seconds (required for 'every') \" }, "
" \" at_epoch \" :{ \" type \" : \" integer \" , \" description \" : \" Unix timestamp to fire at (required for 'at') \" }, "
" \" message \" :{ \" type \" : \" string \" , \" description \" : \" Message to inject when the job fires, triggering an agent turn \" }, "
2026-02-18 19:16:00 +08:00
" \" channel \" :{ \" type \" : \" string \" , \" description \" : \" Optional reply channel (e.g. 'telegram'). If omitted, current turn channel is used when available \" }, "
" \" chat_id \" :{ \" type \" : \" string \" , \" description \" : \" Optional reply chat_id. Required when channel='telegram'. If omitted during a Telegram turn, current chat_id is used \" } "
2026-02-09 01:02:33 +08:00
" }, "
" \" required \" :[ \" name \" , \" schedule_type \" , \" message \" ]} " ,
. execute = tool_cron_add_execute ,
} ;
register_tool ( & ca ) ;
/* Register cron_list */
mimi_tool_t cl = {
. name = " cron_list " ,
. description = " List all scheduled cron jobs with their status, schedule, and IDs. " ,
. input_schema_json =
" { \" type \" : \" object \" , "
" \" properties \" :{}, "
" \" required \" :[]} " ,
. execute = tool_cron_list_execute ,
} ;
register_tool ( & cl ) ;
/* Register cron_remove */
mimi_tool_t cr = {
. name = " cron_remove " ,
. description = " Remove a scheduled cron job by its ID. " ,
. input_schema_json =
" { \" type \" : \" object \" , "
" \" properties \" :{ \" job_id \" :{ \" type \" : \" string \" , \" description \" : \" The 8-character job ID to remove \" }}, "
" \" required \" :[ \" job_id \" ]} " ,
. execute = tool_cron_remove_execute ,
} ;
register_tool ( & cr ) ;
2026-02-07 00:37:36 +08:00
build_tools_json ( ) ;
ESP_LOGI ( TAG , " Tool registry initialized " ) ;
return ESP_OK ;
}
const char * tool_registry_get_tools_json ( void )
{
return s_tools_json ;
}
esp_err_t tool_registry_execute ( const char * name , const char * input_json ,
char * output , size_t output_size )
{
for ( int i = 0 ; i < s_tool_count ; i + + ) {
if ( strcmp ( s_tools [ i ] . name , name ) = = 0 ) {
ESP_LOGI ( TAG , " Executing tool: %s " , name ) ;
return s_tools [ i ] . execute ( input_json , output , output_size ) ;
}
}
ESP_LOGW ( TAG , " Unknown tool: %s " , name ) ;
snprintf ( output , output_size , " Error: unknown tool '%s' " , name ) ;
return ESP_ERR_NOT_FOUND ;
}