feat: add wifi scaner in uart.

Signed-off-by: Bo <boironic@gmail.com>
This commit is contained in:
Bo
2026-02-10 01:16:00 +08:00
committed by lvbo
parent 5bcb28abbd
commit e79e4e4932
9 changed files with 606 additions and 111 deletions

View File

@@ -15,6 +15,17 @@ static const char *TAG = "llm";
static char s_api_key[128] = {0};
static char s_model[64] = MIMI_LLM_DEFAULT_MODEL;
static char s_provider[16] = MIMI_LLM_PROVIDER_DEFAULT;
static void safe_copy(char *dst, size_t dst_size, const char *src)
{
if (!dst || dst_size == 0) return;
if (!src) {
dst[0] = '\0';
return;
}
snprintf(dst, dst_size, "%s", src);
}
/* ── Response buffer ──────────────────────────────────────────── */
@@ -67,16 +78,41 @@ static esp_err_t http_event_handler(esp_http_client_event_t *evt)
return ESP_OK;
}
/* ── Provider helpers ──────────────────────────────────────────── */
static bool provider_is_openai(void)
{
return strcmp(s_provider, "openai") == 0;
}
static const char *llm_api_url(void)
{
return provider_is_openai() ? MIMI_OPENAI_API_URL : MIMI_LLM_API_URL;
}
static const char *llm_api_host(void)
{
return provider_is_openai() ? "api.openai.com" : "api.anthropic.com";
}
static const char *llm_api_path(void)
{
return provider_is_openai() ? "/v1/chat/completions" : "/v1/messages";
}
/* ── Init ─────────────────────────────────────────────────────── */
esp_err_t llm_proxy_init(void)
{
/* Start with build-time defaults */
if (MIMI_SECRET_API_KEY[0] != '\0') {
strncpy(s_api_key, MIMI_SECRET_API_KEY, sizeof(s_api_key) - 1);
safe_copy(s_api_key, sizeof(s_api_key), MIMI_SECRET_API_KEY);
}
if (MIMI_SECRET_MODEL[0] != '\0') {
strncpy(s_model, MIMI_SECRET_MODEL, sizeof(s_model) - 1);
safe_copy(s_model, sizeof(s_model), MIMI_SECRET_MODEL);
}
if (MIMI_SECRET_MODEL_PROVIDER[0] != '\0') {
safe_copy(s_provider, sizeof(s_provider), MIMI_SECRET_MODEL_PROVIDER);
}
/* NVS overrides take highest priority (set via CLI) */
@@ -85,18 +121,23 @@ esp_err_t llm_proxy_init(void)
char tmp[128] = {0};
size_t len = sizeof(tmp);
if (nvs_get_str(nvs, MIMI_NVS_KEY_API_KEY, tmp, &len) == ESP_OK && tmp[0]) {
strncpy(s_api_key, tmp, sizeof(s_api_key) - 1);
safe_copy(s_api_key, sizeof(s_api_key), tmp);
}
len = sizeof(tmp);
memset(tmp, 0, sizeof(tmp));
if (nvs_get_str(nvs, MIMI_NVS_KEY_MODEL, tmp, &len) == ESP_OK && tmp[0]) {
strncpy(s_model, tmp, sizeof(s_model) - 1);
safe_copy(s_model, sizeof(s_model), tmp);
}
len = sizeof(tmp);
memset(tmp, 0, sizeof(tmp));
if (nvs_get_str(nvs, MIMI_NVS_KEY_PROVIDER, tmp, &len) == ESP_OK && tmp[0]) {
safe_copy(s_provider, sizeof(s_provider), tmp);
}
nvs_close(nvs);
}
if (s_api_key[0]) {
ESP_LOGI(TAG, "LLM proxy initialized (model: %s)", s_model);
ESP_LOGI(TAG, "LLM proxy initialized (provider: %s, model: %s)", s_provider, s_model);
} else {
ESP_LOGW(TAG, "No API key. Use CLI: set_api_key <KEY>");
}
@@ -108,7 +149,7 @@ esp_err_t llm_proxy_init(void)
static esp_err_t llm_http_direct(const char *post_data, resp_buf_t *rb, int *out_status)
{
esp_http_client_config_t config = {
.url = MIMI_LLM_API_URL,
.url = llm_api_url(),
.event_handler = http_event_handler,
.user_data = rb,
.timeout_ms = 120 * 1000,
@@ -122,8 +163,16 @@ static esp_err_t llm_http_direct(const char *post_data, resp_buf_t *rb, int *out
esp_http_client_set_method(client, HTTP_METHOD_POST);
esp_http_client_set_header(client, "Content-Type", "application/json");
esp_http_client_set_header(client, "x-api-key", s_api_key);
esp_http_client_set_header(client, "anthropic-version", MIMI_LLM_API_VERSION);
if (provider_is_openai()) {
if (s_api_key[0]) {
char auth[192];
snprintf(auth, sizeof(auth), "Bearer %s", s_api_key);
esp_http_client_set_header(client, "Authorization", auth);
}
} else {
esp_http_client_set_header(client, "x-api-key", s_api_key);
esp_http_client_set_header(client, "anthropic-version", MIMI_LLM_API_VERSION);
}
esp_http_client_set_post_field(client, post_data, strlen(post_data));
esp_err_t err = esp_http_client_perform(client);
@@ -136,20 +185,32 @@ static esp_err_t llm_http_direct(const char *post_data, resp_buf_t *rb, int *out
static esp_err_t llm_http_via_proxy(const char *post_data, resp_buf_t *rb, int *out_status)
{
proxy_conn_t *conn = proxy_conn_open("api.anthropic.com", 443, 30000);
proxy_conn_t *conn = proxy_conn_open(llm_api_host(), 443, 30000);
if (!conn) return ESP_ERR_HTTP_CONNECT;
int body_len = strlen(post_data);
char header[512];
int hlen = snprintf(header, sizeof(header),
"POST /v1/messages HTTP/1.1\r\n"
"Host: api.anthropic.com\r\n"
"Content-Type: application/json\r\n"
"x-api-key: %s\r\n"
"anthropic-version: %s\r\n"
"Content-Length: %d\r\n"
"Connection: close\r\n\r\n",
s_api_key, MIMI_LLM_API_VERSION, body_len);
int hlen = 0;
if (provider_is_openai()) {
hlen = snprintf(header, sizeof(header),
"POST %s HTTP/1.1\r\n"
"Host: %s\r\n"
"Content-Type: application/json\r\n"
"Authorization: Bearer %s\r\n"
"Content-Length: %d\r\n"
"Connection: close\r\n\r\n",
llm_api_path(), llm_api_host(), s_api_key, body_len);
} else {
hlen = snprintf(header, sizeof(header),
"POST %s HTTP/1.1\r\n"
"Host: %s\r\n"
"Content-Type: application/json\r\n"
"x-api-key: %s\r\n"
"anthropic-version: %s\r\n"
"Content-Length: %d\r\n"
"Connection: close\r\n\r\n",
llm_api_path(), llm_api_host(), s_api_key, MIMI_LLM_API_VERSION, body_len);
}
if (proxy_conn_write(conn, header, hlen) < 0 ||
proxy_conn_write(conn, post_data, body_len) < 0) {
@@ -199,7 +260,7 @@ static esp_err_t llm_http_call(const char *post_data, resp_buf_t *rb, int *out_s
/* ── Parse text from JSON response ────────────────────────────── */
static void extract_text(cJSON *root, char *buf, size_t size)
static void extract_text_anthropic(cJSON *root, char *buf, size_t size)
{
buf[0] = '\0';
cJSON *content = cJSON_GetObjectItem(root, "content");
@@ -220,6 +281,190 @@ static void extract_text(cJSON *root, char *buf, size_t size)
buf[off] = '\0';
}
static void extract_text_openai(cJSON *root, char *buf, size_t size)
{
buf[0] = '\0';
cJSON *choices = cJSON_GetObjectItem(root, "choices");
if (!choices || !cJSON_IsArray(choices)) return;
cJSON *choice0 = cJSON_GetArrayItem(choices, 0);
if (!choice0) return;
cJSON *message = cJSON_GetObjectItem(choice0, "message");
if (!message) return;
cJSON *content = cJSON_GetObjectItem(message, "content");
if (!content || !cJSON_IsString(content)) return;
strncpy(buf, content->valuestring, size - 1);
buf[size - 1] = '\0';
}
static cJSON *convert_tools_openai(const char *tools_json)
{
if (!tools_json) return NULL;
cJSON *arr = cJSON_Parse(tools_json);
if (!arr || !cJSON_IsArray(arr)) {
cJSON_Delete(arr);
return NULL;
}
cJSON *out = cJSON_CreateArray();
cJSON *tool;
cJSON_ArrayForEach(tool, arr) {
cJSON *name = cJSON_GetObjectItem(tool, "name");
cJSON *desc = cJSON_GetObjectItem(tool, "description");
cJSON *schema = cJSON_GetObjectItem(tool, "input_schema");
if (!name || !cJSON_IsString(name)) continue;
cJSON *func = cJSON_CreateObject();
cJSON_AddStringToObject(func, "name", name->valuestring);
if (desc && cJSON_IsString(desc)) {
cJSON_AddStringToObject(func, "description", desc->valuestring);
}
if (schema) {
cJSON_AddItemToObject(func, "parameters", cJSON_Duplicate(schema, 1));
}
cJSON *wrap = cJSON_CreateObject();
cJSON_AddStringToObject(wrap, "type", "function");
cJSON_AddItemToObject(wrap, "function", func);
cJSON_AddItemToArray(out, wrap);
}
cJSON_Delete(arr);
return out;
}
static cJSON *convert_messages_openai(const char *system_prompt, cJSON *messages)
{
cJSON *out = cJSON_CreateArray();
if (system_prompt && system_prompt[0]) {
cJSON *sys = cJSON_CreateObject();
cJSON_AddStringToObject(sys, "role", "system");
cJSON_AddStringToObject(sys, "content", system_prompt);
cJSON_AddItemToArray(out, sys);
}
if (!messages || !cJSON_IsArray(messages)) return out;
cJSON *msg;
cJSON_ArrayForEach(msg, messages) {
cJSON *role = cJSON_GetObjectItem(msg, "role");
cJSON *content = cJSON_GetObjectItem(msg, "content");
if (!role || !cJSON_IsString(role)) continue;
if (content && cJSON_IsString(content)) {
cJSON *m = cJSON_CreateObject();
cJSON_AddStringToObject(m, "role", role->valuestring);
cJSON_AddStringToObject(m, "content", content->valuestring);
cJSON_AddItemToArray(out, m);
continue;
}
if (!content || !cJSON_IsArray(content)) continue;
if (strcmp(role->valuestring, "assistant") == 0) {
cJSON *m = cJSON_CreateObject();
cJSON_AddStringToObject(m, "role", "assistant");
/* collect text */
char *text_buf = NULL;
size_t off = 0;
cJSON *block;
cJSON *tool_calls = NULL;
cJSON_ArrayForEach(block, content) {
cJSON *btype = cJSON_GetObjectItem(block, "type");
if (btype && cJSON_IsString(btype) && strcmp(btype->valuestring, "text") == 0) {
cJSON *text = cJSON_GetObjectItem(block, "text");
if (text && cJSON_IsString(text)) {
size_t tlen = strlen(text->valuestring);
char *tmp = realloc(text_buf, off + tlen + 1);
if (tmp) {
text_buf = tmp;
memcpy(text_buf + off, text->valuestring, tlen);
off += tlen;
text_buf[off] = '\0';
}
}
} else if (btype && cJSON_IsString(btype) && strcmp(btype->valuestring, "tool_use") == 0) {
if (!tool_calls) tool_calls = cJSON_CreateArray();
cJSON *id = cJSON_GetObjectItem(block, "id");
cJSON *name = cJSON_GetObjectItem(block, "name");
cJSON *input = cJSON_GetObjectItem(block, "input");
if (!name || !cJSON_IsString(name)) continue;
cJSON *tc = cJSON_CreateObject();
if (id && cJSON_IsString(id)) {
cJSON_AddStringToObject(tc, "id", id->valuestring);
}
cJSON_AddStringToObject(tc, "type", "function");
cJSON *func = cJSON_CreateObject();
cJSON_AddStringToObject(func, "name", name->valuestring);
if (input) {
char *args = cJSON_PrintUnformatted(input);
if (args) {
cJSON_AddStringToObject(func, "arguments", args);
free(args);
}
}
cJSON_AddItemToObject(tc, "function", func);
cJSON_AddItemToArray(tool_calls, tc);
}
}
if (text_buf) {
cJSON_AddStringToObject(m, "content", text_buf);
} else {
cJSON_AddStringToObject(m, "content", "");
}
if (tool_calls) {
cJSON_AddItemToObject(m, "tool_calls", tool_calls);
}
cJSON_AddItemToArray(out, m);
free(text_buf);
} else if (strcmp(role->valuestring, "user") == 0) {
/* tool_result blocks become role=tool */
cJSON *block;
bool has_user_text = false;
char *text_buf = NULL;
size_t off = 0;
cJSON_ArrayForEach(block, content) {
cJSON *btype = cJSON_GetObjectItem(block, "type");
if (btype && cJSON_IsString(btype) && strcmp(btype->valuestring, "tool_result") == 0) {
cJSON *tool_id = cJSON_GetObjectItem(block, "tool_use_id");
cJSON *tcontent = cJSON_GetObjectItem(block, "content");
if (!tool_id || !cJSON_IsString(tool_id)) continue;
cJSON *tm = cJSON_CreateObject();
cJSON_AddStringToObject(tm, "role", "tool");
cJSON_AddStringToObject(tm, "tool_call_id", tool_id->valuestring);
if (tcontent && cJSON_IsString(tcontent)) {
cJSON_AddStringToObject(tm, "content", tcontent->valuestring);
} else {
cJSON_AddStringToObject(tm, "content", "");
}
cJSON_AddItemToArray(out, tm);
} else if (btype && cJSON_IsString(btype) && strcmp(btype->valuestring, "text") == 0) {
cJSON *text = cJSON_GetObjectItem(block, "text");
if (text && cJSON_IsString(text)) {
size_t tlen = strlen(text->valuestring);
char *tmp = realloc(text_buf, off + tlen + 1);
if (tmp) {
text_buf = tmp;
memcpy(text_buf + off, text->valuestring, tlen);
off += tlen;
text_buf[off] = '\0';
}
has_user_text = true;
}
}
}
if (has_user_text) {
cJSON *um = cJSON_CreateObject();
cJSON_AddStringToObject(um, "role", "user");
cJSON_AddStringToObject(um, "content", text_buf);
cJSON_AddItemToArray(out, um);
}
free(text_buf);
}
}
return out;
}
/* ── Public: simple chat (backward compat) ────────────────────── */
esp_err_t llm_chat(const char *system_prompt, const char *messages_json,
@@ -234,18 +479,32 @@ esp_err_t llm_chat(const char *system_prompt, const char *messages_json,
cJSON *body = cJSON_CreateObject();
cJSON_AddStringToObject(body, "model", s_model);
cJSON_AddNumberToObject(body, "max_tokens", MIMI_LLM_MAX_TOKENS);
cJSON_AddStringToObject(body, "system", system_prompt);
cJSON *messages = cJSON_Parse(messages_json);
if (messages) {
cJSON_AddItemToObject(body, "messages", messages);
if (provider_is_openai()) {
cJSON *messages = cJSON_Parse(messages_json);
if (!messages) {
messages = cJSON_CreateArray();
cJSON *msg = cJSON_CreateObject();
cJSON_AddStringToObject(msg, "role", "user");
cJSON_AddStringToObject(msg, "content", messages_json);
cJSON_AddItemToArray(messages, msg);
}
cJSON *openai_msgs = convert_messages_openai(system_prompt, messages);
cJSON_Delete(messages);
cJSON_AddItemToObject(body, "messages", openai_msgs);
} else {
cJSON *arr = cJSON_CreateArray();
cJSON *msg = cJSON_CreateObject();
cJSON_AddStringToObject(msg, "role", "user");
cJSON_AddStringToObject(msg, "content", messages_json);
cJSON_AddItemToArray(arr, msg);
cJSON_AddItemToObject(body, "messages", arr);
cJSON_AddStringToObject(body, "system", system_prompt);
cJSON *messages = cJSON_Parse(messages_json);
if (messages) {
cJSON_AddItemToObject(body, "messages", messages);
} else {
cJSON *arr = cJSON_CreateArray();
cJSON *msg = cJSON_CreateObject();
cJSON_AddStringToObject(msg, "role", "user");
cJSON_AddStringToObject(msg, "content", messages_json);
cJSON_AddItemToArray(arr, msg);
cJSON_AddItemToObject(body, "messages", arr);
}
}
char *post_data = cJSON_PrintUnformatted(body);
@@ -255,8 +514,8 @@ esp_err_t llm_chat(const char *system_prompt, const char *messages_json,
return ESP_ERR_NO_MEM;
}
ESP_LOGI(TAG, "Calling Claude API (model: %s, body: %d bytes)",
s_model, (int)strlen(post_data));
ESP_LOGI(TAG, "Calling LLM API (provider: %s, model: %s, body: %d bytes)",
s_provider, s_model, (int)strlen(post_data));
resp_buf_t rb;
if (resp_buf_init(&rb, MIMI_LLM_STREAM_BUF_SIZE) != ESP_OK) {
@@ -294,13 +553,17 @@ esp_err_t llm_chat(const char *system_prompt, const char *messages_json,
return ESP_FAIL;
}
extract_text(root, response_buf, buf_size);
if (provider_is_openai()) {
extract_text_openai(root, response_buf, buf_size);
} else {
extract_text_anthropic(root, response_buf, buf_size);
}
cJSON_Delete(root);
if (response_buf[0] == '\0') {
snprintf(response_buf, buf_size, "No response from Claude API");
snprintf(response_buf, buf_size, "No response from LLM API");
} else {
ESP_LOGI(TAG, "Claude response: %d bytes", (int)strlen(response_buf));
ESP_LOGI(TAG, "LLM response: %d bytes", (int)strlen(response_buf));
}
return ESP_OK;
@@ -334,17 +597,31 @@ esp_err_t llm_chat_tools(const char *system_prompt,
cJSON *body = cJSON_CreateObject();
cJSON_AddStringToObject(body, "model", s_model);
cJSON_AddNumberToObject(body, "max_tokens", MIMI_LLM_MAX_TOKENS);
cJSON_AddStringToObject(body, "system", system_prompt);
/* Deep-copy messages so caller keeps ownership */
cJSON *msgs_copy = cJSON_Duplicate(messages, 1);
cJSON_AddItemToObject(body, "messages", msgs_copy);
if (provider_is_openai()) {
cJSON *openai_msgs = convert_messages_openai(system_prompt, messages);
cJSON_AddItemToObject(body, "messages", openai_msgs);
/* Add tools array if provided */
if (tools_json) {
cJSON *tools = cJSON_Parse(tools_json);
if (tools) {
cJSON_AddItemToObject(body, "tools", tools);
if (tools_json) {
cJSON *tools = convert_tools_openai(tools_json);
if (tools) {
cJSON_AddItemToObject(body, "tools", tools);
cJSON_AddStringToObject(body, "tool_choice", "auto");
}
}
} else {
cJSON_AddStringToObject(body, "system", system_prompt);
/* Deep-copy messages so caller keeps ownership */
cJSON *msgs_copy = cJSON_Duplicate(messages, 1);
cJSON_AddItemToObject(body, "messages", msgs_copy);
/* Add tools array if provided */
if (tools_json) {
cJSON *tools = cJSON_Parse(tools_json);
if (tools) {
cJSON_AddItemToObject(body, "tools", tools);
}
}
}
@@ -352,8 +629,8 @@ esp_err_t llm_chat_tools(const char *system_prompt,
cJSON_Delete(body);
if (!post_data) return ESP_ERR_NO_MEM;
ESP_LOGI(TAG, "Calling Claude API with tools (model: %s, body: %d bytes)",
s_model, (int)strlen(post_data));
ESP_LOGI(TAG, "Calling LLM API with tools (provider: %s, model: %s, body: %d bytes)",
s_provider, s_model, (int)strlen(post_data));
/* HTTP call */
resp_buf_t rb;
@@ -387,73 +664,128 @@ esp_err_t llm_chat_tools(const char *system_prompt,
return ESP_FAIL;
}
/* stop_reason */
cJSON *stop_reason = cJSON_GetObjectItem(root, "stop_reason");
if (stop_reason && cJSON_IsString(stop_reason)) {
resp->tool_use = (strcmp(stop_reason->valuestring, "tool_use") == 0);
}
if (provider_is_openai()) {
cJSON *choices = cJSON_GetObjectItem(root, "choices");
cJSON *choice0 = choices && cJSON_IsArray(choices) ? cJSON_GetArrayItem(choices, 0) : NULL;
if (choice0) {
cJSON *finish = cJSON_GetObjectItem(choice0, "finish_reason");
if (finish && cJSON_IsString(finish)) {
resp->tool_use = (strcmp(finish->valuestring, "tool_calls") == 0);
}
/* Iterate content blocks */
cJSON *content = cJSON_GetObjectItem(root, "content");
if (content && cJSON_IsArray(content)) {
/* Accumulate total text length first */
size_t total_text = 0;
cJSON *block;
cJSON_ArrayForEach(block, content) {
cJSON *btype = cJSON_GetObjectItem(block, "type");
if (btype && strcmp(btype->valuestring, "text") == 0) {
cJSON *text = cJSON_GetObjectItem(block, "text");
if (text && cJSON_IsString(text)) {
total_text += strlen(text->valuestring);
cJSON *message = cJSON_GetObjectItem(choice0, "message");
if (message) {
cJSON *content = cJSON_GetObjectItem(message, "content");
if (content && cJSON_IsString(content)) {
size_t tlen = strlen(content->valuestring);
resp->text = calloc(1, tlen + 1);
if (resp->text) {
memcpy(resp->text, content->valuestring, tlen);
resp->text_len = tlen;
}
}
cJSON *tool_calls = cJSON_GetObjectItem(message, "tool_calls");
if (tool_calls && cJSON_IsArray(tool_calls)) {
cJSON *tc;
cJSON_ArrayForEach(tc, tool_calls) {
if (resp->call_count >= MIMI_MAX_TOOL_CALLS) break;
llm_tool_call_t *call = &resp->calls[resp->call_count];
cJSON *id = cJSON_GetObjectItem(tc, "id");
cJSON *func = cJSON_GetObjectItem(tc, "function");
if (id && cJSON_IsString(id)) {
strncpy(call->id, id->valuestring, sizeof(call->id) - 1);
}
if (func) {
cJSON *name = cJSON_GetObjectItem(func, "name");
cJSON *args = cJSON_GetObjectItem(func, "arguments");
if (name && cJSON_IsString(name)) {
strncpy(call->name, name->valuestring, sizeof(call->name) - 1);
}
if (args && cJSON_IsString(args)) {
call->input = strdup(args->valuestring);
if (call->input) {
call->input_len = strlen(call->input);
}
}
}
resp->call_count++;
}
if (resp->call_count > 0) {
resp->tool_use = true;
}
}
}
}
} else {
/* stop_reason */
cJSON *stop_reason = cJSON_GetObjectItem(root, "stop_reason");
if (stop_reason && cJSON_IsString(stop_reason)) {
resp->tool_use = (strcmp(stop_reason->valuestring, "tool_use") == 0);
}
/* Allocate and copy text */
if (total_text > 0) {
resp->text = calloc(1, total_text + 1);
if (resp->text) {
cJSON_ArrayForEach(block, content) {
cJSON *btype = cJSON_GetObjectItem(block, "type");
if (!btype || strcmp(btype->valuestring, "text") != 0) continue;
/* Iterate content blocks */
cJSON *content = cJSON_GetObjectItem(root, "content");
if (content && cJSON_IsArray(content)) {
/* Accumulate total text length first */
size_t total_text = 0;
cJSON *block;
cJSON_ArrayForEach(block, content) {
cJSON *btype = cJSON_GetObjectItem(block, "type");
if (btype && strcmp(btype->valuestring, "text") == 0) {
cJSON *text = cJSON_GetObjectItem(block, "text");
if (!text || !cJSON_IsString(text)) continue;
size_t tlen = strlen(text->valuestring);
memcpy(resp->text + resp->text_len, text->valuestring, tlen);
resp->text_len += tlen;
}
resp->text[resp->text_len] = '\0';
}
}
/* Extract tool_use blocks */
cJSON_ArrayForEach(block, content) {
cJSON *btype = cJSON_GetObjectItem(block, "type");
if (!btype || strcmp(btype->valuestring, "tool_use") != 0) continue;
if (resp->call_count >= MIMI_MAX_TOOL_CALLS) break;
llm_tool_call_t *call = &resp->calls[resp->call_count];
cJSON *id = cJSON_GetObjectItem(block, "id");
if (id && cJSON_IsString(id)) {
strncpy(call->id, id->valuestring, sizeof(call->id) - 1);
}
cJSON *name = cJSON_GetObjectItem(block, "name");
if (name && cJSON_IsString(name)) {
strncpy(call->name, name->valuestring, sizeof(call->name) - 1);
}
cJSON *input = cJSON_GetObjectItem(block, "input");
if (input) {
char *input_str = cJSON_PrintUnformatted(input);
if (input_str) {
call->input = input_str;
call->input_len = strlen(input_str);
if (text && cJSON_IsString(text)) {
total_text += strlen(text->valuestring);
}
}
}
resp->call_count++;
/* Allocate and copy text */
if (total_text > 0) {
resp->text = calloc(1, total_text + 1);
if (resp->text) {
cJSON_ArrayForEach(block, content) {
cJSON *btype = cJSON_GetObjectItem(block, "type");
if (!btype || strcmp(btype->valuestring, "text") != 0) continue;
cJSON *text = cJSON_GetObjectItem(block, "text");
if (!text || !cJSON_IsString(text)) continue;
size_t tlen = strlen(text->valuestring);
memcpy(resp->text + resp->text_len, text->valuestring, tlen);
resp->text_len += tlen;
}
resp->text[resp->text_len] = '\0';
}
}
/* Extract tool_use blocks */
cJSON_ArrayForEach(block, content) {
cJSON *btype = cJSON_GetObjectItem(block, "type");
if (!btype || strcmp(btype->valuestring, "tool_use") != 0) continue;
if (resp->call_count >= MIMI_MAX_TOOL_CALLS) break;
llm_tool_call_t *call = &resp->calls[resp->call_count];
cJSON *id = cJSON_GetObjectItem(block, "id");
if (id && cJSON_IsString(id)) {
strncpy(call->id, id->valuestring, sizeof(call->id) - 1);
}
cJSON *name = cJSON_GetObjectItem(block, "name");
if (name && cJSON_IsString(name)) {
strncpy(call->name, name->valuestring, sizeof(call->name) - 1);
}
cJSON *input = cJSON_GetObjectItem(block, "input");
if (input) {
char *input_str = cJSON_PrintUnformatted(input);
if (input_str) {
call->input = input_str;
call->input_len = strlen(input_str);
}
}
resp->call_count++;
}
}
}
@@ -476,7 +808,7 @@ esp_err_t llm_set_api_key(const char *api_key)
ESP_ERROR_CHECK(nvs_commit(nvs));
nvs_close(nvs);
strncpy(s_api_key, api_key, sizeof(s_api_key) - 1);
safe_copy(s_api_key, sizeof(s_api_key), api_key);
ESP_LOGI(TAG, "API key saved");
return ESP_OK;
}
@@ -489,7 +821,20 @@ esp_err_t llm_set_model(const char *model)
ESP_ERROR_CHECK(nvs_commit(nvs));
nvs_close(nvs);
strncpy(s_model, model, sizeof(s_model) - 1);
safe_copy(s_model, sizeof(s_model), model);
ESP_LOGI(TAG, "Model set to: %s", s_model);
return ESP_OK;
}
esp_err_t llm_set_provider(const char *provider)
{
nvs_handle_t nvs;
ESP_ERROR_CHECK(nvs_open(MIMI_NVS_LLM, NVS_READWRITE, &nvs));
ESP_ERROR_CHECK(nvs_set_str(nvs, MIMI_NVS_KEY_PROVIDER, provider));
ESP_ERROR_CHECK(nvs_commit(nvs));
nvs_close(nvs);
safe_copy(s_provider, sizeof(s_provider), provider);
ESP_LOGI(TAG, "Provider set to: %s", s_provider);
return ESP_OK;
}