- 新增 set_timezone LLM 工具,支持通过对话设置时区 - 新增 set_timezone / timezone_show CLI 命令 - 默认时区从 PST 改为 CST-8(中国标准时间 UTC+8) - 支持 POSIX 格式和 18 个城市名映射(Asia/Shanghai 等) - 时区通过 NVS 持久化存储(system_config namespace) - config_show 中显示当前时区配置 - 更新 changelog.md 和 taolun.md 文档
256 lines
9.5 KiB
C
256 lines
9.5 KiB
C
#include "tool_registry.h"
|
|
#include "mimi_config.h"
|
|
#include "tools/tool_web_search.h"
|
|
#include "tools/tool_get_time.h"
|
|
#include "tools/tool_set_timezone.h"
|
|
#include "tools/tool_files.h"
|
|
#include "tools/tool_cron.h"
|
|
#include "tools/tool_gpio.h"
|
|
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include "esp_log.h"
|
|
#include "cJSON.h"
|
|
|
|
static const char *TAG = "tools";
|
|
|
|
#define MAX_TOOLS 16
|
|
|
|
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",
|
|
.description = "Search the web for current information via Tavily (preferred) or Brave when configured.",
|
|
.input_schema_json =
|
|
"{\"type\":\"object\","
|
|
"\"properties\":{\"query\":{\"type\":\"string\",\"description\":\"The search query\"}},"
|
|
"\"required\":[\"query\"]}",
|
|
.execute = tool_web_search_execute,
|
|
};
|
|
register_tool(&ws);
|
|
|
|
/* 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(>);
|
|
|
|
/* Register set_timezone */
|
|
mimi_tool_t stz = {
|
|
.name = "set_timezone",
|
|
.description = "Set the system timezone. Accepts POSIX format (e.g. CST-8, EST5EDT,M3.2.0,M11.1.0) or city name (e.g. Asia/Shanghai, America/New_York).",
|
|
.input_schema_json =
|
|
"{\"type\":\"object\","
|
|
"\"properties\":{\"timezone\":{\"type\":\"string\",\"description\":\"Timezone in POSIX format or city name (e.g. CST-8, Asia/Shanghai)\"}},"
|
|
"\"required\":[\"timezone\"]}",
|
|
.execute = tool_set_timezone_execute,
|
|
};
|
|
register_tool(&stz);
|
|
|
|
/* Register read_file */
|
|
mimi_tool_t rf = {
|
|
.name = "read_file",
|
|
.description = "Read a file from SPIFFS storage. Path must start with " MIMI_SPIFFS_BASE "/.",
|
|
.input_schema_json =
|
|
"{\"type\":\"object\","
|
|
"\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Absolute path starting with " MIMI_SPIFFS_BASE "/\"}},"
|
|
"\"required\":[\"path\"]}",
|
|
.execute = tool_read_file_execute,
|
|
};
|
|
register_tool(&rf);
|
|
|
|
/* Register write_file */
|
|
mimi_tool_t wf = {
|
|
.name = "write_file",
|
|
.description = "Write or overwrite a file on SPIFFS storage. Path must start with " MIMI_SPIFFS_BASE "/.",
|
|
.input_schema_json =
|
|
"{\"type\":\"object\","
|
|
"\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Absolute path starting with " MIMI_SPIFFS_BASE "/\"},"
|
|
"\"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\","
|
|
"\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Absolute path starting with " MIMI_SPIFFS_BASE "/\"},"
|
|
"\"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\","
|
|
"\"properties\":{\"prefix\":{\"type\":\"string\",\"description\":\"Optional path prefix filter, e.g. " MIMI_SPIFFS_BASE "/memory/\"}},"
|
|
"\"required\":[]}",
|
|
.execute = tool_list_dir_execute,
|
|
};
|
|
register_tool(&ld);
|
|
|
|
/* 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\"},"
|
|
"\"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\"}"
|
|
"},"
|
|
"\"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);
|
|
|
|
/* Register GPIO tools */
|
|
tool_gpio_init();
|
|
|
|
mimi_tool_t gw = {
|
|
.name = "gpio_write",
|
|
.description = "Set a GPIO pin HIGH or LOW. Controls LEDs, relays, and other digital outputs.",
|
|
.input_schema_json =
|
|
"{\"type\":\"object\","
|
|
"\"properties\":{\"pin\":{\"type\":\"integer\",\"description\":\"GPIO pin number\"},"
|
|
"\"state\":{\"type\":\"integer\",\"description\":\"1 for HIGH, 0 for LOW\"}},"
|
|
"\"required\":[\"pin\",\"state\"]}",
|
|
.execute = tool_gpio_write_execute,
|
|
};
|
|
register_tool(&gw);
|
|
|
|
mimi_tool_t gr = {
|
|
.name = "gpio_read",
|
|
.description = "Read a GPIO pin state. Returns HIGH or LOW. Use for checking switches, sensors, and digital inputs.",
|
|
.input_schema_json =
|
|
"{\"type\":\"object\","
|
|
"\"properties\":{\"pin\":{\"type\":\"integer\",\"description\":\"GPIO pin number\"}},"
|
|
"\"required\":[\"pin\"]}",
|
|
.execute = tool_gpio_read_execute,
|
|
};
|
|
register_tool(&gr);
|
|
|
|
mimi_tool_t ga = {
|
|
.name = "gpio_read_all",
|
|
.description = "Read all allowed GPIO pin states in a single call. Returns each pin's HIGH/LOW state.",
|
|
.input_schema_json =
|
|
"{\"type\":\"object\","
|
|
"\"properties\":{},"
|
|
"\"required\":[]}",
|
|
.execute = tool_gpio_read_all_execute,
|
|
};
|
|
register_tool(&ga);
|
|
|
|
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;
|
|
}
|