From 3a76f02f2f65edcbcdf552a4fe56914429c44be2 Mon Sep 17 00:00:00 2001 From: Asklv Date: Wed, 18 Feb 2026 19:16:00 +0800 Subject: [PATCH] feat(search): implement Tavily HTTP client for direct and proxy modes Signed-off-by: Asklv --- main/tools/tool_web_search.c | 118 +++++++++++++++++++++++++++++++++++ 1 file changed, 118 insertions(+) diff --git a/main/tools/tool_web_search.c b/main/tools/tool_web_search.c index 3a293f8..b405e00 100644 --- a/main/tools/tool_web_search.c +++ b/main/tools/tool_web_search.c @@ -186,6 +186,20 @@ static void format_tavily_results(cJSON *root, char *output, size_t output_size) } } +static char *build_tavily_payload(const char *query) +{ + cJSON *root = cJSON_CreateObject(); + if (!root) return NULL; + cJSON_AddStringToObject(root, "api_key", s_tavily_key); + cJSON_AddStringToObject(root, "query", query); + cJSON_AddNumberToObject(root, "max_results", SEARCH_RESULT_COUNT); + cJSON_AddBoolToObject(root, "include_answer", false); + cJSON_AddStringToObject(root, "search_depth", "basic"); + char *payload = cJSON_PrintUnformatted(root); + cJSON_Delete(root); + return payload; +} + /* ── Direct HTTPS request ─────────────────────────────────────── */ static esp_err_t brave_search_direct(const char *url, search_buf_t *sb) @@ -278,6 +292,110 @@ static esp_err_t brave_search_via_proxy(const char *path, search_buf_t *sb) return ESP_OK; } +static esp_err_t tavily_search_direct(const char *query, search_buf_t *sb) +{ + char *payload = build_tavily_payload(query); + if (!payload) return ESP_ERR_NO_MEM; + + esp_http_client_config_t config = { + .url = "https://api.tavily.com/search", + .event_handler = http_event_handler, + .user_data = sb, + .timeout_ms = 15000, + .buffer_size = 4096, + .crt_bundle_attach = esp_crt_bundle_attach, + }; + + esp_http_client_handle_t client = esp_http_client_init(&config); + if (!client) { + free(payload); + return ESP_FAIL; + } + + esp_http_client_set_method(client, HTTP_METHOD_POST); + esp_http_client_set_header(client, "Accept", "application/json"); + esp_http_client_set_header(client, "Content-Type", "application/json"); + esp_http_client_set_post_field(client, payload, strlen(payload)); + + esp_err_t err = esp_http_client_perform(client); + int status = esp_http_client_get_status_code(client); + esp_http_client_cleanup(client); + free(payload); + + if (err != ESP_OK) return err; + if (status != 200) { + ESP_LOGE(TAG, "Tavily API returned %d", status); + return ESP_FAIL; + } + return ESP_OK; +} + +static esp_err_t tavily_search_via_proxy(const char *query, search_buf_t *sb) +{ + proxy_conn_t *conn = proxy_conn_open("api.tavily.com", 443, 15000); + if (!conn) return ESP_ERR_HTTP_CONNECT; + + char *payload = build_tavily_payload(query); + if (!payload) { + proxy_conn_close(conn); + return ESP_ERR_NO_MEM; + } + + char header[768]; + int hlen = snprintf(header, sizeof(header), + "POST /search HTTP/1.1\r\n" + "Host: api.tavily.com\r\n" + "Accept: application/json\r\n" + "Content-Type: application/json\r\n" + "Content-Length: %d\r\n" + "Connection: close\r\n\r\n", + (int)strlen(payload)); + + if (proxy_conn_write(conn, header, hlen) < 0 || + proxy_conn_write(conn, payload, strlen(payload)) < 0) { + free(payload); + proxy_conn_close(conn); + return ESP_ERR_HTTP_WRITE_DATA; + } + free(payload); + + char tmp[4096]; + size_t total = 0; + while (1) { + int n = proxy_conn_read(conn, tmp, sizeof(tmp), 15000); + if (n <= 0) break; + size_t copy = (total + n < sb->cap - 1) ? (size_t)n : sb->cap - 1 - total; + if (copy > 0) { + memcpy(sb->data + total, tmp, copy); + total += copy; + } + } + sb->data[total] = '\0'; + sb->len = total; + proxy_conn_close(conn); + + int status = 0; + if (total > 5 && strncmp(sb->data, "HTTP/", 5) == 0) { + const char *sp = strchr(sb->data, ' '); + if (sp) status = atoi(sp + 1); + } + + char *body = strstr(sb->data, "\r\n\r\n"); + if (body) { + body += 4; + size_t blen = total - (body - sb->data); + memmove(sb->data, body, blen); + sb->len = blen; + sb->data[sb->len] = '\0'; + } + + if (status != 200) { + ESP_LOGE(TAG, "Tavily API returned %d via proxy", status); + return ESP_FAIL; + } + return ESP_OK; +} + /* ── Execute ──────────────────────────────────────────────────── */ esp_err_t tool_web_search_execute(const char *input_json, char *output, size_t output_size)