diff --git a/AppRun b/AppRun deleted file mode 100755 index 53bf7c4..0000000 --- a/AppRun +++ /dev/null @@ -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" "$@" diff --git a/Makefile b/Makefile index 2e837ee..a95106d 100755 --- a/Makefile +++ b/Makefile @@ -1,75 +1,94 @@ -all: build build_rpylib run build_mingw +# retoor -# 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: - -@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 - ./collect_so_files.sh - #./prepare_app_image AppImage/usr/bin/r AppImage - appimagetool-x86_64.AppImage AppImage - mv r-x86_64.AppImage r +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 -publish: - curl -OJ https://retoor.molodetz.nl/api/packages/retoor/generic/publish/1.0.0/publish - chmod +x publish - ./publish r +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 - publish rpylib.so -# New MinGW build target -build_mingw: - $(MINGW_CC) main.c $(MINGW_CFLAGS) -o r.exe - publish r.exe - -run: +run: build ./r --verbose -run_free: - ./rf --verbose +install: + cp ./r /usr/local/bin/r -run_rd: - ./rd --verbose - -run_mingw: - ./r.exe --verbose +clean: + rm -rf $(BUILDDIR) $(BINDIR) + rm -f r +appimage: build + -@rm -rf AppImage + 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 $(BINDIR)/r AppImage/usr/bin/r + ./collect_so_files.sh + appimagetool-x86_64.AppImage AppImage + mv r-x86_64.AppImage r 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 diff --git a/agent.h b/agent.h old mode 100644 new mode 100755 diff --git a/auth.h b/auth.h index d2b39f2..33763e1 100755 --- a/auth.h +++ b/auth.h @@ -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; diff --git a/chat.h b/chat.h old mode 100644 new mode 100755 diff --git a/collect_so_files.sh b/collect_so_files.sh deleted file mode 100755 index 930375a..0000000 --- a/collect_so_files.sh +++ /dev/null @@ -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 binary’s dependencies -ldd "$BINARY" | grep -o '/[^ ]\+' | while read -r lib; do - copy_with_deps "$lib" -done - diff --git a/compose.yml b/compose.yml deleted file mode 100755 index 251f6d3..0000000 --- a/compose.yml +++ /dev/null @@ -1,10 +0,0 @@ -services: - shell: - build: . - command: sh - tty: true - stdin_open: true - working_dir: /home - volumes: - - ./:/home - diff --git a/database.db b/database.db deleted file mode 100755 index 1c6819f..0000000 Binary files a/database.db and /dev/null differ diff --git a/http_curl.h b/http_curl.h old mode 100644 new mode 100755 diff --git a/include/agent.h b/include/agent.h new file mode 100755 index 0000000..98b42da --- /dev/null +++ b/include/agent.h @@ -0,0 +1,38 @@ +// retoor + +#ifndef R_AGENT_H +#define R_AGENT_H + +#include "messages.h" +#include "r_error.h" +#include + +#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 diff --git a/include/bash_executor.h b/include/bash_executor.h new file mode 100644 index 0000000..bb09e57 --- /dev/null +++ b/include/bash_executor.h @@ -0,0 +1,9 @@ +// retoor +#ifndef R_BASH_EXECUTOR_H +#define R_BASH_EXECUTOR_H + +#include + +char *r_bash_execute(const char *command, bool interactive); + +#endif diff --git a/include/db.h b/include/db.h new file mode 100755 index 0000000..8ea753b --- /dev/null +++ b/include/db.h @@ -0,0 +1,24 @@ +// retoor + +#ifndef R_DB_H +#define R_DB_H + +#include "r_error.h" +#include + +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 diff --git a/include/http_client.h b/include/http_client.h new file mode 100755 index 0000000..ffc84d9 --- /dev/null +++ b/include/http_client.h @@ -0,0 +1,26 @@ +// retoor + +#ifndef R_HTTP_CLIENT_H +#define R_HTTP_CLIENT_H + +#include "r_error.h" +#include + +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 diff --git a/include/messages.h b/include/messages.h new file mode 100755 index 0000000..24da135 --- /dev/null +++ b/include/messages.h @@ -0,0 +1,33 @@ +// retoor + +#ifndef R_MESSAGES_H +#define R_MESSAGES_H + +#include "r_error.h" +#include +#include + +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 diff --git a/include/r_config.h b/include/r_config.h new file mode 100755 index 0000000..23bc1ce --- /dev/null +++ b/include/r_config.h @@ -0,0 +1,33 @@ +// retoor + +#ifndef R_CONFIG_H +#define R_CONFIG_H + +#include + +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 diff --git a/include/r_error.h b/include/r_error.h new file mode 100755 index 0000000..13defdc --- /dev/null +++ b/include/r_error.h @@ -0,0 +1,39 @@ +// retoor + +#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 diff --git a/include/tool.h b/include/tool.h new file mode 100755 index 0000000..d523ccb --- /dev/null +++ b/include/tool.h @@ -0,0 +1,42 @@ +// retoor + +#ifndef R_TOOL_H +#define R_TOOL_H + +#include "r_error.h" +#include +#include +#include + +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 diff --git a/main.c b/main.c old mode 100644 new mode 100755 index b7616dc..974f0c1 --- a/main.c +++ b/main.c @@ -188,6 +188,11 @@ static void repl(void) { if (!strncmp(line, "!dump", 5)) { 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(); @@ -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; diff --git a/markdown.h b/markdown.h old mode 100644 new mode 100755 diff --git a/r.desktop b/r.desktop deleted file mode 100755 index 09a1745..0000000 --- a/r.desktop +++ /dev/null @@ -1,6 +0,0 @@ -[Desktop Entry] -Name=r -Exec=usr/bin/r -Type=Application -Icon=r -Categories=Utility; diff --git a/r.h b/r.h old mode 100644 new mode 100755 index 6d01732..464a31d --- a/r.h +++ b/r.h @@ -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() { diff --git a/r.png b/r.png deleted file mode 100755 index d5a8df0..0000000 Binary files a/r.png and /dev/null differ diff --git a/review.md b/review.md deleted file mode 100755 index 9a3cf38..0000000 --- a/review.md +++ /dev/null @@ -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. \ No newline at end of file diff --git a/rpylib.so b/rpylib.so deleted file mode 100755 index 91c3308..0000000 Binary files a/rpylib.so and /dev/null differ diff --git a/screenshot1.png b/screenshot1.png deleted file mode 100755 index 8a7dd68..0000000 Binary files a/screenshot1.png and /dev/null differ diff --git a/setup.py b/setup.py deleted file mode 100755 index e5bfaf7..0000000 --- a/setup.py +++ /dev/null @@ -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], -) diff --git a/src/agent.c b/src/agent.c new file mode 100755 index 0000000..b18b7d8 --- /dev/null +++ b/src/agent.c @@ -0,0 +1,415 @@ +// retoor + +#include "agent.h" +#include "http_client.h" +#include "r_config.h" +#include "tool.h" +#include +#include +#include +#include +#include + +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; +} diff --git a/src/bash_executor.c b/src/bash_executor.c new file mode 100644 index 0000000..20f1c44 --- /dev/null +++ b/src/bash_executor.c @@ -0,0 +1,106 @@ +// retoor + +#define _GNU_SOURCE +#include "bash_executor.h" +#include +#include +#include +#include +#include +#include + +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; +} diff --git a/src/db.c b/src/db.c new file mode 100755 index 0000000..5e39a9b --- /dev/null +++ b/src/db.c @@ -0,0 +1,308 @@ +// retoor + +#include "db.h" +#include "r_config.h" +#include +#include +#include +#include +#include + +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; +} diff --git a/src/http_client.c b/src/http_client.c new file mode 100755 index 0000000..98fd179 --- /dev/null +++ b/src/http_client.c @@ -0,0 +1,313 @@ +// retoor + +#include "http_client.h" +#include +#include +#include +#include +#include +#include +#include +#include + +#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; +} diff --git a/src/main.c b/src/main.c new file mode 100755 index 0000000..74a1cc5 --- /dev/null +++ b/src/main.c @@ -0,0 +1,423 @@ +// retoor + +#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 +#include +#include +#include +#include +#include +#include +#include +#include + +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; +} diff --git a/src/messages.c b/src/messages.c new file mode 100755 index 0000000..0e7ca6a --- /dev/null +++ b/src/messages.c @@ -0,0 +1,235 @@ +// retoor + +#include "messages.h" +#include "db.h" +#include +#include +#include +#include +#include + +#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); +} diff --git a/src/r_config.c b/src/r_config.c new file mode 100755 index 0000000..7c42cc4 --- /dev/null +++ b/src/r_config.c @@ -0,0 +1,181 @@ +// retoor + +#include "r_config.h" +#include +#include +#include +#include + +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; +} diff --git a/src/r_error.c b/src/r_error.c new file mode 100755 index 0000000..5806e30 --- /dev/null +++ b/src/r_error.c @@ -0,0 +1,35 @@ +// retoor + +#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]; +} diff --git a/src/tool_registry.c b/src/tool_registry.c new file mode 100755 index 0000000..17f838f --- /dev/null +++ b/src/tool_registry.c @@ -0,0 +1,157 @@ +// retoor + +#include "tool.h" +#include +#include +#include + +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; +} diff --git a/src/tools/tool_db.c b/src/tools/tool_db.c new file mode 100755 index 0000000..998b92b --- /dev/null +++ b/src/tools/tool_db.c @@ -0,0 +1,270 @@ +// retoor + +#include "tool.h" +#include "db.h" +#include "r_config.h" +#include +#include +#include + +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; +} diff --git a/src/tools/tool_file.c b/src/tools/tool_file.c new file mode 100755 index 0000000..932578f --- /dev/null +++ b/src/tools/tool_file.c @@ -0,0 +1,563 @@ +// retoor + +#include "tool.h" +#include "r_config.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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; +} diff --git a/src/tools/tool_http.c b/src/tools/tool_http.c new file mode 100755 index 0000000..aa041a7 --- /dev/null +++ b/src/tools/tool_http.c @@ -0,0 +1,236 @@ +// retoor + +#include "tool.h" +#include "http_client.h" +#include "r_config.h" +#include +#include +#include +#include + +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; +} diff --git a/src/tools/tool_indexer.c b/src/tools/tool_indexer.c new file mode 100755 index 0000000..86a549e --- /dev/null +++ b/src/tools/tool_indexer.c @@ -0,0 +1,190 @@ +// retoor + +#include "tool.h" +#include "r_config.h" +#include +#include +#include +#include +#include +#include + +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; +} diff --git a/src/tools/tool_python.c b/src/tools/tool_python.c new file mode 100755 index 0000000..55f7cc2 --- /dev/null +++ b/src/tools/tool_python.c @@ -0,0 +1,103 @@ +// retoor + +#include "tool.h" +#include "r_config.h" +#include "bash_executor.h" +#include +#include +#include +#include + +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; +} \ No newline at end of file diff --git a/src/tools/tool_terminal.c b/src/tools/tool_terminal.c new file mode 100755 index 0000000..50a1a9c --- /dev/null +++ b/src/tools/tool_terminal.c @@ -0,0 +1,160 @@ +// retoor + +#include "tool.h" +#include "r_config.h" +#include "bash_executor.h" +#include +#include +#include + +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; +} \ No newline at end of file diff --git a/src/tools/tools_init.c b/src/tools/tools_init.c new file mode 100755 index 0000000..ea4d8a9 --- /dev/null +++ b/src/tools/tools_init.c @@ -0,0 +1,56 @@ +// retoor + +#include "tool.h" +#include + +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; + } +} diff --git a/tools.h b/tools.h old mode 100644 new mode 100755