* Copyright (C) 2026 Xiaomi Corporation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
* This file contains code derived from MimiClaw (https://github.com/memovai/mimiclaw)
* Copyright (c) 2026 Ziboyan Wang, licensed under the MIT License.
* See NOTICE file for the original MIT License terms.
*/
* llm_parse.c — Tool call parsing and response format conversion.
*
* Extracted from llm_proxy.c to isolate parsing logic:
* - build_openai_tools_array() — internal to OpenAI format
* - parse_xml_tool_calls() — XML fallback (Format 1)
* - parse_ns_xml_tool_calls() — namespaced XML (Format 2)
* - extract_openai_tool_calls() — OpenAI format extraction
* - llm_response_free() — response cleanup
*/
#include "llm/llm_parse.h"
#include "llm/llm_proxy.h"
#include "agent_compat.h"
#include "agent_config.h"
#include "cJSON.h"
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
static const char* TAG = "llm_parse";
cJSON* build_openai_tools_array(const char* tools_json)
{
if (!tools_json) {
return NULL;
}
cJSON* tools_spec = cJSON_Parse(tools_json);
if (!tools_spec || !cJSON_IsArray(tools_spec)) {
cJSON_Delete(tools_spec);
return NULL;
}
cJSON* tools_arr = cJSON_CreateArray();
cJSON* tool_def;
cJSON_ArrayForEach(tool_def, tools_spec)
{
cJSON* wrapper = cJSON_CreateObject();
cJSON_AddStringToObject(wrapper, "type", "function");
cJSON* func = cJSON_CreateObject();
cJSON* name = cJSON_GetObjectItem(tool_def, "name");
cJSON* desc = cJSON_GetObjectItem(tool_def, "description");
cJSON* schema = cJSON_GetObjectItem(tool_def, "input_schema");
if (name && cJSON_IsString(name)) {
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));
} else {
cJSON* empty = cJSON_CreateObject();
cJSON_AddStringToObject(empty, "type", "object");
cJSON_AddItemToObject(empty, "properties",
cJSON_CreateObject());
cJSON_AddItemToObject(func, "parameters", empty);
}
cJSON_AddItemToObject(wrapper, "function", func);
cJSON_AddItemToArray(tools_arr, wrapper);
}
cJSON_Delete(tools_spec);
return tools_arr;
}
* </parameter> </function> </tool_call> */
void parse_xml_tool_calls(llm_response_t* resp)
{
if (resp->call_count > 0 || !resp->text) {
return;
}
if (!strstr(resp->text, "<tool_call>")) {
return;
}
const char* p = resp->text;
while (resp->call_count < AGENT_MAX_TOOL_CALLS) {
const char* tc_start = strstr(p, "<tool_call>");
const char* tc_end = strstr(p, "</tool_call>");
if (!tc_start || !tc_end || tc_end <= tc_start) {
break;
}
const char* fn_start = strstr(tc_start, "<function=");
const char* fn_close = fn_start
? strchr(fn_start + 10, '>')
: NULL;
if (!fn_start || !fn_close || fn_start > tc_end) {
p = tc_end + 12;
continue;
}
llm_tool_call_t* call = &resp->calls[resp->call_count];
size_t name_len = (size_t)(fn_close - (fn_start + 10));
if (name_len >= sizeof(call->name)) {
name_len = sizeof(call->name) - 1;
}
memcpy(call->name, fn_start + 10, name_len);
call->name[name_len] = '\0';
snprintf(call->id, sizeof(call->id), "xml_%d",
resp->call_count);
cJSON* args = cJSON_CreateObject();
const char* pp = fn_close + 1;
while (pp < tc_end) {
const char* ps = strstr(pp, "<parameter=");
if (!ps || ps >= tc_end) {
break;
}
const char* ke = strchr(ps + 11, '>');
if (!ke || ke >= tc_end) {
break;
}
char key[64];
size_t klen = (size_t)(ke - (ps + 11));
if (klen >= sizeof(key)) {
klen = sizeof(key) - 1;
}
memcpy(key, ps + 11, klen);
key[klen] = '\0';
const char* vs = ke + 1;
const char* ve = strstr(vs, "</parameter>");
if (!ve || ve > tc_end) {
break;
}
size_t vlen = (size_t)(ve - vs);
char* val = calloc(1, vlen + 1);
if (val) {
memcpy(val, vs, vlen);
cJSON_AddStringToObject(args, key, val);
free(val);
}
pp = ve + 12;
}
char* args_str = cJSON_PrintUnformatted(args);
cJSON_Delete(args);
if (args_str) {
call->input = args_str;
call->input_len = strlen(args_str);
}
resp->call_count++;
p = tc_end + 12;
}
if (resp->call_count > 0) {
resp->tool_use = true;
free(resp->text);
resp->text = NULL;
resp->text_len = 0;
syslog(LOG_WARNING,
"[%s] Parsed %d tool call(s) from XML fallback\n",
TAG, resp->call_count);
}
}
* <parameter name="KEY">VAL</parameter> </invoke>
* </PREFIX:tool_call> */
void parse_ns_xml_tool_calls(llm_response_t* resp)
{
if (resp->call_count > 0 || !resp->text) {
return;
}
if (!strstr(resp->text, ":tool_call>")) {
return;
}
const char* p = resp->text;
while (resp->call_count < AGENT_MAX_TOOL_CALLS) {
const char* colon = strstr(p, ":tool_call>");
if (!colon) {
break;
}
const char* tc_start = colon;
while (tc_start > p && *(tc_start - 1) != '<') {
tc_start--;
}
if (tc_start <= p || *(tc_start - 1) != '<') {
break;
}
tc_start--;
size_t prefix_len = (size_t)(colon - (tc_start + 1));
if (prefix_len == 0 || prefix_len > 31) {
p = colon + 11;
continue;
}
char prefix[32];
memcpy(prefix, tc_start + 1, prefix_len);
prefix[prefix_len] = '\0';
char close_tag[64];
snprintf(close_tag, sizeof(close_tag),
"</%s:tool_call>", prefix);
const char* tc_end = strstr(colon, close_tag);
if (!tc_end || tc_end <= tc_start) {
p = colon + 11;
continue;
}
const char* inv = strstr(tc_start, "<invoke name=\"");
if (!inv || inv > tc_end) {
p = tc_end + strlen(close_tag);
continue;
}
const char* ns = inv + 14;
const char* ne = strchr(ns, '"');
if (!ne || ne > tc_end) {
p = tc_end + strlen(close_tag);
continue;
}
llm_tool_call_t* call = &resp->calls[resp->call_count];
size_t name_len = (size_t)(ne - ns);
if (name_len >= sizeof(call->name)) {
name_len = sizeof(call->name) - 1;
}
memcpy(call->name, ns, name_len);
call->name[name_len] = '\0';
snprintf(call->id, sizeof(call->id), "nsxml_%d",
resp->call_count);
cJSON* args = cJSON_CreateObject();
const char* pp = ne;
while (pp < tc_end) {
const char* ps = strstr(pp, "<parameter name=\"");
if (!ps || ps >= tc_end) {
break;
}
const char* ks = ps + 17;
const char* ke = strchr(ks, '"');
if (!ke || ke >= tc_end) {
break;
}
const char* tc = strchr(ke, '>');
if (!tc || tc >= tc_end) {
break;
}
char key[64];
size_t klen = (size_t)(ke - ks);
if (klen >= sizeof(key)) {
klen = sizeof(key) - 1;
}
memcpy(key, ks, klen);
key[klen] = '\0';
const char* vs = tc + 1;
const char* ve = strstr(vs, "</parameter>");
if (!ve || ve > tc_end) {
break;
}
size_t vlen = (size_t)(ve - vs);
char* val = calloc(1, vlen + 1);
if (val) {
memcpy(val, vs, vlen);
cJSON_AddStringToObject(args, key, val);
free(val);
}
pp = ve + 12;
}
char* args_str = cJSON_PrintUnformatted(args);
cJSON_Delete(args);
if (args_str) {
call->input = args_str;
call->input_len = strlen(args_str);
}
resp->call_count++;
p = tc_end + strlen(close_tag);
}
if (resp->call_count > 0) {
resp->tool_use = true;
free(resp->text);
resp->text = NULL;
resp->text_len = 0;
syslog(LOG_WARNING,
"[%s] Parsed %d tool call(s) from namespaced XML\n",
TAG, resp->call_count);
}
}
void extract_openai_tool_calls(cJSON* message, llm_response_t* resp)
{
cJSON* text_content = cJSON_GetObjectItem(message, "content");
if (text_content && cJSON_IsString(text_content)
&& text_content->valuestring) {
size_t tlen = strlen(text_content->valuestring);
resp->text = calloc(1, tlen + 1);
if (resp->text) {
memcpy(resp->text, text_content->valuestring, tlen);
resp->text_len = tlen;
}
}
cJSON* rc = cJSON_GetObjectItem(message, "reasoning_content");
if (rc && cJSON_IsString(rc) && rc->valuestring
&& rc->valuestring[0]) {
resp->reasoning_content = strdup(rc->valuestring);
}
cJSON* tool_calls = cJSON_GetObjectItem(message, "tool_calls");
if (!tool_calls || !cJSON_IsArray(tool_calls)) {
return;
}
cJSON* tc;
cJSON_ArrayForEach(tc, tool_calls)
{
if (resp->call_count >= AGENT_MAX_TOOL_CALLS) {
break;
}
llm_tool_call_t* call = &resp->calls[resp->call_count];
cJSON* id_item = cJSON_GetObjectItem(tc, "id");
if (id_item && cJSON_IsString(id_item)) {
strncpy(call->id, id_item->valuestring,
sizeof(call->id) - 1);
}
cJSON* func_obj = cJSON_GetObjectItem(tc, "function");
if (func_obj) {
cJSON* n = cJSON_GetObjectItem(func_obj, "name");
cJSON* a = cJSON_GetObjectItem(func_obj, "arguments");
if (n && cJSON_IsString(n)) {
strncpy(call->name, n->valuestring,
sizeof(call->name) - 1);
}
if (a && cJSON_IsString(a)) {
size_t alen = strlen(a->valuestring);
call->input = calloc(1, alen + 1);
if (call->input) {
memcpy(call->input, a->valuestring, alen);
call->input_len = alen;
}
}
}
resp->call_count++;
}
if (resp->call_count > 0) {
resp->tool_use = true;
}
}
void llm_response_free(llm_response_t* resp)
{
free(resp->text);
resp->text = NULL;
resp->text_len = 0;
free(resp->reasoning_content);
resp->reasoning_content = NULL;
for (int i = 0; i < resp->call_count; i++) {
free(resp->calls[i].input);
resp->calls[i].input = NULL;
}
resp->call_count = 0;
resp->tool_use = false;
}