2026-01-28 19:34:39 +01:00
|
|
|
// retoor <retoor@molodetz.nl>
|
|
|
|
|
|
|
|
|
|
#include "agent.h"
|
|
|
|
|
#include "http_client.h"
|
|
|
|
|
#include "r_config.h"
|
|
|
|
|
#include "tool.h"
|
2026-01-28 20:15:02 +01:00
|
|
|
#include "context_manager.h"
|
2026-01-28 19:34:39 +01:00
|
|
|
#include <json-c/json.h>
|
|
|
|
|
#include <stdio.h>
|
|
|
|
|
#include <stdlib.h>
|
|
|
|
|
#include <string.h>
|
|
|
|
|
#include <time.h>
|
|
|
|
|
|
|
|
|
|
struct agent_t {
|
|
|
|
|
char *goal;
|
|
|
|
|
int iteration_count;
|
|
|
|
|
int max_iterations;
|
|
|
|
|
int tool_retry_count;
|
|
|
|
|
int max_tool_retries;
|
|
|
|
|
agent_state_t state;
|
|
|
|
|
time_t start_time;
|
|
|
|
|
char *last_error;
|
|
|
|
|
bool verbose;
|
2026-01-29 06:01:05 +01:00
|
|
|
bool is_subagent;
|
2026-01-28 19:34:39 +01:00
|
|
|
messages_handle messages;
|
|
|
|
|
bool owns_messages;
|
|
|
|
|
http_client_handle http;
|
|
|
|
|
tool_registry_t *tools;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static const char *incomplete_phrases[] = {
|
|
|
|
|
"I'll ", "I will ", "Let me ", "I'm going to ",
|
|
|
|
|
"Next, I", "Now I'll", "Now I will", "I'll now",
|
|
|
|
|
"I need to", "I should", "I can ", "Going to ",
|
|
|
|
|
"Will now", "Proceeding", "Starting to", "About to ",
|
|
|
|
|
"First, I", "Then I", "After that", "Following that",
|
2026-01-28 20:06:18 +01:00
|
|
|
"Now let me", "Let's check", "Let me check",
|
2026-01-29 06:01:05 +01:00
|
|
|
"Would you like", "Should I", "I can also", "I could",
|
|
|
|
|
"Do you want", "Shall I",
|
2026-01-28 19:34:39 +01:00
|
|
|
NULL
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static const char *incomplete_endings[] = {
|
2026-01-29 06:01:05 +01:00
|
|
|
"...", ":", "files:", "content:", "implementation:", "?",
|
|
|
|
|
NULL
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static const char *completion_phrases[] = {
|
|
|
|
|
"task is complete", "task complete", "tasks complete",
|
|
|
|
|
"goal is achieved", "goal achieved",
|
|
|
|
|
"all steps completed", "all steps done",
|
|
|
|
|
"fully completed", "is now complete",
|
|
|
|
|
"has been completed", "have been completed",
|
|
|
|
|
"successfully created", "successfully written",
|
|
|
|
|
"setup is complete", "is ready to use",
|
|
|
|
|
NULL
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static const char *passive_phrases[] = {
|
|
|
|
|
"let me know", "feel free", "if you need", "awaiting",
|
|
|
|
|
"ready for", "standby", "standing by", "happy to help",
|
|
|
|
|
"do not hesitate", "anything else",
|
2026-01-28 19:34:39 +01:00
|
|
|
NULL
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
extern tool_registry_t *tools_get_registry(void);
|
|
|
|
|
|
|
|
|
|
agent_handle agent_create(const char *goal, messages_handle messages) {
|
|
|
|
|
struct agent_t *agent = calloc(1, sizeof(struct agent_t));
|
|
|
|
|
if (!agent) return NULL;
|
|
|
|
|
|
|
|
|
|
if (goal) {
|
|
|
|
|
agent->goal = strdup(goal);
|
|
|
|
|
if (!agent->goal) {
|
|
|
|
|
free(agent);
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
r_config_handle cfg = r_config_get_instance();
|
|
|
|
|
|
|
|
|
|
agent->iteration_count = 0;
|
|
|
|
|
agent->max_iterations = AGENT_MAX_ITERATIONS;
|
|
|
|
|
agent->tool_retry_count = 0;
|
|
|
|
|
agent->max_tool_retries = AGENT_MAX_TOOL_RETRIES;
|
|
|
|
|
agent->state = AGENT_STATE_IDLE;
|
|
|
|
|
agent->start_time = time(NULL);
|
|
|
|
|
agent->verbose = r_config_is_verbose(cfg);
|
|
|
|
|
|
|
|
|
|
if (messages) {
|
|
|
|
|
agent->messages = messages;
|
|
|
|
|
agent->owns_messages = false;
|
|
|
|
|
} else {
|
|
|
|
|
agent->messages = messages_create(r_config_get_session_id(cfg));
|
|
|
|
|
agent->owns_messages = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!agent->messages) {
|
|
|
|
|
free(agent->goal);
|
|
|
|
|
free(agent);
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-29 06:01:05 +01:00
|
|
|
const char *system_msg = r_config_get_system_message(cfg);
|
|
|
|
|
if (!system_msg || !*system_msg) {
|
|
|
|
|
bool has_system = false;
|
|
|
|
|
for (int i = 0; i < messages_count(agent->messages); i++) {
|
|
|
|
|
struct json_object *msg = messages_get_object(agent->messages, i);
|
|
|
|
|
struct json_object *role;
|
|
|
|
|
if (json_object_object_get_ex(msg, "role", &role)) {
|
|
|
|
|
const char *role_str = json_object_get_string(role);
|
|
|
|
|
if (role_str && strcmp(role_str, "system") == 0) {
|
|
|
|
|
has_system = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!has_system) {
|
|
|
|
|
messages_add(agent->messages, "system",
|
|
|
|
|
"You are an autonomous AI agent with full system access through function calling. "
|
|
|
|
|
"You have the spawn_agent tool to create specialized sub-agents for different tasks. "
|
|
|
|
|
"Use spawn_agent extensively for: "
|
|
|
|
|
"- research tasks (researcher persona) "
|
|
|
|
|
"- development tasks (developer persona) "
|
|
|
|
|
"- security audits (security persona) "
|
|
|
|
|
"When web_search returns results with URLs, spawn researcher agents to fetch and analyze the content. "
|
|
|
|
|
"Always break complex tasks into sub-tasks using agents for better orchestration. "
|
|
|
|
|
"Continue iterating until goals are fully achieved, using appropriate tools and agents.");
|
|
|
|
|
}
|
|
|
|
|
} else if (system_msg && *system_msg) {
|
|
|
|
|
bool has_system = false;
|
|
|
|
|
for (int i = 0; i < messages_count(agent->messages); i++) {
|
|
|
|
|
struct json_object *msg = messages_get_object(agent->messages, i);
|
|
|
|
|
struct json_object *role;
|
|
|
|
|
if (json_object_object_get_ex(msg, "role", &role)) {
|
|
|
|
|
const char *role_str = json_object_get_string(role);
|
|
|
|
|
if (role_str && strcmp(role_str, "system") == 0) {
|
|
|
|
|
has_system = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (!has_system) {
|
|
|
|
|
messages_add(agent->messages, "system", system_msg);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-28 19:34:39 +01:00
|
|
|
agent->http = http_client_create(r_config_get_api_key(cfg));
|
|
|
|
|
if (!agent->http) {
|
|
|
|
|
if (agent->owns_messages) {
|
|
|
|
|
messages_destroy(agent->messages);
|
|
|
|
|
}
|
|
|
|
|
free(agent->goal);
|
|
|
|
|
free(agent);
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
agent->tools = tools_get_registry();
|
|
|
|
|
|
|
|
|
|
return agent;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void agent_destroy(agent_handle agent) {
|
|
|
|
|
if (!agent) return;
|
|
|
|
|
if (agent->http) http_client_destroy(agent->http);
|
|
|
|
|
if (agent->messages && agent->owns_messages) messages_destroy(agent->messages);
|
|
|
|
|
free(agent->goal);
|
|
|
|
|
free(agent->last_error);
|
|
|
|
|
free(agent);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void agent_set_max_iterations(agent_handle agent, int max) {
|
|
|
|
|
if (agent) agent->max_iterations = max;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void agent_set_verbose(agent_handle agent, bool verbose) {
|
|
|
|
|
if (agent) agent->verbose = verbose;
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-29 06:01:05 +01:00
|
|
|
void agent_set_is_subagent(agent_handle agent, bool is_subagent) {
|
|
|
|
|
if (agent) agent->is_subagent = is_subagent;
|
|
|
|
|
}
|
|
|
|
|
|
chore: remove entire agent, auth, browse, chat, db_utils, and http_curl source trees
This commit deletes the complete implementation of the agent loop, authentication module, web browsing utilities, chat prompt builder, database helper, and HTTP client. All corresponding header files and test harnesses are also removed. The diff shows the deletion of 6 source files and 6 header files totaling over 1000 lines of code, including the agent state machine, API key resolution logic, curl-based HTTP functions, SQLite database initialization and CRUD operations, and the JSON chat prompt construction.
2026-01-29 02:21:11 +01:00
|
|
|
void agent_set_tool_registry(agent_handle agent, tool_registry_t *registry) {
|
|
|
|
|
if (agent && registry) agent->tools = registry;
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-28 19:34:39 +01:00
|
|
|
agent_state_t agent_get_state(agent_handle agent) {
|
|
|
|
|
return agent ? agent->state : AGENT_STATE_ERROR;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const char *agent_get_error(agent_handle agent) {
|
|
|
|
|
return agent ? agent->last_error : NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int agent_get_iteration_count(agent_handle agent) {
|
|
|
|
|
return agent ? agent->iteration_count : 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void agent_set_error(agent_handle agent, const char *error) {
|
|
|
|
|
if (!agent) return;
|
|
|
|
|
free(agent->last_error);
|
|
|
|
|
agent->last_error = error ? strdup(error) : NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static char *agent_build_request(agent_handle agent, const char *role, const char *message) {
|
|
|
|
|
r_config_handle cfg = r_config_get_instance();
|
|
|
|
|
|
|
|
|
|
struct json_object *root = json_object_new_object();
|
|
|
|
|
if (!root) return NULL;
|
|
|
|
|
|
|
|
|
|
json_object_object_add(root, "model",
|
|
|
|
|
json_object_new_string(r_config_get_model(cfg)));
|
|
|
|
|
|
|
|
|
|
if (role && message) {
|
|
|
|
|
messages_add(agent->messages, role, message);
|
|
|
|
|
if (r_config_use_tools(cfg) && agent->tools) {
|
|
|
|
|
json_object_object_add(root, "tools",
|
|
|
|
|
tool_registry_get_descriptions(agent->tools));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
json_object_object_add(root, "messages",
|
|
|
|
|
json_object_get(messages_to_json(agent->messages)));
|
|
|
|
|
json_object_object_add(root, "temperature",
|
|
|
|
|
json_object_new_double(r_config_get_temperature(cfg)));
|
2026-01-29 06:01:05 +01:00
|
|
|
json_object_object_add(root, "max_tokens",
|
|
|
|
|
json_object_new_int(r_config_get_max_tokens(cfg)));
|
2026-01-28 19:34:39 +01:00
|
|
|
|
|
|
|
|
char *result = strdup(json_object_to_json_string_ext(root, JSON_C_TO_STRING_PRETTY));
|
|
|
|
|
json_object_put(root);
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static struct json_object *agent_process_response(agent_handle agent, const char *json_data) {
|
|
|
|
|
r_config_handle cfg = r_config_get_instance();
|
|
|
|
|
|
|
|
|
|
char *response = NULL;
|
|
|
|
|
r_status_t status = http_post(agent->http, r_config_get_api_url(cfg), json_data, &response);
|
|
|
|
|
|
|
|
|
|
if (status != R_SUCCESS || !response) {
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct json_object *parsed = json_tokener_parse(response);
|
|
|
|
|
free(response);
|
|
|
|
|
|
|
|
|
|
if (!parsed) return NULL;
|
|
|
|
|
|
|
|
|
|
struct json_object *error_obj;
|
|
|
|
|
if (json_object_object_get_ex(parsed, "error", &error_obj)) {
|
|
|
|
|
const char *err_str = json_object_to_json_string(error_obj);
|
2026-01-28 20:15:02 +01:00
|
|
|
|
|
|
|
|
// Smart error detection for context overflow
|
2026-01-29 06:01:05 +01:00
|
|
|
if (strcasestr(err_str, "too long") ||
|
|
|
|
|
strcasestr(err_str, "context_length_exceeded") ||
|
|
|
|
|
strcasestr(err_str, "maximum context length") ||
|
|
|
|
|
strcasestr(err_str, "reduce the length") ||
|
|
|
|
|
strcasestr(err_str, "context limit") ||
|
|
|
|
|
strcasestr(err_str, "Input is too long")) {
|
2026-01-28 20:15:02 +01:00
|
|
|
agent_set_error(agent, "CONTEXT_OVERFLOW");
|
|
|
|
|
} else {
|
|
|
|
|
fprintf(stderr, "API Error: %s\n", err_str);
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-28 19:34:39 +01:00
|
|
|
json_object_put(parsed);
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct json_object *choices;
|
|
|
|
|
if (!json_object_object_get_ex(parsed, "choices", &choices)) {
|
|
|
|
|
json_object_put(parsed);
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct json_object *first_choice = json_object_array_get_idx(choices, 0);
|
|
|
|
|
if (!first_choice) {
|
|
|
|
|
json_object_put(parsed);
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return first_choice;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool agent_has_tool_calls(struct json_object *choice) {
|
|
|
|
|
struct json_object *message_obj;
|
|
|
|
|
if (!json_object_object_get_ex(choice, "message", &message_obj)) return false;
|
|
|
|
|
|
|
|
|
|
struct json_object *tool_calls;
|
|
|
|
|
if (!json_object_object_get_ex(message_obj, "tool_calls", &tool_calls)) return false;
|
|
|
|
|
|
|
|
|
|
return json_object_array_length(tool_calls) > 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static struct json_object *agent_get_tool_calls(struct json_object *choice) {
|
|
|
|
|
struct json_object *message_obj;
|
|
|
|
|
if (!json_object_object_get_ex(choice, "message", &message_obj)) return NULL;
|
|
|
|
|
|
|
|
|
|
struct json_object *tool_calls;
|
|
|
|
|
if (!json_object_object_get_ex(message_obj, "tool_calls", &tool_calls)) return NULL;
|
|
|
|
|
|
|
|
|
|
return tool_calls;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static struct json_object *agent_get_message(struct json_object *choice) {
|
|
|
|
|
struct json_object *message_obj;
|
|
|
|
|
if (json_object_object_get_ex(choice, "message", &message_obj)) {
|
|
|
|
|
return message_obj;
|
|
|
|
|
}
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static char *agent_get_content(struct json_object *choice) {
|
|
|
|
|
struct json_object *message_obj;
|
|
|
|
|
if (!json_object_object_get_ex(choice, "message", &message_obj)) return NULL;
|
|
|
|
|
|
|
|
|
|
struct json_object *content_obj;
|
|
|
|
|
if (!json_object_object_get_ex(message_obj, "content", &content_obj)) return NULL;
|
|
|
|
|
|
|
|
|
|
const char *content = json_object_get_string(content_obj);
|
|
|
|
|
return content ? strdup(content) : NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool agent_response_indicates_incomplete(const char *content) {
|
|
|
|
|
if (!content) return false;
|
|
|
|
|
|
2026-01-29 06:01:05 +01:00
|
|
|
// Check for explicit completion phrases first (Overrides incomplete indicators)
|
|
|
|
|
for (int i = 0; completion_phrases[i]; i++) {
|
|
|
|
|
if (strcasestr(content, completion_phrases[i])) return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check for passive/closing phrases (Overrides incomplete indicators)
|
|
|
|
|
// Example: "I will be here if you need me." -> Contains "I will" but is passive.
|
|
|
|
|
for (int i = 0; passive_phrases[i]; i++) {
|
|
|
|
|
if (strcasestr(content, passive_phrases[i])) return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-28 19:34:39 +01:00
|
|
|
for (int i = 0; incomplete_phrases[i]; i++) {
|
2026-01-29 06:01:05 +01:00
|
|
|
if (strcasestr(content, incomplete_phrases[i])) return true;
|
2026-01-28 19:34:39 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
size_t len = strlen(content);
|
|
|
|
|
if (len > 3) {
|
|
|
|
|
for (int i = 0; incomplete_endings[i]; i++) {
|
|
|
|
|
size_t end_len = strlen(incomplete_endings[i]);
|
|
|
|
|
if (len >= end_len && strcmp(content + len - end_len, incomplete_endings[i]) == 0) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
char *agent_run(agent_handle agent, const char *user_message) {
|
|
|
|
|
if (!agent) return NULL;
|
|
|
|
|
|
|
|
|
|
agent->state = AGENT_STATE_RUNNING;
|
|
|
|
|
agent->iteration_count = 0;
|
|
|
|
|
agent->tool_retry_count = 0;
|
|
|
|
|
|
|
|
|
|
if (!user_message || !*user_message) {
|
|
|
|
|
agent->state = AGENT_STATE_ERROR;
|
|
|
|
|
agent_set_error(agent, "Empty user message");
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
messages_load(agent->messages);
|
|
|
|
|
|
|
|
|
|
char *json_data = agent_build_request(agent, "user", user_message);
|
|
|
|
|
if (!json_data) {
|
|
|
|
|
agent->state = AGENT_STATE_ERROR;
|
|
|
|
|
agent_set_error(agent, "Failed to create chat JSON");
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-28 20:06:18 +01:00
|
|
|
char *accumulated_response = NULL;
|
|
|
|
|
size_t accumulated_len = 0;
|
2026-01-28 19:34:39 +01:00
|
|
|
|
2026-01-28 20:06:18 +01:00
|
|
|
while (agent->state == AGENT_STATE_RUNNING || agent->state == AGENT_STATE_EXECUTING_TOOLS) {
|
2026-01-28 19:34:39 +01:00
|
|
|
agent->iteration_count++;
|
|
|
|
|
|
|
|
|
|
if (agent->iteration_count > agent->max_iterations) {
|
|
|
|
|
agent->state = AGENT_STATE_MAX_ITERATIONS;
|
|
|
|
|
agent_set_error(agent, "Maximum iterations reached");
|
|
|
|
|
if (agent->verbose) {
|
|
|
|
|
fprintf(stderr, "[Agent] Max iterations (%d) reached\n", agent->max_iterations);
|
|
|
|
|
}
|
|
|
|
|
free(json_data);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (agent->verbose) {
|
|
|
|
|
fprintf(stderr, "[Agent] Iteration %d/%d\n",
|
|
|
|
|
agent->iteration_count, agent->max_iterations);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct json_object *choice = agent_process_response(agent, json_data);
|
2026-01-28 20:15:02 +01:00
|
|
|
|
|
|
|
|
if (!choice && agent->last_error && strcmp(agent->last_error, "CONTEXT_OVERFLOW") == 0) {
|
|
|
|
|
if (context_manager_shrink(agent->messages) == R_SUCCESS) {
|
|
|
|
|
// Retry with shrunk history
|
|
|
|
|
free(json_data);
|
|
|
|
|
json_data = agent_build_request(agent, NULL, NULL);
|
|
|
|
|
agent->state = AGENT_STATE_RUNNING;
|
|
|
|
|
// Don't increment iteration_count for retries due to context
|
|
|
|
|
agent->iteration_count--;
|
|
|
|
|
continue;
|
|
|
|
|
} else {
|
|
|
|
|
agent_set_error(agent, "Context limit reached and cannot be shrunk further.");
|
|
|
|
|
free(json_data);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-28 19:34:39 +01:00
|
|
|
free(json_data);
|
|
|
|
|
json_data = NULL;
|
|
|
|
|
|
|
|
|
|
if (!choice) {
|
|
|
|
|
agent->tool_retry_count++;
|
|
|
|
|
if (agent->tool_retry_count >= agent->max_tool_retries) {
|
|
|
|
|
agent->state = AGENT_STATE_ERROR;
|
|
|
|
|
agent_set_error(agent, "API request failed after retries");
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
if (agent->verbose) {
|
|
|
|
|
fprintf(stderr, "[Agent] API error, retry %d/%d\n",
|
|
|
|
|
agent->tool_retry_count, agent->max_tool_retries);
|
|
|
|
|
}
|
|
|
|
|
json_data = agent_build_request(agent, NULL, NULL);
|
2026-01-28 20:06:18 +01:00
|
|
|
agent->state = AGENT_STATE_RUNNING;
|
2026-01-28 19:34:39 +01:00
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
agent->tool_retry_count = 0;
|
|
|
|
|
|
|
|
|
|
struct json_object *message_obj = agent_get_message(choice);
|
|
|
|
|
if (message_obj) {
|
|
|
|
|
messages_add_object(agent->messages, json_object_get(message_obj));
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-28 20:06:18 +01:00
|
|
|
char *content = agent_get_content(choice);
|
|
|
|
|
if (content && *content) {
|
2026-01-29 06:01:05 +01:00
|
|
|
// Print content immediately to the user (only if NOT a sub-agent)
|
|
|
|
|
if (!agent->is_subagent) {
|
|
|
|
|
extern void parse_markdown_to_ansi(const char *content);
|
|
|
|
|
parse_markdown_to_ansi(content);
|
|
|
|
|
printf("\n");
|
|
|
|
|
}
|
2026-01-28 20:06:18 +01:00
|
|
|
|
|
|
|
|
size_t content_len = strlen(content);
|
|
|
|
|
char *new_acc = realloc(accumulated_response, accumulated_len + content_len + 2);
|
|
|
|
|
if (new_acc) {
|
|
|
|
|
accumulated_response = new_acc;
|
|
|
|
|
if (accumulated_len > 0) {
|
|
|
|
|
strcat(accumulated_response, "\n");
|
|
|
|
|
accumulated_len += 1;
|
|
|
|
|
}
|
|
|
|
|
strcpy(accumulated_response + accumulated_len, content);
|
|
|
|
|
accumulated_len += content_len;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-28 19:34:39 +01:00
|
|
|
bool has_tools = agent_has_tool_calls(choice);
|
|
|
|
|
|
|
|
|
|
if (agent->verbose) {
|
|
|
|
|
fprintf(stderr, "[Agent] has_tool_calls=%s\n", has_tools ? "true" : "false");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (has_tools) {
|
|
|
|
|
agent->state = AGENT_STATE_EXECUTING_TOOLS;
|
|
|
|
|
|
|
|
|
|
struct json_object *tool_calls = agent_get_tool_calls(choice);
|
|
|
|
|
|
|
|
|
|
if (agent->verbose) {
|
|
|
|
|
int num_tools = json_object_array_length(tool_calls);
|
|
|
|
|
fprintf(stderr, "[Agent] Executing %d tool(s)\n", num_tools);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct json_object *results = tool_registry_execute(agent->tools, tool_calls, agent->verbose);
|
|
|
|
|
|
|
|
|
|
int count = json_object_array_length(results);
|
|
|
|
|
for (int i = 0; i < count; i++) {
|
|
|
|
|
struct json_object *result = json_object_array_get_idx(results, i);
|
|
|
|
|
messages_add_tool_call(agent->messages, json_object_get(result));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
agent->state = AGENT_STATE_RUNNING;
|
|
|
|
|
json_data = agent_build_request(agent, NULL, NULL);
|
|
|
|
|
if (!json_data) {
|
|
|
|
|
agent->state = AGENT_STATE_ERROR;
|
|
|
|
|
agent_set_error(agent, "Failed to create follow-up JSON");
|
2026-01-28 20:06:18 +01:00
|
|
|
free(content);
|
2026-01-28 19:34:39 +01:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-28 20:06:18 +01:00
|
|
|
} else if (content && agent_response_indicates_incomplete(content)) {
|
|
|
|
|
if (agent->verbose) {
|
|
|
|
|
fprintf(stderr, "[Agent] Response indicates incomplete work, auto-continuing\n");
|
|
|
|
|
}
|
2026-01-28 19:34:39 +01:00
|
|
|
|
2026-01-28 20:06:18 +01:00
|
|
|
json_data = agent_build_request(agent, "user",
|
|
|
|
|
"Continue. Execute the necessary actions to complete the task.");
|
|
|
|
|
agent->state = AGENT_STATE_RUNNING;
|
|
|
|
|
if (!json_data) {
|
|
|
|
|
agent->state = AGENT_STATE_ERROR;
|
|
|
|
|
agent_set_error(agent, "Failed to create continue JSON");
|
2026-01-28 19:34:39 +01:00
|
|
|
free(content);
|
2026-01-28 20:06:18 +01:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
agent->state = AGENT_STATE_COMPLETED;
|
2026-01-29 06:01:05 +01:00
|
|
|
if (agent->verbose && !agent->is_subagent) {
|
2026-01-28 20:06:18 +01:00
|
|
|
fprintf(stderr, "[Agent] Completed in %d iteration(s)\n",
|
|
|
|
|
agent->iteration_count);
|
2026-01-28 19:34:39 +01:00
|
|
|
}
|
|
|
|
|
}
|
2026-01-28 20:06:18 +01:00
|
|
|
free(content);
|
2026-01-28 19:34:39 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
free(json_data);
|
2026-01-28 20:06:18 +01:00
|
|
|
return accumulated_response;
|
2026-01-28 19:34:39 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
char *agent_chat(const char *user_message, messages_handle messages) {
|
|
|
|
|
agent_handle agent = agent_create(user_message, messages);
|
|
|
|
|
if (!agent) return NULL;
|
|
|
|
|
|
|
|
|
|
char *response = agent_run(agent, user_message);
|
|
|
|
|
|
|
|
|
|
if (agent->verbose && agent->state != AGENT_STATE_COMPLETED && agent->last_error) {
|
|
|
|
|
fprintf(stderr, "[Agent] Error: %s\n", agent->last_error);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
agent_destroy(agent);
|
|
|
|
|
return response;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
char *agent_chat_with_limit(const char *user_message, int max_iterations, messages_handle messages) {
|
|
|
|
|
agent_handle agent = agent_create(user_message, messages);
|
|
|
|
|
if (!agent) return NULL;
|
|
|
|
|
|
|
|
|
|
agent_set_max_iterations(agent, max_iterations);
|
|
|
|
|
char *response = agent_run(agent, user_message);
|
|
|
|
|
|
|
|
|
|
if (agent->verbose && agent->state != AGENT_STATE_COMPLETED && agent->last_error) {
|
|
|
|
|
fprintf(stderr, "[Agent] Error: %s\n", agent->last_error);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
agent_destroy(agent);
|
|
|
|
|
return response;
|
|
|
|
|
}
|