From a8d7015abd6a4e535c3a9004d21c38cccdd8e453 Mon Sep 17 00:00:00 2001 From: retoor Date: Thu, 18 Dec 2025 01:08:38 +0100 Subject: [PATCH] Agent. --- README.md | 88 +++--------- agent.h | 381 ++++++++++++++++++++++++++++++++++++++++++++++++++++ chat.h | 8 +- db_utils.h | 58 ++++++-- http_curl.h | 7 + indexer.h | 51 ++++--- line.h | 31 +++-- main.c | 43 ++++-- messages.h | 23 ++-- openai.h | 8 +- r.h | 2 +- review.md | 40 +++--- tools.h | 155 ++++++++++++++++++--- utils.h | 40 ++++-- 14 files changed, 750 insertions(+), 185 deletions(-) create mode 100644 agent.h diff --git a/README.md b/README.md index 8a1cdb1..ce00f4f 100755 --- a/README.md +++ b/README.md @@ -1,82 +1,36 @@ -# R Vibe Tool +# Henry IRC Server -## Overview - -R Vibe Tool is a powerful Command-Line Interface (CLI) utility designed for Linux environments, offering advanced AI-assisted development capabilities with elegant markdown output. +A simple IRC server implemented in Python as a package named `henry`. ## Features +- Basic IRC commands: NICK, USER, JOIN, PRIVMSG, QUIT +- Supports multiple clients and channels -- **Flexible AI Integration**: Support for multiple AI models including: - - OpenAI GPT-3.5-turbo (default, if you did not set API key) - - Ollama - - Anthropic Claude - - Grok +## Installation -- **Customizable Behavior**: Configure tool behavior through `~/.rcontext.txt` -- **Agent Support**: Intelligent context-aware assistance -- **Markdown Output**: Clean, readable documentation generation +Clone the repository or copy the `henry` package directory. -## Prerequisites +## Usage -- Linux operating system -- Configured AI model access +Run the IRC server using the CLI entry point: -## Configuration - -### Environment Variables - -TIP: create bash files containg these variables and make them easily accessable. For example by symlinking them to `~/.bash_aliases` or `~/.bash_profile`. Or even easier, make them executable and put them in /usr/local/bin. - -#### Ollama Configuration ```bash -export R_MODEL="qwen2.5:3b" -export R_BASE_URL="https://ollama.molodetz.nl" -./r +python -m henry.cli ``` -#### Claude Configuration +The server listens on `127.0.0.1:6667` by default. + +## Connecting + +Use any IRC client to connect to `localhost` on port `6667`. + +## Example + ```bash -export R_MODEL="claude-3-5-haiku-20241022" -export R_BASE_URL="https://api.anthropic.com" -export R_KEY="sk-ant-[your-key]" -./r +# Using irssi client +irssi -c 127.0.0.1 -p 6667 ``` -#### OpenAI Configuration -```bash -export R_MODEL="gpt-4o-mini" -export R_BASE_URL="https://api.openai.com" -export R_KEY="sk-[your-key]" -./r -``` - -#### Grok Configuration -```bash -export R_MODEL="grok-2" -export R_BASE_URL="https://api.x.ai" -export R_USE_STRICT=false -export R_KEY="xai-gfh" -./r -``` - -## Best Practices - -1. Use `~/.rcontext.txt` to define specific behavioral instructions -2. Include file saving steps in your context instructions -3. Use the `index` tool to initialize only source files, excluding environment and node_modules - -## Example Project - -For a comprehensive example of R Vibe Tool in action, visit: -[Streamii Project](https://molodetz.nl/projects/streamii/README.md.html) - -## Limitations - -- OpenAI key usage is temporary and limited -- Performance may vary depending on the selected AI model - -## Support - -For issues or contributions, please refer to the project repository. - +## License +MIT diff --git a/agent.h b/agent.h new file mode 100644 index 0000000..649d443 --- /dev/null +++ b/agent.h @@ -0,0 +1,381 @@ +// retoor + +#ifndef R_AGENT_H +#define R_AGENT_H + +#include "chat.h" +#include "http_curl.h" +#include "messages.h" +#include "r.h" +#include "tools.h" +#include +#include +#include + +#define AGENT_MAX_ITERATIONS 300 +#define AGENT_MAX_TOOL_RETRIES 3 + +typedef enum { + AGENT_STATUS_RUNNING, + AGENT_STATUS_COMPLETED, + AGENT_STATUS_MAX_ITERATIONS, + AGENT_STATUS_ERROR, + AGENT_STATUS_TOOL_ERROR +} agent_status_t; + +typedef struct { + char *goal; + int iteration_count; + int max_iterations; + int tool_retry_count; + int max_tool_retries; + agent_status_t status; + time_t start_time; + char *last_error; + bool verbose; +} agent_state_t; + +static agent_state_t *current_agent = NULL; + +agent_state_t *agent_create(const char *goal) { + agent_state_t *agent = (agent_state_t *)malloc(sizeof(agent_state_t)); + if (agent == NULL) { + return NULL; + } + + agent->goal = goal ? strdup(goal) : NULL; + agent->iteration_count = 0; + agent->max_iterations = AGENT_MAX_ITERATIONS; + agent->tool_retry_count = 0; + agent->max_tool_retries = AGENT_MAX_TOOL_RETRIES; + agent->status = AGENT_STATUS_RUNNING; + agent->start_time = time(NULL); + agent->last_error = NULL; + agent->verbose = is_verbose; + + return agent; +} + +void agent_destroy(agent_state_t *agent) { + if (agent == NULL) { + return; + } + if (agent->goal) { + free(agent->goal); + } + if (agent->last_error) { + free(agent->last_error); + } + free(agent); +} + +void agent_set_error(agent_state_t *agent, const char *error) { + if (agent == NULL) { + return; + } + if (agent->last_error) { + free(agent->last_error); + } + agent->last_error = error ? strdup(error) : NULL; +} + +static struct json_object *agent_process_response(const char *api_url, + const char *json_data) { + char *response = curl_post(api_url, json_data); + if (!response) { + return NULL; + } + + struct json_object *parsed_json = json_tokener_parse(response); + free(response); + + if (!parsed_json) { + return NULL; + } + + struct json_object *error_object; + if (json_object_object_get_ex(parsed_json, "error", &error_object)) { + const char *err_str = json_object_to_json_string(error_object); + fprintf(stderr, "API Error: %s\n", err_str); + json_object_put(parsed_json); + return NULL; + } + + struct json_object *choices_array; + if (!json_object_object_get_ex(parsed_json, "choices", &choices_array)) { + json_object_put(parsed_json); + return NULL; + } + + struct json_object *first_choice = json_object_array_get_idx(choices_array, 0); + if (!first_choice) { + json_object_put(parsed_json); + 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 const char *agent_get_finish_reason(struct json_object *choice) { + struct json_object *finish_reason_obj; + if (json_object_object_get_ex(choice, "finish_reason", &finish_reason_obj)) { + return json_object_get_string(finish_reason_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 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 bool agent_response_indicates_incomplete(const char *content) { + if (content == NULL) { + return false; + } + + 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", + NULL + }; + + for (int i = 0; incomplete_phrases[i] != NULL; i++) { + if (strstr(content, incomplete_phrases[i]) != NULL) { + return true; + } + } + + const char *incomplete_endings[] = { + "...", ":", "files:", "content:", "implementation:", + NULL + }; + + size_t len = strlen(content); + if (len > 3) { + for (int i = 0; incomplete_endings[i] != NULL; 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_state_t *agent, const char *user_message) { + if (agent == NULL) { + return NULL; + } + + current_agent = agent; + agent->status = AGENT_STATUS_RUNNING; + agent->iteration_count = 0; + agent->tool_retry_count = 0; + + if (user_message == NULL || *user_message == '\0') { + agent->status = AGENT_STATUS_ERROR; + agent_set_error(agent, "Empty user message"); + return NULL; + } + + char *json_data = chat_json("user", user_message); + if (json_data == NULL) { + agent->status = AGENT_STATUS_ERROR; + agent_set_error(agent, "Failed to create chat JSON"); + return NULL; + } + + char *final_response = NULL; + + while (agent->status == AGENT_STATUS_RUNNING) { + agent->iteration_count++; + + if (agent->iteration_count > agent->max_iterations) { + agent->status = AGENT_STATUS_MAX_ITERATIONS; + agent_set_error(agent, "Maximum iterations reached"); + if (agent->verbose) { + fprintf(stderr, "[Agent] Max iterations (%d) reached\n", + agent->max_iterations); + } + break; + } + + if (agent->verbose) { + fprintf(stderr, "[Agent] Iteration %d/%d\n", agent->iteration_count, + agent->max_iterations); + } + + struct json_object *choice = + agent_process_response(get_completions_api_url(), json_data); + + if (choice == NULL) { + agent->tool_retry_count++; + if (agent->tool_retry_count >= agent->max_tool_retries) { + agent->status = AGENT_STATUS_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); + } + continue; + } + + agent->tool_retry_count = 0; + + struct json_object *message_obj = agent_get_message(choice); + if (message_obj) { + message_add_object(json_object_get(message_obj)); + } + + const char *finish_reason = agent_get_finish_reason(choice); + bool has_tools = agent_has_tool_calls(choice); + + if (agent->verbose) { + fprintf(stderr, "[Agent] finish_reason=%s, has_tool_calls=%s\n", + finish_reason ? finish_reason : "null", + has_tools ? "true" : "false"); + } + + if (has_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 *tool_results = tools_execute(tool_calls); + + int results_count = json_object_array_length(tool_results); + for (int i = 0; i < results_count; i++) { + struct json_object *tool_result = + json_object_array_get_idx(tool_results, i); + message_add_tool_call(json_object_get(tool_result)); + } + + json_data = chat_json(NULL, NULL); + if (json_data == NULL) { + agent->status = AGENT_STATUS_ERROR; + agent_set_error(agent, "Failed to create follow-up JSON"); + break; + } + + } else { + char *content = agent_get_content(choice); + + if (content && agent_response_indicates_incomplete(content)) { + if (agent->verbose) { + fprintf(stderr, "[Agent] Response indicates incomplete work, auto-continuing\n"); + } + + free(content); + json_data = chat_json("user", "Continue. Execute the necessary actions to complete the task."); + if (json_data == NULL) { + agent->status = AGENT_STATUS_ERROR; + agent_set_error(agent, "Failed to create continue JSON"); + break; + } + + } else { + final_response = content; + agent->status = AGENT_STATUS_COMPLETED; + + if (agent->verbose) { + fprintf(stderr, "[Agent] Completed in %d iteration(s)\n", + agent->iteration_count); + } + } + } + } + + current_agent = NULL; + return final_response; +} + +char *agent_chat(const char *user_message) { + agent_state_t *agent = agent_create(user_message); + if (agent == NULL) { + return NULL; + } + + char *response = agent_run(agent, user_message); + + if (agent->verbose && agent->status != AGENT_STATUS_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) { + agent_state_t *agent = agent_create(user_message); + if (agent == NULL) { + return NULL; + } + + agent->max_iterations = max_iterations; + char *response = agent_run(agent, user_message); + + if (agent->verbose && agent->status != AGENT_STATUS_COMPLETED && agent->last_error) { + fprintf(stderr, "[Agent] Error: %s\n", agent->last_error); + } + + agent_destroy(agent); + return response; +} + +#endif diff --git a/chat.h b/chat.h index 8e2a2a8..c3b2d23 100644 --- a/chat.h +++ b/chat.h @@ -46,6 +46,9 @@ void chat_free() { char *chat_json(const char *role, const char *message) { chat_free(); json_object *root_object = json_object_new_object(); + if (root_object == NULL) { + return NULL; + } json_object_object_add(root_object, "model", json_object_new_string(get_prompt_model())); @@ -56,12 +59,11 @@ char *chat_json(const char *role, const char *message) { } } - json_object_object_add(root_object, "messages", message_list()); - // json_object_object_add(root_object, "max_tokens", - // json_object_new_int(prompt_max_tokens)); + json_object_object_add(root_object, "messages", json_object_get(message_list())); json_object_object_add(root_object, "temperature", json_object_new_double(PROMPT_TEMPERATURE)); + _prompt = root_object; return (char *)json_object_to_json_string_ext(root_object, JSON_C_TO_STRING_PRETTY); } diff --git a/db_utils.h b/db_utils.h index 9c3913a..f94af07 100755 --- a/db_utils.h +++ b/db_utils.h @@ -72,23 +72,33 @@ json_object *db_get(const char *key) { sqlite3 *db; sqlite3_stmt *stmt; json_object *result = json_object_new_object(); - const char *value = NULL; + if (result == NULL) { + return NULL; + } int rc = sqlite3_open(db_file_expanded(), &db); if (rc != SQLITE_OK) { + json_object_put(result); return NULL; } const char *sql = "SELECT value FROM kv_store WHERE key = ?"; - sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); + rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); + if (rc != SQLITE_OK) { + sqlite3_close(db); + json_object_put(result); + return NULL; + } sqlite3_bind_text(stmt, 1, key, -1, SQLITE_STATIC); if (sqlite3_step(stmt) == SQLITE_ROW) { - value = (const char *)sqlite3_column_text(stmt, 0); - } - - if (value) { - json_object_object_add(result, "value", json_object_new_string(value)); + const char *value = (const char *)sqlite3_column_text(stmt, 0); + if (value) { + json_object_object_add(result, "value", json_object_new_string(value)); + } else { + json_object_object_add(result, "error", + json_object_new_string("Key not found")); + } } else { json_object_object_add(result, "error", json_object_new_string("Key not found")); @@ -107,13 +117,22 @@ json_object *db_query(const char *query) { } json_object *result = json_object_new_array(); - - int rc = sqlite3_open(db_file_expanded(), &db); - if (rc != SQLITE_OK) { + if (result == NULL) { return NULL; } - sqlite3_prepare_v2(db, query, -1, &stmt, NULL); + int rc = sqlite3_open(db_file_expanded(), &db); + if (rc != SQLITE_OK) { + json_object_put(result); + return NULL; + } + + rc = sqlite3_prepare_v2(db, query, -1, &stmt, NULL); + if (rc != SQLITE_OK) { + sqlite3_close(db); + json_object_put(result); + return NULL; + } while (sqlite3_step(stmt) == SQLITE_ROW) { json_object *row = json_object_new_object(); for (int i = 0; i < sqlite3_column_count(stmt); i++) { @@ -182,23 +201,34 @@ json_object *db_execute(const char *query) { } void db_store_file_version(const char *path) { char *expanded = expand_home_directory(path); + if (expanded == NULL) { + return; + } char *content = read_file(expanded); if (!content) { + free(expanded); return; } fprintf(stderr, "Creating backup:: %s\n", expanded); char *formatted = sqlite3_mprintf( "INSERT INTO file_version_history (path, content) VALUES (%Q, %Q)", expanded, content); - db_execute(formatted); - sqlite3_free(formatted); + if (formatted) { + db_execute(formatted); + sqlite3_free(formatted); + } free(content); + free(expanded); } char *db_get_schema() { json_object *tables = db_query("SELECT * FROM sqlite_master WHERE type='table'"); - char *result = strdup(json_object_get_string(tables)); + if (tables == NULL) { + return strdup("[]"); + } + const char *str = json_object_to_json_string(tables); + char *result = str ? strdup(str) : strdup("[]"); json_object_put(tables); return result; } diff --git a/http_curl.h b/http_curl.h index 8111fa8..f84cdaa 100644 --- a/http_curl.h +++ b/http_curl.h @@ -29,6 +29,7 @@ #include "auth.h" #include +#include #include #include #include @@ -42,6 +43,12 @@ static size_t WriteCallback(void *contents, size_t size, size_t nmemb, void *userp) { size_t total_size = size * nmemb; struct ResponseBuffer *response = (struct ResponseBuffer *)userp; + + if (total_size > SIZE_MAX - response->size - 1) { + fprintf(stderr, "Response too large\n"); + return 0; + } + char *ptr = realloc(response->data, response->size + total_size + 1); if (ptr == NULL) { fprintf(stderr, "Failed to allocate memory for response\n"); diff --git a/indexer.h b/indexer.h index 67a6640..d38c9d1 100755 --- a/indexer.h +++ b/indexer.h @@ -60,7 +60,10 @@ static int is_ignored_directory(const char *dir_name) { return 0; } -static void get_file_info(const char *path) { +static int get_file_info(const char *path) { + if (file_count >= MAX_FILES) { + return -1; + } struct stat file_stat; if (stat(path, &file_stat) == 0) { FileInfo info; @@ -75,7 +78,9 @@ static void get_file_info(const char *path) { info.type[sizeof(info.type) - 1] = '\0'; info.size_bytes = file_stat.st_size; file_list[file_count++] = info; + return 0; } + return -1; } char *index_directory(const char *dir_path) { @@ -87,6 +92,10 @@ char *index_directory(const char *dir_path) { struct dirent *entry; json_object *jarray = json_object_new_array(); + if (jarray == NULL) { + closedir(dir); + return NULL; + } while ((entry = readdir(dir)) != NULL) { if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) @@ -105,25 +114,29 @@ char *index_directory(const char *dir_path) { free(subdir_json); } } else if (is_valid_extension(entry->d_name)) { - get_file_info(full_path); - json_object *jfile = json_object_new_object(); - json_object_object_add( - jfile, "file_name", - json_object_new_string(file_list[file_count - 1].name)); - json_object_object_add( - jfile, "modification_date", - json_object_new_string(file_list[file_count - 1].modification_date)); - json_object_object_add( - jfile, "creation_date", - json_object_new_string(file_list[file_count - 1].creation_date)); - json_object_object_add( - jfile, "type", - json_object_new_string(file_list[file_count - 1].type)); - json_object_object_add( - jfile, "size_bytes", - json_object_new_int64(file_list[file_count - 1].size_bytes)); + if (get_file_info(full_path) == 0 && file_count > 0) { + json_object *jfile = json_object_new_object(); + if (jfile == NULL) { + continue; + } + json_object_object_add( + jfile, "file_name", + json_object_new_string(file_list[file_count - 1].name)); + json_object_object_add( + jfile, "modification_date", + json_object_new_string(file_list[file_count - 1].modification_date)); + json_object_object_add( + jfile, "creation_date", + json_object_new_string(file_list[file_count - 1].creation_date)); + json_object_object_add( + jfile, "type", + json_object_new_string(file_list[file_count - 1].type)); + json_object_object_add( + jfile, "size_bytes", + json_object_new_int64(file_list[file_count - 1].size_bytes)); - json_object_array_add(jarray, jfile); + json_object_array_add(jarray, jfile); + } } } closedir(dir); diff --git a/line.h b/line.h index 2e71f98..da22ee0 100755 --- a/line.h +++ b/line.h @@ -25,10 +25,14 @@ bool line_initialized = false; char *get_history_file() { static char result[4096]; - result[0] = 0; + result[0] = '\0'; char *expanded = expand_home_directory(HISTORY_FILE); - strcpy(result, expanded); + if (expanded == NULL) { + return result; + } + strncpy(result, expanded, sizeof(result) - 1); + result[sizeof(result) - 1] = '\0'; free(expanded); return result; } @@ -56,21 +60,32 @@ char *line_command_generator(const char *text, int state) { char *line_file_generator(const char *text, int state) { static int list_index; - glob_t glob_result; + static glob_t glob_result; + static int glob_valid = 0; char pattern[1024]; if (!state) { + if (glob_valid) { + globfree(&glob_result); + glob_valid = 0; + } list_index = 0; - snprintf(pattern, sizeof(pattern), "%s*", - text); // Create a pattern for glob - glob(pattern, GLOB_NOSORT, NULL, &glob_result); + snprintf(pattern, sizeof(pattern), "%s*", text); + if (glob(pattern, GLOB_NOSORT, NULL, &glob_result) == 0) { + glob_valid = 1; + } else { + return NULL; + } } - if (list_index < glob_result.gl_pathc) { + if (glob_valid && (size_t)list_index < glob_result.gl_pathc) { return strdup(glob_result.gl_pathv[list_index++]); } - globfree(&glob_result); + if (glob_valid) { + globfree(&glob_result); + glob_valid = 0; + } return NULL; } diff --git a/main.c b/main.c index 942c705..8b44b9f 100644 --- a/main.c +++ b/main.c @@ -1,3 +1,4 @@ +#include "agent.h" #include "db_utils.h" #include "line.h" #include "markdown.h" @@ -138,7 +139,7 @@ static char *get_prompt_from_args(int argc, char **argv) { static bool try_prompt(int argc, char *argv[]) { char *prompt = get_prompt_from_args(argc, argv); if (prompt) { - char *response = openai_chat("user", prompt); + char *response = agent_chat(prompt); if (!response) { printf("Could not get response from server\n"); free(prompt); @@ -211,7 +212,7 @@ static void repl(void) { exit(0); while (line && *line != '\n') { - char *response = openai_chat("user", line); + char *response = agent_chat(line); if (response) { render(response); printf("\n"); @@ -222,7 +223,8 @@ static void repl(void) { } free(response); } else { - exit(0); + fprintf(stderr, "Agent returned no response\n"); + line = NULL; } } } @@ -233,28 +235,49 @@ static void init(void) { line_init(); auth_init(); db_initialize(); + char *schema = db_get_schema(); char payload[1024 * 1024] = {0}; + snprintf( payload, sizeof(payload), - "# LOCAL DATABASE" - "Your have a local database that you can mutate using the query tool and " - "the get and set tool." - "If you set a value using the tool, make sure that the key is stemmed " - "and lowercased to prevent double entries." - "Dialect is sqlite. This is the schema in json format: %s. ", + "# AUTONOMOUS AGENT INSTRUCTIONS\n" + "You are an autonomous AI agent. You operate in a loop: reason about the task, " + "select and execute tools when needed, observe results, and continue until the goal is achieved.\n\n" + "## Reasoning Pattern (ReAct)\n" + "For complex tasks, think step-by-step:\n" + "1. Thought: What do I need to accomplish? What information do I have?\n" + "2. Action: Which tool should I use? With what parameters?\n" + "3. Observation: What did the tool return? What does this tell me?\n" + "4. Repeat until the goal is complete.\n\n" + "## Tool Usage\n" + "- Use tools proactively to gather information and take actions\n" + "- If a tool fails, analyze the error and try a different approach\n" + "- You can call multiple tools in sequence to accomplish complex tasks\n" + "- Always verify results before concluding\n\n" + "## Local Database\n" + "You have a local SQLite database accessible via db_query, db_get, and db_set tools.\n" + "Use stemmed, lowercase keys to prevent duplicates.\n" + "Schema: %s\n\n" + "## Completion\n" + "When the task is fully complete, provide a clear final response.\n" + "Do not stop mid-task. Continue using tools until the goal is achieved.\n", schema); + free(schema); - fprintf(stderr, "Loading... 📨"); + fprintf(stderr, "Loading..."); openai_system(payload); + char *env_system_message = get_env_system_message(); if (env_system_message && *env_system_message) { openai_system(env_system_message); free(env_system_message); } + if (!openai_include(".rcontext.txt")) { openai_include("~/.rcontext.txt"); } + fprintf(stderr, "\r \r"); } diff --git a/messages.h b/messages.h index af52357..7037758 100755 --- a/messages.h +++ b/messages.h @@ -62,16 +62,19 @@ struct json_object *message_add_tool_result(const char *tool_call_id, char *tool_result) { struct json_object *messages = message_list(); struct json_object *message = json_object_new_object(); + if (message == NULL) { + return NULL; + } json_object_object_add(message, "tool_call_id", json_object_new_string(tool_call_id)); - if (strlen(tool_result) > 104000) { - tool_result[104000] = '\0'; + size_t result_len = strlen(tool_result); + if (result_len > 104000) { + result_len = 104000; } - json_object_object_add(message, "tool_result", - json_object_new_string(tool_result)); + json_object_new_string_len(tool_result, (int)result_len)); json_object_array_add(messages, message); return message; @@ -87,16 +90,18 @@ struct json_object *message_add(const char *role, const char *content); struct json_object *message_add(const char *role, const char *content) { struct json_object *messages = message_list(); struct json_object *message = json_object_new_object(); + if (message == NULL) { + return NULL; + } json_object_object_add(message, "role", json_object_new_string(role)); if (content) { - char *formatted_content = strdup(content); - if (strlen(formatted_content) > 1048570) { - formatted_content[1048570] = '\0'; + size_t content_len = strlen(content); + if (content_len > 1048570) { + content_len = 1048570; } json_object_object_add(message, "content", - json_object_new_string(formatted_content)); - free(formatted_content); + json_object_new_string_len(content, (int)content_len)); } if (!strcmp(role, "user")) { json_object_object_add(message, "tools", tools_descriptions()); diff --git a/openai.h b/openai.h index a00ed7a..5d0bb88 100755 --- a/openai.h +++ b/openai.h @@ -60,18 +60,18 @@ struct json_object *openai_process_chat_message(const char *api_url, if (json_object_object_get_ex(parsed_json, "error", &error_object) && message_array) { - char *all_messages = (char *)json_object_to_json_string(message_array); + const char *all_messages = json_object_to_json_string(message_array); fprintf(stderr, "Messages: "); - fwrite(all_messages, strlen(all_messages), 1, stderr); + if (all_messages) { + fwrite(all_messages, strlen(all_messages), 1, stderr); + } fprintf(stderr, "\n"); - free(all_messages); fprintf(stderr, "%s\n", json_object_to_json_string(parsed_json)); json_object_put(parsed_json); messages_remove_last(); - messages_remove_last(); return NULL; diff --git a/r.h b/r.h index ef639d8..1ffe72a 100644 --- a/r.h +++ b/r.h @@ -4,7 +4,7 @@ #include "utils.h" #include #include -bool is_verbose = true; +bool is_verbose = false; char *models_api_url = "https://api.openai.com/v1/models"; char *completions_api_url = "https://api.openai.com/v1/chat/completions"; diff --git a/review.md b/review.md index 025ff97..9a3cf38 100755 --- a/review.md +++ b/review.md @@ -1,29 +1,23 @@ -## AI Chat Prompt and API Integration with JSON-C +# Application Review Report -This project offers functionalities for creating and managing AI chat interactions. It utilizes the JSON-C library to handle JSON objects, providing easy integration with HTTP networking and message handling. +## Entry Point +- The application's entry point is defined in `main.c`. +- Currently, it prints "Hello, World!". -### Features -- **Chat Prompt Management**: Facilitates creating JSON-based chat prompts using an AI model configuration. -- **OpenAI API Interaction**: Includes functions to fetch models and system interactions via OpenAI's APIs. -- **Command-Line Functionality**: Provides autocomplete and history features for user inputs using the readline library. -- **HTTP Requests over SSL/TLS**: Handles HTTP POST and GET operations using OpenSSL for secure connections. -- **Python Interpreter Plugin**: Executes Python scripts within a C application setting using Python’s C API. -- **Syntax Highlighting**: Uses ANSI color formatting for syntax highlighting and markdown conversion. +## Code Structure +- The application has a simple structure with a single source file. -### Highlights -- Modular functions ensure clean separation of concerns and improve code readability. -- Utilizes OpenSSL for secure HTTP requests and JSON-C for efficient JSON handling. -- The inclusion of various open-source alternatives like Rasa and Botpress are suggested for expanding functionalities. +## Testing +- Basic test setup to verify that `main()` returns 0. +- The test code attempts to call `main()` directly, leading to a multiple definition error. -### Licensing -All code is licensed under the MIT License, granting permission for free use, distribution, and modification while disclaiming warranties. +## Issues Identified +- The test setup is incorrect because calling `main()` directly causes a multiple definition error. +- Proper testing should involve refactoring the code to separate logic from the entry point. -### Key Improvements -- Enhanced error handling for JSON operations and API interactions. -- Optimal memory management practices, including safe allocation and deallocation of resources. -- The use of modern and secure methods for sensitive information management (e.g., API keys). +## Recommendations +- Refactor the application code to isolate logic from `main()` to allow easier testing. +- Expand the code to include more functionalities and corresponding tests. +- Review and improve build process and structure for maintainability. -### Open Source Alternatives -- **Rasa**: A machine learning framework for text-and-voice-based assistant automation. -- **Libcurl**: A library supporting HTTP requests and SSL/TLS protocols. -- **GNU Readline**: Provides similar command-line features with history and autocomplete. \ No newline at end of file +The review will be updated after fixing these issues. \ No newline at end of file diff --git a/tools.h b/tools.h index 452294b..575e40a 100644 --- a/tools.h +++ b/tools.h @@ -33,6 +33,96 @@ #include "indexer.h" #include "r.h" +static void tool_print_action(const char *func_name, struct json_object *args) { + if (args == NULL) { + fprintf(stderr, " -> %s\n", func_name); + return; + } + + if (!strcmp(func_name, "linux_terminal_execute")) { + struct json_object *cmd; + if (json_object_object_get_ex(args, "command", &cmd)) { + fprintf(stderr, " -> Running command: %s\n", json_object_get_string(cmd)); + } + } else if (!strcmp(func_name, "linux_terminal_execute_interactive")) { + struct json_object *cmd; + if (json_object_object_get_ex(args, "command", &cmd)) { + fprintf(stderr, " -> Running interactive: %s\n", json_object_get_string(cmd)); + } + } else if (!strcmp(func_name, "read_file")) { + struct json_object *path; + if (json_object_object_get_ex(args, "path", &path)) { + fprintf(stderr, " -> Reading file: %s\n", json_object_get_string(path)); + } + } else if (!strcmp(func_name, "write_file")) { + struct json_object *path; + if (json_object_object_get_ex(args, "path", &path)) { + fprintf(stderr, " -> Writing file: %s\n", json_object_get_string(path)); + } + } else if (!strcmp(func_name, "directory_glob") || !strcmp(func_name, "directory_rglob")) { + struct json_object *path; + if (json_object_object_get_ex(args, "path", &path)) { + fprintf(stderr, " -> Listing: %s\n", json_object_get_string(path)); + } + } else if (!strcmp(func_name, "chdir")) { + struct json_object *path; + if (json_object_object_get_ex(args, "path", &path)) { + fprintf(stderr, " -> Changing directory: %s\n", json_object_get_string(path)); + } + } else if (!strcmp(func_name, "getpwd")) { + fprintf(stderr, " -> Getting current directory\n"); + } else if (!strcmp(func_name, "http_fetch")) { + struct json_object *url; + if (json_object_object_get_ex(args, "url", &url)) { + fprintf(stderr, " -> Fetching URL: %s\n", json_object_get_string(url)); + } + } else if (!strcmp(func_name, "web_search")) { + struct json_object *q; + if (json_object_object_get_ex(args, "query", &q)) { + fprintf(stderr, " -> Searching web: %s\n", json_object_get_string(q)); + } + } else if (!strcmp(func_name, "web_search_news")) { + struct json_object *q; + if (json_object_object_get_ex(args, "query", &q)) { + fprintf(stderr, " -> Searching news: %s\n", json_object_get_string(q)); + } + } else if (!strcmp(func_name, "db_get")) { + struct json_object *key; + if (json_object_object_get_ex(args, "key", &key)) { + fprintf(stderr, " -> Getting from database: %s\n", json_object_get_string(key)); + } + } else if (!strcmp(func_name, "db_set")) { + struct json_object *key; + if (json_object_object_get_ex(args, "key", &key)) { + fprintf(stderr, " -> Storing in database: %s\n", json_object_get_string(key)); + } + } else if (!strcmp(func_name, "db_query")) { + struct json_object *q; + if (json_object_object_get_ex(args, "query", &q)) { + const char *query = json_object_get_string(q); + if (strlen(query) > 60) { + fprintf(stderr, " -> Executing SQL: %.60s...\n", query); + } else { + fprintf(stderr, " -> Executing SQL: %s\n", query); + } + } + } else if (!strcmp(func_name, "mkdir")) { + struct json_object *path; + if (json_object_object_get_ex(args, "path", &path)) { + fprintf(stderr, " -> Creating directory: %s\n", json_object_get_string(path)); + } + } else if (!strcmp(func_name, "index_source_directory")) { + struct json_object *path; + if (json_object_object_get_ex(args, "path", &path)) { + fprintf(stderr, " -> Indexing directory: %s\n", json_object_get_string(path)); + } + } else if (!strcmp(func_name, "python_execute")) { + fprintf(stderr, " -> Executing Python code\n"); + } else { + fprintf(stderr, " -> %s\n", func_name); + } +} + struct json_object *tool_description_http_get(); struct json_object *tool_description_linux_terminal(); struct json_object *tool_description_directory_glob(); @@ -414,9 +504,11 @@ char *tool_function_linux_terminal(char *command) { char *tool_function_linux_terminal_interactive(char *command) { int result_code = system(command); - char *result = malloc(100); - result[0] = 0; - sprintf(result, "Command exited with status code %d.", result_code); + char *result = malloc(128); + if (result == NULL) { + return strdup("Memory allocation failed."); + } + snprintf(result, 128, "Command exited with status code %d.", result_code); return result; } @@ -836,17 +928,24 @@ void ensure_parent_directory_exists(char *path_including_file_name) { return; char *path = strdup(path_including_file_name); + if (path == NULL) + return; + char *last_slash = strrchr(path, '/'); if (last_slash != NULL) { *last_slash = '\0'; - free(tool_function_mkdir(path)); + char *result = tool_function_mkdir(path); + if (result) + free(result); } free(path); } char *tool_function_read_file(char *path) { - char *expanded_path = expand_home_directory(path); + if (expanded_path == NULL) { + return strdup("Failed to expand path!"); + } FILE *fp = fopen(expanded_path, "r"); free(expanded_path); if (fp == NULL) { @@ -856,16 +955,19 @@ char *tool_function_read_file(char *path) { fseek(fp, 0, SEEK_END); long size = ftell(fp); + if (size < 0) { + fclose(fp); + return strdup("Failed to determine file size!"); + } rewind(fp); - char *content = (char *)malloc(size + 1); + char *content = (char *)malloc((size_t)size + 1); if (content == NULL) { fclose(fp); return strdup("Memory allocation failed!"); } - ssize_t read_size = fread(content, 1, size, fp); - + size_t read_size = fread(content, 1, (size_t)size, fp); content[read_size] = '\0'; fclose(fp); @@ -917,16 +1019,28 @@ void recursive_glob(const char *pattern, glob_t *results) { for (size_t i = 0; i < current_matches.gl_pathc; i++) { char *path = current_matches.gl_pathv[i]; + char *path_dup = strdup(path); + if (path_dup == NULL) { + continue; + } if (results->gl_pathc == 0) { results->gl_pathc = 1; results->gl_pathv = malloc(sizeof(char *)); - results->gl_pathv[0] = strdup(path); + if (results->gl_pathv == NULL) { + free(path_dup); + continue; + } + results->gl_pathv[0] = path_dup; } else { + char **new_pathv = realloc(results->gl_pathv, (results->gl_pathc + 1) * sizeof(char *)); + if (new_pathv == NULL) { + free(path_dup); + continue; + } + results->gl_pathv = new_pathv; + results->gl_pathv[results->gl_pathc] = path_dup; results->gl_pathc++; - results->gl_pathv = - realloc(results->gl_pathv, results->gl_pathc * sizeof(char *)); - results->gl_pathv[results->gl_pathc - 1] = strdup(path); } } @@ -935,6 +1049,10 @@ void recursive_glob(const char *pattern, glob_t *results) { struct stat statbuf; char *pattern_copy = strdup(pattern); + if (pattern_copy == NULL) { + globfree(¤t_matches); + return; + } char *last_slash = strrchr(pattern_copy, '/'); char *dir_path; char *file_pattern; @@ -1214,12 +1332,15 @@ struct json_object *tool_description_linux_terminal() { } char *tool_function_mkdir(char *path) { - char temp[2048]; + char temp[4096]; char *p = NULL; size_t len; - snprintf(temp, sizeof(temp), "%s", path); - len = strlen(temp); + len = strlen(path); + if (len >= sizeof(temp)) { + return strdup("Path too long!"); + } + memcpy(temp, path, len + 1); if (temp[len - 1] == '/') { temp[len - 1] = '\0'; @@ -1314,8 +1435,10 @@ struct json_object *tools_execute(struct json_object *tools_array) { struct json_object *arguments = json_tokener_parse(json_object_get_string(arguments_obj)); + tool_print_action(function_name, arguments); + if (is_verbose) - fprintf(stderr, "Executing function %s with arguments %s\n", + fprintf(stderr, " [verbose] %s args: %s\n", function_name, json_object_to_json_string(arguments)); } } diff --git a/utils.h b/utils.h index 7788521..d4fa2e4 100755 --- a/utils.h +++ b/utils.h @@ -66,15 +66,17 @@ char *expand_home_directory(const char *path) { return NULL; // Error getting home directory } - // Allocate memory for the expanded path - size_t expanded_size = strlen(home_dir) + strlen(path); + size_t home_len = strlen(home_dir); + size_t path_len = strlen(path + 1); + size_t expanded_size = home_len + path_len + 1; char *expanded_path = (char *)malloc(expanded_size); if (expanded_path == NULL) { - return NULL; // Memory allocation error + return NULL; } - // Construct the expanded path - snprintf(expanded_path, expanded_size, "%s%s", home_dir, path + 1); + memcpy(expanded_path, home_dir, home_len); + memcpy(expanded_path + home_len, path + 1, path_len); + expanded_path[home_len + path_len] = '\0'; return expanded_path; } @@ -90,21 +92,37 @@ unsigned long hash(const char *str) { } char *joinpath(const char *base_url, const char *path) { - static char result[1024]; - result[0] = '\0'; - strcat(result, base_url); - if (base_url[strlen(base_url) - 1] != '/') { - strcat(result, "/"); + static char result[4096]; + size_t base_len = strlen(base_url); + size_t pos = 0; + + if (base_len >= sizeof(result) - 2) { + base_len = sizeof(result) - 2; + } + memcpy(result, base_url, base_len); + pos = base_len; + + if (pos > 0 && result[pos - 1] != '/') { + result[pos++] = '/'; } if (path[0] == '/') { path++; } - strcat(result, path); + + size_t path_len = strlen(path); + if (pos + path_len >= sizeof(result)) { + path_len = sizeof(result) - pos - 1; + } + memcpy(result + pos, path, path_len); + result[pos + path_len] = '\0'; return result; } char *read_file(const char *path) { char *expanded_path = expand_home_directory(path); + if (expanded_path == NULL) { + return NULL; + } FILE *file = fopen(expanded_path, "r"); free(expanded_path); if (file == NULL) {