From 78c7fc9b1ad244837f3f94598f2c56281ac09271 Mon Sep 17 00:00:00 2001 From: crispyberry Date: Sat, 7 Feb 2026 13:05:02 +0800 Subject: [PATCH] feat: add get_current_time built-in tool and timezone config Fetches real time via HTTP Date header (works through proxy), sets system clock, and returns formatted local time to the agent. Also adds MIMI_TIMEZONE config and updates default model to claude-opus-4-5. Co-Authored-By: Claude Opus 4.6 --- main/CMakeLists.txt | 1 + main/mimi_config.h | 5 +- main/tools/tool_get_time.c | 161 +++++++++++++++++++++++++++++++++++++ main/tools/tool_get_time.h | 10 +++ main/tools/tool_registry.c | 13 +++ main/wifi/wifi_manager.c | 1 + 6 files changed, 190 insertions(+), 1 deletion(-) create mode 100644 main/tools/tool_get_time.c create mode 100644 main/tools/tool_get_time.h diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index f7e302a..ae5c514 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -15,6 +15,7 @@ idf_component_register( "proxy/http_proxy.c" "tools/tool_registry.c" "tools/tool_web_search.c" + "tools/tool_get_time.c" INCLUDE_DIRS "." REQUIRES diff --git a/main/mimi_config.h b/main/mimi_config.h index 972abdd..e447047 100644 --- a/main/mimi_config.h +++ b/main/mimi_config.h @@ -52,8 +52,11 @@ #define MIMI_AGENT_MAX_TOOL_ITER 10 #define MIMI_MAX_TOOL_CALLS 4 +/* Timezone (POSIX TZ format) */ +#define MIMI_TIMEZONE "PST8PDT,M3.2.0,M11.1.0" + /* LLM */ -#define MIMI_LLM_DEFAULT_MODEL "claude-opus-4-6" +#define MIMI_LLM_DEFAULT_MODEL "claude-opus-4-5" #define MIMI_LLM_MAX_TOKENS 4096 #define MIMI_LLM_API_URL "https://api.anthropic.com/v1/messages" #define MIMI_LLM_API_VERSION "2023-06-01" diff --git a/main/tools/tool_get_time.c b/main/tools/tool_get_time.c new file mode 100644 index 0000000..e0fa60e --- /dev/null +++ b/main/tools/tool_get_time.c @@ -0,0 +1,161 @@ +#include "tool_get_time.h" +#include "mimi_config.h" +#include "proxy/http_proxy.h" + +#include +#include +#include +#include +#include "esp_log.h" +#include "esp_http_client.h" +#include "esp_crt_bundle.h" + +static const char *TAG = "tool_time"; + +static const char *MONTHS[] = { + "Jan","Feb","Mar","Apr","May","Jun", + "Jul","Aug","Sep","Oct","Nov","Dec" +}; + +/* Parse "Sat, 01 Feb 2025 10:25:00 GMT" → set system clock, return formatted string */ +static bool parse_and_set_time(const char *date_str, char *out, size_t out_size) +{ + int day, year, hour, min, sec; + char mon_str[4] = {0}; + + if (sscanf(date_str, "%*[^,], %d %3s %d %d:%d:%d", + &day, mon_str, &year, &hour, &min, &sec) != 6) { + return false; + } + + int mon = -1; + for (int i = 0; i < 12; i++) { + if (strcmp(mon_str, MONTHS[i]) == 0) { mon = i; break; } + } + if (mon < 0) return false; + + struct tm tm = { + .tm_sec = sec, .tm_min = min, .tm_hour = hour, + .tm_mday = day, .tm_mon = mon, .tm_year = year - 1900, + }; + + /* Convert UTC to epoch — mktime expects local, so temporarily set UTC */ + setenv("TZ", "UTC0", 1); + tzset(); + time_t t = mktime(&tm); + + /* Restore timezone */ + setenv("TZ", MIMI_TIMEZONE, 1); + tzset(); + + if (t < 0) return false; + + struct timeval tv = { .tv_sec = t }; + settimeofday(&tv, NULL); + + /* Format in local time */ + struct tm local; + localtime_r(&t, &local); + strftime(out, out_size, "%Y-%m-%d %H:%M:%S %Z (%A)", &local); + + return true; +} + +/* Fetch time via proxy: HEAD request to api.telegram.org, parse Date header */ +static esp_err_t fetch_time_via_proxy(char *out, size_t out_size) +{ + proxy_conn_t *conn = proxy_conn_open("api.telegram.org", 443, 10000); + if (!conn) return ESP_ERR_HTTP_CONNECT; + + const char *req = + "HEAD / HTTP/1.1\r\n" + "Host: api.telegram.org\r\n" + "Connection: close\r\n\r\n"; + + if (proxy_conn_write(conn, req, strlen(req)) < 0) { + proxy_conn_close(conn); + return ESP_ERR_HTTP_WRITE_DATA; + } + + char buf[1024]; + int total = 0; + while (total < (int)sizeof(buf) - 1) { + int n = proxy_conn_read(conn, buf + total, sizeof(buf) - 1 - total, 10000); + if (n <= 0) break; + total += n; + buf[total] = '\0'; + if (strstr(buf, "\r\n\r\n")) break; + } + proxy_conn_close(conn); + + /* Find Date header */ + char *date_hdr = strcasestr(buf, "\r\nDate: "); + if (!date_hdr) return ESP_ERR_NOT_FOUND; + date_hdr += 8; + + char *eol = strstr(date_hdr, "\r\n"); + if (!eol) return ESP_ERR_NOT_FOUND; + + char date_val[64]; + size_t dlen = eol - date_hdr; + if (dlen >= sizeof(date_val)) return ESP_ERR_NOT_FOUND; + memcpy(date_val, date_hdr, dlen); + date_val[dlen] = '\0'; + + if (!parse_and_set_time(date_val, out, out_size)) return ESP_FAIL; + return ESP_OK; +} + +/* Fetch time via direct HTTPS */ +static esp_err_t fetch_time_direct(char *out, size_t out_size) +{ + esp_http_client_config_t config = { + .url = "https://api.telegram.org/", + .method = HTTP_METHOD_HEAD, + .timeout_ms = 10000, + .crt_bundle_attach = esp_crt_bundle_attach, + }; + + esp_http_client_handle_t client = esp_http_client_init(&config); + if (!client) return ESP_FAIL; + + esp_err_t err = esp_http_client_perform(client); + if (err != ESP_OK) { + esp_http_client_cleanup(client); + return err; + } + + /* Get Date header */ + char date_val[64] = {0}; + err = esp_http_client_get_header(client, "Date", (char **)&date_val); + /* esp_http_client_get_header returns pointer, not copy */ + char *date_ptr = NULL; + esp_http_client_get_header(client, "Date", &date_ptr); + esp_http_client_cleanup(client); + + if (!date_ptr || date_ptr[0] == '\0') return ESP_ERR_NOT_FOUND; + + if (!parse_and_set_time(date_ptr, out, out_size)) return ESP_FAIL; + return ESP_OK; +} + +esp_err_t tool_get_time_execute(const char *input_json, char *output, size_t output_size) +{ + ESP_LOGI(TAG, "Fetching current time..."); + + esp_err_t err; + if (http_proxy_is_enabled()) { + err = fetch_time_via_proxy(output, output_size); + } else { + err = fetch_time_direct(output, output_size); + } + + if (err == ESP_OK) { + ESP_LOGI(TAG, "Time: %s", output); + } else { + snprintf(output, output_size, "Error: failed to fetch time (%s)", esp_err_to_name(err)); + ESP_LOGE(TAG, "%s", output); + } + + return err; +} diff --git a/main/tools/tool_get_time.h b/main/tools/tool_get_time.h new file mode 100644 index 0000000..1e73113 --- /dev/null +++ b/main/tools/tool_get_time.h @@ -0,0 +1,10 @@ +#pragma once + +#include "esp_err.h" +#include + +/** + * Execute get_current_time tool. + * Fetches current time via HTTP Date header, sets system clock, returns time string. + */ +esp_err_t tool_get_time_execute(const char *input_json, char *output, size_t output_size); diff --git a/main/tools/tool_registry.c b/main/tools/tool_registry.c index 17b0d29..dd2483c 100644 --- a/main/tools/tool_registry.c +++ b/main/tools/tool_registry.c @@ -1,5 +1,6 @@ #include "tool_registry.h" #include "tools/tool_web_search.h" +#include "tools/tool_get_time.h" #include #include "esp_log.h" @@ -65,6 +66,18 @@ esp_err_t tool_registry_init(void) }; 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(>); + build_tools_json(); ESP_LOGI(TAG, "Tool registry initialized"); diff --git a/main/wifi/wifi_manager.c b/main/wifi/wifi_manager.c index a5178b5..71c1ebf 100644 --- a/main/wifi/wifi_manager.c +++ b/main/wifi/wifi_manager.c @@ -44,6 +44,7 @@ static void event_handler(void *arg, esp_event_base_t event_base, ESP_LOGI(TAG, "Connected! IP: %s", s_ip_str); s_retry_count = 0; s_connected = true; + xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT); } }