This commit is contained in:
retoor 2026-01-28 19:34:39 +01:00
parent 930e0889fd
commit 3f8e1ec53a
43 changed files with 4086 additions and 132 deletions

4
AppRun
View File

@ -1,4 +0,0 @@
#!/bin/sh
HERE="$(dirname "$(readlink -f "$0")")"
export LD_LIBRARY_PATH="$HERE/usr/lib:$LD_LIBRARY_PATH"
exec "$HERE/usr/bin/r" "$@"

123
Makefile
View File

@ -1,75 +1,94 @@
all: build build_rpylib run build_mingw
# retoor <retoor@molodetz.nl>
# Variables for compiler and flags
CC = gcc
CFLAGS = -Ofast -Werror -Wall -lreadline -lncurses -lcurl -ljson-c -lsqlite3 -lm -lpthread $(pkg-config --cflags --libs gnutls gmp) -lssl -lcrypto
CFLAGS = -Ofast -Werror -Wall -I./include
LDFLAGS = -lreadline -lncurses -lcurl -ljson-c -lsqlite3 -lm -lpthread -lssl -lcrypto
# MinGW Variables
MINGW_CC = x86_64-w64-mingw32-gcc # Change to x86_64-w64-mingw32-gcc for 64-bit
MINGW_CFLAGS = -Ofast -Werror -Wall -lreadline -lcurl -lssl -lcrypto -ljson-c -lm -lglob
SRCDIR = src
TOOLSDIR = src/tools
BUILDDIR = build
BINDIR = bin
# Targets
build: publish
mkdir -p bin
$(CC) main.c $(CFLAGS) -o bin/r
cp bin/r r
publish r
SRC_CORE = $(SRCDIR)/r_error.c \
$(SRCDIR)/r_config.c \
$(SRCDIR)/tool_registry.c \
$(SRCDIR)/db.c \
$(SRCDIR)/http_client.c \
$(SRCDIR)/messages.c \
$(SRCDIR)/agent.c \
$(SRCDIR)/bash_executor.c \
$(SRCDIR)/main.c
appimage:
SRC_TOOLS = $(TOOLSDIR)/tools_init.c \
$(TOOLSDIR)/tool_terminal.c \
$(TOOLSDIR)/tool_file.c \
$(TOOLSDIR)/tool_db.c \
$(TOOLSDIR)/tool_http.c \
$(TOOLSDIR)/tool_python.c \
$(TOOLSDIR)/tool_indexer.c
SRC = $(SRC_CORE) $(SRC_TOOLS)
OBJ_CORE = $(patsubst $(SRCDIR)/%.c,$(BUILDDIR)/%.o,$(SRC_CORE))
OBJ_TOOLS = $(patsubst $(TOOLSDIR)/%.c,$(BUILDDIR)/tools/%.o,$(SRC_TOOLS))
OBJ = $(OBJ_CORE) $(OBJ_TOOLS)
.PHONY: all clean build build_legacy run
all: build
build: $(BINDIR)/r
cp $(BINDIR)/r r
$(BINDIR)/r: $(OBJ)
@mkdir -p $(BINDIR)
$(CC) $(OBJ) $(LDFLAGS) -o $@
$(BUILDDIR)/%.o: $(SRCDIR)/%.c
@mkdir -p $(dir $@)
$(CC) $(CFLAGS) -c $< -o $@
$(BUILDDIR)/tools/%.o: $(TOOLSDIR)/%.c
@mkdir -p $(dir $@)
$(CC) $(CFLAGS) -c $< -o $@
build_legacy:
@mkdir -p $(BINDIR)
$(CC) main.c $(CFLAGS) $(LDFLAGS) -o $(BINDIR)/r_legacy
cp $(BINDIR)/r_legacy r
build_rpylib:
$(CC) -shared -o rpylib.so -fPIC rpylib.c -lpython3.12 `python3-config --includes` -I/usr/include/CL -ljson-c -lcurl -lsqlite3
run: build
./r --verbose
install:
cp ./r /usr/local/bin/r
clean:
rm -rf $(BUILDDIR) $(BINDIR)
rm -f r
appimage: build
-@rm -rf AppImage
mkdir -p AppImage
mkdir -p AppImage/usr
mkdir -p AppImage/usr/bin
mkdir -p AppImage/lib
cp AppRun AppImage/AppRun
cp r.desktop AppImage/r.desktop
cp r.png AppImage/r.png
cp bin/r AppImage/usr/bin/r
cp $(BINDIR)/r AppImage/usr/bin/r
./collect_so_files.sh
#./prepare_app_image AppImage/usr/bin/r AppImage
appimagetool-x86_64.AppImage AppImage
mv r-x86_64.AppImage r
publish:
curl -OJ https://retoor.molodetz.nl/api/packages/retoor/generic/publish/1.0.0/publish
chmod +x publish
./publish r
build_rpylib:
$(CC) -shared -o rpylib.so -fPIC rpylib.c -lpython3.12 `python3-config --includes` -I/usr/include/CL -ljson-c -lcurl -lsqlite3
publish rpylib.so
# New MinGW build target
build_mingw:
$(MINGW_CC) main.c $(MINGW_CFLAGS) -o r.exe
publish r.exe
run:
./r --verbose
run_free:
./rf --verbose
run_rd:
./rd --verbose
run_mingw:
./r.exe --verbose
docker: docker_make docker_run
docker_make:
docker build -t r .
docker_run:
docker run -v .:/app --rm -it r
build_deb:
dpkg-deb --build r_package
# --- RAG Side Project ---
rag_test: rag_test.c rag.c db_utils.c rag.h db_utils.h
$(CC) -o rag_test rag_test.c rag.c db_utils.c -lsqlite3 -ljson-c
run_rag_test: rag_test
./rag_test

0
agent.h Normal file → Executable file
View File

10
auth.h
View File

@ -30,6 +30,11 @@ void auth_init() {
auth_type = AUTH_TYPE_API_KEY;
return;
}
api_key = getenv("OPENROUTER_API_KEY");
if (api_key) {
auth_type = AUTH_TYPE_API_KEY;
return;
}
api_key = getenv("OPENAI_API_KEY");
if (api_key) {
auth_type = AUTH_TYPE_API_KEY;
@ -49,6 +54,11 @@ const char *resolve_api_key() {
auth_type = AUTH_TYPE_API_KEY;
return api_key;
}
api_key = getenv("OPENROUTER_API_KEY");
if (api_key) {
auth_type = AUTH_TYPE_API_KEY;
return api_key;
}
api_key = getenv("OPENAI_API_KEY");
if (api_key) {
auth_type = AUTH_TYPE_API_KEY;

0
chat.h Normal file → Executable file
View File

View File

@ -1,27 +0,0 @@
#!/bin/bash
# Script: collect_so_files.sh
BINARY="AppImage/usr/bin/r"
LIB_DIR="AppImage/usr/lib"
mkdir -p "$LIB_DIR"
# Function to copy a library and its dependencies
copy_with_deps() {
local lib="$1"
if [ -f "$lib" ] && [ ! -f "$LIB_DIR/$(basename "$lib")" ]; then
cp "$lib" "$LIB_DIR/"
echo "Copied: $lib"
# Recursively check dependencies of this library
ldd "$lib" | grep -o '/[^ ]\+' | while read -r dep; do
if [ -f "$dep" ]; then
copy_with_deps "$dep"
fi
done
fi
}
# Start with the binarys dependencies
ldd "$BINARY" | grep -o '/[^ ]\+' | while read -r lib; do
copy_with_deps "$lib"
done

View File

@ -1,10 +0,0 @@
services:
shell:
build: .
command: sh
tty: true
stdin_open: true
working_dir: /home
volumes:
- ./:/home

Binary file not shown.

0
http_curl.h Normal file → Executable file
View File

38
include/agent.h Executable file
View File

@ -0,0 +1,38 @@
// retoor <retoor@molodetz.nl>
#ifndef R_AGENT_H
#define R_AGENT_H
#include "messages.h"
#include "r_error.h"
#include <stdbool.h>
#define AGENT_MAX_ITERATIONS 300
#define AGENT_MAX_TOOL_RETRIES 3
typedef enum {
AGENT_STATE_IDLE,
AGENT_STATE_RUNNING,
AGENT_STATE_EXECUTING_TOOLS,
AGENT_STATE_COMPLETED,
AGENT_STATE_MAX_ITERATIONS,
AGENT_STATE_ERROR
} agent_state_t;
typedef struct agent_t *agent_handle;
agent_handle agent_create(const char *goal, messages_handle messages);
void agent_destroy(agent_handle agent);
void agent_set_max_iterations(agent_handle agent, int max);
void agent_set_verbose(agent_handle agent, bool verbose);
agent_state_t agent_get_state(agent_handle agent);
const char *agent_get_error(agent_handle agent);
int agent_get_iteration_count(agent_handle agent);
char *agent_run(agent_handle agent, const char *user_message);
char *agent_chat(const char *user_message, messages_handle messages);
char *agent_chat_with_limit(const char *user_message, int max_iterations, messages_handle messages);
#endif

9
include/bash_executor.h Normal file
View File

@ -0,0 +1,9 @@
// retoor <retoor@molodetz.nl>
#ifndef R_BASH_EXECUTOR_H
#define R_BASH_EXECUTOR_H
#include <stdbool.h>
char *r_bash_execute(const char *command, bool interactive);
#endif

24
include/db.h Executable file
View File

@ -0,0 +1,24 @@
// retoor <retoor@molodetz.nl>
#ifndef R_DB_H
#define R_DB_H
#include "r_error.h"
#include <json-c/json.h>
typedef struct db_t *db_handle;
db_handle db_open(const char *path);
void db_close(db_handle db);
r_status_t db_init(db_handle db);
r_status_t db_kv_set(db_handle db, const char *key, const char *value);
r_status_t db_kv_get(db_handle db, const char *key, char **value);
r_status_t db_execute(db_handle db, const char *sql, struct json_object **result);
char *db_get_schema(db_handle db);
r_status_t db_store_file_version(db_handle db, const char *path);
r_status_t db_save_conversation(db_handle db, const char *session_key, const char *data);
r_status_t db_load_conversation(db_handle db, const char *session_key, char **data);
#endif

26
include/http_client.h Executable file
View File

@ -0,0 +1,26 @@
// retoor <retoor@molodetz.nl>
#ifndef R_HTTP_CLIENT_H
#define R_HTTP_CLIENT_H
#include "r_error.h"
#include <stdbool.h>
typedef struct http_client_t *http_client_handle;
http_client_handle http_client_create(const char *bearer_token);
void http_client_destroy(http_client_handle client);
void http_client_set_show_spinner(http_client_handle client, bool show);
void http_client_set_timeout(http_client_handle client, long timeout_seconds);
void http_client_set_connect_timeout(http_client_handle client, long timeout_seconds);
r_status_t http_post(http_client_handle client, const char *url,
const char *data, char **response);
r_status_t http_get(http_client_handle client, const char *url, char **response);
r_status_t http_post_simple(const char *url, const char *bearer_token,
const char *data, char **response);
r_status_t http_get_simple(const char *url, const char *bearer_token, char **response);
#endif

33
include/messages.h Executable file
View File

@ -0,0 +1,33 @@
// retoor <retoor@molodetz.nl>
#ifndef R_MESSAGES_H
#define R_MESSAGES_H
#include "r_error.h"
#include <json-c/json.h>
#include <stdbool.h>
typedef struct messages_t *messages_handle;
messages_handle messages_create(const char *session_id);
void messages_destroy(messages_handle msgs);
r_status_t messages_set_session_id(messages_handle msgs, const char *session_id);
const char *messages_get_session_id(messages_handle msgs);
r_status_t messages_add(messages_handle msgs, const char *role, const char *content);
r_status_t messages_add_object(messages_handle msgs, struct json_object *message);
r_status_t messages_add_tool_call(messages_handle msgs, struct json_object *message);
r_status_t messages_add_tool_result(messages_handle msgs, const char *tool_call_id, const char *result);
r_status_t messages_remove_last(messages_handle msgs);
r_status_t messages_clear(messages_handle msgs);
r_status_t messages_save(messages_handle msgs);
r_status_t messages_load(messages_handle msgs);
struct json_object *messages_to_json(messages_handle msgs);
char *messages_to_string(messages_handle msgs);
int messages_count(messages_handle msgs);
#endif

33
include/r_config.h Executable file
View File

@ -0,0 +1,33 @@
// retoor <retoor@molodetz.nl>
#ifndef R_CONFIG_H
#define R_CONFIG_H
#include <stdbool.h>
typedef struct r_config_t *r_config_handle;
r_config_handle r_config_get_instance(void);
void r_config_destroy(void);
const char *r_config_get_api_url(r_config_handle cfg);
const char *r_config_get_models_url(r_config_handle cfg);
const char *r_config_get_model(r_config_handle cfg);
void r_config_set_model(r_config_handle cfg, const char *model);
const char *r_config_get_api_key(r_config_handle cfg);
const char *r_config_get_db_path(r_config_handle cfg);
bool r_config_use_tools(r_config_handle cfg);
bool r_config_use_strict(r_config_handle cfg);
bool r_config_is_verbose(r_config_handle cfg);
void r_config_set_verbose(r_config_handle cfg, bool verbose);
double r_config_get_temperature(r_config_handle cfg);
const char *r_config_get_session_id(r_config_handle cfg);
bool r_config_set_session_id(r_config_handle cfg, const char *session_id);
const char *r_config_get_system_message(r_config_handle cfg);
#endif

39
include/r_error.h Executable file
View File

@ -0,0 +1,39 @@
// retoor <retoor@molodetz.nl>
#ifndef R_ERROR_H
#define R_ERROR_H
typedef enum {
R_SUCCESS = 0,
R_ERROR_INVALID_ARG,
R_ERROR_OUT_OF_MEMORY,
R_ERROR_NOT_FOUND,
R_ERROR_PARSE,
R_ERROR_DB_CONNECTION,
R_ERROR_DB_QUERY,
R_ERROR_DB_NOT_FOUND,
R_ERROR_HTTP_CONNECTION,
R_ERROR_HTTP_TIMEOUT,
R_ERROR_HTTP_RESPONSE,
R_ERROR_JSON_PARSE,
R_ERROR_FILE_NOT_FOUND,
R_ERROR_FILE_READ,
R_ERROR_FILE_WRITE,
R_ERROR_TOOL_NOT_FOUND,
R_ERROR_TOOL_EXECUTION,
R_ERROR_API_KEY_MISSING,
R_ERROR_API_ERROR,
R_ERROR_MAX_ITERATIONS,
R_ERROR_SESSION_INVALID,
R_ERROR_UNKNOWN
} r_status_t;
const char *r_status_string(r_status_t status);
#define R_CHECK(expr) do { r_status_t _s = (expr); if (_s != R_SUCCESS) return _s; } while(0)
#define R_CHECK_NULL(ptr) do { if (!(ptr)) return R_ERROR_INVALID_ARG; } while(0)
#define R_CHECK_ALLOC(ptr) do { if (!(ptr)) return R_ERROR_OUT_OF_MEMORY; } while(0)
#endif

42
include/tool.h Executable file
View File

@ -0,0 +1,42 @@
// retoor <retoor@molodetz.nl>
#ifndef R_TOOL_H
#define R_TOOL_H
#include "r_error.h"
#include <json-c/json.h>
#include <stdbool.h>
#include <stddef.h>
typedef struct tool_t tool_t;
typedef struct {
struct json_object *(*get_description)(void);
char *(*execute)(tool_t *self, struct json_object *args);
void (*print_action)(const char *name, struct json_object *args);
} tool_vtable_t;
struct tool_t {
const tool_vtable_t *vtable;
const char *name;
};
typedef struct {
tool_t **tools;
size_t count;
size_t capacity;
} tool_registry_t;
tool_registry_t *tool_registry_create(void);
void tool_registry_destroy(tool_registry_t *registry);
r_status_t tool_registry_register(tool_registry_t *registry, tool_t *tool);
tool_t *tool_registry_find(tool_registry_t *registry, const char *name);
struct json_object *tool_registry_get_descriptions(tool_registry_t *registry);
struct json_object *tool_registry_execute(tool_registry_t *registry,
struct json_object *tool_calls,
bool verbose);
tool_registry_t *tools_get_registry(void);
void tools_registry_shutdown(void);
#endif

6
main.c Normal file → Executable file
View File

@ -189,6 +189,11 @@ static void repl(void) {
printf("%s\n", message_json());
continue;
}
if(!strncmp(line,"!debug",6))
{
printf("%s\n", R_BASE_URL);
continue;
}
if (!strncmp(line, "!clear", 6)) {
messages_remove();
fprintf(stderr, "Session cleared.\n");
@ -213,6 +218,7 @@ static void repl(void) {
json_object_to_json_string(tools_descriptions()));
continue;
}
if (!strncmp(line, "!models", 7)) {
printf("Current model: %s\n", openai_fetch_models());
continue;

0
markdown.h Normal file → Executable file
View File

View File

@ -1,6 +0,0 @@
[Desktop Entry]
Name=r
Exec=usr/bin/r
Type=Application
Icon=r
Categories=Utility;

6
r.h Normal file → Executable file
View File

@ -84,8 +84,12 @@ bool get_use_strict() {
char *get_completions_api_url() {
if (getenv("R_BASE_URL") != NULL) {
return joinpath(getenv("R_BASE_URL"), "v1/chat/completions");
char * path = joinpath(getenv("R_BASE_URL"), "v1/chat/completions");
printf("%s\n",path);
return path;
}
printf("%s\n",completions_api_url);
return completions_api_url;
}
char *get_models_api_url() {

BIN
r.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

View File

@ -1,23 +0,0 @@
# Application Review Report
## Entry Point
- The application's entry point is defined in `main.c`.
- Currently, it prints "Hello, World!".
## Code Structure
- The application has a simple structure with a single source file.
## Testing
- Basic test setup to verify that `main()` returns 0.
- The test code attempts to call `main()` directly, leading to a multiple definition error.
## 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.
## 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.
The review will be updated after fixing these issues.

BIN
rpylib.so

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

View File

@ -1,10 +0,0 @@
from setuptools import setup, Extension
module = Extension("rpylib", sources=["rpylib.c"])
setup(
name="rpylib",
version="1.0",
description="AI Module",
ext_modules=[module],
)

415
src/agent.c Executable file
View File

@ -0,0 +1,415 @@
// retoor <retoor@molodetz.nl>
#include "agent.h"
#include "http_client.h"
#include "r_config.h"
#include "tool.h"
#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;
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",
NULL
};
static const char *incomplete_endings[] = {
"...", ":", "files:", "content:", "implementation:",
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;
}
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;
}
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)));
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);
fprintf(stderr, "API Error: %s\n", err_str);
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;
for (int i = 0; incomplete_phrases[i]; i++) {
if (strstr(content, incomplete_phrases[i])) return true;
}
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;
}
char *final_response = NULL;
while (agent->state == AGENT_STATE_RUNNING) {
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);
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);
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));
}
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");
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 = agent_build_request(agent, "user",
"Continue. Execute the necessary actions to complete the task.");
if (!json_data) {
agent->state = AGENT_STATE_ERROR;
agent_set_error(agent, "Failed to create continue JSON");
break;
}
} else {
final_response = content;
agent->state = AGENT_STATE_COMPLETED;
if (agent->verbose) {
fprintf(stderr, "[Agent] Completed in %d iteration(s)\n",
agent->iteration_count);
}
}
}
}
free(json_data);
return final_response;
}
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;
}

106
src/bash_executor.c Normal file
View File

@ -0,0 +1,106 @@
// retoor <retoor@molodetz.nl>
#define _GNU_SOURCE
#include "bash_executor.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
char *r_bash_execute(const char *command, bool interactive) {
if (!command) {
return strdup("Error: null command");
}
size_t len = strlen(command);
char *cmd_with_nl = malloc(len + 2);
if (!cmd_with_nl) {
return strdup("Error: memory allocation failed");
}
strcpy(cmd_with_nl, command);
if (len > 0 && cmd_with_nl[len - 1] != '\n') {
cmd_with_nl[len] = '\n';
cmd_with_nl[len + 1] = '\0';
} else if (len == 0) {
cmd_with_nl[0] = '\n';
cmd_with_nl[1] = '\0';
}
char tmp_script[] = "/tmp/r_bash_XXXXXX.sh";
int fd = mkstemps(tmp_script, 3);
if (fd == -1) {
free(cmd_with_nl);
perror("mkstemps");
return strdup("Error: failed to create temp script");
}
if (write(fd, cmd_with_nl, strlen(cmd_with_nl)) == -1) {
close(fd);
free(cmd_with_nl);
unlink(tmp_script);
return strdup("Error: failed to write to temp script");
}
close(fd);
free(cmd_with_nl);
char *output = NULL;
if (interactive) {
char *run_cmd = NULL;
if (asprintf(&run_cmd, "bash %s", tmp_script) == -1) {
unlink(tmp_script);
return strdup("Error: asprintf failed");
}
int status = system(run_cmd);
free(run_cmd);
if (asprintf(&output, "Command exited with status %d", status) == -1) {
output = strdup("Command completed (asprintf failed for status).");
}
} else {
char *run_cmd = NULL;
if (asprintf(&run_cmd, "bash %s 2>&1", tmp_script) == -1) {
unlink(tmp_script);
return strdup("Error: asprintf failed");
}
FILE *fp = popen(run_cmd, "r");
free(run_cmd);
if (!fp) {
unlink(tmp_script);
return strdup("Error: popen failed");
}
char buffer[1024];
size_t total_size = 0;
while (fgets(buffer, sizeof(buffer), fp)) {
fprintf(stderr, "%s", buffer);
size_t chunk_len = strlen(buffer);
char *new_output = realloc(output, total_size + chunk_len + 1);
if (!new_output) {
free(output);
pclose(fp);
unlink(tmp_script);
return strdup("Error: memory allocation failed");
}
output = new_output;
strcpy(output + total_size, buffer);
total_size += chunk_len;
}
pclose(fp);
if (!output) {
output = strdup("");
}
}
unlink(tmp_script);
return output;
}

308
src/db.c Executable file
View File

@ -0,0 +1,308 @@
// retoor <retoor@molodetz.nl>
#include "db.h"
#include "r_config.h"
#include <sqlite3.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
struct db_t {
sqlite3 *conn;
char *path;
};
static char *expand_home_directory(const char *path) {
if (!path) return NULL;
if (path[0] != '~') return strdup(path);
const char *home_dir = getenv("HOME");
if (!home_dir) home_dir = getenv("USERPROFILE");
if (!home_dir) return strdup(path);
size_t home_len = strlen(home_dir);
size_t path_len = strlen(path);
char *expanded = malloc(home_len + path_len);
if (!expanded) return NULL;
strcpy(expanded, home_dir);
strcat(expanded, path + 1);
return expanded;
}
db_handle db_open(const char *path) {
struct db_t *db = calloc(1, sizeof(struct db_t));
if (!db) return NULL;
if (!path) {
r_config_handle cfg = r_config_get_instance();
path = r_config_get_db_path(cfg);
}
db->path = expand_home_directory(path);
if (!db->path) {
free(db);
return NULL;
}
if (sqlite3_open(db->path, &db->conn) != SQLITE_OK) {
free(db->path);
free(db);
return NULL;
}
db_init(db);
return db;
}
void db_close(db_handle db) {
if (!db) return;
if (db->conn) sqlite3_close(db->conn);
free(db->path);
free(db);
}
r_status_t db_init(db_handle db) {
if (!db || !db->conn) return R_ERROR_INVALID_ARG;
const char *sql =
"CREATE TABLE IF NOT EXISTS kv ("
" key TEXT PRIMARY KEY,"
" value TEXT NOT NULL,"
" created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,"
" updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP"
");"
"CREATE TABLE IF NOT EXISTS file_versions ("
" id INTEGER PRIMARY KEY AUTOINCREMENT,"
" path TEXT NOT NULL,"
" content TEXT NOT NULL,"
" created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP"
");"
"CREATE TABLE IF NOT EXISTS conversations ("
" session_key TEXT PRIMARY KEY,"
" data TEXT NOT NULL,"
" updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP"
");";
char *err_msg = NULL;
if (sqlite3_exec(db->conn, sql, NULL, NULL, &err_msg) != SQLITE_OK) {
sqlite3_free(err_msg);
return R_ERROR_DB_QUERY;
}
return R_SUCCESS;
}
r_status_t db_kv_set(db_handle db, const char *key, const char *value) {
if (!db || !db->conn || !key || !value) return R_ERROR_INVALID_ARG;
char *sql = sqlite3_mprintf(
"INSERT OR REPLACE INTO kv (key, value, updated_at) VALUES (%Q, %Q, CURRENT_TIMESTAMP)",
key, value);
if (!sql) return R_ERROR_OUT_OF_MEMORY;
char *err_msg = NULL;
int rc = sqlite3_exec(db->conn, sql, NULL, NULL, &err_msg);
sqlite3_free(sql);
if (rc != SQLITE_OK) {
sqlite3_free(err_msg);
return R_ERROR_DB_QUERY;
}
return R_SUCCESS;
}
r_status_t db_kv_get(db_handle db, const char *key, char **value) {
if (!db || !db->conn || !key || !value) return R_ERROR_INVALID_ARG;
*value = NULL;
const char *sql = "SELECT value FROM kv WHERE key = ?";
sqlite3_stmt *stmt = NULL;
if (sqlite3_prepare_v2(db->conn, sql, -1, &stmt, NULL) != SQLITE_OK) {
return R_ERROR_DB_QUERY;
}
sqlite3_bind_text(stmt, 1, key, -1, SQLITE_STATIC);
int rc = sqlite3_step(stmt);
if (rc == SQLITE_ROW) {
const char *val = (const char *)sqlite3_column_text(stmt, 0);
*value = val ? strdup(val) : NULL;
}
sqlite3_finalize(stmt);
return (rc == SQLITE_ROW) ? R_SUCCESS : R_ERROR_DB_NOT_FOUND;
}
r_status_t db_execute(db_handle db, const char *sql, struct json_object **result) {
if (!db || !db->conn || !sql || !result) return R_ERROR_INVALID_ARG;
*result = NULL;
const char *select_check = sql;
while (*select_check == ' ') select_check++;
if (strncasecmp(select_check, "SELECT", 6) != 0) {
char *err_msg = NULL;
int rc = sqlite3_exec(db->conn, sql, NULL, NULL, &err_msg);
if (rc != SQLITE_OK) {
struct json_object *error_obj = json_object_new_object();
json_object_object_add(error_obj, "error",
json_object_new_string(err_msg ? err_msg : "Query failed"));
sqlite3_free(err_msg);
*result = error_obj;
return R_ERROR_DB_QUERY;
}
struct json_object *success_obj = json_object_new_object();
json_object_object_add(success_obj, "success", json_object_new_boolean(1));
*result = success_obj;
return R_SUCCESS;
}
sqlite3_stmt *stmt = NULL;
if (sqlite3_prepare_v2(db->conn, sql, -1, &stmt, NULL) != SQLITE_OK) {
struct json_object *error_obj = json_object_new_object();
json_object_object_add(error_obj, "error",
json_object_new_string(sqlite3_errmsg(db->conn)));
*result = error_obj;
return R_ERROR_DB_QUERY;
}
struct json_object *array = json_object_new_array();
int col_count = sqlite3_column_count(stmt);
while (sqlite3_step(stmt) == SQLITE_ROW) {
struct json_object *row = json_object_new_object();
for (int i = 0; i < col_count; i++) {
const char *col_name = sqlite3_column_name(stmt, i);
int col_type = sqlite3_column_type(stmt, i);
switch (col_type) {
case SQLITE_INTEGER:
json_object_object_add(row, col_name,
json_object_new_int64(sqlite3_column_int64(stmt, i)));
break;
case SQLITE_FLOAT:
json_object_object_add(row, col_name,
json_object_new_double(sqlite3_column_double(stmt, i)));
break;
case SQLITE_TEXT:
json_object_object_add(row, col_name,
json_object_new_string((const char *)sqlite3_column_text(stmt, i)));
break;
case SQLITE_NULL:
json_object_object_add(row, col_name, NULL);
break;
default:
json_object_object_add(row, col_name,
json_object_new_string((const char *)sqlite3_column_text(stmt, i)));
break;
}
}
json_object_array_add(array, row);
}
sqlite3_finalize(stmt);
*result = array;
return R_SUCCESS;
}
char *db_get_schema(db_handle db) {
if (!db || !db->conn) return strdup("Database not available");
struct json_object *result = NULL;
r_status_t status = db_execute(db,
"SELECT name, sql FROM sqlite_master WHERE type='table' ORDER BY name",
&result);
if (status != R_SUCCESS || !result) {
return strdup("Failed to get schema");
}
char *schema = strdup(json_object_to_json_string_ext(result, JSON_C_TO_STRING_PRETTY));
json_object_put(result);
return schema;
}
r_status_t db_store_file_version(db_handle db, const char *path) {
if (!db || !db->conn || !path) return R_ERROR_INVALID_ARG;
FILE *fp = fopen(path, "r");
if (!fp) return R_ERROR_FILE_NOT_FOUND;
fseek(fp, 0, SEEK_END);
long size = ftell(fp);
rewind(fp);
if (size <= 0 || size > 1000000) {
fclose(fp);
return R_SUCCESS;
}
char *content = malloc(size + 1);
if (!content) {
fclose(fp);
return R_ERROR_OUT_OF_MEMORY;
}
size_t read_size = fread(content, 1, size, fp);
content[read_size] = '\0';
fclose(fp);
char *sql = sqlite3_mprintf(
"INSERT INTO file_versions (path, content) VALUES (%Q, %Q)",
path, content);
free(content);
if (!sql) return R_ERROR_OUT_OF_MEMORY;
char *err_msg = NULL;
int rc = sqlite3_exec(db->conn, sql, NULL, NULL, &err_msg);
sqlite3_free(sql);
sqlite3_free(err_msg);
return (rc == SQLITE_OK) ? R_SUCCESS : R_ERROR_DB_QUERY;
}
r_status_t db_save_conversation(db_handle db, const char *session_key, const char *data) {
if (!db || !db->conn || !session_key || !data) return R_ERROR_INVALID_ARG;
char *sql = sqlite3_mprintf(
"INSERT OR REPLACE INTO conversations (session_key, data, updated_at) "
"VALUES (%Q, %Q, CURRENT_TIMESTAMP)",
session_key, data);
if (!sql) return R_ERROR_OUT_OF_MEMORY;
char *err_msg = NULL;
int rc = sqlite3_exec(db->conn, sql, NULL, NULL, &err_msg);
sqlite3_free(sql);
sqlite3_free(err_msg);
return (rc == SQLITE_OK) ? R_SUCCESS : R_ERROR_DB_QUERY;
}
r_status_t db_load_conversation(db_handle db, const char *session_key, char **data) {
if (!db || !db->conn || !session_key || !data) return R_ERROR_INVALID_ARG;
*data = NULL;
const char *sql = "SELECT data FROM conversations WHERE session_key = ?";
sqlite3_stmt *stmt = NULL;
if (sqlite3_prepare_v2(db->conn, sql, -1, &stmt, NULL) != SQLITE_OK) {
return R_ERROR_DB_QUERY;
}
sqlite3_bind_text(stmt, 1, session_key, -1, SQLITE_STATIC);
int rc = sqlite3_step(stmt);
if (rc == SQLITE_ROW) {
const char *val = (const char *)sqlite3_column_text(stmt, 0);
*data = val ? strdup(val) : NULL;
}
sqlite3_finalize(stmt);
return (rc == SQLITE_ROW) ? R_SUCCESS : R_ERROR_DB_NOT_FOUND;
}

313
src/http_client.c Executable file
View File

@ -0,0 +1,313 @@
// retoor <retoor@molodetz.nl>
#include "http_client.h"
#include <curl/curl.h>
#include <pthread.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#define HTTP_MAX_RETRIES 3
#define HTTP_RETRY_DELAY_MS 2000
struct http_client_t {
char *bearer_token;
long timeout_seconds;
long connect_timeout_seconds;
bool show_spinner;
};
struct response_buffer_t {
char *data;
size_t size;
};
static struct timespec spinner_start_time = {0, 0};
static volatile int spinner_running = 0;
static double get_elapsed_seconds(void) {
struct timespec now;
clock_gettime(CLOCK_MONOTONIC, &now);
return (now.tv_sec - spinner_start_time.tv_sec) +
(now.tv_nsec - spinner_start_time.tv_nsec) / 1e9;
}
static void *spinner_thread(void *arg) {
(void)arg;
const char *frames[] = {"", "", "", "", "", "", "", "", "", ""};
int frame = 0;
while (spinner_running) {
double elapsed = get_elapsed_seconds();
fprintf(stderr, "\r%s Querying AI... (%.1fs) ", frames[frame % 10], elapsed);
fflush(stderr);
frame++;
usleep(80000);
}
return NULL;
}
static size_t write_callback(void *contents, size_t size, size_t nmemb, void *userp) {
size_t total_size = size * nmemb;
struct response_buffer_t *response = (struct response_buffer_t *)userp;
if (total_size > SIZE_MAX - response->size - 1) {
return 0;
}
char *ptr = realloc(response->data, response->size + total_size + 1);
if (!ptr) {
return 0;
}
response->data = ptr;
memcpy(&(response->data[response->size]), contents, total_size);
response->size += total_size;
response->data[response->size] = '\0';
return total_size;
}
http_client_handle http_client_create(const char *bearer_token) {
struct http_client_t *client = calloc(1, sizeof(struct http_client_t));
if (!client) return NULL;
if (bearer_token) {
client->bearer_token = strdup(bearer_token);
if (!client->bearer_token) {
free(client);
return NULL;
}
}
client->timeout_seconds = 300;
client->connect_timeout_seconds = 10;
client->show_spinner = true;
return client;
}
void http_client_destroy(http_client_handle client) {
if (!client) return;
free(client->bearer_token);
free(client);
}
void http_client_set_show_spinner(http_client_handle client, bool show) {
if (client) client->show_spinner = show;
}
void http_client_set_timeout(http_client_handle client, long timeout_seconds) {
if (client) client->timeout_seconds = timeout_seconds;
}
void http_client_set_connect_timeout(http_client_handle client, long timeout_seconds) {
if (client) client->connect_timeout_seconds = timeout_seconds;
}
r_status_t http_post(http_client_handle client, const char *url,
const char *data, char **response) {
if (!client || !url || !response) return R_ERROR_INVALID_ARG;
CURL *curl = NULL;
struct curl_slist *headers = NULL;
struct response_buffer_t resp = {NULL, 0};
int retry_count = 0;
pthread_t spinner_tid = 0;
r_status_t status = R_SUCCESS;
*response = NULL;
if (client->show_spinner) {
clock_gettime(CLOCK_MONOTONIC, &spinner_start_time);
spinner_running = 1;
pthread_create(&spinner_tid, NULL, spinner_thread, NULL);
}
while (retry_count < HTTP_MAX_RETRIES) {
free(resp.data);
resp.data = malloc(1);
resp.size = 0;
if (!resp.data) {
status = R_ERROR_OUT_OF_MEMORY;
goto cleanup;
}
resp.data[0] = '\0';
curl = curl_easy_init();
if (!curl) {
status = R_ERROR_HTTP_CONNECTION;
goto cleanup;
}
curl_easy_setopt(curl, CURLOPT_URL, url);
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, client->connect_timeout_seconds);
curl_easy_setopt(curl, CURLOPT_TIMEOUT, client->timeout_seconds);
headers = curl_slist_append(headers, "Content-Type: application/json");
if (client->bearer_token) {
char bearer_header[2048];
snprintf(bearer_header, sizeof(bearer_header), "Authorization: Bearer %s",
client->bearer_token);
headers = curl_slist_append(headers, bearer_header);
}
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&resp);
CURLcode res = curl_easy_perform(curl);
curl_slist_free_all(headers);
headers = NULL;
curl_easy_cleanup(curl);
curl = NULL;
if (res == CURLE_OK) {
*response = resp.data;
resp.data = NULL;
status = R_SUCCESS;
goto cleanup;
}
retry_count++;
if (client->show_spinner) {
spinner_running = 0;
pthread_join(spinner_tid, NULL);
spinner_tid = 0;
fprintf(stderr, "\r \r");
}
fprintf(stderr, "Network error: %s (attempt %d/%d)\n",
curl_easy_strerror(res), retry_count, HTTP_MAX_RETRIES);
if (retry_count < HTTP_MAX_RETRIES) {
fprintf(stderr, "Retrying in %d seconds...\n", HTTP_RETRY_DELAY_MS / 1000);
usleep(HTTP_RETRY_DELAY_MS * 1000);
if (client->show_spinner) {
clock_gettime(CLOCK_MONOTONIC, &spinner_start_time);
spinner_running = 1;
pthread_create(&spinner_tid, NULL, spinner_thread, NULL);
}
}
}
status = R_ERROR_HTTP_TIMEOUT;
cleanup:
if (client->show_spinner && spinner_tid) {
spinner_running = 0;
pthread_join(spinner_tid, NULL);
fprintf(stderr, "\r \r");
fflush(stderr);
}
if (headers) curl_slist_free_all(headers);
if (curl) curl_easy_cleanup(curl);
free(resp.data);
return status;
}
r_status_t http_get(http_client_handle client, const char *url, char **response) {
if (!client || !url || !response) return R_ERROR_INVALID_ARG;
CURL *curl = NULL;
struct curl_slist *headers = NULL;
struct response_buffer_t resp = {NULL, 0};
int retry_count = 0;
r_status_t status = R_SUCCESS;
*response = NULL;
while (retry_count < HTTP_MAX_RETRIES) {
free(resp.data);
resp.data = malloc(1);
resp.size = 0;
if (!resp.data) {
status = R_ERROR_OUT_OF_MEMORY;
goto cleanup;
}
resp.data[0] = '\0';
curl = curl_easy_init();
if (!curl) {
status = R_ERROR_HTTP_CONNECTION;
goto cleanup;
}
curl_easy_setopt(curl, CURLOPT_URL, url);
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, client->connect_timeout_seconds);
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 60L);
headers = curl_slist_append(headers, "Content-Type: application/json");
if (client->bearer_token) {
char bearer_header[2048];
snprintf(bearer_header, sizeof(bearer_header), "Authorization: Bearer %s",
client->bearer_token);
headers = curl_slist_append(headers, bearer_header);
}
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&resp);
CURLcode res = curl_easy_perform(curl);
curl_slist_free_all(headers);
headers = NULL;
curl_easy_cleanup(curl);
curl = NULL;
if (res == CURLE_OK) {
*response = resp.data;
resp.data = NULL;
status = R_SUCCESS;
goto cleanup;
}
retry_count++;
fprintf(stderr, "Network error: %s (attempt %d/%d)\n",
curl_easy_strerror(res), retry_count, HTTP_MAX_RETRIES);
if (retry_count < HTTP_MAX_RETRIES) {
fprintf(stderr, "Retrying in %d seconds...\n", HTTP_RETRY_DELAY_MS / 1000);
usleep(HTTP_RETRY_DELAY_MS * 1000);
}
}
status = R_ERROR_HTTP_TIMEOUT;
cleanup:
if (headers) curl_slist_free_all(headers);
if (curl) curl_easy_cleanup(curl);
free(resp.data);
return status;
}
r_status_t http_post_simple(const char *url, const char *bearer_token,
const char *data, char **response) {
http_client_handle client = http_client_create(bearer_token);
if (!client) return R_ERROR_OUT_OF_MEMORY;
r_status_t status = http_post(client, url, data, response);
http_client_destroy(client);
return status;
}
r_status_t http_get_simple(const char *url, const char *bearer_token, char **response) {
http_client_handle client = http_client_create(bearer_token);
if (!client) return R_ERROR_OUT_OF_MEMORY;
http_client_set_show_spinner(client, false);
r_status_t status = http_get(client, url, response);
http_client_destroy(client);
return status;
}

423
src/main.c Executable file
View File

@ -0,0 +1,423 @@
// retoor <retoor@molodetz.nl>
#include "agent.h"
#include "db.h"
#include "http_client.h"
#include "r_config.h"
#include "r_error.h"
#include "tool.h"
#include "../line.h"
#include "../markdown.h"
#include "../utils.h"
#include <json-c/json.h>
#include <locale.h>
#include <signal.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
static volatile sig_atomic_t sigint_count = 0;
static time_t first_sigint_time = 0;
static bool syntax_highlight_enabled = true;
static bool api_mode = false;
static db_handle global_db = NULL;
static messages_handle global_messages = NULL;
extern tool_registry_t *tools_get_registry(void);
extern void tools_registry_shutdown(void);
static void render(const char *content);
static bool include_file(const char *path);
static char *get_prompt_from_stdin(char *prompt);
static char *get_prompt_from_args(int argc, char **argv);
static bool try_prompt(int argc, char *argv[]);
static void repl(void);
static void init(void);
static void cleanup(void);
static void handle_sigint(int sig);
static char *get_env_string(void) {
FILE *fp = popen("env", "r");
if (!fp) return NULL;
size_t buffer_size = 1024;
size_t total_size = 0;
char *output = malloc(buffer_size);
if (!output) {
pclose(fp);
return NULL;
}
size_t bytes_read;
while ((bytes_read = fread(output + total_size, 1, buffer_size - total_size, fp)) > 0) {
total_size += bytes_read;
if (total_size >= buffer_size) {
buffer_size *= 2;
char *temp = realloc(output, buffer_size);
if (!temp) {
free(output);
pclose(fp);
return NULL;
}
output = temp;
}
}
output[total_size] = '\0';
pclose(fp);
return output;
}
static char *get_prompt_from_stdin(char *prompt) {
int index = 0;
int c;
while ((c = getchar()) != EOF) {
prompt[index++] = (char)c;
}
prompt[index] = '\0';
return prompt;
}
static char *get_prompt_from_args(int argc, char **argv) {
r_config_handle cfg = r_config_get_instance();
char *prompt = malloc(10 * 1024 * 1024 + 1);
char *system_msg = malloc(1024 * 1024);
if (!prompt || !system_msg) {
free(prompt);
free(system_msg);
return NULL;
}
system_msg[0] = '\0';
bool get_from_stdin = false;
for (int i = 1; i < argc; i++) {
if (strcmp(argv[i], "--stdin") == 0) {
fprintf(stderr, "Reading from stdin.\n");
get_from_stdin = true;
} else if (strcmp(argv[i], "--verbose") == 0) {
r_config_set_verbose(cfg, true);
} else if (strcmp(argv[i], "--py") == 0 && i + 1 < argc) {
char *py_file_path = expand_home_directory(argv[++i]);
fprintf(stderr, "Including \"%s\".\n", py_file_path);
include_file(py_file_path);
free(py_file_path);
} else if (strcmp(argv[i], "--context") == 0 && i + 1 < argc) {
char *context_file_path = argv[++i];
fprintf(stderr, "Including \"%s\".\n", context_file_path);
include_file(context_file_path);
} else if (strcmp(argv[i], "--api") == 0) {
api_mode = true;
} else if (strcmp(argv[i], "--nh") == 0) {
syntax_highlight_enabled = false;
fprintf(stderr, "Syntax highlighting disabled.\n");
} else if (strncmp(argv[i], "--session=", 10) == 0) {
continue;
} else if (strcmp(argv[i], "-s") == 0 || strcmp(argv[i], "--session") == 0) {
i++;
continue;
} else {
strcat(system_msg, argv[i]);
strcat(system_msg, (i < argc - 1) ? " " : ".");
}
}
if (get_from_stdin) {
if (*system_msg && global_messages) {
messages_add(global_messages, "system", system_msg);
}
prompt = get_prompt_from_stdin(prompt);
free(system_msg);
} else {
free(prompt);
prompt = system_msg;
}
if (!*prompt) {
free(prompt);
return NULL;
}
return prompt;
}
static bool try_prompt(int argc, char *argv[]) {
char *prompt = get_prompt_from_args(argc, argv);
if (prompt) {
char *response = agent_chat(prompt, global_messages);
if (!response) {
printf("Could not get response from server\n");
free(prompt);
return false;
}
render(response);
free(response);
free(prompt);
return true;
}
return false;
}
static bool include_file(const char *path) {
char *file_content = read_file(path);
if (!file_content) return false;
if (global_messages) {
messages_add(global_messages, "system", file_content);
}
free(file_content);
return true;
}
static void render(const char *content) {
if (syntax_highlight_enabled) {
parse_markdown_to_ansi(content);
} else {
printf("%s", content);
}
}
static void repl(void) {
r_config_handle cfg = r_config_get_instance();
tool_registry_t *tools = tools_get_registry();
line_init();
char *line = NULL;
while (true) {
line = line_read("> ");
if (!line || !*line) continue;
if (!strncmp(line, "!dump", 5)) {
char *json = messages_to_string(global_messages);
if (json) {
printf("%s\n", json);
free(json);
}
continue;
}
if (!strncmp(line, "!clear", 6)) {
messages_clear(global_messages);
fprintf(stderr, "Session cleared.\n");
continue;
}
if (!strncmp(line, "!session", 8)) {
printf("Session: %s\n", messages_get_session_id(global_messages));
continue;
}
if (!strncmp(line, "!verbose", 8)) {
bool verbose = !r_config_is_verbose(cfg);
r_config_set_verbose(cfg, verbose);
fprintf(stderr, "%s\n", verbose ? "Verbose mode enabled" : "Verbose mode disabled");
continue;
}
if (line && *line != '\n') {
line_add_history(line);
}
if (!strncmp(line, "!tools", 6)) {
struct json_object *descs = tool_registry_get_descriptions(tools);
printf("Available tools: %s\n", json_object_to_json_string(descs));
continue;
}
if (!strncmp(line, "!models", 7)) {
http_client_handle http = http_client_create(r_config_get_api_key(cfg));
if (http) {
http_client_set_show_spinner(http, false);
char *response = NULL;
if (http_get(http, r_config_get_models_url(cfg), &response) == R_SUCCESS && response) {
printf("Models: %s\n", response);
free(response);
}
http_client_destroy(http);
}
continue;
}
if (!strncmp(line, "!model", 6)) {
if (line[6] == ' ') {
r_config_set_model(cfg, line + 7);
}
printf("Current model: %s\n", r_config_get_model(cfg));
continue;
}
if (!strncmp(line, "exit", 4)) {
exit(0);
}
while (line && *line != '\n') {
char *response = agent_chat(line, global_messages);
if (response) {
render(response);
printf("\n");
if (strstr(response, "_STEP_")) {
line = "continue";
} else {
line = NULL;
}
free(response);
} else {
fprintf(stderr, "Agent returned no response\n");
line = NULL;
}
}
}
}
static void init(void) {
setbuf(stdout, NULL);
line_init();
r_config_handle cfg = r_config_get_instance();
global_db = db_open(NULL);
global_messages = messages_create(r_config_get_session_id(cfg));
char *schema = db_get_schema(global_db);
char payload[1024 * 1024] = {0};
time_t now = time(NULL);
struct tm *tm_info = localtime(&now);
char datetime[64];
strftime(datetime, sizeof(datetime), "%Y-%m-%d %H:%M:%S %Z", tm_info);
char cwd[4096];
if (!getcwd(cwd, sizeof(cwd))) {
strcpy(cwd, "unknown");
}
snprintf(
payload, sizeof(payload),
"# AUTONOMOUS AGENT INSTRUCTIONS\n"
"Current date/time: %s\n"
"Working directory: %s\n\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\n"
"## CRITICAL OUTPUT RULES\n"
"- You MUST include the actual content/data from tool results in your response\n"
"- When you search the web, QUOTE the relevant information found\n"
"- When you run a command, SHOW the output\n"
"- NEVER say 'I found information' without showing what you found\n"
"- NEVER say 'task complete' or 'report provided' - SHOW THE ACTUAL DATA\n"
"- The user cannot see tool results - only YOUR response. Include everything relevant.\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"
"## Response Format\n"
"Your response IS the only thing the user sees. Tool outputs are hidden from them.\n"
"You MUST copy/paste relevant data from tool results into your response.\n"
"Bad: 'I searched and found information about X.'\n"
"Good: 'Here is what I found: [actual content from search results]'\n",
datetime, cwd, schema ? schema : "{}");
free(schema);
fprintf(stderr, "Loading...");
if (global_messages) {
messages_add(global_messages, "system", payload);
}
const char *env_system_msg = r_config_get_system_message(cfg);
if (env_system_msg && *env_system_msg && global_messages) {
messages_add(global_messages, "system", env_system_msg);
}
if (!include_file(".rcontext.txt")) {
include_file("~/.rcontext.txt");
}
fprintf(stderr, "\r \r");
}
static void cleanup(void) {
if (global_messages) {
messages_destroy(global_messages);
global_messages = NULL;
}
if (global_db) {
db_close(global_db);
global_db = NULL;
}
tools_registry_shutdown();
r_config_destroy();
}
static void handle_sigint(int sig) {
(void)sig;
time_t current_time = time(NULL);
printf("\n");
if (sigint_count == 0) {
first_sigint_time = current_time;
sigint_count++;
} else {
if (difftime(current_time, first_sigint_time) <= 1) {
cleanup();
exit(0);
} else {
sigint_count = 1;
first_sigint_time = current_time;
}
}
}
static void parse_session_arg(int argc, char *argv[]) {
r_config_handle cfg = r_config_get_instance();
for (int i = 1; i < argc; i++) {
if (strncmp(argv[i], "--session=", 10) == 0) {
const char *name = argv[i] + 10;
if (!r_config_set_session_id(cfg, name)) {
fprintf(stderr, "Error: Invalid session name '%s'\n", name);
exit(1);
}
return;
}
if ((strcmp(argv[i], "-s") == 0 || strcmp(argv[i], "--session") == 0) && i + 1 < argc) {
const char *name = argv[++i];
if (!r_config_set_session_id(cfg, name)) {
fprintf(stderr, "Error: Invalid session name '%s'\n", name);
exit(1);
}
return;
}
}
}
int main(int argc, char *argv[]) {
signal(SIGINT, handle_sigint);
atexit(cleanup);
parse_session_arg(argc, argv);
init();
char *env_string = get_env_string();
if (env_string && *env_string && global_messages) {
messages_add(global_messages, "system", env_string);
free(env_string);
}
messages_load(global_messages);
if (try_prompt(argc, argv)) {
return 0;
}
repl();
return 0;
}

235
src/messages.c Executable file
View File

@ -0,0 +1,235 @@
// retoor <retoor@molodetz.nl>
#include "messages.h"
#include "db.h"
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define MAX_CONTENT_LENGTH 1048570
#define MAX_TOOL_RESULT_LENGTH 104000
struct messages_t {
struct json_object *array;
char *session_id;
db_handle db;
bool loaded;
};
static bool is_valid_session_id(const char *session_id) {
if (!session_id || !*session_id) return false;
if (strlen(session_id) > 200) return false;
for (const char *p = session_id; *p; p++) {
if (!isalnum((unsigned char)*p) && *p != '-' && *p != '_' && *p != '.') {
return false;
}
}
return true;
}
static char *generate_default_session_id(void) {
char *session_id = malloc(32);
if (!session_id) return NULL;
snprintf(session_id, 32, "session-%d", getppid());
return session_id;
}
messages_handle messages_create(const char *session_id) {
struct messages_t *msgs = calloc(1, sizeof(struct messages_t));
if (!msgs) return NULL;
msgs->array = json_object_new_array();
if (!msgs->array) {
free(msgs);
return NULL;
}
if (session_id && is_valid_session_id(session_id)) {
msgs->session_id = strdup(session_id);
} else {
msgs->session_id = generate_default_session_id();
}
if (!msgs->session_id) {
json_object_put(msgs->array);
free(msgs);
return NULL;
}
msgs->db = db_open(NULL);
return msgs;
}
void messages_destroy(messages_handle msgs) {
if (!msgs) return;
if (msgs->array) json_object_put(msgs->array);
if (msgs->db) db_close(msgs->db);
free(msgs->session_id);
free(msgs);
}
r_status_t messages_set_session_id(messages_handle msgs, const char *session_id) {
if (!msgs || !is_valid_session_id(session_id)) return R_ERROR_INVALID_ARG;
free(msgs->session_id);
msgs->session_id = strdup(session_id);
msgs->loaded = false;
return msgs->session_id ? R_SUCCESS : R_ERROR_OUT_OF_MEMORY;
}
const char *messages_get_session_id(messages_handle msgs) {
return msgs ? msgs->session_id : NULL;
}
r_status_t messages_add(messages_handle msgs, const char *role, const char *content) {
if (!msgs || !msgs->array || !role) return R_ERROR_INVALID_ARG;
struct json_object *message = json_object_new_object();
if (!message) return R_ERROR_OUT_OF_MEMORY;
json_object_object_add(message, "role", json_object_new_string(role));
if (content) {
size_t len = strlen(content);
if (len > MAX_CONTENT_LENGTH) len = MAX_CONTENT_LENGTH;
json_object_object_add(message, "content",
json_object_new_string_len(content, (int)len));
}
json_object_array_add(msgs->array, message);
if (strcmp(role, "system") != 0) {
messages_save(msgs);
}
return R_SUCCESS;
}
r_status_t messages_add_object(messages_handle msgs, struct json_object *message) {
if (!msgs || !msgs->array || !message) return R_ERROR_INVALID_ARG;
json_object_array_add(msgs->array, message);
messages_save(msgs);
return R_SUCCESS;
}
r_status_t messages_add_tool_call(messages_handle msgs, struct json_object *message) {
if (!msgs || !msgs->array || !message) return R_ERROR_INVALID_ARG;
json_object_array_add(msgs->array, message);
messages_save(msgs);
return R_SUCCESS;
}
r_status_t messages_add_tool_result(messages_handle msgs, const char *tool_call_id, const char *result) {
if (!msgs || !msgs->array || !tool_call_id || !result) return R_ERROR_INVALID_ARG;
struct json_object *message = json_object_new_object();
if (!message) return R_ERROR_OUT_OF_MEMORY;
json_object_object_add(message, "tool_call_id", json_object_new_string(tool_call_id));
size_t len = strlen(result);
if (len > MAX_TOOL_RESULT_LENGTH) len = MAX_TOOL_RESULT_LENGTH;
json_object_object_add(message, "tool_result",
json_object_new_string_len(result, (int)len));
json_object_array_add(msgs->array, message);
messages_save(msgs);
return R_SUCCESS;
}
r_status_t messages_remove_last(messages_handle msgs) {
if (!msgs || !msgs->array) return R_ERROR_INVALID_ARG;
int size = json_object_array_length(msgs->array);
if (size == 0) return R_ERROR_NOT_FOUND;
json_object_array_del_idx(msgs->array, size - 1, 1);
messages_save(msgs);
return R_SUCCESS;
}
r_status_t messages_clear(messages_handle msgs) {
if (!msgs) return R_ERROR_INVALID_ARG;
if (msgs->array) json_object_put(msgs->array);
msgs->array = json_object_new_array();
if (!msgs->array) return R_ERROR_OUT_OF_MEMORY;
messages_save(msgs);
return R_SUCCESS;
}
r_status_t messages_save(messages_handle msgs) {
if (!msgs || !msgs->array || !msgs->db) return R_ERROR_INVALID_ARG;
char key[512];
snprintf(key, sizeof(key), "session:%s", msgs->session_id);
const char *json_str = json_object_to_json_string_ext(msgs->array, JSON_C_TO_STRING_PLAIN);
if (!json_str) return R_ERROR_OUT_OF_MEMORY;
return db_save_conversation(msgs->db, key, json_str);
}
r_status_t messages_load(messages_handle msgs) {
if (!msgs || !msgs->db) return R_ERROR_INVALID_ARG;
if (msgs->loaded) return R_SUCCESS;
char key[512];
snprintf(key, sizeof(key), "session:%s", msgs->session_id);
char *data = NULL;
r_status_t status = db_load_conversation(msgs->db, key, &data);
if (status != R_SUCCESS || !data) {
if (status == R_SUCCESS) msgs->loaded = true;
return status == R_SUCCESS ? R_ERROR_NOT_FOUND : status;
}
struct json_object *loaded = json_tokener_parse(data);
free(data);
if (!loaded || !json_object_is_type(loaded, json_type_array)) {
if (loaded) json_object_put(loaded);
return R_ERROR_PARSE;
}
int len = json_object_array_length(loaded);
for (int i = 0; i < len; i++) {
struct json_object *msg = json_object_array_get_idx(loaded, i);
struct json_object *role_obj;
if (json_object_object_get_ex(msg, "role", &role_obj)) {
const char *role = json_object_get_string(role_obj);
if (role && strcmp(role, "system") != 0) {
json_object_array_add(msgs->array, json_object_get(msg));
}
} else {
json_object_array_add(msgs->array, json_object_get(msg));
}
}
json_object_put(loaded);
msgs->loaded = true;
return R_SUCCESS;
}
struct json_object *messages_to_json(messages_handle msgs) {
return msgs ? msgs->array : NULL;
}
char *messages_to_string(messages_handle msgs) {
if (!msgs || !msgs->array) return NULL;
return strdup(json_object_to_json_string_ext(msgs->array, JSON_C_TO_STRING_PRETTY));
}
int messages_count(messages_handle msgs) {
if (!msgs || !msgs->array) return 0;
return json_object_array_length(msgs->array);
}

181
src/r_config.c Executable file
View File

@ -0,0 +1,181 @@
// retoor <retoor@molodetz.nl>
#include "r_config.h"
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct r_config_t {
char *api_url;
char *models_url;
char *model;
char *api_key;
char *db_path;
char *session_id;
char *system_message;
double temperature;
bool use_tools;
bool use_strict;
bool verbose;
};
static struct r_config_t *instance = NULL;
static char *strdup_safe(const char *s) {
return s ? strdup(s) : NULL;
}
static bool resolve_env_bool(const char *env_name, bool default_val) {
const char *val = getenv(env_name);
if (!val) return default_val;
if (!strcmp(val, "true") || !strcmp(val, "1")) return true;
if (!strcmp(val, "false") || !strcmp(val, "0")) return false;
return default_val;
}
static const char *resolve_api_key(void) {
const char * key = getenv("OPENROUTER_API_KEY");
if (key && *key) return key;
key = getenv("R_KEY");
if (key && *key) return key;
key = getenv("OPENAI_API_KEY");
if (key && *key) return key;
return "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";
}
static bool is_valid_session_id(const char *session_id) {
if (!session_id || !*session_id) return false;
if (strlen(session_id) > 255) return false;
for (const char *p = session_id; *p; p++) {
if (!isalnum((unsigned char)*p) && *p != '-' && *p != '_' && *p != '.') {
return false;
}
}
return true;
}
r_config_handle r_config_get_instance(void) {
if (instance) return instance;
instance = calloc(1, sizeof(struct r_config_t));
if (!instance) return NULL;
const char *base_url = getenv("R_BASE_URL");
if (base_url && *base_url) {
size_t len = strlen(base_url);
instance->api_url = malloc(len + 32);
instance->models_url = malloc(len + 32);
if (instance->api_url && instance->models_url) {
snprintf(instance->api_url, len + 32, "%s/v1/chat/completions", base_url);
snprintf(instance->models_url, len + 32, "%s/v1/models", base_url);
}
} else {
instance->api_url = strdup("https://api.openai.com/v1/chat/completions");
instance->models_url = strdup("https://api.openai.com/v1/models");
}
const char *model = getenv("R_MODEL");
instance->model = strdup(model && *model ? model : "gpt-4o-mini");
instance->api_key = strdup(resolve_api_key());
instance->db_path = strdup("~/.r.db");
instance->temperature = 0.1;
instance->use_tools = resolve_env_bool("R_USE_TOOLS", true);
instance->use_strict = resolve_env_bool("R_USE_STRICT", true);
instance->verbose = false;
const char *session = getenv("R_SESSION");
if (session && is_valid_session_id(session)) {
instance->session_id = strdup(session);
} else {
instance->session_id = NULL;
}
instance->system_message = strdup_safe(getenv("R_SYSTEM_MESSAGE"));
return instance;
}
void r_config_destroy(void) {
if (!instance) return;
free(instance->api_url);
free(instance->models_url);
free(instance->model);
free(instance->api_key);
free(instance->db_path);
free(instance->session_id);
free(instance->system_message);
free(instance);
instance = NULL;
}
const char *r_config_get_api_url(r_config_handle cfg) {
return cfg ? cfg->api_url : NULL;
}
const char *r_config_get_models_url(r_config_handle cfg) {
return cfg ? cfg->models_url : NULL;
}
const char *r_config_get_model(r_config_handle cfg) {
return cfg ? cfg->model : NULL;
}
void r_config_set_model(r_config_handle cfg, const char *model) {
if (!cfg || !model) return;
free(cfg->model);
cfg->model = strdup(model);
}
const char *r_config_get_api_key(r_config_handle cfg) {
return cfg ? cfg->api_key : NULL;
}
const char *r_config_get_db_path(r_config_handle cfg) {
return cfg ? cfg->db_path : NULL;
}
bool r_config_use_tools(r_config_handle cfg) {
return cfg ? cfg->use_tools : true;
}
bool r_config_use_strict(r_config_handle cfg) {
return cfg ? cfg->use_strict : true;
}
bool r_config_is_verbose(r_config_handle cfg) {
return cfg ? cfg->verbose : false;
}
void r_config_set_verbose(r_config_handle cfg, bool verbose) {
if (cfg) cfg->verbose = verbose;
}
double r_config_get_temperature(r_config_handle cfg) {
return cfg ? cfg->temperature : 0.1;
}
const char *r_config_get_session_id(r_config_handle cfg) {
return cfg ? cfg->session_id : NULL;
}
bool r_config_set_session_id(r_config_handle cfg, const char *session_id) {
if (!cfg || !is_valid_session_id(session_id)) return false;
free(cfg->session_id);
cfg->session_id = strdup(session_id);
return cfg->session_id != NULL;
}
const char *r_config_get_system_message(r_config_handle cfg) {
return cfg ? cfg->system_message : NULL;
}

35
src/r_error.c Executable file
View File

@ -0,0 +1,35 @@
// retoor <retoor@molodetz.nl>
#include "r_error.h"
static const char *error_messages[] = {
[R_SUCCESS] = "Success",
[R_ERROR_INVALID_ARG] = "Invalid argument",
[R_ERROR_OUT_OF_MEMORY] = "Out of memory",
[R_ERROR_NOT_FOUND] = "Not found",
[R_ERROR_PARSE] = "Parse error",
[R_ERROR_DB_CONNECTION] = "Database connection failed",
[R_ERROR_DB_QUERY] = "Database query failed",
[R_ERROR_DB_NOT_FOUND] = "Database key not found",
[R_ERROR_HTTP_CONNECTION] = "HTTP connection failed",
[R_ERROR_HTTP_TIMEOUT] = "HTTP request timed out",
[R_ERROR_HTTP_RESPONSE] = "HTTP response error",
[R_ERROR_JSON_PARSE] = "JSON parse error",
[R_ERROR_FILE_NOT_FOUND] = "File not found",
[R_ERROR_FILE_READ] = "File read error",
[R_ERROR_FILE_WRITE] = "File write error",
[R_ERROR_TOOL_NOT_FOUND] = "Tool not found",
[R_ERROR_TOOL_EXECUTION] = "Tool execution failed",
[R_ERROR_API_KEY_MISSING] = "API key missing",
[R_ERROR_API_ERROR] = "API error",
[R_ERROR_MAX_ITERATIONS] = "Maximum iterations reached",
[R_ERROR_SESSION_INVALID] = "Invalid session name",
[R_ERROR_UNKNOWN] = "Unknown error"
};
const char *r_status_string(r_status_t status) {
if (status < 0 || status > R_ERROR_UNKNOWN) {
return error_messages[R_ERROR_UNKNOWN];
}
return error_messages[status];
}

157
src/tool_registry.c Executable file
View File

@ -0,0 +1,157 @@
// retoor <retoor@molodetz.nl>
#include "tool.h"
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
tool_registry_t *tool_registry_create(void) {
tool_registry_t *registry = calloc(1, sizeof(tool_registry_t));
if (!registry) return NULL;
registry->capacity = 32;
registry->tools = calloc(registry->capacity, sizeof(tool_t *));
if (!registry->tools) {
free(registry);
return NULL;
}
return registry;
}
void tool_registry_destroy(tool_registry_t *registry) {
if (!registry) return;
free(registry->tools);
free(registry);
}
r_status_t tool_registry_register(tool_registry_t *registry, tool_t *tool) {
if (!registry || !tool) return R_ERROR_INVALID_ARG;
if (registry->count >= registry->capacity) {
size_t new_capacity = registry->capacity * 2;
tool_t **new_tools = realloc(registry->tools, new_capacity * sizeof(tool_t *));
if (!new_tools) return R_ERROR_OUT_OF_MEMORY;
registry->tools = new_tools;
registry->capacity = new_capacity;
}
registry->tools[registry->count++] = tool;
return R_SUCCESS;
}
tool_t *tool_registry_find(tool_registry_t *registry, const char *name) {
if (!registry || !name) return NULL;
for (size_t i = 0; i < registry->count; i++) {
if (strcmp(registry->tools[i]->name, name) == 0) {
return registry->tools[i];
}
}
return NULL;
}
struct json_object *tool_registry_get_descriptions(tool_registry_t *registry) {
if (!registry) return NULL;
struct json_object *array = json_object_new_array();
if (!array) return NULL;
for (size_t i = 0; i < registry->count; i++) {
tool_t *tool = registry->tools[i];
if (tool->vtable->get_description) {
struct json_object *desc = tool->vtable->get_description();
if (desc) {
json_object_array_add(array, desc);
}
}
}
return array;
}
struct json_object *tool_registry_execute(tool_registry_t *registry,
struct json_object *tool_calls,
bool verbose) {
if (!registry || !tool_calls) return NULL;
struct json_object *results = json_object_new_array();
if (!results) return NULL;
int len = json_object_array_length(tool_calls);
for (int i = 0; i < len; i++) {
struct json_object *call = json_object_array_get_idx(tool_calls, i);
struct json_object *result = json_object_new_object();
struct json_object *id_obj = json_object_object_get(call, "id");
if (id_obj) {
json_object_object_add(result, "tool_call_id",
json_object_new_string(json_object_get_string(id_obj)));
}
json_object_object_add(result, "role", json_object_new_string("tool"));
struct json_object *type_obj;
if (json_object_object_get_ex(call, "type", &type_obj)) {
if (strcmp(json_object_get_string(type_obj), "function") != 0) {
json_object_put(result);
continue;
}
}
struct json_object *function_obj;
if (!json_object_object_get_ex(call, "function", &function_obj)) {
json_object_object_add(result, "content",
json_object_new_string("Error: missing function object"));
json_object_array_add(results, result);
continue;
}
struct json_object *name_obj;
if (!json_object_object_get_ex(function_obj, "name", &name_obj)) {
json_object_object_add(result, "content",
json_object_new_string("Error: missing function name"));
json_object_array_add(results, result);
continue;
}
const char *name = json_object_get_string(name_obj);
tool_t *tool = tool_registry_find(registry, name);
if (!tool) {
fprintf(stderr, "Unknown function: %s\n", name);
json_object_object_add(result, "content",
json_object_new_string("Error: function not found."));
json_object_array_add(results, result);
continue;
}
struct json_object *args = NULL;
struct json_object *args_obj;
if (json_object_object_get_ex(function_obj, "arguments", &args_obj)) {
args = json_tokener_parse(json_object_get_string(args_obj));
}
if (tool->vtable->print_action) {
tool->vtable->print_action(tool->name, args);
}
if (verbose && args) {
fprintf(stderr, " [verbose] %s args: %s\n",
tool->name, json_object_to_json_string(args));
}
char *output = NULL;
if (tool->vtable->execute) {
output = tool->vtable->execute(tool, args);
}
json_object_object_add(result, "content",
json_object_new_string(output ? output : ""));
free(output);
if (args) json_object_put(args);
json_object_array_add(results, result);
}
return results;
}

270
src/tools/tool_db.c Executable file
View File

@ -0,0 +1,270 @@
// retoor <retoor@molodetz.nl>
#include "tool.h"
#include "db.h"
#include "r_config.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static struct json_object *db_get_get_description(void);
static char *db_get_execute(tool_t *self, struct json_object *args);
static void db_get_print_action(const char *name, struct json_object *args);
static struct json_object *db_set_get_description(void);
static char *db_set_execute(tool_t *self, struct json_object *args);
static void db_set_print_action(const char *name, struct json_object *args);
static struct json_object *db_query_get_description(void);
static char *db_query_execute(tool_t *self, struct json_object *args);
static void db_query_print_action(const char *name, struct json_object *args);
static const tool_vtable_t db_get_vtable = {
.get_description = db_get_get_description,
.execute = db_get_execute,
.print_action = db_get_print_action
};
static const tool_vtable_t db_set_vtable = {
.get_description = db_set_get_description,
.execute = db_set_execute,
.print_action = db_set_print_action
};
static const tool_vtable_t db_query_vtable = {
.get_description = db_query_get_description,
.execute = db_query_execute,
.print_action = db_query_print_action
};
static tool_t db_get_tool = { .vtable = &db_get_vtable, .name = "db_get" };
static tool_t db_set_tool = { .vtable = &db_set_vtable, .name = "db_set" };
static tool_t db_query_tool = { .vtable = &db_query_vtable, .name = "db_query" };
tool_t *tool_db_get_create(void) { return &db_get_tool; }
tool_t *tool_db_set_create(void) { return &db_set_tool; }
tool_t *tool_db_query_create(void) { return &db_query_tool; }
static void db_get_print_action(const char *name, struct json_object *args) {
(void)name;
if (!args) return;
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));
}
}
static char *db_get_execute(tool_t *self, struct json_object *args) {
(void)self;
struct json_object *key_obj;
if (!json_object_object_get_ex(args, "key", &key_obj)) {
return strdup("Error: missing 'key' argument");
}
db_handle db = db_open(NULL);
if (!db) return strdup("Failed to open database.");
char *value = NULL;
r_status_t status = db_kv_get(db, json_object_get_string(key_obj), &value);
db_close(db);
if (status != R_SUCCESS || !value) {
return strdup("Failed to retrieve value from the database.");
}
struct json_object *result = json_object_new_object();
json_object_object_add(result, "value", json_object_new_string(value));
char *response = strdup(json_object_to_json_string(result));
json_object_put(result);
free(value);
return response;
}
static struct json_object *db_get_get_description(void) {
struct json_object *root = json_object_new_object();
json_object_object_add(root, "type", json_object_new_string("function"));
struct json_object *function = json_object_new_object();
json_object_object_add(function, "name", json_object_new_string("db_get"));
json_object_object_add(function, "description",
json_object_new_string("Retrieves a value from the database for a given key."));
struct json_object *parameters = json_object_new_object();
json_object_object_add(parameters, "type", json_object_new_string("object"));
struct json_object *properties = json_object_new_object();
struct json_object *key = json_object_new_object();
json_object_object_add(key, "type", json_object_new_string("string"));
json_object_object_add(key, "description",
json_object_new_string("The key to retrieve from the database."));
json_object_object_add(properties, "key", key);
json_object_object_add(parameters, "properties", properties);
struct json_object *required = json_object_new_array();
json_object_array_add(required, json_object_new_string("key"));
json_object_object_add(parameters, "required", required);
json_object_object_add(parameters, "additionalProperties", json_object_new_boolean(0));
json_object_object_add(function, "parameters", parameters);
r_config_handle cfg = r_config_get_instance();
if (r_config_use_strict(cfg)) {
json_object_object_add(function, "strict", json_object_new_boolean(1));
}
json_object_object_add(root, "function", function);
return root;
}
static void db_set_print_action(const char *name, struct json_object *args) {
(void)name;
if (!args) return;
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));
}
}
static char *db_set_execute(tool_t *self, struct json_object *args) {
(void)self;
struct json_object *key_obj, *value_obj;
if (!json_object_object_get_ex(args, "key", &key_obj)) {
return strdup("Error: missing 'key' argument");
}
if (!json_object_object_get_ex(args, "value", &value_obj)) {
return strdup("Error: missing 'value' argument");
}
db_handle db = db_open(NULL);
if (!db) return strdup("Failed to open database.");
r_status_t status = db_kv_set(db, json_object_get_string(key_obj),
json_object_get_string(value_obj));
db_close(db);
if (status != R_SUCCESS) {
return strdup("Failed to set value in the database.");
}
return strdup("Value successfully stored.");
}
static struct json_object *db_set_get_description(void) {
struct json_object *root = json_object_new_object();
json_object_object_add(root, "type", json_object_new_string("function"));
struct json_object *function = json_object_new_object();
json_object_object_add(function, "name", json_object_new_string("db_set"));
json_object_object_add(function, "description",
json_object_new_string("Sets a value in the database for a given key."));
struct json_object *parameters = json_object_new_object();
json_object_object_add(parameters, "type", json_object_new_string("object"));
struct json_object *properties = json_object_new_object();
struct json_object *key = json_object_new_object();
json_object_object_add(key, "type", json_object_new_string("string"));
json_object_object_add(key, "description",
json_object_new_string("The key to set in the database."));
json_object_object_add(properties, "key", key);
struct json_object *value = json_object_new_object();
json_object_object_add(value, "type", json_object_new_string("string"));
json_object_object_add(value, "description",
json_object_new_string("The value to set for the given key."));
json_object_object_add(properties, "value", value);
json_object_object_add(parameters, "properties", properties);
struct json_object *required = json_object_new_array();
json_object_array_add(required, json_object_new_string("key"));
json_object_array_add(required, json_object_new_string("value"));
json_object_object_add(parameters, "required", required);
json_object_object_add(parameters, "additionalProperties", json_object_new_boolean(0));
json_object_object_add(function, "parameters", parameters);
r_config_handle cfg = r_config_get_instance();
if (r_config_use_strict(cfg)) {
json_object_object_add(function, "strict", json_object_new_boolean(1));
}
json_object_object_add(root, "function", function);
return root;
}
static void db_query_print_action(const char *name, struct json_object *args) {
(void)name;
if (!args) return;
struct json_object *query;
if (json_object_object_get_ex(args, "query", &query)) {
const char *q = json_object_get_string(query);
if (strlen(q) > 60) {
fprintf(stderr, " -> Executing SQL: %.60s...\n", q);
} else {
fprintf(stderr, " -> Executing SQL: %s\n", q);
}
}
}
static char *db_query_execute(tool_t *self, struct json_object *args) {
(void)self;
struct json_object *query_obj;
if (!json_object_object_get_ex(args, "query", &query_obj)) {
return strdup("Error: missing 'query' argument");
}
db_handle db = db_open(NULL);
if (!db) return strdup("Failed to open database.");
struct json_object *result = NULL;
r_status_t status = db_execute(db, json_object_get_string(query_obj), &result);
db_close(db);
if (status != R_SUCCESS) {
return strdup("Failed to execute query on the database.");
}
char *response = strdup(json_object_to_json_string(result));
json_object_put(result);
return response;
}
static struct json_object *db_query_get_description(void) {
struct json_object *root = json_object_new_object();
json_object_object_add(root, "type", json_object_new_string("function"));
struct json_object *function = json_object_new_object();
json_object_object_add(function, "name", json_object_new_string("db_query"));
json_object_object_add(function, "description",
json_object_new_string("Executes a query on the database and returns the results."));
struct json_object *parameters = json_object_new_object();
json_object_object_add(parameters, "type", json_object_new_string("object"));
struct json_object *properties = json_object_new_object();
struct json_object *query = json_object_new_object();
json_object_object_add(query, "type", json_object_new_string("string"));
json_object_object_add(query, "description",
json_object_new_string("The SQL query to execute."));
json_object_object_add(properties, "query", query);
json_object_object_add(parameters, "properties", properties);
struct json_object *required = json_object_new_array();
json_object_array_add(required, json_object_new_string("query"));
json_object_object_add(parameters, "required", required);
json_object_object_add(parameters, "additionalProperties", json_object_new_boolean(0));
json_object_object_add(function, "parameters", parameters);
r_config_handle cfg = r_config_get_instance();
if (r_config_use_strict(cfg)) {
json_object_object_add(function, "strict", json_object_new_boolean(1));
}
json_object_object_add(root, "function", function);
return root;
}

563
src/tools/tool_file.c Executable file
View File

@ -0,0 +1,563 @@
// retoor <retoor@molodetz.nl>
#include "tool.h"
#include "r_config.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include <dirent.h>
#include <glob.h>
#include <errno.h>
#include <time.h>
#include <limits.h>
static char *expand_home_directory(const char *path);
static const char *get_file_type(struct stat *st);
static void format_time(time_t raw_time, char *buffer, size_t size);
static struct json_object *read_file_get_description(void);
static char *read_file_execute(tool_t *self, struct json_object *args);
static void read_file_print_action(const char *name, struct json_object *args);
static struct json_object *write_file_get_description(void);
static char *write_file_execute(tool_t *self, struct json_object *args);
static void write_file_print_action(const char *name, struct json_object *args);
static struct json_object *directory_glob_get_description(void);
static char *directory_glob_execute(tool_t *self, struct json_object *args);
static void directory_glob_print_action(const char *name, struct json_object *args);
static struct json_object *mkdir_get_description(void);
static char *mkdir_execute(tool_t *self, struct json_object *args);
static void mkdir_print_action(const char *name, struct json_object *args);
static struct json_object *chdir_get_description(void);
static char *chdir_execute(tool_t *self, struct json_object *args);
static void chdir_print_action(const char *name, struct json_object *args);
static struct json_object *getpwd_get_description(void);
static char *getpwd_execute(tool_t *self, struct json_object *args);
static void getpwd_print_action(const char *name, struct json_object *args);
static const tool_vtable_t read_file_vtable = {
.get_description = read_file_get_description,
.execute = read_file_execute,
.print_action = read_file_print_action
};
static const tool_vtable_t write_file_vtable = {
.get_description = write_file_get_description,
.execute = write_file_execute,
.print_action = write_file_print_action
};
static const tool_vtable_t directory_glob_vtable = {
.get_description = directory_glob_get_description,
.execute = directory_glob_execute,
.print_action = directory_glob_print_action
};
static const tool_vtable_t mkdir_vtable = {
.get_description = mkdir_get_description,
.execute = mkdir_execute,
.print_action = mkdir_print_action
};
static const tool_vtable_t chdir_vtable = {
.get_description = chdir_get_description,
.execute = chdir_execute,
.print_action = chdir_print_action
};
static const tool_vtable_t getpwd_vtable = {
.get_description = getpwd_get_description,
.execute = getpwd_execute,
.print_action = getpwd_print_action
};
static tool_t read_file_tool = { .vtable = &read_file_vtable, .name = "read_file" };
static tool_t write_file_tool = { .vtable = &write_file_vtable, .name = "write_file" };
static tool_t directory_glob_tool = { .vtable = &directory_glob_vtable, .name = "directory_glob" };
static tool_t mkdir_tool = { .vtable = &mkdir_vtable, .name = "mkdir" };
static tool_t chdir_tool = { .vtable = &chdir_vtable, .name = "chdir" };
static tool_t getpwd_tool = { .vtable = &getpwd_vtable, .name = "getpwd" };
tool_t *tool_read_file_create(void) { return &read_file_tool; }
tool_t *tool_write_file_create(void) { return &write_file_tool; }
tool_t *tool_directory_glob_create(void) { return &directory_glob_tool; }
tool_t *tool_mkdir_create(void) { return &mkdir_tool; }
tool_t *tool_chdir_create(void) { return &chdir_tool; }
tool_t *tool_getpwd_create(void) { return &getpwd_tool; }
static char *expand_home_directory(const char *path) {
if (!path) return NULL;
if (path[0] != '~') return strdup(path);
const char *home_dir = getenv("HOME");
if (!home_dir) home_dir = getenv("USERPROFILE");
if (!home_dir) return strdup(path);
size_t home_len = strlen(home_dir);
size_t path_len = strlen(path);
char *expanded = malloc(home_len + path_len);
if (!expanded) return NULL;
strcpy(expanded, home_dir);
strcat(expanded, path + 1);
return expanded;
}
static const char *get_file_type(struct stat *st) {
if (S_ISREG(st->st_mode)) return "file";
if (S_ISDIR(st->st_mode)) return "directory";
return "other";
}
static void format_time(time_t raw_time, char *buffer, size_t size) {
struct tm *time_info = localtime(&raw_time);
strftime(buffer, size, "%Y-%m-%d %H:%M:%S", time_info);
}
static char *make_dir_recursive(const char *path) {
char temp[4096];
char *p = NULL;
size_t 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';
for (p = temp + 1; *p; p++) {
if (*p == '/') {
*p = '\0';
if (mkdir(temp, 0777) != 0 && errno != EEXIST) {
return strdup("Failed to create directory!");
}
*p = '/';
}
}
if (mkdir(temp, 0777) != 0 && errno != EEXIST) {
return strdup("Failed to create directory!");
}
return strdup("Directory successfully created.");
}
static void ensure_parent_directory_exists(const char *path_including_file_name) {
if (!path_including_file_name) return;
char *path = strdup(path_including_file_name);
if (!path) return;
char *last_slash = strrchr(path, '/');
if (last_slash) {
*last_slash = '\0';
char *result = make_dir_recursive(path);
free(result);
}
free(path);
}
static void read_file_print_action(const char *name, struct json_object *args) {
(void)name;
if (!args) return;
struct json_object *path;
if (json_object_object_get_ex(args, "path", &path)) {
fprintf(stderr, " -> Reading file: %s\n", json_object_get_string(path));
}
}
static char *read_file_execute(tool_t *self, struct json_object *args) {
(void)self;
struct json_object *path_obj;
if (!json_object_object_get_ex(args, "path", &path_obj)) {
return strdup("Error: missing 'path' argument");
}
char *expanded_path = expand_home_directory(json_object_get_string(path_obj));
if (!expanded_path) return strdup("Failed to expand path!");
FILE *fp = fopen(expanded_path, "r");
free(expanded_path);
if (!fp) return strdup("Failed to open file for reading!");
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 = malloc((size_t)size + 1);
if (!content) {
fclose(fp);
return strdup("Memory allocation failed!");
}
size_t read_size = fread(content, 1, (size_t)size, fp);
content[read_size] = '\0';
fclose(fp);
return content;
}
static struct json_object *read_file_get_description(void) {
struct json_object *root = json_object_new_object();
json_object_object_add(root, "type", json_object_new_string("function"));
struct json_object *function = json_object_new_object();
json_object_object_add(function, "name", json_object_new_string("read_file"));
json_object_object_add(function, "description",
json_object_new_string("Reads / opens / loads a file and returns its contents as a string."));
struct json_object *parameters = json_object_new_object();
json_object_object_add(parameters, "type", json_object_new_string("object"));
struct json_object *properties = json_object_new_object();
struct json_object *path = json_object_new_object();
json_object_object_add(path, "type", json_object_new_string("string"));
json_object_object_add(path, "description",
json_object_new_string("Path to the file to read / open / load."));
json_object_object_add(properties, "path", path);
json_object_object_add(parameters, "properties", properties);
struct json_object *required = json_object_new_array();
json_object_array_add(required, json_object_new_string("path"));
json_object_object_add(parameters, "required", required);
json_object_object_add(parameters, "additionalProperties", json_object_new_boolean(0));
json_object_object_add(function, "parameters", parameters);
r_config_handle cfg = r_config_get_instance();
if (r_config_use_strict(cfg)) {
json_object_object_add(function, "strict", json_object_new_boolean(1));
}
json_object_object_add(root, "function", function);
return root;
}
static void write_file_print_action(const char *name, struct json_object *args) {
(void)name;
if (!args) return;
struct json_object *path;
if (json_object_object_get_ex(args, "path", &path)) {
fprintf(stderr, " -> Writing file: %s\n", json_object_get_string(path));
}
}
static char *write_file_execute(tool_t *self, struct json_object *args) {
(void)self;
struct json_object *path_obj, *content_obj;
if (!json_object_object_get_ex(args, "path", &path_obj)) {
return strdup("Error: missing 'path' argument");
}
if (!json_object_object_get_ex(args, "content", &content_obj)) {
return strdup("Error: missing 'content' argument");
}
const char *path = json_object_get_string(path_obj);
const char *content = json_object_get_string(content_obj);
ensure_parent_directory_exists(path);
FILE *fp = fopen(path, "w+");
if (!fp) return strdup("Failed to open file for writing!");
fwrite(content, 1, strlen(content), fp);
fclose(fp);
return strdup("File successfully written.");
}
static struct json_object *write_file_get_description(void) {
struct json_object *root = json_object_new_object();
json_object_object_add(root, "type", json_object_new_string("function"));
struct json_object *function = json_object_new_object();
json_object_object_add(function, "name", json_object_new_string("write_file"));
json_object_object_add(function, "description",
json_object_new_string("Writes / saves / stores content to a file. Content should be in plain format, not json encoded."));
struct json_object *parameters = json_object_new_object();
json_object_object_add(parameters, "type", json_object_new_string("object"));
struct json_object *properties = json_object_new_object();
struct json_object *path = json_object_new_object();
json_object_object_add(path, "type", json_object_new_string("string"));
json_object_object_add(path, "description",
json_object_new_string("Path to the plain file to write / save / store."));
json_object_object_add(properties, "path", path);
struct json_object *content = json_object_new_object();
json_object_object_add(content, "type", json_object_new_string("string"));
json_object_object_add(content, "description",
json_object_new_string("Plain content to write / save / store into the file."));
json_object_object_add(properties, "content", content);
json_object_object_add(parameters, "properties", properties);
struct json_object *required = json_object_new_array();
json_object_array_add(required, json_object_new_string("path"));
json_object_array_add(required, json_object_new_string("content"));
json_object_object_add(parameters, "required", required);
json_object_object_add(parameters, "additionalProperties", json_object_new_boolean(0));
json_object_object_add(function, "parameters", parameters);
r_config_handle cfg = r_config_get_instance();
if (r_config_use_strict(cfg)) {
json_object_object_add(function, "strict", json_object_new_boolean(1));
}
json_object_object_add(root, "function", function);
return root;
}
static void directory_glob_print_action(const char *name, struct json_object *args) {
(void)name;
if (!args) return;
struct json_object *path;
if (json_object_object_get_ex(args, "path", &path)) {
fprintf(stderr, " -> Listing: %s\n", json_object_get_string(path));
}
}
static char *directory_glob_execute(tool_t *self, struct json_object *args) {
(void)self;
struct json_object *path_obj;
if (!json_object_object_get_ex(args, "path", &path_obj)) {
return strdup("Error: missing 'path' argument");
}
char target_dir[PATH_MAX];
strncpy(target_dir, json_object_get_string(path_obj), PATH_MAX - 1);
target_dir[PATH_MAX - 1] = '\0';
if (strcmp(target_dir, ".") == 0) {
target_dir[0] = '*';
target_dir[1] = '\0';
}
glob_t results;
struct stat file_stat;
char mod_time[20], create_time[20];
if (glob(target_dir, GLOB_TILDE, NULL, &results) != 0) {
return strdup("[]");
}
json_object *json_array = json_object_new_array();
for (size_t i = 0; i < results.gl_pathc; i++) {
const char *file_path = results.gl_pathv[i];
if (stat(file_path, &file_stat) == -1) continue;
format_time(file_stat.st_mtime, mod_time, sizeof(mod_time));
format_time(file_stat.st_ctime, create_time, sizeof(create_time));
json_object *json_entry = json_object_new_object();
json_object_object_add(json_entry, "name", json_object_new_string(file_path));
json_object_object_add(json_entry, "modification_date", json_object_new_string(mod_time));
json_object_object_add(json_entry, "creation_date", json_object_new_string(create_time));
json_object_object_add(json_entry, "type", json_object_new_string(get_file_type(&file_stat)));
json_object_object_add(json_entry, "size_bytes", json_object_new_int64(file_stat.st_size));
json_object_array_add(json_array, json_entry);
}
globfree(&results);
char *result = strdup(json_object_to_json_string_ext(json_array, JSON_C_TO_STRING_PRETTY));
json_object_put(json_array);
return result;
}
static struct json_object *directory_glob_get_description(void) {
struct json_object *root = json_object_new_object();
json_object_object_add(root, "type", json_object_new_string("function"));
struct json_object *function = json_object_new_object();
json_object_object_add(function, "name", json_object_new_string("directory_glob"));
json_object_object_add(function, "description",
json_object_new_string("List the contents of a specified directory in glob format. Result is a JSON array containing objects with keys: name, modification_date(iso), creation_date(iso), type, and size_bytes."));
struct json_object *parameters = json_object_new_object();
json_object_object_add(parameters, "type", json_object_new_string("object"));
struct json_object *properties = json_object_new_object();
struct json_object *directory = json_object_new_object();
json_object_object_add(directory, "type", json_object_new_string("string"));
json_object_object_add(directory, "description",
json_object_new_string("Path to the directory to list in glob format."));
json_object_object_add(properties, "path", directory);
json_object_object_add(parameters, "properties", properties);
struct json_object *required = json_object_new_array();
json_object_array_add(required, json_object_new_string("path"));
json_object_object_add(parameters, "required", required);
json_object_object_add(parameters, "additionalProperties", json_object_new_boolean(0));
json_object_object_add(function, "parameters", parameters);
r_config_handle cfg = r_config_get_instance();
if (r_config_use_strict(cfg)) {
json_object_object_add(function, "strict", json_object_new_boolean(1));
}
json_object_object_add(root, "function", function);
return root;
}
static void mkdir_print_action(const char *name, struct json_object *args) {
(void)name;
if (!args) return;
struct json_object *path;
if (json_object_object_get_ex(args, "path", &path)) {
fprintf(stderr, " -> Creating directory: %s\n", json_object_get_string(path));
}
}
static char *mkdir_execute(tool_t *self, struct json_object *args) {
(void)self;
struct json_object *path_obj;
if (!json_object_object_get_ex(args, "path", &path_obj)) {
return strdup("Error: missing 'path' argument");
}
return make_dir_recursive(json_object_get_string(path_obj));
}
static struct json_object *mkdir_get_description(void) {
struct json_object *root = json_object_new_object();
json_object_object_add(root, "type", json_object_new_string("function"));
struct json_object *function = json_object_new_object();
json_object_object_add(function, "name", json_object_new_string("mkdir"));
json_object_object_add(function, "description",
json_object_new_string("Creates a new directory with the specified path."));
struct json_object *parameters = json_object_new_object();
json_object_object_add(parameters, "type", json_object_new_string("object"));
struct json_object *properties = json_object_new_object();
struct json_object *path = json_object_new_object();
json_object_object_add(path, "type", json_object_new_string("string"));
json_object_object_add(path, "description",
json_object_new_string("Path of the directory to create."));
json_object_object_add(properties, "path", path);
json_object_object_add(parameters, "properties", properties);
struct json_object *required = json_object_new_array();
json_object_array_add(required, json_object_new_string("path"));
json_object_object_add(parameters, "required", required);
json_object_object_add(parameters, "additionalProperties", json_object_new_boolean(0));
json_object_object_add(function, "parameters", parameters);
r_config_handle cfg = r_config_get_instance();
if (r_config_use_strict(cfg)) {
json_object_object_add(function, "strict", json_object_new_boolean(1));
}
json_object_object_add(root, "function", function);
return root;
}
static void chdir_print_action(const char *name, struct json_object *args) {
(void)name;
if (!args) return;
struct json_object *path;
if (json_object_object_get_ex(args, "path", &path)) {
fprintf(stderr, " -> Changing directory: %s\n", json_object_get_string(path));
}
}
static char *chdir_execute(tool_t *self, struct json_object *args) {
(void)self;
struct json_object *path_obj;
if (!json_object_object_get_ex(args, "path", &path_obj)) {
return strdup("Error: missing 'path' argument");
}
if (chdir(json_object_get_string(path_obj)) != 0) {
return strdup("Failed to change directory!");
}
return strdup("Directory successfully changed.");
}
static struct json_object *chdir_get_description(void) {
struct json_object *root = json_object_new_object();
json_object_object_add(root, "type", json_object_new_string("function"));
struct json_object *function = json_object_new_object();
json_object_object_add(function, "name", json_object_new_string("chdir"));
json_object_object_add(function, "description",
json_object_new_string("Changes the current working directory to the specified path. Call this function when `cd` is prompted."));
struct json_object *parameters = json_object_new_object();
json_object_object_add(parameters, "type", json_object_new_string("object"));
struct json_object *properties = json_object_new_object();
struct json_object *path = json_object_new_object();
json_object_object_add(path, "type", json_object_new_string("string"));
json_object_object_add(path, "description",
json_object_new_string("Path to change the current working directory to."));
json_object_object_add(properties, "path", path);
json_object_object_add(parameters, "properties", properties);
struct json_object *required = json_object_new_array();
json_object_array_add(required, json_object_new_string("path"));
json_object_object_add(parameters, "required", required);
json_object_object_add(parameters, "additionalProperties", json_object_new_boolean(0));
json_object_object_add(function, "parameters", parameters);
r_config_handle cfg = r_config_get_instance();
if (r_config_use_strict(cfg)) {
json_object_object_add(function, "strict", json_object_new_boolean(1));
}
json_object_object_add(root, "function", function);
return root;
}
static void getpwd_print_action(const char *name, struct json_object *args) {
(void)name;
(void)args;
fprintf(stderr, " -> Getting current directory\n");
}
static char *getpwd_execute(tool_t *self, struct json_object *args) {
(void)self;
(void)args;
char *cwd = malloc(PATH_MAX);
if (!cwd) return strdup("Failed to allocate memory for current working directory!");
if (!getcwd(cwd, PATH_MAX)) {
free(cwd);
return strdup("Failed to get current working directory!");
}
return cwd;
}
static struct json_object *getpwd_get_description(void) {
struct json_object *root = json_object_new_object();
json_object_object_add(root, "type", json_object_new_string("function"));
struct json_object *function = json_object_new_object();
json_object_object_add(function, "name", json_object_new_string("getpwd"));
json_object_object_add(function, "description",
json_object_new_string("Returns the current working directory as a string."));
json_object_object_add(function, "parameters", json_object_new_null());
json_object_object_add(root, "function", function);
return root;
}

236
src/tools/tool_http.c Executable file
View File

@ -0,0 +1,236 @@
// retoor <retoor@molodetz.nl>
#include "tool.h"
#include "http_client.h"
#include "r_config.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <curl/curl.h>
static struct json_object *http_fetch_get_description(void);
static char *http_fetch_execute(tool_t *self, struct json_object *args);
static void http_fetch_print_action(const char *name, struct json_object *args);
static struct json_object *web_search_get_description(void);
static char *web_search_execute(tool_t *self, struct json_object *args);
static void web_search_print_action(const char *name, struct json_object *args);
static struct json_object *web_search_news_get_description(void);
static char *web_search_news_execute(tool_t *self, struct json_object *args);
static const tool_vtable_t http_fetch_vtable = {
.get_description = http_fetch_get_description,
.execute = http_fetch_execute,
.print_action = http_fetch_print_action
};
static const tool_vtable_t web_search_vtable = {
.get_description = web_search_get_description,
.execute = web_search_execute,
.print_action = web_search_print_action
};
static const tool_vtable_t web_search_news_vtable = {
.get_description = web_search_news_get_description,
.execute = web_search_news_execute,
.print_action = web_search_print_action
};
static tool_t http_fetch_tool = { .vtable = &http_fetch_vtable, .name = "http_fetch" };
static tool_t web_search_tool = { .vtable = &web_search_vtable, .name = "web_search" };
static tool_t web_search_news_tool = { .vtable = &web_search_news_vtable, .name = "web_search_news" };
tool_t *tool_http_fetch_create(void) { return &http_fetch_tool; }
tool_t *tool_web_search_create(void) { return &web_search_tool; }
tool_t *tool_web_search_news_create(void) { return &web_search_news_tool; }
static void http_fetch_print_action(const char *name, struct json_object *args) {
(void)name;
if (!args) return;
struct json_object *url;
if (json_object_object_get_ex(args, "url", &url)) {
fprintf(stderr, " -> Fetching URL: %s\n", json_object_get_string(url));
}
}
static char *http_fetch_execute(tool_t *self, struct json_object *args) {
(void)self;
struct json_object *url_obj;
if (!json_object_object_get_ex(args, "url", &url_obj)) {
return strdup("Error: missing 'url' argument");
}
http_client_handle client = http_client_create(NULL);
if (!client) return strdup("Failed to create HTTP client.");
char *response = NULL;
r_status_t status = http_get(client, json_object_get_string(url_obj), &response);
http_client_destroy(client);
if (status != R_SUCCESS || !response) {
return strdup("Failed to fetch URL.");
}
return response;
}
static struct json_object *http_fetch_get_description(void) {
struct json_object *root = json_object_new_object();
json_object_object_add(root, "type", json_object_new_string("function"));
struct json_object *function = json_object_new_object();
json_object_object_add(function, "name", json_object_new_string("http_fetch"));
json_object_object_add(function, "description",
json_object_new_string("Get the contents of a URL."));
struct json_object *parameters = json_object_new_object();
json_object_object_add(parameters, "type", json_object_new_string("object"));
struct json_object *properties = json_object_new_object();
struct json_object *url = json_object_new_object();
json_object_object_add(url, "type", json_object_new_string("string"));
json_object_object_add(url, "description",
json_object_new_string("Fetch URL contents."));
json_object_object_add(properties, "url", url);
json_object_object_add(parameters, "properties", properties);
struct json_object *required = json_object_new_array();
json_object_array_add(required, json_object_new_string("url"));
json_object_object_add(parameters, "required", required);
json_object_object_add(parameters, "additionalProperties", json_object_new_boolean(0));
json_object_object_add(function, "parameters", parameters);
r_config_handle cfg = r_config_get_instance();
if (r_config_use_strict(cfg)) {
json_object_object_add(function, "strict", json_object_new_boolean(1));
}
json_object_object_add(root, "function", function);
return root;
}
static void web_search_print_action(const char *name, struct json_object *args) {
(void)name;
if (!args) return;
struct json_object *query;
if (json_object_object_get_ex(args, "query", &query)) {
fprintf(stderr, " -> Searching web: %s\n", json_object_get_string(query));
}
}
static char *do_web_search(const char *query) {
if (!query) return strdup("Query cannot be NULL.");
char *q_encoded = curl_easy_escape(NULL, query, 0);
if (!q_encoded) return strdup("Failed to encode query.");
char url[4096];
snprintf(url, sizeof(url), "https://static.molodetz.nl/search.cgi?query=%s", q_encoded);
curl_free(q_encoded);
http_client_handle client = http_client_create(NULL);
if (!client) return strdup("Failed to create HTTP client.");
char *response = NULL;
r_status_t status = http_get(client, url, &response);
http_client_destroy(client);
if (status != R_SUCCESS || !response) {
return strdup("Failed to search.");
}
return response;
}
static char *web_search_execute(tool_t *self, struct json_object *args) {
(void)self;
struct json_object *query_obj;
if (!json_object_object_get_ex(args, "query", &query_obj)) {
return strdup("Error: missing 'query' argument");
}
return do_web_search(json_object_get_string(query_obj));
}
static struct json_object *web_search_get_description(void) {
struct json_object *root = json_object_new_object();
json_object_object_add(root, "type", json_object_new_string("function"));
struct json_object *function = json_object_new_object();
json_object_object_add(function, "name", json_object_new_string("web_search"));
json_object_object_add(function, "description",
json_object_new_string("Searches for information based on a query using search engines like google."));
struct json_object *parameters = json_object_new_object();
json_object_object_add(parameters, "type", json_object_new_string("object"));
struct json_object *properties = json_object_new_object();
struct json_object *query = json_object_new_object();
json_object_object_add(query, "type", json_object_new_string("string"));
json_object_object_add(query, "description",
json_object_new_string("The url encoded query string to search for information."));
json_object_object_add(properties, "query", query);
json_object_object_add(parameters, "properties", properties);
struct json_object *required = json_object_new_array();
json_object_array_add(required, json_object_new_string("query"));
json_object_object_add(parameters, "required", required);
json_object_object_add(parameters, "additionalProperties", json_object_new_boolean(0));
json_object_object_add(function, "parameters", parameters);
r_config_handle cfg = r_config_get_instance();
if (r_config_use_strict(cfg)) {
json_object_object_add(function, "strict", json_object_new_boolean(1));
}
json_object_object_add(root, "function", function);
return root;
}
static char *web_search_news_execute(tool_t *self, struct json_object *args) {
(void)self;
struct json_object *query_obj;
if (!json_object_object_get_ex(args, "query", &query_obj)) {
return strdup("Error: missing 'query' argument");
}
return do_web_search(json_object_get_string(query_obj));
}
static struct json_object *web_search_news_get_description(void) {
struct json_object *root = json_object_new_object();
json_object_object_add(root, "type", json_object_new_string("function"));
struct json_object *function = json_object_new_object();
json_object_object_add(function, "name", json_object_new_string("web_search_news"));
json_object_object_add(function, "description",
json_object_new_string("Searches for news articles based on a query."));
struct json_object *parameters = json_object_new_object();
json_object_object_add(parameters, "type", json_object_new_string("object"));
struct json_object *properties = json_object_new_object();
struct json_object *query = json_object_new_object();
json_object_object_add(query, "type", json_object_new_string("string"));
json_object_object_add(query, "description",
json_object_new_string("The url encoded query string to search for news articles."));
json_object_object_add(properties, "query", query);
json_object_object_add(parameters, "properties", properties);
struct json_object *required = json_object_new_array();
json_object_array_add(required, json_object_new_string("query"));
json_object_object_add(parameters, "required", required);
json_object_object_add(parameters, "additionalProperties", json_object_new_boolean(0));
json_object_object_add(function, "parameters", parameters);
r_config_handle cfg = r_config_get_instance();
if (r_config_use_strict(cfg)) {
json_object_object_add(function, "strict", json_object_new_boolean(1));
}
json_object_object_add(root, "function", function);
return root;
}

190
src/tools/tool_indexer.c Executable file
View File

@ -0,0 +1,190 @@
// retoor <retoor@molodetz.nl>
#include "tool.h"
#include "r_config.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dirent.h>
#include <sys/stat.h>
#include <limits.h>
static struct json_object *index_source_directory_get_description(void);
static char *index_source_directory_execute(tool_t *self, struct json_object *args);
static void index_source_directory_print_action(const char *name, struct json_object *args);
static const tool_vtable_t index_source_directory_vtable = {
.get_description = index_source_directory_get_description,
.execute = index_source_directory_execute,
.print_action = index_source_directory_print_action
};
static tool_t index_source_directory_tool = {
.vtable = &index_source_directory_vtable,
.name = "index_source_directory"
};
tool_t *tool_index_source_directory_create(void) {
return &index_source_directory_tool;
}
static void index_source_directory_print_action(const char *name, struct json_object *args) {
(void)name;
if (!args) return;
struct json_object *path;
if (json_object_object_get_ex(args, "path", &path)) {
fprintf(stderr, " -> Indexing directory: %s\n", json_object_get_string(path));
}
}
static int is_source_file(const char *filename) {
const char *extensions[] = {
".c", ".h", ".cpp", ".hpp", ".cc", ".cxx",
".py", ".js", ".ts", ".java", ".go", ".rs",
".rb", ".php", ".swift", ".kt", ".scala",
".sh", ".bash", ".zsh", ".fish",
".json", ".xml", ".yaml", ".yml", ".toml",
".md", ".txt", ".rst",
".html", ".css", ".scss", ".sass",
".sql", ".graphql",
".lua", ".pl", ".pm",
".r", ".R", ".jl",
".vim", ".el", ".clj", ".cljs",
".ex", ".exs", ".erl", ".hrl",
".hs", ".lhs", ".ml", ".mli",
".f", ".f90", ".f95",
".asm", ".s",
".makefile", ".mk", ".cmake",
NULL
};
const char *ext = strrchr(filename, '.');
if (!ext) {
if (strcmp(filename, "Makefile") == 0 ||
strcmp(filename, "makefile") == 0 ||
strcmp(filename, "Dockerfile") == 0 ||
strcmp(filename, "Jenkinsfile") == 0 ||
strcmp(filename, "Vagrantfile") == 0 ||
strcmp(filename, "Gemfile") == 0 ||
strcmp(filename, "Rakefile") == 0) {
return 1;
}
return 0;
}
for (int i = 0; extensions[i]; i++) {
if (strcasecmp(ext, extensions[i]) == 0) return 1;
}
return 0;
}
static void index_directory_recursive(const char *path, struct json_object *result_array) {
DIR *dir = opendir(path);
if (!dir) return;
struct dirent *entry;
while ((entry = readdir(dir))) {
if (entry->d_name[0] == '.') continue;
if (strcmp(entry->d_name, "node_modules") == 0) continue;
if (strcmp(entry->d_name, "__pycache__") == 0) continue;
if (strcmp(entry->d_name, ".git") == 0) continue;
if (strcmp(entry->d_name, "build") == 0) continue;
if (strcmp(entry->d_name, "dist") == 0) continue;
if (strcmp(entry->d_name, "vendor") == 0) continue;
char full_path[PATH_MAX];
snprintf(full_path, sizeof(full_path), "%s/%s", path, entry->d_name);
struct stat st;
if (stat(full_path, &st) != 0) continue;
if (S_ISDIR(st.st_mode)) {
index_directory_recursive(full_path, result_array);
} else if (S_ISREG(st.st_mode) && is_source_file(entry->d_name)) {
FILE *fp = fopen(full_path, "r");
if (!fp) continue;
fseek(fp, 0, SEEK_END);
long size = ftell(fp);
rewind(fp);
if (size > 100000) {
fclose(fp);
continue;
}
char *content = malloc(size + 1);
if (!content) {
fclose(fp);
continue;
}
size_t read_size = fread(content, 1, size, fp);
content[read_size] = '\0';
fclose(fp);
struct json_object *file_obj = json_object_new_object();
json_object_object_add(file_obj, "path", json_object_new_string(full_path));
json_object_object_add(file_obj, "content", json_object_new_string(content));
json_object_array_add(result_array, file_obj);
free(content);
}
}
closedir(dir);
}
static char *index_source_directory_execute(tool_t *self, struct json_object *args) {
(void)self;
struct json_object *path_obj;
if (!json_object_object_get_ex(args, "path", &path_obj)) {
return strdup("Error: missing 'path' argument");
}
const char *path = json_object_get_string(path_obj);
struct json_object *result_array = json_object_new_array();
index_directory_recursive(path, result_array);
char *result = strdup(json_object_to_json_string_ext(result_array, JSON_C_TO_STRING_PRETTY));
json_object_put(result_array);
return result;
}
static struct json_object *index_source_directory_get_description(void) {
struct json_object *root = json_object_new_object();
json_object_object_add(root, "type", json_object_new_string("function"));
struct json_object *function = json_object_new_object();
json_object_object_add(function, "name", json_object_new_string("index_source_directory"));
json_object_object_add(function, "description",
json_object_new_string("Returns a JSON array containing every source file with it's contents from given directory. Execute with '.' for current directory."));
struct json_object *parameters = json_object_new_object();
json_object_object_add(parameters, "type", json_object_new_string("object"));
struct json_object *properties = json_object_new_object();
struct json_object *path = json_object_new_object();
json_object_object_add(path, "type", json_object_new_string("string"));
json_object_object_add(path, "description",
json_object_new_string("Path to index and retreive files from."));
json_object_object_add(properties, "path", path);
json_object_object_add(parameters, "properties", properties);
struct json_object *required = json_object_new_array();
json_object_array_add(required, json_object_new_string("path"));
json_object_object_add(parameters, "required", required);
json_object_object_add(parameters, "additionalProperties", json_object_new_boolean(0));
json_object_object_add(function, "parameters", parameters);
r_config_handle cfg = r_config_get_instance();
if (r_config_use_strict(cfg)) {
json_object_object_add(function, "strict", json_object_new_boolean(1));
}
json_object_object_add(root, "function", function);
return root;
}

103
src/tools/tool_python.c Executable file
View File

@ -0,0 +1,103 @@
// retoor <retoor@molodetz.nl>
#include "tool.h"
#include "r_config.h"
#include "bash_executor.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
static struct json_object *python_execute_get_description(void);
static char *python_execute_execute(tool_t *self, struct json_object *args);
static void python_execute_print_action(const char *name, struct json_object *args);
static const tool_vtable_t python_execute_vtable = {
.get_description = python_execute_get_description,
.execute = python_execute_execute,
.print_action = python_execute_print_action
};
static tool_t python_execute_tool = {
.vtable = &python_execute_vtable,
.name = "python_execute"
};
tool_t *tool_python_execute_create(void) {
return &python_execute_tool;
}
static void python_execute_print_action(const char *name, struct json_object *args) {
(void)name;
(void)args;
fprintf(stderr, " -> Executing Python code\n");
}
static char *python_execute_execute(tool_t *self, struct json_object *args) {
(void)self;
struct json_object *source_obj;
if (!json_object_object_get_ex(args, "source", &source_obj)) {
return strdup("Error: missing 'source' argument");
}
const char *source_code = json_object_get_string(source_obj);
char *output = NULL;
int fd = -1;
char tmp_file[] = "/tmp/r_python_tool_XXXXXX.py";
fd = mkstemps(tmp_file, 3);
if (fd == -1) {
return strdup("Failed to create temporary file for Python code.");
}
FILE *fp = fdopen(fd, "w");
if (!fp) {
close(fd);
unlink(tmp_file);
return strdup("Failed to open temporary file for writing.");
}
fwrite(source_code, 1, strlen(source_code), fp);
fclose(fp);
char command[4096];
snprintf(command, sizeof(command), "python3 '%s'", tmp_file);
output = r_bash_execute(command, false);
unlink(tmp_file);
return output;
}
static struct json_object *python_execute_get_description(void) {
struct json_object *root = json_object_new_object();
json_object_object_add(root, "type", json_object_new_string("function"));
struct json_object *function = json_object_new_object();
json_object_object_add(function, "name", json_object_new_string("python_execute"));
json_object_object_add(function, "description",
json_object_new_string("Executes Python source code using the python3 interpreter and returns stdout/stderr."));
struct json_object *parameters = json_object_new_object();
json_object_object_add(parameters, "type", json_object_new_string("object"));
struct json_object *properties = json_object_new_object();
struct json_object *source = json_object_new_object();
json_object_object_add(source, "type", json_object_new_string("string"));
json_object_object_add(source, "description",
json_object_new_string("Python source code to execute."));
json_object_object_add(properties, "source", source);
json_object_object_add(parameters, "properties", properties);
struct json_object *required = json_object_new_array();
json_object_array_add(required, json_object_new_string("source"));
json_object_object_add(parameters, "required", required);
json_object_object_add(parameters, "additionalProperties", json_object_new_boolean(0));
json_object_object_add(function, "parameters", parameters);
json_object_object_add(root, "function", function);
return root;
}

160
src/tools/tool_terminal.c Executable file
View File

@ -0,0 +1,160 @@
// retoor <retoor@molodetz.nl>
#include "tool.h"
#include "r_config.h"
#include "bash_executor.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static struct json_object *terminal_get_description(void);
static char *terminal_execute(tool_t *self, struct json_object *args);
static void terminal_print_action(const char *name, struct json_object *args);
static struct json_object *terminal_interactive_get_description(void);
static char *terminal_interactive_execute(tool_t *self, struct json_object *args);
static const tool_vtable_t terminal_vtable = {
.get_description = terminal_get_description,
.execute = terminal_execute,
.print_action = terminal_print_action
};
static const tool_vtable_t terminal_interactive_vtable = {
.get_description = terminal_interactive_get_description,
.execute = terminal_interactive_execute,
.print_action = terminal_print_action
};
static tool_t terminal_tool = {
.vtable = &terminal_vtable,
.name = "linux_terminal_execute"
};
static tool_t terminal_interactive_tool = {
.vtable = &terminal_interactive_vtable,
.name = "linux_terminal_execute_interactive"
};
tool_t *tool_terminal_create(void) {
return &terminal_tool;
}
tool_t *tool_terminal_interactive_create(void) {
return &terminal_interactive_tool;
}
static void terminal_print_action(const char *name, struct json_object *args) {
if (!args) {
fprintf(stderr, " -> %s\n", name);
return;
}
struct json_object *cmd;
if (json_object_object_get_ex(args, "command", &cmd)) {
if (strcmp(name, "linux_terminal_execute_interactive") == 0) {
fprintf(stderr, " -> Running interactive: %s\n", json_object_get_string(cmd));
} else {
fprintf(stderr, " -> Running command: %s\n", json_object_get_string(cmd));
}
}
}
static char *terminal_execute(tool_t *self, struct json_object *args) {
(void)self;
struct json_object *cmd_obj;
if (!json_object_object_get_ex(args, "command", &cmd_obj)) {
return strdup("Error: missing 'command' argument");
}
const char *command = json_object_get_string(cmd_obj);
return r_bash_execute(command, false);
}
static char *terminal_interactive_execute(tool_t *self, struct json_object *args) {
(void)self;
struct json_object *cmd_obj;
if (!json_object_object_get_ex(args, "command", &cmd_obj)) {
return strdup("Error: missing 'command' argument");
}
const char *command = json_object_get_string(cmd_obj);
return r_bash_execute(command, true);
}
static struct json_object *terminal_get_description(void) {
struct json_object *root = json_object_new_object();
json_object_object_add(root, "type", json_object_new_string("function"));
struct json_object *function = json_object_new_object();
json_object_object_add(function, "name",
json_object_new_string("linux_terminal_execute"));
json_object_object_add(function, "description",
json_object_new_string("Execute a linux_terminal command on user terminal."));
struct json_object *parameters = json_object_new_object();
json_object_object_add(parameters, "type", json_object_new_string("object"));
struct json_object *properties = json_object_new_object();
struct json_object *cmd = json_object_new_object();
json_object_object_add(cmd, "type", json_object_new_string("string"));
json_object_object_add(cmd, "description",
json_object_new_string("Bash command to execute."));
json_object_object_add(properties, "command", cmd);
json_object_object_add(parameters, "properties", properties);
struct json_object *required = json_object_new_array();
json_object_array_add(required, json_object_new_string("command"));
json_object_object_add(parameters, "required", required);
json_object_object_add(parameters, "additionalProperties", json_object_new_boolean(0));
json_object_object_add(function, "parameters", parameters);
r_config_handle cfg = r_config_get_instance();
if (r_config_use_strict(cfg)) {
json_object_object_add(function, "strict", json_object_new_boolean(1));
}
json_object_object_add(root, "function", function);
return root;
}
static struct json_object *terminal_interactive_get_description(void) {
struct json_object *root = json_object_new_object();
json_object_object_add(root, "type", json_object_new_string("function"));
struct json_object *function = json_object_new_object();
json_object_object_add(function, "name",
json_object_new_string("linux_terminal_execute_interactive"));
json_object_object_add(function, "description",
json_object_new_string("Executes interactive terminal for user. You will not be able to read the result. Do not use if you need to know output."));
struct json_object *parameters = json_object_new_object();
json_object_object_add(parameters, "type", json_object_new_string("object"));
struct json_object *properties = json_object_new_object();
struct json_object *cmd = json_object_new_object();
json_object_object_add(cmd, "type", json_object_new_string("string"));
json_object_object_add(cmd, "description",
json_object_new_string("Executable with parameters to execute interactively."));
json_object_object_add(properties, "command", cmd);
json_object_object_add(parameters, "properties", properties);
struct json_object *required = json_object_new_array();
json_object_array_add(required, json_object_new_string("command"));
json_object_object_add(parameters, "required", required);
json_object_object_add(parameters, "additionalProperties", json_object_new_boolean(0));
json_object_object_add(function, "parameters", parameters);
r_config_handle cfg = r_config_get_instance();
if (r_config_use_strict(cfg)) {
json_object_object_add(function, "strict", json_object_new_boolean(1));
}
json_object_object_add(root, "function", function);
return root;
}

56
src/tools/tools_init.c Executable file
View File

@ -0,0 +1,56 @@
// retoor <retoor@molodetz.nl>
#include "tool.h"
#include <stdlib.h>
extern tool_t *tool_terminal_create(void);
extern tool_t *tool_terminal_interactive_create(void);
extern tool_t *tool_read_file_create(void);
extern tool_t *tool_write_file_create(void);
extern tool_t *tool_directory_glob_create(void);
extern tool_t *tool_mkdir_create(void);
extern tool_t *tool_chdir_create(void);
extern tool_t *tool_getpwd_create(void);
extern tool_t *tool_http_fetch_create(void);
extern tool_t *tool_web_search_create(void);
extern tool_t *tool_web_search_news_create(void);
extern tool_t *tool_db_get_create(void);
extern tool_t *tool_db_set_create(void);
extern tool_t *tool_db_query_create(void);
extern tool_t *tool_python_execute_create(void);
extern tool_t *tool_index_source_directory_create(void);
static tool_registry_t *global_registry = NULL;
tool_registry_t *tools_get_registry(void) {
if (global_registry) return global_registry;
global_registry = tool_registry_create();
if (!global_registry) return NULL;
tool_registry_register(global_registry, tool_terminal_create());
tool_registry_register(global_registry, tool_terminal_interactive_create());
tool_registry_register(global_registry, tool_read_file_create());
tool_registry_register(global_registry, tool_write_file_create());
tool_registry_register(global_registry, tool_directory_glob_create());
tool_registry_register(global_registry, tool_mkdir_create());
tool_registry_register(global_registry, tool_chdir_create());
tool_registry_register(global_registry, tool_getpwd_create());
tool_registry_register(global_registry, tool_http_fetch_create());
tool_registry_register(global_registry, tool_web_search_create());
tool_registry_register(global_registry, tool_web_search_news_create());
tool_registry_register(global_registry, tool_db_get_create());
tool_registry_register(global_registry, tool_db_set_create());
tool_registry_register(global_registry, tool_db_query_create());
tool_registry_register(global_registry, tool_python_execute_create());
tool_registry_register(global_registry, tool_index_source_directory_create());
return global_registry;
}
void tools_registry_shutdown(void) {
if (global_registry) {
tool_registry_destroy(global_registry);
global_registry = NULL;
}
}

0
tools.h Normal file → Executable file
View File