This commit is contained in:
retoor 2025-12-18 01:08:38 +01:00
parent d740eb62a1
commit a8d7015abd
14 changed files with 750 additions and 185 deletions

View File

@ -1,82 +1,36 @@
# R Vibe Tool # Henry IRC Server
## Overview A simple IRC server implemented in Python as a package named `henry`.
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.
## Features ## Features
- Basic IRC commands: NICK, USER, JOIN, PRIVMSG, QUIT
- Supports multiple clients and channels
- **Flexible AI Integration**: Support for multiple AI models including: ## Installation
- OpenAI GPT-3.5-turbo (default, if you did not set API key)
- Ollama
- Anthropic Claude
- Grok
- **Customizable Behavior**: Configure tool behavior through `~/.rcontext.txt` Clone the repository or copy the `henry` package directory.
- **Agent Support**: Intelligent context-aware assistance
- **Markdown Output**: Clean, readable documentation generation
## Prerequisites ## Usage
- Linux operating system Run the IRC server using the CLI entry point:
- Configured AI model access
## 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 ```bash
export R_MODEL="qwen2.5:3b" python -m henry.cli
export R_BASE_URL="https://ollama.molodetz.nl"
./r
``` ```
#### 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 ```bash
export R_MODEL="claude-3-5-haiku-20241022" # Using irssi client
export R_BASE_URL="https://api.anthropic.com" irssi -c 127.0.0.1 -p 6667
export R_KEY="sk-ant-[your-key]"
./r
``` ```
#### OpenAI Configuration ## License
```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.
MIT

381
agent.h Normal file
View File

@ -0,0 +1,381 @@
// retoor <retoor@molodetz.nl>
#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 <stdbool.h>
#include <string.h>
#include <time.h>
#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

8
chat.h
View File

@ -46,6 +46,9 @@ void chat_free() {
char *chat_json(const char *role, const char *message) { char *chat_json(const char *role, const char *message) {
chat_free(); chat_free();
json_object *root_object = json_object_new_object(); json_object *root_object = json_object_new_object();
if (root_object == NULL) {
return NULL;
}
json_object_object_add(root_object, "model", json_object_object_add(root_object, "model",
json_object_new_string(get_prompt_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, "messages", json_object_get(message_list()));
// json_object_object_add(root_object, "max_tokens",
// json_object_new_int(prompt_max_tokens));
json_object_object_add(root_object, "temperature", json_object_object_add(root_object, "temperature",
json_object_new_double(PROMPT_TEMPERATURE)); json_object_new_double(PROMPT_TEMPERATURE));
_prompt = root_object;
return (char *)json_object_to_json_string_ext(root_object, return (char *)json_object_to_json_string_ext(root_object,
JSON_C_TO_STRING_PRETTY); JSON_C_TO_STRING_PRETTY);
} }

View File

@ -72,27 +72,37 @@ json_object *db_get(const char *key) {
sqlite3 *db; sqlite3 *db;
sqlite3_stmt *stmt; sqlite3_stmt *stmt;
json_object *result = json_object_new_object(); json_object *result = json_object_new_object();
const char *value = NULL; if (result == NULL) {
return NULL;
}
int rc = sqlite3_open(db_file_expanded(), &db); int rc = sqlite3_open(db_file_expanded(), &db);
if (rc != SQLITE_OK) { if (rc != SQLITE_OK) {
json_object_put(result);
return NULL; return NULL;
} }
const char *sql = "SELECT value FROM kv_store WHERE key = ?"; 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); sqlite3_bind_text(stmt, 1, key, -1, SQLITE_STATIC);
if (sqlite3_step(stmt) == SQLITE_ROW) { if (sqlite3_step(stmt) == SQLITE_ROW) {
value = (const char *)sqlite3_column_text(stmt, 0); const char *value = (const char *)sqlite3_column_text(stmt, 0);
}
if (value) { if (value) {
json_object_object_add(result, "value", json_object_new_string(value)); json_object_object_add(result, "value", json_object_new_string(value));
} else { } else {
json_object_object_add(result, "error", json_object_object_add(result, "error",
json_object_new_string("Key not found")); json_object_new_string("Key not found"));
} }
} else {
json_object_object_add(result, "error",
json_object_new_string("Key not found"));
}
sqlite3_finalize(stmt); sqlite3_finalize(stmt);
sqlite3_close(db); sqlite3_close(db);
@ -107,13 +117,22 @@ json_object *db_query(const char *query) {
} }
json_object *result = json_object_new_array(); json_object *result = json_object_new_array();
if (result == NULL) {
int rc = sqlite3_open(db_file_expanded(), &db);
if (rc != SQLITE_OK) {
return 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) { while (sqlite3_step(stmt) == SQLITE_ROW) {
json_object *row = json_object_new_object(); json_object *row = json_object_new_object();
for (int i = 0; i < sqlite3_column_count(stmt); i++) { 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) { void db_store_file_version(const char *path) {
char *expanded = expand_home_directory(path); char *expanded = expand_home_directory(path);
if (expanded == NULL) {
return;
}
char *content = read_file(expanded); char *content = read_file(expanded);
if (!content) { if (!content) {
free(expanded);
return; return;
} }
fprintf(stderr, "Creating backup:: %s\n", expanded); fprintf(stderr, "Creating backup:: %s\n", expanded);
char *formatted = sqlite3_mprintf( char *formatted = sqlite3_mprintf(
"INSERT INTO file_version_history (path, content) VALUES (%Q, %Q)", "INSERT INTO file_version_history (path, content) VALUES (%Q, %Q)",
expanded, content); expanded, content);
if (formatted) {
db_execute(formatted); db_execute(formatted);
sqlite3_free(formatted); sqlite3_free(formatted);
}
free(content); free(content);
free(expanded);
} }
char *db_get_schema() { char *db_get_schema() {
json_object *tables = json_object *tables =
db_query("SELECT * FROM sqlite_master WHERE type='table'"); 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); json_object_put(tables);
return result; return result;
} }

View File

@ -29,6 +29,7 @@
#include "auth.h" #include "auth.h"
#include <curl/curl.h> #include <curl/curl.h>
#include <stdint.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
@ -42,6 +43,12 @@ static size_t WriteCallback(void *contents, size_t size, size_t nmemb,
void *userp) { void *userp) {
size_t total_size = size * nmemb; size_t total_size = size * nmemb;
struct ResponseBuffer *response = (struct ResponseBuffer *)userp; 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); char *ptr = realloc(response->data, response->size + total_size + 1);
if (ptr == NULL) { if (ptr == NULL) {
fprintf(stderr, "Failed to allocate memory for response\n"); fprintf(stderr, "Failed to allocate memory for response\n");

View File

@ -60,7 +60,10 @@ static int is_ignored_directory(const char *dir_name) {
return 0; 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; struct stat file_stat;
if (stat(path, &file_stat) == 0) { if (stat(path, &file_stat) == 0) {
FileInfo info; FileInfo info;
@ -75,7 +78,9 @@ static void get_file_info(const char *path) {
info.type[sizeof(info.type) - 1] = '\0'; info.type[sizeof(info.type) - 1] = '\0';
info.size_bytes = file_stat.st_size; info.size_bytes = file_stat.st_size;
file_list[file_count++] = info; file_list[file_count++] = info;
return 0;
} }
return -1;
} }
char *index_directory(const char *dir_path) { char *index_directory(const char *dir_path) {
@ -87,6 +92,10 @@ char *index_directory(const char *dir_path) {
struct dirent *entry; struct dirent *entry;
json_object *jarray = json_object_new_array(); json_object *jarray = json_object_new_array();
if (jarray == NULL) {
closedir(dir);
return NULL;
}
while ((entry = readdir(dir)) != NULL) { while ((entry = readdir(dir)) != NULL) {
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
@ -105,8 +114,11 @@ char *index_directory(const char *dir_path) {
free(subdir_json); free(subdir_json);
} }
} else if (is_valid_extension(entry->d_name)) { } else if (is_valid_extension(entry->d_name)) {
get_file_info(full_path); if (get_file_info(full_path) == 0 && file_count > 0) {
json_object *jfile = json_object_new_object(); json_object *jfile = json_object_new_object();
if (jfile == NULL) {
continue;
}
json_object_object_add( json_object_object_add(
jfile, "file_name", jfile, "file_name",
json_object_new_string(file_list[file_count - 1].name)); json_object_new_string(file_list[file_count - 1].name));
@ -126,6 +138,7 @@ char *index_directory(const char *dir_path) {
json_object_array_add(jarray, jfile); json_object_array_add(jarray, jfile);
} }
} }
}
closedir(dir); closedir(dir);
char *result = strdup(json_object_to_json_string(jarray)); char *result = strdup(json_object_to_json_string(jarray));

29
line.h
View File

@ -25,10 +25,14 @@ bool line_initialized = false;
char *get_history_file() { char *get_history_file() {
static char result[4096]; static char result[4096];
result[0] = 0; result[0] = '\0';
char *expanded = expand_home_directory(HISTORY_FILE); 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); free(expanded);
return result; return result;
} }
@ -56,21 +60,32 @@ char *line_command_generator(const char *text, int state) {
char *line_file_generator(const char *text, int state) { char *line_file_generator(const char *text, int state) {
static int list_index; static int list_index;
glob_t glob_result; static glob_t glob_result;
static int glob_valid = 0;
char pattern[1024]; char pattern[1024];
if (!state) { if (!state) {
if (glob_valid) {
globfree(&glob_result);
glob_valid = 0;
}
list_index = 0; list_index = 0;
snprintf(pattern, sizeof(pattern), "%s*", snprintf(pattern, sizeof(pattern), "%s*", text);
text); // Create a pattern for glob if (glob(pattern, GLOB_NOSORT, NULL, &glob_result) == 0) {
glob(pattern, GLOB_NOSORT, NULL, &glob_result); 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++]); return strdup(glob_result.gl_pathv[list_index++]);
} }
if (glob_valid) {
globfree(&glob_result); globfree(&glob_result);
glob_valid = 0;
}
return NULL; return NULL;
} }

43
main.c
View File

@ -1,3 +1,4 @@
#include "agent.h"
#include "db_utils.h" #include "db_utils.h"
#include "line.h" #include "line.h"
#include "markdown.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[]) { static bool try_prompt(int argc, char *argv[]) {
char *prompt = get_prompt_from_args(argc, argv); char *prompt = get_prompt_from_args(argc, argv);
if (prompt) { if (prompt) {
char *response = openai_chat("user", prompt); char *response = agent_chat(prompt);
if (!response) { if (!response) {
printf("Could not get response from server\n"); printf("Could not get response from server\n");
free(prompt); free(prompt);
@ -211,7 +212,7 @@ static void repl(void) {
exit(0); exit(0);
while (line && *line != '\n') { while (line && *line != '\n') {
char *response = openai_chat("user", line); char *response = agent_chat(line);
if (response) { if (response) {
render(response); render(response);
printf("\n"); printf("\n");
@ -222,7 +223,8 @@ static void repl(void) {
} }
free(response); free(response);
} else { } else {
exit(0); fprintf(stderr, "Agent returned no response\n");
line = NULL;
} }
} }
} }
@ -233,28 +235,49 @@ static void init(void) {
line_init(); line_init();
auth_init(); auth_init();
db_initialize(); db_initialize();
char *schema = db_get_schema(); char *schema = db_get_schema();
char payload[1024 * 1024] = {0}; char payload[1024 * 1024] = {0};
snprintf( snprintf(
payload, sizeof(payload), payload, sizeof(payload),
"# LOCAL DATABASE" "# AUTONOMOUS AGENT INSTRUCTIONS\n"
"Your have a local database that you can mutate using the query tool and " "You are an autonomous AI agent. You operate in a loop: reason about the task, "
"the get and set tool." "select and execute tools when needed, observe results, and continue until the goal is achieved.\n\n"
"If you set a value using the tool, make sure that the key is stemmed " "## Reasoning Pattern (ReAct)\n"
"and lowercased to prevent double entries." "For complex tasks, think step-by-step:\n"
"Dialect is sqlite. This is the schema in json format: %s. ", "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); schema);
free(schema); free(schema);
fprintf(stderr, "Loading... 📨"); fprintf(stderr, "Loading...");
openai_system(payload); openai_system(payload);
char *env_system_message = get_env_system_message(); char *env_system_message = get_env_system_message();
if (env_system_message && *env_system_message) { if (env_system_message && *env_system_message) {
openai_system(env_system_message); openai_system(env_system_message);
free(env_system_message); free(env_system_message);
} }
if (!openai_include(".rcontext.txt")) { if (!openai_include(".rcontext.txt")) {
openai_include("~/.rcontext.txt"); openai_include("~/.rcontext.txt");
} }
fprintf(stderr, "\r \r"); fprintf(stderr, "\r \r");
} }

View File

@ -62,16 +62,19 @@ struct json_object *message_add_tool_result(const char *tool_call_id,
char *tool_result) { char *tool_result) {
struct json_object *messages = message_list(); struct json_object *messages = message_list();
struct json_object *message = json_object_new_object(); struct json_object *message = json_object_new_object();
if (message == NULL) {
return NULL;
}
json_object_object_add(message, "tool_call_id", json_object_object_add(message, "tool_call_id",
json_object_new_string(tool_call_id)); json_object_new_string(tool_call_id));
if (strlen(tool_result) > 104000) { size_t result_len = strlen(tool_result);
tool_result[104000] = '\0'; if (result_len > 104000) {
result_len = 104000;
} }
json_object_object_add(message, "tool_result", 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); json_object_array_add(messages, message);
return 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 *message_add(const char *role, const char *content) {
struct json_object *messages = message_list(); struct json_object *messages = message_list();
struct json_object *message = json_object_new_object(); struct json_object *message = json_object_new_object();
if (message == NULL) {
return NULL;
}
json_object_object_add(message, "role", json_object_new_string(role)); json_object_object_add(message, "role", json_object_new_string(role));
if (content) { if (content) {
char *formatted_content = strdup(content); size_t content_len = strlen(content);
if (strlen(formatted_content) > 1048570) { if (content_len > 1048570) {
formatted_content[1048570] = '\0'; content_len = 1048570;
} }
json_object_object_add(message, "content", json_object_object_add(message, "content",
json_object_new_string(formatted_content)); json_object_new_string_len(content, (int)content_len));
free(formatted_content);
} }
if (!strcmp(role, "user")) { if (!strcmp(role, "user")) {
json_object_object_add(message, "tools", tools_descriptions()); json_object_object_add(message, "tools", tools_descriptions());

View File

@ -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) && if (json_object_object_get_ex(parsed_json, "error", &error_object) &&
message_array) { 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: "); fprintf(stderr, "Messages: ");
if (all_messages) {
fwrite(all_messages, strlen(all_messages), 1, stderr); fwrite(all_messages, strlen(all_messages), 1, stderr);
}
fprintf(stderr, "\n"); fprintf(stderr, "\n");
free(all_messages);
fprintf(stderr, "%s\n", json_object_to_json_string(parsed_json)); fprintf(stderr, "%s\n", json_object_to_json_string(parsed_json));
json_object_put(parsed_json); json_object_put(parsed_json);
messages_remove_last(); messages_remove_last();
messages_remove_last(); messages_remove_last();
return NULL; return NULL;

2
r.h
View File

@ -4,7 +4,7 @@
#include "utils.h" #include "utils.h"
#include <stdbool.h> #include <stdbool.h>
#include <string.h> #include <string.h>
bool is_verbose = true; bool is_verbose = false;
char *models_api_url = "https://api.openai.com/v1/models"; char *models_api_url = "https://api.openai.com/v1/models";
char *completions_api_url = "https://api.openai.com/v1/chat/completions"; char *completions_api_url = "https://api.openai.com/v1/chat/completions";

View File

@ -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 ## Code Structure
- **Chat Prompt Management**: Facilitates creating JSON-based chat prompts using an AI model configuration. - The application has a simple structure with a single source file.
- **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 Pythons C API.
- **Syntax Highlighting**: Uses ANSI color formatting for syntax highlighting and markdown conversion.
### Highlights ## Testing
- Modular functions ensure clean separation of concerns and improve code readability. - Basic test setup to verify that `main()` returns 0.
- Utilizes OpenSSL for secure HTTP requests and JSON-C for efficient JSON handling. - The test code attempts to call `main()` directly, leading to a multiple definition error.
- The inclusion of various open-source alternatives like Rasa and Botpress are suggested for expanding functionalities.
### Licensing ## Issues Identified
All code is licensed under the MIT License, granting permission for free use, distribution, and modification while disclaiming warranties. - 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 ## Recommendations
- Enhanced error handling for JSON operations and API interactions. - Refactor the application code to isolate logic from `main()` to allow easier testing.
- Optimal memory management practices, including safe allocation and deallocation of resources. - Expand the code to include more functionalities and corresponding tests.
- The use of modern and secure methods for sensitive information management (e.g., API keys). - Review and improve build process and structure for maintainability.
### Open Source Alternatives The review will be updated after fixing these issues.
- **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.

155
tools.h
View File

@ -33,6 +33,96 @@
#include "indexer.h" #include "indexer.h"
#include "r.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_http_get();
struct json_object *tool_description_linux_terminal(); struct json_object *tool_description_linux_terminal();
struct json_object *tool_description_directory_glob(); 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) { char *tool_function_linux_terminal_interactive(char *command) {
int result_code = system(command); int result_code = system(command);
char *result = malloc(100); char *result = malloc(128);
result[0] = 0; if (result == NULL) {
sprintf(result, "Command exited with status code %d.", result_code); return strdup("Memory allocation failed.");
}
snprintf(result, 128, "Command exited with status code %d.", result_code);
return result; return result;
} }
@ -836,17 +928,24 @@ void ensure_parent_directory_exists(char *path_including_file_name) {
return; return;
char *path = strdup(path_including_file_name); char *path = strdup(path_including_file_name);
if (path == NULL)
return;
char *last_slash = strrchr(path, '/'); char *last_slash = strrchr(path, '/');
if (last_slash != NULL) { if (last_slash != NULL) {
*last_slash = '\0'; *last_slash = '\0';
free(tool_function_mkdir(path)); char *result = tool_function_mkdir(path);
if (result)
free(result);
} }
free(path); free(path);
} }
char *tool_function_read_file(char *path) { char *tool_function_read_file(char *path) {
char *expanded_path = expand_home_directory(path); char *expanded_path = expand_home_directory(path);
if (expanded_path == NULL) {
return strdup("Failed to expand path!");
}
FILE *fp = fopen(expanded_path, "r"); FILE *fp = fopen(expanded_path, "r");
free(expanded_path); free(expanded_path);
if (fp == NULL) { if (fp == NULL) {
@ -856,16 +955,19 @@ char *tool_function_read_file(char *path) {
fseek(fp, 0, SEEK_END); fseek(fp, 0, SEEK_END);
long size = ftell(fp); long size = ftell(fp);
if (size < 0) {
fclose(fp);
return strdup("Failed to determine file size!");
}
rewind(fp); rewind(fp);
char *content = (char *)malloc(size + 1); char *content = (char *)malloc((size_t)size + 1);
if (content == NULL) { if (content == NULL) {
fclose(fp); fclose(fp);
return strdup("Memory allocation failed!"); 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'; content[read_size] = '\0';
fclose(fp); 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++) { for (size_t i = 0; i < current_matches.gl_pathc; i++) {
char *path = current_matches.gl_pathv[i]; char *path = current_matches.gl_pathv[i];
char *path_dup = strdup(path);
if (path_dup == NULL) {
continue;
}
if (results->gl_pathc == 0) { if (results->gl_pathc == 0) {
results->gl_pathc = 1; results->gl_pathc = 1;
results->gl_pathv = malloc(sizeof(char *)); 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 { } 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_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; struct stat statbuf;
char *pattern_copy = strdup(pattern); char *pattern_copy = strdup(pattern);
if (pattern_copy == NULL) {
globfree(&current_matches);
return;
}
char *last_slash = strrchr(pattern_copy, '/'); char *last_slash = strrchr(pattern_copy, '/');
char *dir_path; char *dir_path;
char *file_pattern; char *file_pattern;
@ -1214,12 +1332,15 @@ struct json_object *tool_description_linux_terminal() {
} }
char *tool_function_mkdir(char *path) { char *tool_function_mkdir(char *path) {
char temp[2048]; char temp[4096];
char *p = NULL; char *p = NULL;
size_t len; size_t len;
snprintf(temp, sizeof(temp), "%s", path); len = strlen(path);
len = strlen(temp); if (len >= sizeof(temp)) {
return strdup("Path too long!");
}
memcpy(temp, path, len + 1);
if (temp[len - 1] == '/') { if (temp[len - 1] == '/') {
temp[len - 1] = '\0'; temp[len - 1] = '\0';
@ -1314,8 +1435,10 @@ struct json_object *tools_execute(struct json_object *tools_array) {
struct json_object *arguments = struct json_object *arguments =
json_tokener_parse(json_object_get_string(arguments_obj)); json_tokener_parse(json_object_get_string(arguments_obj));
tool_print_action(function_name, arguments);
if (is_verbose) 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)); function_name, json_object_to_json_string(arguments));
} }
} }

40
utils.h
View File

@ -66,15 +66,17 @@ char *expand_home_directory(const char *path) {
return NULL; // Error getting home directory return NULL; // Error getting home directory
} }
// Allocate memory for the expanded path size_t home_len = strlen(home_dir);
size_t expanded_size = strlen(home_dir) + strlen(path); size_t path_len = strlen(path + 1);
size_t expanded_size = home_len + path_len + 1;
char *expanded_path = (char *)malloc(expanded_size); char *expanded_path = (char *)malloc(expanded_size);
if (expanded_path == NULL) { if (expanded_path == NULL) {
return NULL; // Memory allocation error return NULL;
} }
// Construct the expanded path memcpy(expanded_path, home_dir, home_len);
snprintf(expanded_path, expanded_size, "%s%s", home_dir, path + 1); memcpy(expanded_path + home_len, path + 1, path_len);
expanded_path[home_len + path_len] = '\0';
return expanded_path; return expanded_path;
} }
@ -90,21 +92,37 @@ unsigned long hash(const char *str) {
} }
char *joinpath(const char *base_url, const char *path) { char *joinpath(const char *base_url, const char *path) {
static char result[1024]; static char result[4096];
result[0] = '\0'; size_t base_len = strlen(base_url);
strcat(result, base_url); size_t pos = 0;
if (base_url[strlen(base_url) - 1] != '/') {
strcat(result, "/"); 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] == '/') { if (path[0] == '/') {
path++; 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; return result;
} }
char *read_file(const char *path) { char *read_file(const char *path) {
char *expanded_path = expand_home_directory(path); char *expanded_path = expand_home_directory(path);
if (expanded_path == NULL) {
return NULL;
}
FILE *file = fopen(expanded_path, "r"); FILE *file = fopen(expanded_path, "r");
free(expanded_path); free(expanded_path);
if (file == NULL) { if (file == NULL) {