Update.
This commit is contained in:
parent
930e0889fd
commit
3f8e1ec53a
4
AppRun
4
AppRun
@ -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" "$@"
|
|
||||||
121
Makefile
121
Makefile
@ -1,75 +1,94 @@
|
|||||||
all: build build_rpylib run build_mingw
|
# retoor <retoor@molodetz.nl>
|
||||||
|
|
||||||
# Variables for compiler and flags
|
|
||||||
CC = gcc
|
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
|
SRCDIR = src
|
||||||
MINGW_CC = x86_64-w64-mingw32-gcc # Change to x86_64-w64-mingw32-gcc for 64-bit
|
TOOLSDIR = src/tools
|
||||||
MINGW_CFLAGS = -Ofast -Werror -Wall -lreadline -lcurl -lssl -lcrypto -ljson-c -lm -lglob
|
BUILDDIR = build
|
||||||
|
BINDIR = bin
|
||||||
|
|
||||||
# Targets
|
SRC_CORE = $(SRCDIR)/r_error.c \
|
||||||
build: publish
|
$(SRCDIR)/r_config.c \
|
||||||
mkdir -p bin
|
$(SRCDIR)/tool_registry.c \
|
||||||
$(CC) main.c $(CFLAGS) -o bin/r
|
$(SRCDIR)/db.c \
|
||||||
cp bin/r r
|
$(SRCDIR)/http_client.c \
|
||||||
publish r
|
$(SRCDIR)/messages.c \
|
||||||
|
$(SRCDIR)/agent.c \
|
||||||
|
$(SRCDIR)/bash_executor.c \
|
||||||
|
$(SRCDIR)/main.c
|
||||||
|
|
||||||
appimage:
|
SRC_TOOLS = $(TOOLSDIR)/tools_init.c \
|
||||||
-@rm -rf AppImage
|
$(TOOLSDIR)/tool_terminal.c \
|
||||||
mkdir -p AppImage
|
$(TOOLSDIR)/tool_file.c \
|
||||||
mkdir -p AppImage/usr
|
$(TOOLSDIR)/tool_db.c \
|
||||||
mkdir -p AppImage/usr/bin
|
$(TOOLSDIR)/tool_http.c \
|
||||||
mkdir -p AppImage/lib
|
$(TOOLSDIR)/tool_python.c \
|
||||||
cp AppRun AppImage/AppRun
|
$(TOOLSDIR)/tool_indexer.c
|
||||||
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
|
|
||||||
|
|
||||||
publish:
|
SRC = $(SRC_CORE) $(SRC_TOOLS)
|
||||||
curl -OJ https://retoor.molodetz.nl/api/packages/retoor/generic/publish/1.0.0/publish
|
|
||||||
chmod +x publish
|
|
||||||
./publish r
|
|
||||||
|
|
||||||
|
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:
|
build_rpylib:
|
||||||
$(CC) -shared -o rpylib.so -fPIC rpylib.c -lpython3.12 `python3-config --includes` -I/usr/include/CL -ljson-c -lcurl -lsqlite3
|
$(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
|
run: build
|
||||||
build_mingw:
|
|
||||||
$(MINGW_CC) main.c $(MINGW_CFLAGS) -o r.exe
|
|
||||||
publish r.exe
|
|
||||||
|
|
||||||
run:
|
|
||||||
./r --verbose
|
./r --verbose
|
||||||
|
|
||||||
run_free:
|
install:
|
||||||
./rf --verbose
|
cp ./r /usr/local/bin/r
|
||||||
|
|
||||||
run_rd:
|
clean:
|
||||||
./rd --verbose
|
rm -rf $(BUILDDIR) $(BINDIR)
|
||||||
|
rm -f r
|
||||||
run_mingw:
|
|
||||||
./r.exe --verbose
|
|
||||||
|
|
||||||
|
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: docker_make docker_run
|
||||||
|
|
||||||
docker_make:
|
docker_make:
|
||||||
docker build -t r .
|
docker build -t r .
|
||||||
|
|
||||||
docker_run:
|
docker_run:
|
||||||
docker run -v .:/app --rm -it r
|
docker run -v .:/app --rm -it r
|
||||||
|
|
||||||
build_deb:
|
build_deb:
|
||||||
dpkg-deb --build r_package
|
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
|
|
||||||
|
|||||||
10
auth.h
10
auth.h
@ -30,6 +30,11 @@ void auth_init() {
|
|||||||
auth_type = AUTH_TYPE_API_KEY;
|
auth_type = AUTH_TYPE_API_KEY;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
api_key = getenv("OPENROUTER_API_KEY");
|
||||||
|
if (api_key) {
|
||||||
|
auth_type = AUTH_TYPE_API_KEY;
|
||||||
|
return;
|
||||||
|
}
|
||||||
api_key = getenv("OPENAI_API_KEY");
|
api_key = getenv("OPENAI_API_KEY");
|
||||||
if (api_key) {
|
if (api_key) {
|
||||||
auth_type = AUTH_TYPE_API_KEY;
|
auth_type = AUTH_TYPE_API_KEY;
|
||||||
@ -49,6 +54,11 @@ const char *resolve_api_key() {
|
|||||||
auth_type = AUTH_TYPE_API_KEY;
|
auth_type = AUTH_TYPE_API_KEY;
|
||||||
return 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");
|
api_key = getenv("OPENAI_API_KEY");
|
||||||
if (api_key) {
|
if (api_key) {
|
||||||
auth_type = AUTH_TYPE_API_KEY;
|
auth_type = AUTH_TYPE_API_KEY;
|
||||||
|
|||||||
@ -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
|
|
||||||
|
|
||||||
10
compose.yml
10
compose.yml
@ -1,10 +0,0 @@
|
|||||||
services:
|
|
||||||
shell:
|
|
||||||
build: .
|
|
||||||
command: sh
|
|
||||||
tty: true
|
|
||||||
stdin_open: true
|
|
||||||
working_dir: /home
|
|
||||||
volumes:
|
|
||||||
- ./:/home
|
|
||||||
|
|
||||||
BIN
database.db
BIN
database.db
Binary file not shown.
0
http_curl.h
Normal file → Executable file
0
http_curl.h
Normal file → Executable file
38
include/agent.h
Executable file
38
include/agent.h
Executable file
@ -0,0 +1,38 @@
|
|||||||
|
// retoor <retoor@molodetz.nl>
|
||||||
|
|
||||||
|
#ifndef R_AGENT_H
|
||||||
|
#define R_AGENT_H
|
||||||
|
|
||||||
|
#include "messages.h"
|
||||||
|
#include "r_error.h"
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
#define AGENT_MAX_ITERATIONS 300
|
||||||
|
#define AGENT_MAX_TOOL_RETRIES 3
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
AGENT_STATE_IDLE,
|
||||||
|
AGENT_STATE_RUNNING,
|
||||||
|
AGENT_STATE_EXECUTING_TOOLS,
|
||||||
|
AGENT_STATE_COMPLETED,
|
||||||
|
AGENT_STATE_MAX_ITERATIONS,
|
||||||
|
AGENT_STATE_ERROR
|
||||||
|
} agent_state_t;
|
||||||
|
|
||||||
|
typedef struct agent_t *agent_handle;
|
||||||
|
|
||||||
|
agent_handle agent_create(const char *goal, messages_handle messages);
|
||||||
|
void agent_destroy(agent_handle agent);
|
||||||
|
|
||||||
|
void agent_set_max_iterations(agent_handle agent, int max);
|
||||||
|
void agent_set_verbose(agent_handle agent, bool verbose);
|
||||||
|
|
||||||
|
agent_state_t agent_get_state(agent_handle agent);
|
||||||
|
const char *agent_get_error(agent_handle agent);
|
||||||
|
int agent_get_iteration_count(agent_handle agent);
|
||||||
|
|
||||||
|
char *agent_run(agent_handle agent, const char *user_message);
|
||||||
|
char *agent_chat(const char *user_message, messages_handle messages);
|
||||||
|
char *agent_chat_with_limit(const char *user_message, int max_iterations, messages_handle messages);
|
||||||
|
|
||||||
|
#endif
|
||||||
9
include/bash_executor.h
Normal file
9
include/bash_executor.h
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
// retoor <retoor@molodetz.nl>
|
||||||
|
#ifndef R_BASH_EXECUTOR_H
|
||||||
|
#define R_BASH_EXECUTOR_H
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
char *r_bash_execute(const char *command, bool interactive);
|
||||||
|
|
||||||
|
#endif
|
||||||
24
include/db.h
Executable file
24
include/db.h
Executable file
@ -0,0 +1,24 @@
|
|||||||
|
// retoor <retoor@molodetz.nl>
|
||||||
|
|
||||||
|
#ifndef R_DB_H
|
||||||
|
#define R_DB_H
|
||||||
|
|
||||||
|
#include "r_error.h"
|
||||||
|
#include <json-c/json.h>
|
||||||
|
|
||||||
|
typedef struct db_t *db_handle;
|
||||||
|
|
||||||
|
db_handle db_open(const char *path);
|
||||||
|
void db_close(db_handle db);
|
||||||
|
|
||||||
|
r_status_t db_init(db_handle db);
|
||||||
|
r_status_t db_kv_set(db_handle db, const char *key, const char *value);
|
||||||
|
r_status_t db_kv_get(db_handle db, const char *key, char **value);
|
||||||
|
r_status_t db_execute(db_handle db, const char *sql, struct json_object **result);
|
||||||
|
|
||||||
|
char *db_get_schema(db_handle db);
|
||||||
|
r_status_t db_store_file_version(db_handle db, const char *path);
|
||||||
|
r_status_t db_save_conversation(db_handle db, const char *session_key, const char *data);
|
||||||
|
r_status_t db_load_conversation(db_handle db, const char *session_key, char **data);
|
||||||
|
|
||||||
|
#endif
|
||||||
26
include/http_client.h
Executable file
26
include/http_client.h
Executable file
@ -0,0 +1,26 @@
|
|||||||
|
// retoor <retoor@molodetz.nl>
|
||||||
|
|
||||||
|
#ifndef R_HTTP_CLIENT_H
|
||||||
|
#define R_HTTP_CLIENT_H
|
||||||
|
|
||||||
|
#include "r_error.h"
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
typedef struct http_client_t *http_client_handle;
|
||||||
|
|
||||||
|
http_client_handle http_client_create(const char *bearer_token);
|
||||||
|
void http_client_destroy(http_client_handle client);
|
||||||
|
|
||||||
|
void http_client_set_show_spinner(http_client_handle client, bool show);
|
||||||
|
void http_client_set_timeout(http_client_handle client, long timeout_seconds);
|
||||||
|
void http_client_set_connect_timeout(http_client_handle client, long timeout_seconds);
|
||||||
|
|
||||||
|
r_status_t http_post(http_client_handle client, const char *url,
|
||||||
|
const char *data, char **response);
|
||||||
|
r_status_t http_get(http_client_handle client, const char *url, char **response);
|
||||||
|
|
||||||
|
r_status_t http_post_simple(const char *url, const char *bearer_token,
|
||||||
|
const char *data, char **response);
|
||||||
|
r_status_t http_get_simple(const char *url, const char *bearer_token, char **response);
|
||||||
|
|
||||||
|
#endif
|
||||||
33
include/messages.h
Executable file
33
include/messages.h
Executable file
@ -0,0 +1,33 @@
|
|||||||
|
// retoor <retoor@molodetz.nl>
|
||||||
|
|
||||||
|
#ifndef R_MESSAGES_H
|
||||||
|
#define R_MESSAGES_H
|
||||||
|
|
||||||
|
#include "r_error.h"
|
||||||
|
#include <json-c/json.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
typedef struct messages_t *messages_handle;
|
||||||
|
|
||||||
|
messages_handle messages_create(const char *session_id);
|
||||||
|
void messages_destroy(messages_handle msgs);
|
||||||
|
|
||||||
|
r_status_t messages_set_session_id(messages_handle msgs, const char *session_id);
|
||||||
|
const char *messages_get_session_id(messages_handle msgs);
|
||||||
|
|
||||||
|
r_status_t messages_add(messages_handle msgs, const char *role, const char *content);
|
||||||
|
r_status_t messages_add_object(messages_handle msgs, struct json_object *message);
|
||||||
|
r_status_t messages_add_tool_call(messages_handle msgs, struct json_object *message);
|
||||||
|
r_status_t messages_add_tool_result(messages_handle msgs, const char *tool_call_id, const char *result);
|
||||||
|
|
||||||
|
r_status_t messages_remove_last(messages_handle msgs);
|
||||||
|
r_status_t messages_clear(messages_handle msgs);
|
||||||
|
|
||||||
|
r_status_t messages_save(messages_handle msgs);
|
||||||
|
r_status_t messages_load(messages_handle msgs);
|
||||||
|
|
||||||
|
struct json_object *messages_to_json(messages_handle msgs);
|
||||||
|
char *messages_to_string(messages_handle msgs);
|
||||||
|
int messages_count(messages_handle msgs);
|
||||||
|
|
||||||
|
#endif
|
||||||
33
include/r_config.h
Executable file
33
include/r_config.h
Executable file
@ -0,0 +1,33 @@
|
|||||||
|
// retoor <retoor@molodetz.nl>
|
||||||
|
|
||||||
|
#ifndef R_CONFIG_H
|
||||||
|
#define R_CONFIG_H
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
typedef struct r_config_t *r_config_handle;
|
||||||
|
|
||||||
|
r_config_handle r_config_get_instance(void);
|
||||||
|
void r_config_destroy(void);
|
||||||
|
|
||||||
|
const char *r_config_get_api_url(r_config_handle cfg);
|
||||||
|
const char *r_config_get_models_url(r_config_handle cfg);
|
||||||
|
const char *r_config_get_model(r_config_handle cfg);
|
||||||
|
void r_config_set_model(r_config_handle cfg, const char *model);
|
||||||
|
|
||||||
|
const char *r_config_get_api_key(r_config_handle cfg);
|
||||||
|
const char *r_config_get_db_path(r_config_handle cfg);
|
||||||
|
|
||||||
|
bool r_config_use_tools(r_config_handle cfg);
|
||||||
|
bool r_config_use_strict(r_config_handle cfg);
|
||||||
|
bool r_config_is_verbose(r_config_handle cfg);
|
||||||
|
void r_config_set_verbose(r_config_handle cfg, bool verbose);
|
||||||
|
|
||||||
|
double r_config_get_temperature(r_config_handle cfg);
|
||||||
|
|
||||||
|
const char *r_config_get_session_id(r_config_handle cfg);
|
||||||
|
bool r_config_set_session_id(r_config_handle cfg, const char *session_id);
|
||||||
|
|
||||||
|
const char *r_config_get_system_message(r_config_handle cfg);
|
||||||
|
|
||||||
|
#endif
|
||||||
39
include/r_error.h
Executable file
39
include/r_error.h
Executable file
@ -0,0 +1,39 @@
|
|||||||
|
// retoor <retoor@molodetz.nl>
|
||||||
|
|
||||||
|
#ifndef R_ERROR_H
|
||||||
|
#define R_ERROR_H
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
R_SUCCESS = 0,
|
||||||
|
R_ERROR_INVALID_ARG,
|
||||||
|
R_ERROR_OUT_OF_MEMORY,
|
||||||
|
R_ERROR_NOT_FOUND,
|
||||||
|
R_ERROR_PARSE,
|
||||||
|
R_ERROR_DB_CONNECTION,
|
||||||
|
R_ERROR_DB_QUERY,
|
||||||
|
R_ERROR_DB_NOT_FOUND,
|
||||||
|
R_ERROR_HTTP_CONNECTION,
|
||||||
|
R_ERROR_HTTP_TIMEOUT,
|
||||||
|
R_ERROR_HTTP_RESPONSE,
|
||||||
|
R_ERROR_JSON_PARSE,
|
||||||
|
R_ERROR_FILE_NOT_FOUND,
|
||||||
|
R_ERROR_FILE_READ,
|
||||||
|
R_ERROR_FILE_WRITE,
|
||||||
|
R_ERROR_TOOL_NOT_FOUND,
|
||||||
|
R_ERROR_TOOL_EXECUTION,
|
||||||
|
R_ERROR_API_KEY_MISSING,
|
||||||
|
R_ERROR_API_ERROR,
|
||||||
|
R_ERROR_MAX_ITERATIONS,
|
||||||
|
R_ERROR_SESSION_INVALID,
|
||||||
|
R_ERROR_UNKNOWN
|
||||||
|
} r_status_t;
|
||||||
|
|
||||||
|
const char *r_status_string(r_status_t status);
|
||||||
|
|
||||||
|
#define R_CHECK(expr) do { r_status_t _s = (expr); if (_s != R_SUCCESS) return _s; } while(0)
|
||||||
|
|
||||||
|
#define R_CHECK_NULL(ptr) do { if (!(ptr)) return R_ERROR_INVALID_ARG; } while(0)
|
||||||
|
|
||||||
|
#define R_CHECK_ALLOC(ptr) do { if (!(ptr)) return R_ERROR_OUT_OF_MEMORY; } while(0)
|
||||||
|
|
||||||
|
#endif
|
||||||
42
include/tool.h
Executable file
42
include/tool.h
Executable file
@ -0,0 +1,42 @@
|
|||||||
|
// retoor <retoor@molodetz.nl>
|
||||||
|
|
||||||
|
#ifndef R_TOOL_H
|
||||||
|
#define R_TOOL_H
|
||||||
|
|
||||||
|
#include "r_error.h"
|
||||||
|
#include <json-c/json.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
typedef struct tool_t tool_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
struct json_object *(*get_description)(void);
|
||||||
|
char *(*execute)(tool_t *self, struct json_object *args);
|
||||||
|
void (*print_action)(const char *name, struct json_object *args);
|
||||||
|
} tool_vtable_t;
|
||||||
|
|
||||||
|
struct tool_t {
|
||||||
|
const tool_vtable_t *vtable;
|
||||||
|
const char *name;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
tool_t **tools;
|
||||||
|
size_t count;
|
||||||
|
size_t capacity;
|
||||||
|
} tool_registry_t;
|
||||||
|
|
||||||
|
tool_registry_t *tool_registry_create(void);
|
||||||
|
void tool_registry_destroy(tool_registry_t *registry);
|
||||||
|
r_status_t tool_registry_register(tool_registry_t *registry, tool_t *tool);
|
||||||
|
tool_t *tool_registry_find(tool_registry_t *registry, const char *name);
|
||||||
|
struct json_object *tool_registry_get_descriptions(tool_registry_t *registry);
|
||||||
|
struct json_object *tool_registry_execute(tool_registry_t *registry,
|
||||||
|
struct json_object *tool_calls,
|
||||||
|
bool verbose);
|
||||||
|
|
||||||
|
tool_registry_t *tools_get_registry(void);
|
||||||
|
void tools_registry_shutdown(void);
|
||||||
|
|
||||||
|
#endif
|
||||||
6
main.c
Normal file → Executable file
6
main.c
Normal file → Executable file
@ -188,6 +188,11 @@ static void repl(void) {
|
|||||||
if (!strncmp(line, "!dump", 5)) {
|
if (!strncmp(line, "!dump", 5)) {
|
||||||
printf("%s\n", message_json());
|
printf("%s\n", message_json());
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
|
if(!strncmp(line,"!debug",6))
|
||||||
|
{
|
||||||
|
printf("%s\n", R_BASE_URL);
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
if (!strncmp(line, "!clear", 6)) {
|
if (!strncmp(line, "!clear", 6)) {
|
||||||
messages_remove();
|
messages_remove();
|
||||||
@ -213,6 +218,7 @@ static void repl(void) {
|
|||||||
json_object_to_json_string(tools_descriptions()));
|
json_object_to_json_string(tools_descriptions()));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!strncmp(line, "!models", 7)) {
|
if (!strncmp(line, "!models", 7)) {
|
||||||
printf("Current model: %s\n", openai_fetch_models());
|
printf("Current model: %s\n", openai_fetch_models());
|
||||||
continue;
|
continue;
|
||||||
|
|||||||
0
markdown.h
Normal file → Executable file
0
markdown.h
Normal file → Executable file
@ -1,6 +0,0 @@
|
|||||||
[Desktop Entry]
|
|
||||||
Name=r
|
|
||||||
Exec=usr/bin/r
|
|
||||||
Type=Application
|
|
||||||
Icon=r
|
|
||||||
Categories=Utility;
|
|
||||||
6
r.h
Normal file → Executable file
6
r.h
Normal file → Executable file
@ -84,8 +84,12 @@ bool get_use_strict() {
|
|||||||
|
|
||||||
char *get_completions_api_url() {
|
char *get_completions_api_url() {
|
||||||
if (getenv("R_BASE_URL") != NULL) {
|
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;
|
return completions_api_url;
|
||||||
}
|
}
|
||||||
char *get_models_api_url() {
|
char *get_models_api_url() {
|
||||||
|
|||||||
23
review.md
23
review.md
@ -1,23 +0,0 @@
|
|||||||
# Application Review Report
|
|
||||||
|
|
||||||
## Entry Point
|
|
||||||
- The application's entry point is defined in `main.c`.
|
|
||||||
- Currently, it prints "Hello, World!".
|
|
||||||
|
|
||||||
## Code Structure
|
|
||||||
- The application has a simple structure with a single source file.
|
|
||||||
|
|
||||||
## Testing
|
|
||||||
- Basic test setup to verify that `main()` returns 0.
|
|
||||||
- The test code attempts to call `main()` directly, leading to a multiple definition error.
|
|
||||||
|
|
||||||
## Issues Identified
|
|
||||||
- The test setup is incorrect because calling `main()` directly causes a multiple definition error.
|
|
||||||
- Proper testing should involve refactoring the code to separate logic from the entry point.
|
|
||||||
|
|
||||||
## Recommendations
|
|
||||||
- Refactor the application code to isolate logic from `main()` to allow easier testing.
|
|
||||||
- Expand the code to include more functionalities and corresponding tests.
|
|
||||||
- Review and improve build process and structure for maintainability.
|
|
||||||
|
|
||||||
The review will be updated after fixing these issues.
|
|
||||||
BIN
screenshot1.png
BIN
screenshot1.png
Binary file not shown.
|
Before Width: | Height: | Size: 18 KiB |
10
setup.py
10
setup.py
@ -1,10 +0,0 @@
|
|||||||
from setuptools import setup, Extension
|
|
||||||
|
|
||||||
module = Extension("rpylib", sources=["rpylib.c"])
|
|
||||||
|
|
||||||
setup(
|
|
||||||
name="rpylib",
|
|
||||||
version="1.0",
|
|
||||||
description="AI Module",
|
|
||||||
ext_modules=[module],
|
|
||||||
)
|
|
||||||
415
src/agent.c
Executable file
415
src/agent.c
Executable file
@ -0,0 +1,415 @@
|
|||||||
|
// retoor <retoor@molodetz.nl>
|
||||||
|
|
||||||
|
#include "agent.h"
|
||||||
|
#include "http_client.h"
|
||||||
|
#include "r_config.h"
|
||||||
|
#include "tool.h"
|
||||||
|
#include <json-c/json.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
|
struct agent_t {
|
||||||
|
char *goal;
|
||||||
|
int iteration_count;
|
||||||
|
int max_iterations;
|
||||||
|
int tool_retry_count;
|
||||||
|
int max_tool_retries;
|
||||||
|
agent_state_t state;
|
||||||
|
time_t start_time;
|
||||||
|
char *last_error;
|
||||||
|
bool verbose;
|
||||||
|
messages_handle messages;
|
||||||
|
bool owns_messages;
|
||||||
|
http_client_handle http;
|
||||||
|
tool_registry_t *tools;
|
||||||
|
};
|
||||||
|
|
||||||
|
static const char *incomplete_phrases[] = {
|
||||||
|
"I'll ", "I will ", "Let me ", "I'm going to ",
|
||||||
|
"Next, I", "Now I'll", "Now I will", "I'll now",
|
||||||
|
"I need to", "I should", "I can ", "Going to ",
|
||||||
|
"Will now", "Proceeding", "Starting to", "About to ",
|
||||||
|
"First, I", "Then I", "After that", "Following that",
|
||||||
|
NULL
|
||||||
|
};
|
||||||
|
|
||||||
|
static const char *incomplete_endings[] = {
|
||||||
|
"...", ":", "files:", "content:", "implementation:",
|
||||||
|
NULL
|
||||||
|
};
|
||||||
|
|
||||||
|
extern tool_registry_t *tools_get_registry(void);
|
||||||
|
|
||||||
|
agent_handle agent_create(const char *goal, messages_handle messages) {
|
||||||
|
struct agent_t *agent = calloc(1, sizeof(struct agent_t));
|
||||||
|
if (!agent) return NULL;
|
||||||
|
|
||||||
|
if (goal) {
|
||||||
|
agent->goal = strdup(goal);
|
||||||
|
if (!agent->goal) {
|
||||||
|
free(agent);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
r_config_handle cfg = r_config_get_instance();
|
||||||
|
|
||||||
|
agent->iteration_count = 0;
|
||||||
|
agent->max_iterations = AGENT_MAX_ITERATIONS;
|
||||||
|
agent->tool_retry_count = 0;
|
||||||
|
agent->max_tool_retries = AGENT_MAX_TOOL_RETRIES;
|
||||||
|
agent->state = AGENT_STATE_IDLE;
|
||||||
|
agent->start_time = time(NULL);
|
||||||
|
agent->verbose = r_config_is_verbose(cfg);
|
||||||
|
|
||||||
|
if (messages) {
|
||||||
|
agent->messages = messages;
|
||||||
|
agent->owns_messages = false;
|
||||||
|
} else {
|
||||||
|
agent->messages = messages_create(r_config_get_session_id(cfg));
|
||||||
|
agent->owns_messages = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!agent->messages) {
|
||||||
|
free(agent->goal);
|
||||||
|
free(agent);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
agent->http = http_client_create(r_config_get_api_key(cfg));
|
||||||
|
if (!agent->http) {
|
||||||
|
if (agent->owns_messages) {
|
||||||
|
messages_destroy(agent->messages);
|
||||||
|
}
|
||||||
|
free(agent->goal);
|
||||||
|
free(agent);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
agent->tools = tools_get_registry();
|
||||||
|
|
||||||
|
return agent;
|
||||||
|
}
|
||||||
|
|
||||||
|
void agent_destroy(agent_handle agent) {
|
||||||
|
if (!agent) return;
|
||||||
|
if (agent->http) http_client_destroy(agent->http);
|
||||||
|
if (agent->messages && agent->owns_messages) messages_destroy(agent->messages);
|
||||||
|
free(agent->goal);
|
||||||
|
free(agent->last_error);
|
||||||
|
free(agent);
|
||||||
|
}
|
||||||
|
|
||||||
|
void agent_set_max_iterations(agent_handle agent, int max) {
|
||||||
|
if (agent) agent->max_iterations = max;
|
||||||
|
}
|
||||||
|
|
||||||
|
void agent_set_verbose(agent_handle agent, bool verbose) {
|
||||||
|
if (agent) agent->verbose = verbose;
|
||||||
|
}
|
||||||
|
|
||||||
|
agent_state_t agent_get_state(agent_handle agent) {
|
||||||
|
return agent ? agent->state : AGENT_STATE_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *agent_get_error(agent_handle agent) {
|
||||||
|
return agent ? agent->last_error : NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
int agent_get_iteration_count(agent_handle agent) {
|
||||||
|
return agent ? agent->iteration_count : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void agent_set_error(agent_handle agent, const char *error) {
|
||||||
|
if (!agent) return;
|
||||||
|
free(agent->last_error);
|
||||||
|
agent->last_error = error ? strdup(error) : NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static char *agent_build_request(agent_handle agent, const char *role, const char *message) {
|
||||||
|
r_config_handle cfg = r_config_get_instance();
|
||||||
|
|
||||||
|
struct json_object *root = json_object_new_object();
|
||||||
|
if (!root) return NULL;
|
||||||
|
|
||||||
|
json_object_object_add(root, "model",
|
||||||
|
json_object_new_string(r_config_get_model(cfg)));
|
||||||
|
|
||||||
|
if (role && message) {
|
||||||
|
messages_add(agent->messages, role, message);
|
||||||
|
if (r_config_use_tools(cfg) && agent->tools) {
|
||||||
|
json_object_object_add(root, "tools",
|
||||||
|
tool_registry_get_descriptions(agent->tools));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
json_object_object_add(root, "messages",
|
||||||
|
json_object_get(messages_to_json(agent->messages)));
|
||||||
|
json_object_object_add(root, "temperature",
|
||||||
|
json_object_new_double(r_config_get_temperature(cfg)));
|
||||||
|
|
||||||
|
char *result = strdup(json_object_to_json_string_ext(root, JSON_C_TO_STRING_PRETTY));
|
||||||
|
json_object_put(root);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct json_object *agent_process_response(agent_handle agent, const char *json_data) {
|
||||||
|
r_config_handle cfg = r_config_get_instance();
|
||||||
|
|
||||||
|
char *response = NULL;
|
||||||
|
r_status_t status = http_post(agent->http, r_config_get_api_url(cfg), json_data, &response);
|
||||||
|
|
||||||
|
if (status != R_SUCCESS || !response) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct json_object *parsed = json_tokener_parse(response);
|
||||||
|
free(response);
|
||||||
|
|
||||||
|
if (!parsed) return NULL;
|
||||||
|
|
||||||
|
struct json_object *error_obj;
|
||||||
|
if (json_object_object_get_ex(parsed, "error", &error_obj)) {
|
||||||
|
const char *err_str = json_object_to_json_string(error_obj);
|
||||||
|
fprintf(stderr, "API Error: %s\n", err_str);
|
||||||
|
json_object_put(parsed);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct json_object *choices;
|
||||||
|
if (!json_object_object_get_ex(parsed, "choices", &choices)) {
|
||||||
|
json_object_put(parsed);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct json_object *first_choice = json_object_array_get_idx(choices, 0);
|
||||||
|
if (!first_choice) {
|
||||||
|
json_object_put(parsed);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return first_choice;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool agent_has_tool_calls(struct json_object *choice) {
|
||||||
|
struct json_object *message_obj;
|
||||||
|
if (!json_object_object_get_ex(choice, "message", &message_obj)) return false;
|
||||||
|
|
||||||
|
struct json_object *tool_calls;
|
||||||
|
if (!json_object_object_get_ex(message_obj, "tool_calls", &tool_calls)) return false;
|
||||||
|
|
||||||
|
return json_object_array_length(tool_calls) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct json_object *agent_get_tool_calls(struct json_object *choice) {
|
||||||
|
struct json_object *message_obj;
|
||||||
|
if (!json_object_object_get_ex(choice, "message", &message_obj)) return NULL;
|
||||||
|
|
||||||
|
struct json_object *tool_calls;
|
||||||
|
if (!json_object_object_get_ex(message_obj, "tool_calls", &tool_calls)) return NULL;
|
||||||
|
|
||||||
|
return tool_calls;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct json_object *agent_get_message(struct json_object *choice) {
|
||||||
|
struct json_object *message_obj;
|
||||||
|
if (json_object_object_get_ex(choice, "message", &message_obj)) {
|
||||||
|
return message_obj;
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static char *agent_get_content(struct json_object *choice) {
|
||||||
|
struct json_object *message_obj;
|
||||||
|
if (!json_object_object_get_ex(choice, "message", &message_obj)) return NULL;
|
||||||
|
|
||||||
|
struct json_object *content_obj;
|
||||||
|
if (!json_object_object_get_ex(message_obj, "content", &content_obj)) return NULL;
|
||||||
|
|
||||||
|
const char *content = json_object_get_string(content_obj);
|
||||||
|
return content ? strdup(content) : NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool agent_response_indicates_incomplete(const char *content) {
|
||||||
|
if (!content) return false;
|
||||||
|
|
||||||
|
for (int i = 0; incomplete_phrases[i]; i++) {
|
||||||
|
if (strstr(content, incomplete_phrases[i])) return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t len = strlen(content);
|
||||||
|
if (len > 3) {
|
||||||
|
for (int i = 0; incomplete_endings[i]; i++) {
|
||||||
|
size_t end_len = strlen(incomplete_endings[i]);
|
||||||
|
if (len >= end_len && strcmp(content + len - end_len, incomplete_endings[i]) == 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *agent_run(agent_handle agent, const char *user_message) {
|
||||||
|
if (!agent) return NULL;
|
||||||
|
|
||||||
|
agent->state = AGENT_STATE_RUNNING;
|
||||||
|
agent->iteration_count = 0;
|
||||||
|
agent->tool_retry_count = 0;
|
||||||
|
|
||||||
|
if (!user_message || !*user_message) {
|
||||||
|
agent->state = AGENT_STATE_ERROR;
|
||||||
|
agent_set_error(agent, "Empty user message");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
messages_load(agent->messages);
|
||||||
|
|
||||||
|
char *json_data = agent_build_request(agent, "user", user_message);
|
||||||
|
if (!json_data) {
|
||||||
|
agent->state = AGENT_STATE_ERROR;
|
||||||
|
agent_set_error(agent, "Failed to create chat JSON");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *final_response = NULL;
|
||||||
|
|
||||||
|
while (agent->state == AGENT_STATE_RUNNING) {
|
||||||
|
agent->iteration_count++;
|
||||||
|
|
||||||
|
if (agent->iteration_count > agent->max_iterations) {
|
||||||
|
agent->state = AGENT_STATE_MAX_ITERATIONS;
|
||||||
|
agent_set_error(agent, "Maximum iterations reached");
|
||||||
|
if (agent->verbose) {
|
||||||
|
fprintf(stderr, "[Agent] Max iterations (%d) reached\n", agent->max_iterations);
|
||||||
|
}
|
||||||
|
free(json_data);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (agent->verbose) {
|
||||||
|
fprintf(stderr, "[Agent] Iteration %d/%d\n",
|
||||||
|
agent->iteration_count, agent->max_iterations);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct json_object *choice = agent_process_response(agent, json_data);
|
||||||
|
free(json_data);
|
||||||
|
json_data = NULL;
|
||||||
|
|
||||||
|
if (!choice) {
|
||||||
|
agent->tool_retry_count++;
|
||||||
|
if (agent->tool_retry_count >= agent->max_tool_retries) {
|
||||||
|
agent->state = AGENT_STATE_ERROR;
|
||||||
|
agent_set_error(agent, "API request failed after retries");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (agent->verbose) {
|
||||||
|
fprintf(stderr, "[Agent] API error, retry %d/%d\n",
|
||||||
|
agent->tool_retry_count, agent->max_tool_retries);
|
||||||
|
}
|
||||||
|
json_data = agent_build_request(agent, NULL, NULL);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
agent->tool_retry_count = 0;
|
||||||
|
|
||||||
|
struct json_object *message_obj = agent_get_message(choice);
|
||||||
|
if (message_obj) {
|
||||||
|
messages_add_object(agent->messages, json_object_get(message_obj));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool has_tools = agent_has_tool_calls(choice);
|
||||||
|
|
||||||
|
if (agent->verbose) {
|
||||||
|
fprintf(stderr, "[Agent] has_tool_calls=%s\n", has_tools ? "true" : "false");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (has_tools) {
|
||||||
|
agent->state = AGENT_STATE_EXECUTING_TOOLS;
|
||||||
|
|
||||||
|
struct json_object *tool_calls = agent_get_tool_calls(choice);
|
||||||
|
|
||||||
|
if (agent->verbose) {
|
||||||
|
int num_tools = json_object_array_length(tool_calls);
|
||||||
|
fprintf(stderr, "[Agent] Executing %d tool(s)\n", num_tools);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct json_object *results = tool_registry_execute(agent->tools, tool_calls, agent->verbose);
|
||||||
|
|
||||||
|
int count = json_object_array_length(results);
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
struct json_object *result = json_object_array_get_idx(results, i);
|
||||||
|
messages_add_tool_call(agent->messages, json_object_get(result));
|
||||||
|
}
|
||||||
|
|
||||||
|
agent->state = AGENT_STATE_RUNNING;
|
||||||
|
json_data = agent_build_request(agent, NULL, NULL);
|
||||||
|
if (!json_data) {
|
||||||
|
agent->state = AGENT_STATE_ERROR;
|
||||||
|
agent_set_error(agent, "Failed to create follow-up JSON");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
char *content = agent_get_content(choice);
|
||||||
|
|
||||||
|
if (content && agent_response_indicates_incomplete(content)) {
|
||||||
|
if (agent->verbose) {
|
||||||
|
fprintf(stderr, "[Agent] Response indicates incomplete work, auto-continuing\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
free(content);
|
||||||
|
json_data = agent_build_request(agent, "user",
|
||||||
|
"Continue. Execute the necessary actions to complete the task.");
|
||||||
|
if (!json_data) {
|
||||||
|
agent->state = AGENT_STATE_ERROR;
|
||||||
|
agent_set_error(agent, "Failed to create continue JSON");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
final_response = content;
|
||||||
|
agent->state = AGENT_STATE_COMPLETED;
|
||||||
|
|
||||||
|
if (agent->verbose) {
|
||||||
|
fprintf(stderr, "[Agent] Completed in %d iteration(s)\n",
|
||||||
|
agent->iteration_count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
free(json_data);
|
||||||
|
return final_response;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *agent_chat(const char *user_message, messages_handle messages) {
|
||||||
|
agent_handle agent = agent_create(user_message, messages);
|
||||||
|
if (!agent) return NULL;
|
||||||
|
|
||||||
|
char *response = agent_run(agent, user_message);
|
||||||
|
|
||||||
|
if (agent->verbose && agent->state != AGENT_STATE_COMPLETED && agent->last_error) {
|
||||||
|
fprintf(stderr, "[Agent] Error: %s\n", agent->last_error);
|
||||||
|
}
|
||||||
|
|
||||||
|
agent_destroy(agent);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *agent_chat_with_limit(const char *user_message, int max_iterations, messages_handle messages) {
|
||||||
|
agent_handle agent = agent_create(user_message, messages);
|
||||||
|
if (!agent) return NULL;
|
||||||
|
|
||||||
|
agent_set_max_iterations(agent, max_iterations);
|
||||||
|
char *response = agent_run(agent, user_message);
|
||||||
|
|
||||||
|
if (agent->verbose && agent->state != AGENT_STATE_COMPLETED && agent->last_error) {
|
||||||
|
fprintf(stderr, "[Agent] Error: %s\n", agent->last_error);
|
||||||
|
}
|
||||||
|
|
||||||
|
agent_destroy(agent);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
106
src/bash_executor.c
Normal file
106
src/bash_executor.c
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
// retoor <retoor@molodetz.nl>
|
||||||
|
|
||||||
|
#define _GNU_SOURCE
|
||||||
|
#include "bash_executor.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
|
||||||
|
char *r_bash_execute(const char *command, bool interactive) {
|
||||||
|
if (!command) {
|
||||||
|
return strdup("Error: null command");
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t len = strlen(command);
|
||||||
|
char *cmd_with_nl = malloc(len + 2);
|
||||||
|
if (!cmd_with_nl) {
|
||||||
|
return strdup("Error: memory allocation failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
strcpy(cmd_with_nl, command);
|
||||||
|
if (len > 0 && cmd_with_nl[len - 1] != '\n') {
|
||||||
|
cmd_with_nl[len] = '\n';
|
||||||
|
cmd_with_nl[len + 1] = '\0';
|
||||||
|
} else if (len == 0) {
|
||||||
|
cmd_with_nl[0] = '\n';
|
||||||
|
cmd_with_nl[1] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
char tmp_script[] = "/tmp/r_bash_XXXXXX.sh";
|
||||||
|
int fd = mkstemps(tmp_script, 3);
|
||||||
|
if (fd == -1) {
|
||||||
|
free(cmd_with_nl);
|
||||||
|
perror("mkstemps");
|
||||||
|
return strdup("Error: failed to create temp script");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (write(fd, cmd_with_nl, strlen(cmd_with_nl)) == -1) {
|
||||||
|
close(fd);
|
||||||
|
free(cmd_with_nl);
|
||||||
|
unlink(tmp_script);
|
||||||
|
return strdup("Error: failed to write to temp script");
|
||||||
|
}
|
||||||
|
|
||||||
|
close(fd);
|
||||||
|
free(cmd_with_nl);
|
||||||
|
|
||||||
|
char *output = NULL;
|
||||||
|
|
||||||
|
if (interactive) {
|
||||||
|
char *run_cmd = NULL;
|
||||||
|
if (asprintf(&run_cmd, "bash %s", tmp_script) == -1) {
|
||||||
|
unlink(tmp_script);
|
||||||
|
return strdup("Error: asprintf failed");
|
||||||
|
}
|
||||||
|
int status = system(run_cmd);
|
||||||
|
free(run_cmd);
|
||||||
|
|
||||||
|
if (asprintf(&output, "Command exited with status %d", status) == -1) {
|
||||||
|
output = strdup("Command completed (asprintf failed for status).");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
char *run_cmd = NULL;
|
||||||
|
if (asprintf(&run_cmd, "bash %s 2>&1", tmp_script) == -1) {
|
||||||
|
unlink(tmp_script);
|
||||||
|
return strdup("Error: asprintf failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
FILE *fp = popen(run_cmd, "r");
|
||||||
|
free(run_cmd);
|
||||||
|
|
||||||
|
if (!fp) {
|
||||||
|
unlink(tmp_script);
|
||||||
|
return strdup("Error: popen failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
char buffer[1024];
|
||||||
|
size_t total_size = 0;
|
||||||
|
|
||||||
|
while (fgets(buffer, sizeof(buffer), fp)) {
|
||||||
|
fprintf(stderr, "%s", buffer);
|
||||||
|
|
||||||
|
size_t chunk_len = strlen(buffer);
|
||||||
|
char *new_output = realloc(output, total_size + chunk_len + 1);
|
||||||
|
if (!new_output) {
|
||||||
|
free(output);
|
||||||
|
pclose(fp);
|
||||||
|
unlink(tmp_script);
|
||||||
|
return strdup("Error: memory allocation failed");
|
||||||
|
}
|
||||||
|
output = new_output;
|
||||||
|
strcpy(output + total_size, buffer);
|
||||||
|
total_size += chunk_len;
|
||||||
|
}
|
||||||
|
pclose(fp);
|
||||||
|
|
||||||
|
if (!output) {
|
||||||
|
output = strdup("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unlink(tmp_script);
|
||||||
|
return output;
|
||||||
|
}
|
||||||
308
src/db.c
Executable file
308
src/db.c
Executable file
@ -0,0 +1,308 @@
|
|||||||
|
// retoor <retoor@molodetz.nl>
|
||||||
|
|
||||||
|
#include "db.h"
|
||||||
|
#include "r_config.h"
|
||||||
|
#include <sqlite3.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
|
struct db_t {
|
||||||
|
sqlite3 *conn;
|
||||||
|
char *path;
|
||||||
|
};
|
||||||
|
|
||||||
|
static char *expand_home_directory(const char *path) {
|
||||||
|
if (!path) return NULL;
|
||||||
|
if (path[0] != '~') return strdup(path);
|
||||||
|
|
||||||
|
const char *home_dir = getenv("HOME");
|
||||||
|
if (!home_dir) home_dir = getenv("USERPROFILE");
|
||||||
|
if (!home_dir) return strdup(path);
|
||||||
|
|
||||||
|
size_t home_len = strlen(home_dir);
|
||||||
|
size_t path_len = strlen(path);
|
||||||
|
char *expanded = malloc(home_len + path_len);
|
||||||
|
if (!expanded) return NULL;
|
||||||
|
|
||||||
|
strcpy(expanded, home_dir);
|
||||||
|
strcat(expanded, path + 1);
|
||||||
|
return expanded;
|
||||||
|
}
|
||||||
|
|
||||||
|
db_handle db_open(const char *path) {
|
||||||
|
struct db_t *db = calloc(1, sizeof(struct db_t));
|
||||||
|
if (!db) return NULL;
|
||||||
|
|
||||||
|
if (!path) {
|
||||||
|
r_config_handle cfg = r_config_get_instance();
|
||||||
|
path = r_config_get_db_path(cfg);
|
||||||
|
}
|
||||||
|
|
||||||
|
db->path = expand_home_directory(path);
|
||||||
|
if (!db->path) {
|
||||||
|
free(db);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sqlite3_open(db->path, &db->conn) != SQLITE_OK) {
|
||||||
|
free(db->path);
|
||||||
|
free(db);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
db_init(db);
|
||||||
|
return db;
|
||||||
|
}
|
||||||
|
|
||||||
|
void db_close(db_handle db) {
|
||||||
|
if (!db) return;
|
||||||
|
if (db->conn) sqlite3_close(db->conn);
|
||||||
|
free(db->path);
|
||||||
|
free(db);
|
||||||
|
}
|
||||||
|
|
||||||
|
r_status_t db_init(db_handle db) {
|
||||||
|
if (!db || !db->conn) return R_ERROR_INVALID_ARG;
|
||||||
|
|
||||||
|
const char *sql =
|
||||||
|
"CREATE TABLE IF NOT EXISTS kv ("
|
||||||
|
" key TEXT PRIMARY KEY,"
|
||||||
|
" value TEXT NOT NULL,"
|
||||||
|
" created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,"
|
||||||
|
" updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP"
|
||||||
|
");"
|
||||||
|
"CREATE TABLE IF NOT EXISTS file_versions ("
|
||||||
|
" id INTEGER PRIMARY KEY AUTOINCREMENT,"
|
||||||
|
" path TEXT NOT NULL,"
|
||||||
|
" content TEXT NOT NULL,"
|
||||||
|
" created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP"
|
||||||
|
");"
|
||||||
|
"CREATE TABLE IF NOT EXISTS conversations ("
|
||||||
|
" session_key TEXT PRIMARY KEY,"
|
||||||
|
" data TEXT NOT NULL,"
|
||||||
|
" updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP"
|
||||||
|
");";
|
||||||
|
|
||||||
|
char *err_msg = NULL;
|
||||||
|
if (sqlite3_exec(db->conn, sql, NULL, NULL, &err_msg) != SQLITE_OK) {
|
||||||
|
sqlite3_free(err_msg);
|
||||||
|
return R_ERROR_DB_QUERY;
|
||||||
|
}
|
||||||
|
return R_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
r_status_t db_kv_set(db_handle db, const char *key, const char *value) {
|
||||||
|
if (!db || !db->conn || !key || !value) return R_ERROR_INVALID_ARG;
|
||||||
|
|
||||||
|
char *sql = sqlite3_mprintf(
|
||||||
|
"INSERT OR REPLACE INTO kv (key, value, updated_at) VALUES (%Q, %Q, CURRENT_TIMESTAMP)",
|
||||||
|
key, value);
|
||||||
|
if (!sql) return R_ERROR_OUT_OF_MEMORY;
|
||||||
|
|
||||||
|
char *err_msg = NULL;
|
||||||
|
int rc = sqlite3_exec(db->conn, sql, NULL, NULL, &err_msg);
|
||||||
|
sqlite3_free(sql);
|
||||||
|
|
||||||
|
if (rc != SQLITE_OK) {
|
||||||
|
sqlite3_free(err_msg);
|
||||||
|
return R_ERROR_DB_QUERY;
|
||||||
|
}
|
||||||
|
return R_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
r_status_t db_kv_get(db_handle db, const char *key, char **value) {
|
||||||
|
if (!db || !db->conn || !key || !value) return R_ERROR_INVALID_ARG;
|
||||||
|
|
||||||
|
*value = NULL;
|
||||||
|
const char *sql = "SELECT value FROM kv WHERE key = ?";
|
||||||
|
sqlite3_stmt *stmt = NULL;
|
||||||
|
|
||||||
|
if (sqlite3_prepare_v2(db->conn, sql, -1, &stmt, NULL) != SQLITE_OK) {
|
||||||
|
return R_ERROR_DB_QUERY;
|
||||||
|
}
|
||||||
|
|
||||||
|
sqlite3_bind_text(stmt, 1, key, -1, SQLITE_STATIC);
|
||||||
|
|
||||||
|
int rc = sqlite3_step(stmt);
|
||||||
|
if (rc == SQLITE_ROW) {
|
||||||
|
const char *val = (const char *)sqlite3_column_text(stmt, 0);
|
||||||
|
*value = val ? strdup(val) : NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
sqlite3_finalize(stmt);
|
||||||
|
return (rc == SQLITE_ROW) ? R_SUCCESS : R_ERROR_DB_NOT_FOUND;
|
||||||
|
}
|
||||||
|
|
||||||
|
r_status_t db_execute(db_handle db, const char *sql, struct json_object **result) {
|
||||||
|
if (!db || !db->conn || !sql || !result) return R_ERROR_INVALID_ARG;
|
||||||
|
|
||||||
|
*result = NULL;
|
||||||
|
|
||||||
|
const char *select_check = sql;
|
||||||
|
while (*select_check == ' ') select_check++;
|
||||||
|
|
||||||
|
if (strncasecmp(select_check, "SELECT", 6) != 0) {
|
||||||
|
char *err_msg = NULL;
|
||||||
|
int rc = sqlite3_exec(db->conn, sql, NULL, NULL, &err_msg);
|
||||||
|
if (rc != SQLITE_OK) {
|
||||||
|
struct json_object *error_obj = json_object_new_object();
|
||||||
|
json_object_object_add(error_obj, "error",
|
||||||
|
json_object_new_string(err_msg ? err_msg : "Query failed"));
|
||||||
|
sqlite3_free(err_msg);
|
||||||
|
*result = error_obj;
|
||||||
|
return R_ERROR_DB_QUERY;
|
||||||
|
}
|
||||||
|
struct json_object *success_obj = json_object_new_object();
|
||||||
|
json_object_object_add(success_obj, "success", json_object_new_boolean(1));
|
||||||
|
*result = success_obj;
|
||||||
|
return R_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
sqlite3_stmt *stmt = NULL;
|
||||||
|
if (sqlite3_prepare_v2(db->conn, sql, -1, &stmt, NULL) != SQLITE_OK) {
|
||||||
|
struct json_object *error_obj = json_object_new_object();
|
||||||
|
json_object_object_add(error_obj, "error",
|
||||||
|
json_object_new_string(sqlite3_errmsg(db->conn)));
|
||||||
|
*result = error_obj;
|
||||||
|
return R_ERROR_DB_QUERY;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct json_object *array = json_object_new_array();
|
||||||
|
int col_count = sqlite3_column_count(stmt);
|
||||||
|
|
||||||
|
while (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||||
|
struct json_object *row = json_object_new_object();
|
||||||
|
|
||||||
|
for (int i = 0; i < col_count; i++) {
|
||||||
|
const char *col_name = sqlite3_column_name(stmt, i);
|
||||||
|
int col_type = sqlite3_column_type(stmt, i);
|
||||||
|
|
||||||
|
switch (col_type) {
|
||||||
|
case SQLITE_INTEGER:
|
||||||
|
json_object_object_add(row, col_name,
|
||||||
|
json_object_new_int64(sqlite3_column_int64(stmt, i)));
|
||||||
|
break;
|
||||||
|
case SQLITE_FLOAT:
|
||||||
|
json_object_object_add(row, col_name,
|
||||||
|
json_object_new_double(sqlite3_column_double(stmt, i)));
|
||||||
|
break;
|
||||||
|
case SQLITE_TEXT:
|
||||||
|
json_object_object_add(row, col_name,
|
||||||
|
json_object_new_string((const char *)sqlite3_column_text(stmt, i)));
|
||||||
|
break;
|
||||||
|
case SQLITE_NULL:
|
||||||
|
json_object_object_add(row, col_name, NULL);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
json_object_object_add(row, col_name,
|
||||||
|
json_object_new_string((const char *)sqlite3_column_text(stmt, i)));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
json_object_array_add(array, row);
|
||||||
|
}
|
||||||
|
|
||||||
|
sqlite3_finalize(stmt);
|
||||||
|
*result = array;
|
||||||
|
return R_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *db_get_schema(db_handle db) {
|
||||||
|
if (!db || !db->conn) return strdup("Database not available");
|
||||||
|
|
||||||
|
struct json_object *result = NULL;
|
||||||
|
r_status_t status = db_execute(db,
|
||||||
|
"SELECT name, sql FROM sqlite_master WHERE type='table' ORDER BY name",
|
||||||
|
&result);
|
||||||
|
|
||||||
|
if (status != R_SUCCESS || !result) {
|
||||||
|
return strdup("Failed to get schema");
|
||||||
|
}
|
||||||
|
|
||||||
|
char *schema = strdup(json_object_to_json_string_ext(result, JSON_C_TO_STRING_PRETTY));
|
||||||
|
json_object_put(result);
|
||||||
|
return schema;
|
||||||
|
}
|
||||||
|
|
||||||
|
r_status_t db_store_file_version(db_handle db, const char *path) {
|
||||||
|
if (!db || !db->conn || !path) return R_ERROR_INVALID_ARG;
|
||||||
|
|
||||||
|
FILE *fp = fopen(path, "r");
|
||||||
|
if (!fp) return R_ERROR_FILE_NOT_FOUND;
|
||||||
|
|
||||||
|
fseek(fp, 0, SEEK_END);
|
||||||
|
long size = ftell(fp);
|
||||||
|
rewind(fp);
|
||||||
|
|
||||||
|
if (size <= 0 || size > 1000000) {
|
||||||
|
fclose(fp);
|
||||||
|
return R_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *content = malloc(size + 1);
|
||||||
|
if (!content) {
|
||||||
|
fclose(fp);
|
||||||
|
return R_ERROR_OUT_OF_MEMORY;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t read_size = fread(content, 1, size, fp);
|
||||||
|
content[read_size] = '\0';
|
||||||
|
fclose(fp);
|
||||||
|
|
||||||
|
char *sql = sqlite3_mprintf(
|
||||||
|
"INSERT INTO file_versions (path, content) VALUES (%Q, %Q)",
|
||||||
|
path, content);
|
||||||
|
free(content);
|
||||||
|
|
||||||
|
if (!sql) return R_ERROR_OUT_OF_MEMORY;
|
||||||
|
|
||||||
|
char *err_msg = NULL;
|
||||||
|
int rc = sqlite3_exec(db->conn, sql, NULL, NULL, &err_msg);
|
||||||
|
sqlite3_free(sql);
|
||||||
|
sqlite3_free(err_msg);
|
||||||
|
|
||||||
|
return (rc == SQLITE_OK) ? R_SUCCESS : R_ERROR_DB_QUERY;
|
||||||
|
}
|
||||||
|
|
||||||
|
r_status_t db_save_conversation(db_handle db, const char *session_key, const char *data) {
|
||||||
|
if (!db || !db->conn || !session_key || !data) return R_ERROR_INVALID_ARG;
|
||||||
|
|
||||||
|
char *sql = sqlite3_mprintf(
|
||||||
|
"INSERT OR REPLACE INTO conversations (session_key, data, updated_at) "
|
||||||
|
"VALUES (%Q, %Q, CURRENT_TIMESTAMP)",
|
||||||
|
session_key, data);
|
||||||
|
if (!sql) return R_ERROR_OUT_OF_MEMORY;
|
||||||
|
|
||||||
|
char *err_msg = NULL;
|
||||||
|
int rc = sqlite3_exec(db->conn, sql, NULL, NULL, &err_msg);
|
||||||
|
sqlite3_free(sql);
|
||||||
|
sqlite3_free(err_msg);
|
||||||
|
|
||||||
|
return (rc == SQLITE_OK) ? R_SUCCESS : R_ERROR_DB_QUERY;
|
||||||
|
}
|
||||||
|
|
||||||
|
r_status_t db_load_conversation(db_handle db, const char *session_key, char **data) {
|
||||||
|
if (!db || !db->conn || !session_key || !data) return R_ERROR_INVALID_ARG;
|
||||||
|
|
||||||
|
*data = NULL;
|
||||||
|
const char *sql = "SELECT data FROM conversations WHERE session_key = ?";
|
||||||
|
sqlite3_stmt *stmt = NULL;
|
||||||
|
|
||||||
|
if (sqlite3_prepare_v2(db->conn, sql, -1, &stmt, NULL) != SQLITE_OK) {
|
||||||
|
return R_ERROR_DB_QUERY;
|
||||||
|
}
|
||||||
|
|
||||||
|
sqlite3_bind_text(stmt, 1, session_key, -1, SQLITE_STATIC);
|
||||||
|
|
||||||
|
int rc = sqlite3_step(stmt);
|
||||||
|
if (rc == SQLITE_ROW) {
|
||||||
|
const char *val = (const char *)sqlite3_column_text(stmt, 0);
|
||||||
|
*data = val ? strdup(val) : NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
sqlite3_finalize(stmt);
|
||||||
|
return (rc == SQLITE_ROW) ? R_SUCCESS : R_ERROR_DB_NOT_FOUND;
|
||||||
|
}
|
||||||
313
src/http_client.c
Executable file
313
src/http_client.c
Executable file
@ -0,0 +1,313 @@
|
|||||||
|
// retoor <retoor@molodetz.nl>
|
||||||
|
|
||||||
|
#include "http_client.h"
|
||||||
|
#include <curl/curl.h>
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#define HTTP_MAX_RETRIES 3
|
||||||
|
#define HTTP_RETRY_DELAY_MS 2000
|
||||||
|
|
||||||
|
struct http_client_t {
|
||||||
|
char *bearer_token;
|
||||||
|
long timeout_seconds;
|
||||||
|
long connect_timeout_seconds;
|
||||||
|
bool show_spinner;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct response_buffer_t {
|
||||||
|
char *data;
|
||||||
|
size_t size;
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct timespec spinner_start_time = {0, 0};
|
||||||
|
static volatile int spinner_running = 0;
|
||||||
|
|
||||||
|
static double get_elapsed_seconds(void) {
|
||||||
|
struct timespec now;
|
||||||
|
clock_gettime(CLOCK_MONOTONIC, &now);
|
||||||
|
return (now.tv_sec - spinner_start_time.tv_sec) +
|
||||||
|
(now.tv_nsec - spinner_start_time.tv_nsec) / 1e9;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void *spinner_thread(void *arg) {
|
||||||
|
(void)arg;
|
||||||
|
const char *frames[] = {"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"};
|
||||||
|
int frame = 0;
|
||||||
|
while (spinner_running) {
|
||||||
|
double elapsed = get_elapsed_seconds();
|
||||||
|
fprintf(stderr, "\r%s Querying AI... (%.1fs) ", frames[frame % 10], elapsed);
|
||||||
|
fflush(stderr);
|
||||||
|
frame++;
|
||||||
|
usleep(80000);
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t write_callback(void *contents, size_t size, size_t nmemb, void *userp) {
|
||||||
|
size_t total_size = size * nmemb;
|
||||||
|
struct response_buffer_t *response = (struct response_buffer_t *)userp;
|
||||||
|
|
||||||
|
if (total_size > SIZE_MAX - response->size - 1) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *ptr = realloc(response->data, response->size + total_size + 1);
|
||||||
|
if (!ptr) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
response->data = ptr;
|
||||||
|
memcpy(&(response->data[response->size]), contents, total_size);
|
||||||
|
response->size += total_size;
|
||||||
|
response->data[response->size] = '\0';
|
||||||
|
return total_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
http_client_handle http_client_create(const char *bearer_token) {
|
||||||
|
struct http_client_t *client = calloc(1, sizeof(struct http_client_t));
|
||||||
|
if (!client) return NULL;
|
||||||
|
|
||||||
|
if (bearer_token) {
|
||||||
|
client->bearer_token = strdup(bearer_token);
|
||||||
|
if (!client->bearer_token) {
|
||||||
|
free(client);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
client->timeout_seconds = 300;
|
||||||
|
client->connect_timeout_seconds = 10;
|
||||||
|
client->show_spinner = true;
|
||||||
|
|
||||||
|
return client;
|
||||||
|
}
|
||||||
|
|
||||||
|
void http_client_destroy(http_client_handle client) {
|
||||||
|
if (!client) return;
|
||||||
|
free(client->bearer_token);
|
||||||
|
free(client);
|
||||||
|
}
|
||||||
|
|
||||||
|
void http_client_set_show_spinner(http_client_handle client, bool show) {
|
||||||
|
if (client) client->show_spinner = show;
|
||||||
|
}
|
||||||
|
|
||||||
|
void http_client_set_timeout(http_client_handle client, long timeout_seconds) {
|
||||||
|
if (client) client->timeout_seconds = timeout_seconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
void http_client_set_connect_timeout(http_client_handle client, long timeout_seconds) {
|
||||||
|
if (client) client->connect_timeout_seconds = timeout_seconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
r_status_t http_post(http_client_handle client, const char *url,
|
||||||
|
const char *data, char **response) {
|
||||||
|
if (!client || !url || !response) return R_ERROR_INVALID_ARG;
|
||||||
|
|
||||||
|
CURL *curl = NULL;
|
||||||
|
struct curl_slist *headers = NULL;
|
||||||
|
struct response_buffer_t resp = {NULL, 0};
|
||||||
|
int retry_count = 0;
|
||||||
|
pthread_t spinner_tid = 0;
|
||||||
|
r_status_t status = R_SUCCESS;
|
||||||
|
|
||||||
|
*response = NULL;
|
||||||
|
|
||||||
|
if (client->show_spinner) {
|
||||||
|
clock_gettime(CLOCK_MONOTONIC, &spinner_start_time);
|
||||||
|
spinner_running = 1;
|
||||||
|
pthread_create(&spinner_tid, NULL, spinner_thread, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
while (retry_count < HTTP_MAX_RETRIES) {
|
||||||
|
free(resp.data);
|
||||||
|
resp.data = malloc(1);
|
||||||
|
resp.size = 0;
|
||||||
|
|
||||||
|
if (!resp.data) {
|
||||||
|
status = R_ERROR_OUT_OF_MEMORY;
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
resp.data[0] = '\0';
|
||||||
|
|
||||||
|
curl = curl_easy_init();
|
||||||
|
if (!curl) {
|
||||||
|
status = R_ERROR_HTTP_CONNECTION;
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
curl_easy_setopt(curl, CURLOPT_URL, url);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, client->connect_timeout_seconds);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_TIMEOUT, client->timeout_seconds);
|
||||||
|
|
||||||
|
headers = curl_slist_append(headers, "Content-Type: application/json");
|
||||||
|
if (client->bearer_token) {
|
||||||
|
char bearer_header[2048];
|
||||||
|
snprintf(bearer_header, sizeof(bearer_header), "Authorization: Bearer %s",
|
||||||
|
client->bearer_token);
|
||||||
|
headers = curl_slist_append(headers, bearer_header);
|
||||||
|
}
|
||||||
|
|
||||||
|
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&resp);
|
||||||
|
|
||||||
|
CURLcode res = curl_easy_perform(curl);
|
||||||
|
|
||||||
|
curl_slist_free_all(headers);
|
||||||
|
headers = NULL;
|
||||||
|
curl_easy_cleanup(curl);
|
||||||
|
curl = NULL;
|
||||||
|
|
||||||
|
if (res == CURLE_OK) {
|
||||||
|
*response = resp.data;
|
||||||
|
resp.data = NULL;
|
||||||
|
status = R_SUCCESS;
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
retry_count++;
|
||||||
|
|
||||||
|
if (client->show_spinner) {
|
||||||
|
spinner_running = 0;
|
||||||
|
pthread_join(spinner_tid, NULL);
|
||||||
|
spinner_tid = 0;
|
||||||
|
fprintf(stderr, "\r \r");
|
||||||
|
}
|
||||||
|
|
||||||
|
fprintf(stderr, "Network error: %s (attempt %d/%d)\n",
|
||||||
|
curl_easy_strerror(res), retry_count, HTTP_MAX_RETRIES);
|
||||||
|
|
||||||
|
if (retry_count < HTTP_MAX_RETRIES) {
|
||||||
|
fprintf(stderr, "Retrying in %d seconds...\n", HTTP_RETRY_DELAY_MS / 1000);
|
||||||
|
usleep(HTTP_RETRY_DELAY_MS * 1000);
|
||||||
|
|
||||||
|
if (client->show_spinner) {
|
||||||
|
clock_gettime(CLOCK_MONOTONIC, &spinner_start_time);
|
||||||
|
spinner_running = 1;
|
||||||
|
pthread_create(&spinner_tid, NULL, spinner_thread, NULL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
status = R_ERROR_HTTP_TIMEOUT;
|
||||||
|
|
||||||
|
cleanup:
|
||||||
|
if (client->show_spinner && spinner_tid) {
|
||||||
|
spinner_running = 0;
|
||||||
|
pthread_join(spinner_tid, NULL);
|
||||||
|
fprintf(stderr, "\r \r");
|
||||||
|
fflush(stderr);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (headers) curl_slist_free_all(headers);
|
||||||
|
if (curl) curl_easy_cleanup(curl);
|
||||||
|
free(resp.data);
|
||||||
|
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
r_status_t http_get(http_client_handle client, const char *url, char **response) {
|
||||||
|
if (!client || !url || !response) return R_ERROR_INVALID_ARG;
|
||||||
|
|
||||||
|
CURL *curl = NULL;
|
||||||
|
struct curl_slist *headers = NULL;
|
||||||
|
struct response_buffer_t resp = {NULL, 0};
|
||||||
|
int retry_count = 0;
|
||||||
|
r_status_t status = R_SUCCESS;
|
||||||
|
|
||||||
|
*response = NULL;
|
||||||
|
|
||||||
|
while (retry_count < HTTP_MAX_RETRIES) {
|
||||||
|
free(resp.data);
|
||||||
|
resp.data = malloc(1);
|
||||||
|
resp.size = 0;
|
||||||
|
|
||||||
|
if (!resp.data) {
|
||||||
|
status = R_ERROR_OUT_OF_MEMORY;
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
resp.data[0] = '\0';
|
||||||
|
|
||||||
|
curl = curl_easy_init();
|
||||||
|
if (!curl) {
|
||||||
|
status = R_ERROR_HTTP_CONNECTION;
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
curl_easy_setopt(curl, CURLOPT_URL, url);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, client->connect_timeout_seconds);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 60L);
|
||||||
|
|
||||||
|
headers = curl_slist_append(headers, "Content-Type: application/json");
|
||||||
|
if (client->bearer_token) {
|
||||||
|
char bearer_header[2048];
|
||||||
|
snprintf(bearer_header, sizeof(bearer_header), "Authorization: Bearer %s",
|
||||||
|
client->bearer_token);
|
||||||
|
headers = curl_slist_append(headers, bearer_header);
|
||||||
|
}
|
||||||
|
|
||||||
|
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&resp);
|
||||||
|
|
||||||
|
CURLcode res = curl_easy_perform(curl);
|
||||||
|
|
||||||
|
curl_slist_free_all(headers);
|
||||||
|
headers = NULL;
|
||||||
|
curl_easy_cleanup(curl);
|
||||||
|
curl = NULL;
|
||||||
|
|
||||||
|
if (res == CURLE_OK) {
|
||||||
|
*response = resp.data;
|
||||||
|
resp.data = NULL;
|
||||||
|
status = R_SUCCESS;
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
retry_count++;
|
||||||
|
fprintf(stderr, "Network error: %s (attempt %d/%d)\n",
|
||||||
|
curl_easy_strerror(res), retry_count, HTTP_MAX_RETRIES);
|
||||||
|
|
||||||
|
if (retry_count < HTTP_MAX_RETRIES) {
|
||||||
|
fprintf(stderr, "Retrying in %d seconds...\n", HTTP_RETRY_DELAY_MS / 1000);
|
||||||
|
usleep(HTTP_RETRY_DELAY_MS * 1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
status = R_ERROR_HTTP_TIMEOUT;
|
||||||
|
|
||||||
|
cleanup:
|
||||||
|
if (headers) curl_slist_free_all(headers);
|
||||||
|
if (curl) curl_easy_cleanup(curl);
|
||||||
|
free(resp.data);
|
||||||
|
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
r_status_t http_post_simple(const char *url, const char *bearer_token,
|
||||||
|
const char *data, char **response) {
|
||||||
|
http_client_handle client = http_client_create(bearer_token);
|
||||||
|
if (!client) return R_ERROR_OUT_OF_MEMORY;
|
||||||
|
|
||||||
|
r_status_t status = http_post(client, url, data, response);
|
||||||
|
http_client_destroy(client);
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
r_status_t http_get_simple(const char *url, const char *bearer_token, char **response) {
|
||||||
|
http_client_handle client = http_client_create(bearer_token);
|
||||||
|
if (!client) return R_ERROR_OUT_OF_MEMORY;
|
||||||
|
|
||||||
|
http_client_set_show_spinner(client, false);
|
||||||
|
r_status_t status = http_get(client, url, response);
|
||||||
|
http_client_destroy(client);
|
||||||
|
return status;
|
||||||
|
}
|
||||||
423
src/main.c
Executable file
423
src/main.c
Executable file
@ -0,0 +1,423 @@
|
|||||||
|
// retoor <retoor@molodetz.nl>
|
||||||
|
|
||||||
|
#include "agent.h"
|
||||||
|
#include "db.h"
|
||||||
|
#include "http_client.h"
|
||||||
|
#include "r_config.h"
|
||||||
|
#include "r_error.h"
|
||||||
|
#include "tool.h"
|
||||||
|
|
||||||
|
#include "../line.h"
|
||||||
|
#include "../markdown.h"
|
||||||
|
#include "../utils.h"
|
||||||
|
|
||||||
|
#include <json-c/json.h>
|
||||||
|
#include <locale.h>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
static volatile sig_atomic_t sigint_count = 0;
|
||||||
|
static time_t first_sigint_time = 0;
|
||||||
|
static bool syntax_highlight_enabled = true;
|
||||||
|
static bool api_mode = false;
|
||||||
|
|
||||||
|
static db_handle global_db = NULL;
|
||||||
|
static messages_handle global_messages = NULL;
|
||||||
|
|
||||||
|
extern tool_registry_t *tools_get_registry(void);
|
||||||
|
extern void tools_registry_shutdown(void);
|
||||||
|
|
||||||
|
static void render(const char *content);
|
||||||
|
static bool include_file(const char *path);
|
||||||
|
static char *get_prompt_from_stdin(char *prompt);
|
||||||
|
static char *get_prompt_from_args(int argc, char **argv);
|
||||||
|
static bool try_prompt(int argc, char *argv[]);
|
||||||
|
static void repl(void);
|
||||||
|
static void init(void);
|
||||||
|
static void cleanup(void);
|
||||||
|
static void handle_sigint(int sig);
|
||||||
|
|
||||||
|
static char *get_env_string(void) {
|
||||||
|
FILE *fp = popen("env", "r");
|
||||||
|
if (!fp) return NULL;
|
||||||
|
|
||||||
|
size_t buffer_size = 1024;
|
||||||
|
size_t total_size = 0;
|
||||||
|
char *output = malloc(buffer_size);
|
||||||
|
if (!output) {
|
||||||
|
pclose(fp);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t bytes_read;
|
||||||
|
while ((bytes_read = fread(output + total_size, 1, buffer_size - total_size, fp)) > 0) {
|
||||||
|
total_size += bytes_read;
|
||||||
|
if (total_size >= buffer_size) {
|
||||||
|
buffer_size *= 2;
|
||||||
|
char *temp = realloc(output, buffer_size);
|
||||||
|
if (!temp) {
|
||||||
|
free(output);
|
||||||
|
pclose(fp);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
output = temp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
output[total_size] = '\0';
|
||||||
|
pclose(fp);
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
static char *get_prompt_from_stdin(char *prompt) {
|
||||||
|
int index = 0;
|
||||||
|
int c;
|
||||||
|
while ((c = getchar()) != EOF) {
|
||||||
|
prompt[index++] = (char)c;
|
||||||
|
}
|
||||||
|
prompt[index] = '\0';
|
||||||
|
return prompt;
|
||||||
|
}
|
||||||
|
|
||||||
|
static char *get_prompt_from_args(int argc, char **argv) {
|
||||||
|
r_config_handle cfg = r_config_get_instance();
|
||||||
|
|
||||||
|
char *prompt = malloc(10 * 1024 * 1024 + 1);
|
||||||
|
char *system_msg = malloc(1024 * 1024);
|
||||||
|
if (!prompt || !system_msg) {
|
||||||
|
free(prompt);
|
||||||
|
free(system_msg);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
system_msg[0] = '\0';
|
||||||
|
bool get_from_stdin = false;
|
||||||
|
|
||||||
|
for (int i = 1; i < argc; i++) {
|
||||||
|
if (strcmp(argv[i], "--stdin") == 0) {
|
||||||
|
fprintf(stderr, "Reading from stdin.\n");
|
||||||
|
get_from_stdin = true;
|
||||||
|
} else if (strcmp(argv[i], "--verbose") == 0) {
|
||||||
|
r_config_set_verbose(cfg, true);
|
||||||
|
} else if (strcmp(argv[i], "--py") == 0 && i + 1 < argc) {
|
||||||
|
char *py_file_path = expand_home_directory(argv[++i]);
|
||||||
|
fprintf(stderr, "Including \"%s\".\n", py_file_path);
|
||||||
|
include_file(py_file_path);
|
||||||
|
free(py_file_path);
|
||||||
|
} else if (strcmp(argv[i], "--context") == 0 && i + 1 < argc) {
|
||||||
|
char *context_file_path = argv[++i];
|
||||||
|
fprintf(stderr, "Including \"%s\".\n", context_file_path);
|
||||||
|
include_file(context_file_path);
|
||||||
|
} else if (strcmp(argv[i], "--api") == 0) {
|
||||||
|
api_mode = true;
|
||||||
|
} else if (strcmp(argv[i], "--nh") == 0) {
|
||||||
|
syntax_highlight_enabled = false;
|
||||||
|
fprintf(stderr, "Syntax highlighting disabled.\n");
|
||||||
|
} else if (strncmp(argv[i], "--session=", 10) == 0) {
|
||||||
|
continue;
|
||||||
|
} else if (strcmp(argv[i], "-s") == 0 || strcmp(argv[i], "--session") == 0) {
|
||||||
|
i++;
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
strcat(system_msg, argv[i]);
|
||||||
|
strcat(system_msg, (i < argc - 1) ? " " : ".");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (get_from_stdin) {
|
||||||
|
if (*system_msg && global_messages) {
|
||||||
|
messages_add(global_messages, "system", system_msg);
|
||||||
|
}
|
||||||
|
prompt = get_prompt_from_stdin(prompt);
|
||||||
|
free(system_msg);
|
||||||
|
} else {
|
||||||
|
free(prompt);
|
||||||
|
prompt = system_msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!*prompt) {
|
||||||
|
free(prompt);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
return prompt;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool try_prompt(int argc, char *argv[]) {
|
||||||
|
char *prompt = get_prompt_from_args(argc, argv);
|
||||||
|
if (prompt) {
|
||||||
|
char *response = agent_chat(prompt, global_messages);
|
||||||
|
if (!response) {
|
||||||
|
printf("Could not get response from server\n");
|
||||||
|
free(prompt);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
render(response);
|
||||||
|
free(response);
|
||||||
|
free(prompt);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool include_file(const char *path) {
|
||||||
|
char *file_content = read_file(path);
|
||||||
|
if (!file_content) return false;
|
||||||
|
|
||||||
|
if (global_messages) {
|
||||||
|
messages_add(global_messages, "system", file_content);
|
||||||
|
}
|
||||||
|
free(file_content);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void render(const char *content) {
|
||||||
|
if (syntax_highlight_enabled) {
|
||||||
|
parse_markdown_to_ansi(content);
|
||||||
|
} else {
|
||||||
|
printf("%s", content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void repl(void) {
|
||||||
|
r_config_handle cfg = r_config_get_instance();
|
||||||
|
tool_registry_t *tools = tools_get_registry();
|
||||||
|
|
||||||
|
line_init();
|
||||||
|
char *line = NULL;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
line = line_read("> ");
|
||||||
|
if (!line || !*line) continue;
|
||||||
|
|
||||||
|
if (!strncmp(line, "!dump", 5)) {
|
||||||
|
char *json = messages_to_string(global_messages);
|
||||||
|
if (json) {
|
||||||
|
printf("%s\n", json);
|
||||||
|
free(json);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!strncmp(line, "!clear", 6)) {
|
||||||
|
messages_clear(global_messages);
|
||||||
|
fprintf(stderr, "Session cleared.\n");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!strncmp(line, "!session", 8)) {
|
||||||
|
printf("Session: %s\n", messages_get_session_id(global_messages));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!strncmp(line, "!verbose", 8)) {
|
||||||
|
bool verbose = !r_config_is_verbose(cfg);
|
||||||
|
r_config_set_verbose(cfg, verbose);
|
||||||
|
fprintf(stderr, "%s\n", verbose ? "Verbose mode enabled" : "Verbose mode disabled");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (line && *line != '\n') {
|
||||||
|
line_add_history(line);
|
||||||
|
}
|
||||||
|
if (!strncmp(line, "!tools", 6)) {
|
||||||
|
struct json_object *descs = tool_registry_get_descriptions(tools);
|
||||||
|
printf("Available tools: %s\n", json_object_to_json_string(descs));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!strncmp(line, "!models", 7)) {
|
||||||
|
http_client_handle http = http_client_create(r_config_get_api_key(cfg));
|
||||||
|
if (http) {
|
||||||
|
http_client_set_show_spinner(http, false);
|
||||||
|
char *response = NULL;
|
||||||
|
if (http_get(http, r_config_get_models_url(cfg), &response) == R_SUCCESS && response) {
|
||||||
|
printf("Models: %s\n", response);
|
||||||
|
free(response);
|
||||||
|
}
|
||||||
|
http_client_destroy(http);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!strncmp(line, "!model", 6)) {
|
||||||
|
if (line[6] == ' ') {
|
||||||
|
r_config_set_model(cfg, line + 7);
|
||||||
|
}
|
||||||
|
printf("Current model: %s\n", r_config_get_model(cfg));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!strncmp(line, "exit", 4)) {
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
while (line && *line != '\n') {
|
||||||
|
char *response = agent_chat(line, global_messages);
|
||||||
|
if (response) {
|
||||||
|
render(response);
|
||||||
|
printf("\n");
|
||||||
|
if (strstr(response, "_STEP_")) {
|
||||||
|
line = "continue";
|
||||||
|
} else {
|
||||||
|
line = NULL;
|
||||||
|
}
|
||||||
|
free(response);
|
||||||
|
} else {
|
||||||
|
fprintf(stderr, "Agent returned no response\n");
|
||||||
|
line = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void init(void) {
|
||||||
|
setbuf(stdout, NULL);
|
||||||
|
line_init();
|
||||||
|
|
||||||
|
r_config_handle cfg = r_config_get_instance();
|
||||||
|
|
||||||
|
global_db = db_open(NULL);
|
||||||
|
global_messages = messages_create(r_config_get_session_id(cfg));
|
||||||
|
|
||||||
|
char *schema = db_get_schema(global_db);
|
||||||
|
char payload[1024 * 1024] = {0};
|
||||||
|
|
||||||
|
time_t now = time(NULL);
|
||||||
|
struct tm *tm_info = localtime(&now);
|
||||||
|
char datetime[64];
|
||||||
|
strftime(datetime, sizeof(datetime), "%Y-%m-%d %H:%M:%S %Z", tm_info);
|
||||||
|
|
||||||
|
char cwd[4096];
|
||||||
|
if (!getcwd(cwd, sizeof(cwd))) {
|
||||||
|
strcpy(cwd, "unknown");
|
||||||
|
}
|
||||||
|
|
||||||
|
snprintf(
|
||||||
|
payload, sizeof(payload),
|
||||||
|
"# AUTONOMOUS AGENT INSTRUCTIONS\n"
|
||||||
|
"Current date/time: %s\n"
|
||||||
|
"Working directory: %s\n\n"
|
||||||
|
"You are an autonomous AI agent. You operate in a loop: reason about the task, "
|
||||||
|
"select and execute tools when needed, observe results, and continue until the goal is achieved.\n\n"
|
||||||
|
"## Reasoning Pattern (ReAct)\n"
|
||||||
|
"For complex tasks, think step-by-step:\n"
|
||||||
|
"1. Thought: What do I need to accomplish? What information do I have?\n"
|
||||||
|
"2. Action: Which tool should I use? With what parameters?\n"
|
||||||
|
"3. Observation: What did the tool return? What does this tell me?\n"
|
||||||
|
"4. Repeat until the goal is complete.\n\n"
|
||||||
|
"## Tool Usage\n"
|
||||||
|
"- Use tools proactively to gather information and take actions\n"
|
||||||
|
"- If a tool fails, analyze the error and try a different approach\n"
|
||||||
|
"- You can call multiple tools in sequence to accomplish complex tasks\n\n"
|
||||||
|
"## CRITICAL OUTPUT RULES\n"
|
||||||
|
"- You MUST include the actual content/data from tool results in your response\n"
|
||||||
|
"- When you search the web, QUOTE the relevant information found\n"
|
||||||
|
"- When you run a command, SHOW the output\n"
|
||||||
|
"- NEVER say 'I found information' without showing what you found\n"
|
||||||
|
"- NEVER say 'task complete' or 'report provided' - SHOW THE ACTUAL DATA\n"
|
||||||
|
"- The user cannot see tool results - only YOUR response. Include everything relevant.\n\n"
|
||||||
|
"## Local Database\n"
|
||||||
|
"You have a local SQLite database accessible via db_query, db_get, and db_set tools.\n"
|
||||||
|
"Use stemmed, lowercase keys to prevent duplicates.\n"
|
||||||
|
"Schema: %s\n\n"
|
||||||
|
"## Response Format\n"
|
||||||
|
"Your response IS the only thing the user sees. Tool outputs are hidden from them.\n"
|
||||||
|
"You MUST copy/paste relevant data from tool results into your response.\n"
|
||||||
|
"Bad: 'I searched and found information about X.'\n"
|
||||||
|
"Good: 'Here is what I found: [actual content from search results]'\n",
|
||||||
|
datetime, cwd, schema ? schema : "{}");
|
||||||
|
|
||||||
|
free(schema);
|
||||||
|
fprintf(stderr, "Loading...");
|
||||||
|
|
||||||
|
if (global_messages) {
|
||||||
|
messages_add(global_messages, "system", payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *env_system_msg = r_config_get_system_message(cfg);
|
||||||
|
if (env_system_msg && *env_system_msg && global_messages) {
|
||||||
|
messages_add(global_messages, "system", env_system_msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!include_file(".rcontext.txt")) {
|
||||||
|
include_file("~/.rcontext.txt");
|
||||||
|
}
|
||||||
|
|
||||||
|
fprintf(stderr, "\r \r");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void cleanup(void) {
|
||||||
|
if (global_messages) {
|
||||||
|
messages_destroy(global_messages);
|
||||||
|
global_messages = NULL;
|
||||||
|
}
|
||||||
|
if (global_db) {
|
||||||
|
db_close(global_db);
|
||||||
|
global_db = NULL;
|
||||||
|
}
|
||||||
|
tools_registry_shutdown();
|
||||||
|
r_config_destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void handle_sigint(int sig) {
|
||||||
|
(void)sig;
|
||||||
|
time_t current_time = time(NULL);
|
||||||
|
printf("\n");
|
||||||
|
if (sigint_count == 0) {
|
||||||
|
first_sigint_time = current_time;
|
||||||
|
sigint_count++;
|
||||||
|
} else {
|
||||||
|
if (difftime(current_time, first_sigint_time) <= 1) {
|
||||||
|
cleanup();
|
||||||
|
exit(0);
|
||||||
|
} else {
|
||||||
|
sigint_count = 1;
|
||||||
|
first_sigint_time = current_time;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void parse_session_arg(int argc, char *argv[]) {
|
||||||
|
r_config_handle cfg = r_config_get_instance();
|
||||||
|
|
||||||
|
for (int i = 1; i < argc; i++) {
|
||||||
|
if (strncmp(argv[i], "--session=", 10) == 0) {
|
||||||
|
const char *name = argv[i] + 10;
|
||||||
|
if (!r_config_set_session_id(cfg, name)) {
|
||||||
|
fprintf(stderr, "Error: Invalid session name '%s'\n", name);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ((strcmp(argv[i], "-s") == 0 || strcmp(argv[i], "--session") == 0) && i + 1 < argc) {
|
||||||
|
const char *name = argv[++i];
|
||||||
|
if (!r_config_set_session_id(cfg, name)) {
|
||||||
|
fprintf(stderr, "Error: Invalid session name '%s'\n", name);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char *argv[]) {
|
||||||
|
signal(SIGINT, handle_sigint);
|
||||||
|
atexit(cleanup);
|
||||||
|
|
||||||
|
parse_session_arg(argc, argv);
|
||||||
|
init();
|
||||||
|
|
||||||
|
char *env_string = get_env_string();
|
||||||
|
if (env_string && *env_string && global_messages) {
|
||||||
|
messages_add(global_messages, "system", env_string);
|
||||||
|
free(env_string);
|
||||||
|
}
|
||||||
|
|
||||||
|
messages_load(global_messages);
|
||||||
|
|
||||||
|
if (try_prompt(argc, argv)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
repl();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
235
src/messages.c
Executable file
235
src/messages.c
Executable file
@ -0,0 +1,235 @@
|
|||||||
|
// retoor <retoor@molodetz.nl>
|
||||||
|
|
||||||
|
#include "messages.h"
|
||||||
|
#include "db.h"
|
||||||
|
#include <ctype.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#define MAX_CONTENT_LENGTH 1048570
|
||||||
|
#define MAX_TOOL_RESULT_LENGTH 104000
|
||||||
|
|
||||||
|
struct messages_t {
|
||||||
|
struct json_object *array;
|
||||||
|
char *session_id;
|
||||||
|
db_handle db;
|
||||||
|
bool loaded;
|
||||||
|
};
|
||||||
|
|
||||||
|
static bool is_valid_session_id(const char *session_id) {
|
||||||
|
if (!session_id || !*session_id) return false;
|
||||||
|
if (strlen(session_id) > 200) return false;
|
||||||
|
|
||||||
|
for (const char *p = session_id; *p; p++) {
|
||||||
|
if (!isalnum((unsigned char)*p) && *p != '-' && *p != '_' && *p != '.') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static char *generate_default_session_id(void) {
|
||||||
|
char *session_id = malloc(32);
|
||||||
|
if (!session_id) return NULL;
|
||||||
|
|
||||||
|
snprintf(session_id, 32, "session-%d", getppid());
|
||||||
|
return session_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
messages_handle messages_create(const char *session_id) {
|
||||||
|
struct messages_t *msgs = calloc(1, sizeof(struct messages_t));
|
||||||
|
if (!msgs) return NULL;
|
||||||
|
|
||||||
|
msgs->array = json_object_new_array();
|
||||||
|
if (!msgs->array) {
|
||||||
|
free(msgs);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (session_id && is_valid_session_id(session_id)) {
|
||||||
|
msgs->session_id = strdup(session_id);
|
||||||
|
} else {
|
||||||
|
msgs->session_id = generate_default_session_id();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!msgs->session_id) {
|
||||||
|
json_object_put(msgs->array);
|
||||||
|
free(msgs);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
msgs->db = db_open(NULL);
|
||||||
|
|
||||||
|
return msgs;
|
||||||
|
}
|
||||||
|
|
||||||
|
void messages_destroy(messages_handle msgs) {
|
||||||
|
if (!msgs) return;
|
||||||
|
if (msgs->array) json_object_put(msgs->array);
|
||||||
|
if (msgs->db) db_close(msgs->db);
|
||||||
|
free(msgs->session_id);
|
||||||
|
free(msgs);
|
||||||
|
}
|
||||||
|
|
||||||
|
r_status_t messages_set_session_id(messages_handle msgs, const char *session_id) {
|
||||||
|
if (!msgs || !is_valid_session_id(session_id)) return R_ERROR_INVALID_ARG;
|
||||||
|
|
||||||
|
free(msgs->session_id);
|
||||||
|
msgs->session_id = strdup(session_id);
|
||||||
|
msgs->loaded = false;
|
||||||
|
return msgs->session_id ? R_SUCCESS : R_ERROR_OUT_OF_MEMORY;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *messages_get_session_id(messages_handle msgs) {
|
||||||
|
return msgs ? msgs->session_id : NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
r_status_t messages_add(messages_handle msgs, const char *role, const char *content) {
|
||||||
|
if (!msgs || !msgs->array || !role) return R_ERROR_INVALID_ARG;
|
||||||
|
|
||||||
|
struct json_object *message = json_object_new_object();
|
||||||
|
if (!message) return R_ERROR_OUT_OF_MEMORY;
|
||||||
|
|
||||||
|
json_object_object_add(message, "role", json_object_new_string(role));
|
||||||
|
|
||||||
|
if (content) {
|
||||||
|
size_t len = strlen(content);
|
||||||
|
if (len > MAX_CONTENT_LENGTH) len = MAX_CONTENT_LENGTH;
|
||||||
|
json_object_object_add(message, "content",
|
||||||
|
json_object_new_string_len(content, (int)len));
|
||||||
|
}
|
||||||
|
|
||||||
|
json_object_array_add(msgs->array, message);
|
||||||
|
|
||||||
|
if (strcmp(role, "system") != 0) {
|
||||||
|
messages_save(msgs);
|
||||||
|
}
|
||||||
|
|
||||||
|
return R_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
r_status_t messages_add_object(messages_handle msgs, struct json_object *message) {
|
||||||
|
if (!msgs || !msgs->array || !message) return R_ERROR_INVALID_ARG;
|
||||||
|
|
||||||
|
json_object_array_add(msgs->array, message);
|
||||||
|
messages_save(msgs);
|
||||||
|
return R_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
r_status_t messages_add_tool_call(messages_handle msgs, struct json_object *message) {
|
||||||
|
if (!msgs || !msgs->array || !message) return R_ERROR_INVALID_ARG;
|
||||||
|
|
||||||
|
json_object_array_add(msgs->array, message);
|
||||||
|
messages_save(msgs);
|
||||||
|
return R_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
r_status_t messages_add_tool_result(messages_handle msgs, const char *tool_call_id, const char *result) {
|
||||||
|
if (!msgs || !msgs->array || !tool_call_id || !result) return R_ERROR_INVALID_ARG;
|
||||||
|
|
||||||
|
struct json_object *message = json_object_new_object();
|
||||||
|
if (!message) return R_ERROR_OUT_OF_MEMORY;
|
||||||
|
|
||||||
|
json_object_object_add(message, "tool_call_id", json_object_new_string(tool_call_id));
|
||||||
|
|
||||||
|
size_t len = strlen(result);
|
||||||
|
if (len > MAX_TOOL_RESULT_LENGTH) len = MAX_TOOL_RESULT_LENGTH;
|
||||||
|
json_object_object_add(message, "tool_result",
|
||||||
|
json_object_new_string_len(result, (int)len));
|
||||||
|
|
||||||
|
json_object_array_add(msgs->array, message);
|
||||||
|
messages_save(msgs);
|
||||||
|
return R_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
r_status_t messages_remove_last(messages_handle msgs) {
|
||||||
|
if (!msgs || !msgs->array) return R_ERROR_INVALID_ARG;
|
||||||
|
|
||||||
|
int size = json_object_array_length(msgs->array);
|
||||||
|
if (size == 0) return R_ERROR_NOT_FOUND;
|
||||||
|
|
||||||
|
json_object_array_del_idx(msgs->array, size - 1, 1);
|
||||||
|
messages_save(msgs);
|
||||||
|
return R_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
r_status_t messages_clear(messages_handle msgs) {
|
||||||
|
if (!msgs) return R_ERROR_INVALID_ARG;
|
||||||
|
|
||||||
|
if (msgs->array) json_object_put(msgs->array);
|
||||||
|
msgs->array = json_object_new_array();
|
||||||
|
if (!msgs->array) return R_ERROR_OUT_OF_MEMORY;
|
||||||
|
|
||||||
|
messages_save(msgs);
|
||||||
|
return R_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
r_status_t messages_save(messages_handle msgs) {
|
||||||
|
if (!msgs || !msgs->array || !msgs->db) return R_ERROR_INVALID_ARG;
|
||||||
|
|
||||||
|
char key[512];
|
||||||
|
snprintf(key, sizeof(key), "session:%s", msgs->session_id);
|
||||||
|
|
||||||
|
const char *json_str = json_object_to_json_string_ext(msgs->array, JSON_C_TO_STRING_PLAIN);
|
||||||
|
if (!json_str) return R_ERROR_OUT_OF_MEMORY;
|
||||||
|
|
||||||
|
return db_save_conversation(msgs->db, key, json_str);
|
||||||
|
}
|
||||||
|
|
||||||
|
r_status_t messages_load(messages_handle msgs) {
|
||||||
|
if (!msgs || !msgs->db) return R_ERROR_INVALID_ARG;
|
||||||
|
if (msgs->loaded) return R_SUCCESS;
|
||||||
|
|
||||||
|
char key[512];
|
||||||
|
snprintf(key, sizeof(key), "session:%s", msgs->session_id);
|
||||||
|
|
||||||
|
char *data = NULL;
|
||||||
|
r_status_t status = db_load_conversation(msgs->db, key, &data);
|
||||||
|
if (status != R_SUCCESS || !data) {
|
||||||
|
if (status == R_SUCCESS) msgs->loaded = true;
|
||||||
|
return status == R_SUCCESS ? R_ERROR_NOT_FOUND : status;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct json_object *loaded = json_tokener_parse(data);
|
||||||
|
free(data);
|
||||||
|
|
||||||
|
if (!loaded || !json_object_is_type(loaded, json_type_array)) {
|
||||||
|
if (loaded) json_object_put(loaded);
|
||||||
|
return R_ERROR_PARSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
int len = json_object_array_length(loaded);
|
||||||
|
for (int i = 0; i < len; i++) {
|
||||||
|
struct json_object *msg = json_object_array_get_idx(loaded, i);
|
||||||
|
struct json_object *role_obj;
|
||||||
|
|
||||||
|
if (json_object_object_get_ex(msg, "role", &role_obj)) {
|
||||||
|
const char *role = json_object_get_string(role_obj);
|
||||||
|
if (role && strcmp(role, "system") != 0) {
|
||||||
|
json_object_array_add(msgs->array, json_object_get(msg));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
json_object_array_add(msgs->array, json_object_get(msg));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
json_object_put(loaded);
|
||||||
|
msgs->loaded = true;
|
||||||
|
return R_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct json_object *messages_to_json(messages_handle msgs) {
|
||||||
|
return msgs ? msgs->array : NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *messages_to_string(messages_handle msgs) {
|
||||||
|
if (!msgs || !msgs->array) return NULL;
|
||||||
|
return strdup(json_object_to_json_string_ext(msgs->array, JSON_C_TO_STRING_PRETTY));
|
||||||
|
}
|
||||||
|
|
||||||
|
int messages_count(messages_handle msgs) {
|
||||||
|
if (!msgs || !msgs->array) return 0;
|
||||||
|
return json_object_array_length(msgs->array);
|
||||||
|
}
|
||||||
181
src/r_config.c
Executable file
181
src/r_config.c
Executable file
@ -0,0 +1,181 @@
|
|||||||
|
// retoor <retoor@molodetz.nl>
|
||||||
|
|
||||||
|
#include "r_config.h"
|
||||||
|
#include <ctype.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
struct r_config_t {
|
||||||
|
char *api_url;
|
||||||
|
char *models_url;
|
||||||
|
char *model;
|
||||||
|
char *api_key;
|
||||||
|
char *db_path;
|
||||||
|
char *session_id;
|
||||||
|
char *system_message;
|
||||||
|
double temperature;
|
||||||
|
bool use_tools;
|
||||||
|
bool use_strict;
|
||||||
|
bool verbose;
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct r_config_t *instance = NULL;
|
||||||
|
|
||||||
|
static char *strdup_safe(const char *s) {
|
||||||
|
return s ? strdup(s) : NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool resolve_env_bool(const char *env_name, bool default_val) {
|
||||||
|
const char *val = getenv(env_name);
|
||||||
|
if (!val) return default_val;
|
||||||
|
if (!strcmp(val, "true") || !strcmp(val, "1")) return true;
|
||||||
|
if (!strcmp(val, "false") || !strcmp(val, "0")) return false;
|
||||||
|
return default_val;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char *resolve_api_key(void) {
|
||||||
|
|
||||||
|
const char * key = getenv("OPENROUTER_API_KEY");
|
||||||
|
if (key && *key) return key;
|
||||||
|
|
||||||
|
|
||||||
|
key = getenv("R_KEY");
|
||||||
|
if (key && *key) return key;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
key = getenv("OPENAI_API_KEY");
|
||||||
|
if (key && *key) return key;
|
||||||
|
|
||||||
|
|
||||||
|
return "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";
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool is_valid_session_id(const char *session_id) {
|
||||||
|
if (!session_id || !*session_id) return false;
|
||||||
|
if (strlen(session_id) > 255) return false;
|
||||||
|
|
||||||
|
for (const char *p = session_id; *p; p++) {
|
||||||
|
if (!isalnum((unsigned char)*p) && *p != '-' && *p != '_' && *p != '.') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
r_config_handle r_config_get_instance(void) {
|
||||||
|
if (instance) return instance;
|
||||||
|
|
||||||
|
instance = calloc(1, sizeof(struct r_config_t));
|
||||||
|
if (!instance) return NULL;
|
||||||
|
|
||||||
|
const char *base_url = getenv("R_BASE_URL");
|
||||||
|
if (base_url && *base_url) {
|
||||||
|
size_t len = strlen(base_url);
|
||||||
|
instance->api_url = malloc(len + 32);
|
||||||
|
instance->models_url = malloc(len + 32);
|
||||||
|
if (instance->api_url && instance->models_url) {
|
||||||
|
snprintf(instance->api_url, len + 32, "%s/v1/chat/completions", base_url);
|
||||||
|
snprintf(instance->models_url, len + 32, "%s/v1/models", base_url);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
instance->api_url = strdup("https://api.openai.com/v1/chat/completions");
|
||||||
|
instance->models_url = strdup("https://api.openai.com/v1/models");
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *model = getenv("R_MODEL");
|
||||||
|
instance->model = strdup(model && *model ? model : "gpt-4o-mini");
|
||||||
|
|
||||||
|
instance->api_key = strdup(resolve_api_key());
|
||||||
|
instance->db_path = strdup("~/.r.db");
|
||||||
|
instance->temperature = 0.1;
|
||||||
|
instance->use_tools = resolve_env_bool("R_USE_TOOLS", true);
|
||||||
|
instance->use_strict = resolve_env_bool("R_USE_STRICT", true);
|
||||||
|
instance->verbose = false;
|
||||||
|
|
||||||
|
const char *session = getenv("R_SESSION");
|
||||||
|
if (session && is_valid_session_id(session)) {
|
||||||
|
instance->session_id = strdup(session);
|
||||||
|
} else {
|
||||||
|
instance->session_id = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
instance->system_message = strdup_safe(getenv("R_SYSTEM_MESSAGE"));
|
||||||
|
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
void r_config_destroy(void) {
|
||||||
|
if (!instance) return;
|
||||||
|
free(instance->api_url);
|
||||||
|
free(instance->models_url);
|
||||||
|
free(instance->model);
|
||||||
|
free(instance->api_key);
|
||||||
|
free(instance->db_path);
|
||||||
|
free(instance->session_id);
|
||||||
|
free(instance->system_message);
|
||||||
|
free(instance);
|
||||||
|
instance = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *r_config_get_api_url(r_config_handle cfg) {
|
||||||
|
return cfg ? cfg->api_url : NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *r_config_get_models_url(r_config_handle cfg) {
|
||||||
|
return cfg ? cfg->models_url : NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *r_config_get_model(r_config_handle cfg) {
|
||||||
|
return cfg ? cfg->model : NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void r_config_set_model(r_config_handle cfg, const char *model) {
|
||||||
|
if (!cfg || !model) return;
|
||||||
|
free(cfg->model);
|
||||||
|
cfg->model = strdup(model);
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *r_config_get_api_key(r_config_handle cfg) {
|
||||||
|
return cfg ? cfg->api_key : NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *r_config_get_db_path(r_config_handle cfg) {
|
||||||
|
return cfg ? cfg->db_path : NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool r_config_use_tools(r_config_handle cfg) {
|
||||||
|
return cfg ? cfg->use_tools : true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool r_config_use_strict(r_config_handle cfg) {
|
||||||
|
return cfg ? cfg->use_strict : true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool r_config_is_verbose(r_config_handle cfg) {
|
||||||
|
return cfg ? cfg->verbose : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void r_config_set_verbose(r_config_handle cfg, bool verbose) {
|
||||||
|
if (cfg) cfg->verbose = verbose;
|
||||||
|
}
|
||||||
|
|
||||||
|
double r_config_get_temperature(r_config_handle cfg) {
|
||||||
|
return cfg ? cfg->temperature : 0.1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *r_config_get_session_id(r_config_handle cfg) {
|
||||||
|
return cfg ? cfg->session_id : NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool r_config_set_session_id(r_config_handle cfg, const char *session_id) {
|
||||||
|
if (!cfg || !is_valid_session_id(session_id)) return false;
|
||||||
|
free(cfg->session_id);
|
||||||
|
cfg->session_id = strdup(session_id);
|
||||||
|
return cfg->session_id != NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *r_config_get_system_message(r_config_handle cfg) {
|
||||||
|
return cfg ? cfg->system_message : NULL;
|
||||||
|
}
|
||||||
35
src/r_error.c
Executable file
35
src/r_error.c
Executable file
@ -0,0 +1,35 @@
|
|||||||
|
// retoor <retoor@molodetz.nl>
|
||||||
|
|
||||||
|
#include "r_error.h"
|
||||||
|
|
||||||
|
static const char *error_messages[] = {
|
||||||
|
[R_SUCCESS] = "Success",
|
||||||
|
[R_ERROR_INVALID_ARG] = "Invalid argument",
|
||||||
|
[R_ERROR_OUT_OF_MEMORY] = "Out of memory",
|
||||||
|
[R_ERROR_NOT_FOUND] = "Not found",
|
||||||
|
[R_ERROR_PARSE] = "Parse error",
|
||||||
|
[R_ERROR_DB_CONNECTION] = "Database connection failed",
|
||||||
|
[R_ERROR_DB_QUERY] = "Database query failed",
|
||||||
|
[R_ERROR_DB_NOT_FOUND] = "Database key not found",
|
||||||
|
[R_ERROR_HTTP_CONNECTION] = "HTTP connection failed",
|
||||||
|
[R_ERROR_HTTP_TIMEOUT] = "HTTP request timed out",
|
||||||
|
[R_ERROR_HTTP_RESPONSE] = "HTTP response error",
|
||||||
|
[R_ERROR_JSON_PARSE] = "JSON parse error",
|
||||||
|
[R_ERROR_FILE_NOT_FOUND] = "File not found",
|
||||||
|
[R_ERROR_FILE_READ] = "File read error",
|
||||||
|
[R_ERROR_FILE_WRITE] = "File write error",
|
||||||
|
[R_ERROR_TOOL_NOT_FOUND] = "Tool not found",
|
||||||
|
[R_ERROR_TOOL_EXECUTION] = "Tool execution failed",
|
||||||
|
[R_ERROR_API_KEY_MISSING] = "API key missing",
|
||||||
|
[R_ERROR_API_ERROR] = "API error",
|
||||||
|
[R_ERROR_MAX_ITERATIONS] = "Maximum iterations reached",
|
||||||
|
[R_ERROR_SESSION_INVALID] = "Invalid session name",
|
||||||
|
[R_ERROR_UNKNOWN] = "Unknown error"
|
||||||
|
};
|
||||||
|
|
||||||
|
const char *r_status_string(r_status_t status) {
|
||||||
|
if (status < 0 || status > R_ERROR_UNKNOWN) {
|
||||||
|
return error_messages[R_ERROR_UNKNOWN];
|
||||||
|
}
|
||||||
|
return error_messages[status];
|
||||||
|
}
|
||||||
157
src/tool_registry.c
Executable file
157
src/tool_registry.c
Executable file
@ -0,0 +1,157 @@
|
|||||||
|
// retoor <retoor@molodetz.nl>
|
||||||
|
|
||||||
|
#include "tool.h"
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
tool_registry_t *tool_registry_create(void) {
|
||||||
|
tool_registry_t *registry = calloc(1, sizeof(tool_registry_t));
|
||||||
|
if (!registry) return NULL;
|
||||||
|
|
||||||
|
registry->capacity = 32;
|
||||||
|
registry->tools = calloc(registry->capacity, sizeof(tool_t *));
|
||||||
|
if (!registry->tools) {
|
||||||
|
free(registry);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
return registry;
|
||||||
|
}
|
||||||
|
|
||||||
|
void tool_registry_destroy(tool_registry_t *registry) {
|
||||||
|
if (!registry) return;
|
||||||
|
free(registry->tools);
|
||||||
|
free(registry);
|
||||||
|
}
|
||||||
|
|
||||||
|
r_status_t tool_registry_register(tool_registry_t *registry, tool_t *tool) {
|
||||||
|
if (!registry || !tool) return R_ERROR_INVALID_ARG;
|
||||||
|
|
||||||
|
if (registry->count >= registry->capacity) {
|
||||||
|
size_t new_capacity = registry->capacity * 2;
|
||||||
|
tool_t **new_tools = realloc(registry->tools, new_capacity * sizeof(tool_t *));
|
||||||
|
if (!new_tools) return R_ERROR_OUT_OF_MEMORY;
|
||||||
|
registry->tools = new_tools;
|
||||||
|
registry->capacity = new_capacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
registry->tools[registry->count++] = tool;
|
||||||
|
return R_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
tool_t *tool_registry_find(tool_registry_t *registry, const char *name) {
|
||||||
|
if (!registry || !name) return NULL;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < registry->count; i++) {
|
||||||
|
if (strcmp(registry->tools[i]->name, name) == 0) {
|
||||||
|
return registry->tools[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct json_object *tool_registry_get_descriptions(tool_registry_t *registry) {
|
||||||
|
if (!registry) return NULL;
|
||||||
|
|
||||||
|
struct json_object *array = json_object_new_array();
|
||||||
|
if (!array) return NULL;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < registry->count; i++) {
|
||||||
|
tool_t *tool = registry->tools[i];
|
||||||
|
if (tool->vtable->get_description) {
|
||||||
|
struct json_object *desc = tool->vtable->get_description();
|
||||||
|
if (desc) {
|
||||||
|
json_object_array_add(array, desc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return array;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct json_object *tool_registry_execute(tool_registry_t *registry,
|
||||||
|
struct json_object *tool_calls,
|
||||||
|
bool verbose) {
|
||||||
|
if (!registry || !tool_calls) return NULL;
|
||||||
|
|
||||||
|
struct json_object *results = json_object_new_array();
|
||||||
|
if (!results) return NULL;
|
||||||
|
|
||||||
|
int len = json_object_array_length(tool_calls);
|
||||||
|
|
||||||
|
for (int i = 0; i < len; i++) {
|
||||||
|
struct json_object *call = json_object_array_get_idx(tool_calls, i);
|
||||||
|
struct json_object *result = json_object_new_object();
|
||||||
|
|
||||||
|
struct json_object *id_obj = json_object_object_get(call, "id");
|
||||||
|
if (id_obj) {
|
||||||
|
json_object_object_add(result, "tool_call_id",
|
||||||
|
json_object_new_string(json_object_get_string(id_obj)));
|
||||||
|
}
|
||||||
|
json_object_object_add(result, "role", json_object_new_string("tool"));
|
||||||
|
|
||||||
|
struct json_object *type_obj;
|
||||||
|
if (json_object_object_get_ex(call, "type", &type_obj)) {
|
||||||
|
if (strcmp(json_object_get_string(type_obj), "function") != 0) {
|
||||||
|
json_object_put(result);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct json_object *function_obj;
|
||||||
|
if (!json_object_object_get_ex(call, "function", &function_obj)) {
|
||||||
|
json_object_object_add(result, "content",
|
||||||
|
json_object_new_string("Error: missing function object"));
|
||||||
|
json_object_array_add(results, result);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct json_object *name_obj;
|
||||||
|
if (!json_object_object_get_ex(function_obj, "name", &name_obj)) {
|
||||||
|
json_object_object_add(result, "content",
|
||||||
|
json_object_new_string("Error: missing function name"));
|
||||||
|
json_object_array_add(results, result);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *name = json_object_get_string(name_obj);
|
||||||
|
tool_t *tool = tool_registry_find(registry, name);
|
||||||
|
|
||||||
|
if (!tool) {
|
||||||
|
fprintf(stderr, "Unknown function: %s\n", name);
|
||||||
|
json_object_object_add(result, "content",
|
||||||
|
json_object_new_string("Error: function not found."));
|
||||||
|
json_object_array_add(results, result);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct json_object *args = NULL;
|
||||||
|
struct json_object *args_obj;
|
||||||
|
if (json_object_object_get_ex(function_obj, "arguments", &args_obj)) {
|
||||||
|
args = json_tokener_parse(json_object_get_string(args_obj));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tool->vtable->print_action) {
|
||||||
|
tool->vtable->print_action(tool->name, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (verbose && args) {
|
||||||
|
fprintf(stderr, " [verbose] %s args: %s\n",
|
||||||
|
tool->name, json_object_to_json_string(args));
|
||||||
|
}
|
||||||
|
|
||||||
|
char *output = NULL;
|
||||||
|
if (tool->vtable->execute) {
|
||||||
|
output = tool->vtable->execute(tool, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
json_object_object_add(result, "content",
|
||||||
|
json_object_new_string(output ? output : ""));
|
||||||
|
|
||||||
|
free(output);
|
||||||
|
if (args) json_object_put(args);
|
||||||
|
|
||||||
|
json_object_array_add(results, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
270
src/tools/tool_db.c
Executable file
270
src/tools/tool_db.c
Executable file
@ -0,0 +1,270 @@
|
|||||||
|
// retoor <retoor@molodetz.nl>
|
||||||
|
|
||||||
|
#include "tool.h"
|
||||||
|
#include "db.h"
|
||||||
|
#include "r_config.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
static struct json_object *db_get_get_description(void);
|
||||||
|
static char *db_get_execute(tool_t *self, struct json_object *args);
|
||||||
|
static void db_get_print_action(const char *name, struct json_object *args);
|
||||||
|
|
||||||
|
static struct json_object *db_set_get_description(void);
|
||||||
|
static char *db_set_execute(tool_t *self, struct json_object *args);
|
||||||
|
static void db_set_print_action(const char *name, struct json_object *args);
|
||||||
|
|
||||||
|
static struct json_object *db_query_get_description(void);
|
||||||
|
static char *db_query_execute(tool_t *self, struct json_object *args);
|
||||||
|
static void db_query_print_action(const char *name, struct json_object *args);
|
||||||
|
|
||||||
|
static const tool_vtable_t db_get_vtable = {
|
||||||
|
.get_description = db_get_get_description,
|
||||||
|
.execute = db_get_execute,
|
||||||
|
.print_action = db_get_print_action
|
||||||
|
};
|
||||||
|
|
||||||
|
static const tool_vtable_t db_set_vtable = {
|
||||||
|
.get_description = db_set_get_description,
|
||||||
|
.execute = db_set_execute,
|
||||||
|
.print_action = db_set_print_action
|
||||||
|
};
|
||||||
|
|
||||||
|
static const tool_vtable_t db_query_vtable = {
|
||||||
|
.get_description = db_query_get_description,
|
||||||
|
.execute = db_query_execute,
|
||||||
|
.print_action = db_query_print_action
|
||||||
|
};
|
||||||
|
|
||||||
|
static tool_t db_get_tool = { .vtable = &db_get_vtable, .name = "db_get" };
|
||||||
|
static tool_t db_set_tool = { .vtable = &db_set_vtable, .name = "db_set" };
|
||||||
|
static tool_t db_query_tool = { .vtable = &db_query_vtable, .name = "db_query" };
|
||||||
|
|
||||||
|
tool_t *tool_db_get_create(void) { return &db_get_tool; }
|
||||||
|
tool_t *tool_db_set_create(void) { return &db_set_tool; }
|
||||||
|
tool_t *tool_db_query_create(void) { return &db_query_tool; }
|
||||||
|
|
||||||
|
static void db_get_print_action(const char *name, struct json_object *args) {
|
||||||
|
(void)name;
|
||||||
|
if (!args) return;
|
||||||
|
struct json_object *key;
|
||||||
|
if (json_object_object_get_ex(args, "key", &key)) {
|
||||||
|
fprintf(stderr, " -> Getting from database: %s\n", json_object_get_string(key));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static char *db_get_execute(tool_t *self, struct json_object *args) {
|
||||||
|
(void)self;
|
||||||
|
struct json_object *key_obj;
|
||||||
|
if (!json_object_object_get_ex(args, "key", &key_obj)) {
|
||||||
|
return strdup("Error: missing 'key' argument");
|
||||||
|
}
|
||||||
|
|
||||||
|
db_handle db = db_open(NULL);
|
||||||
|
if (!db) return strdup("Failed to open database.");
|
||||||
|
|
||||||
|
char *value = NULL;
|
||||||
|
r_status_t status = db_kv_get(db, json_object_get_string(key_obj), &value);
|
||||||
|
db_close(db);
|
||||||
|
|
||||||
|
if (status != R_SUCCESS || !value) {
|
||||||
|
return strdup("Failed to retrieve value from the database.");
|
||||||
|
}
|
||||||
|
|
||||||
|
struct json_object *result = json_object_new_object();
|
||||||
|
json_object_object_add(result, "value", json_object_new_string(value));
|
||||||
|
char *response = strdup(json_object_to_json_string(result));
|
||||||
|
json_object_put(result);
|
||||||
|
free(value);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct json_object *db_get_get_description(void) {
|
||||||
|
struct json_object *root = json_object_new_object();
|
||||||
|
json_object_object_add(root, "type", json_object_new_string("function"));
|
||||||
|
|
||||||
|
struct json_object *function = json_object_new_object();
|
||||||
|
json_object_object_add(function, "name", json_object_new_string("db_get"));
|
||||||
|
json_object_object_add(function, "description",
|
||||||
|
json_object_new_string("Retrieves a value from the database for a given key."));
|
||||||
|
|
||||||
|
struct json_object *parameters = json_object_new_object();
|
||||||
|
json_object_object_add(parameters, "type", json_object_new_string("object"));
|
||||||
|
|
||||||
|
struct json_object *properties = json_object_new_object();
|
||||||
|
struct json_object *key = json_object_new_object();
|
||||||
|
json_object_object_add(key, "type", json_object_new_string("string"));
|
||||||
|
json_object_object_add(key, "description",
|
||||||
|
json_object_new_string("The key to retrieve from the database."));
|
||||||
|
json_object_object_add(properties, "key", key);
|
||||||
|
|
||||||
|
json_object_object_add(parameters, "properties", properties);
|
||||||
|
|
||||||
|
struct json_object *required = json_object_new_array();
|
||||||
|
json_object_array_add(required, json_object_new_string("key"));
|
||||||
|
json_object_object_add(parameters, "required", required);
|
||||||
|
json_object_object_add(parameters, "additionalProperties", json_object_new_boolean(0));
|
||||||
|
|
||||||
|
json_object_object_add(function, "parameters", parameters);
|
||||||
|
|
||||||
|
r_config_handle cfg = r_config_get_instance();
|
||||||
|
if (r_config_use_strict(cfg)) {
|
||||||
|
json_object_object_add(function, "strict", json_object_new_boolean(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
json_object_object_add(root, "function", function);
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void db_set_print_action(const char *name, struct json_object *args) {
|
||||||
|
(void)name;
|
||||||
|
if (!args) return;
|
||||||
|
struct json_object *key;
|
||||||
|
if (json_object_object_get_ex(args, "key", &key)) {
|
||||||
|
fprintf(stderr, " -> Storing in database: %s\n", json_object_get_string(key));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static char *db_set_execute(tool_t *self, struct json_object *args) {
|
||||||
|
(void)self;
|
||||||
|
struct json_object *key_obj, *value_obj;
|
||||||
|
|
||||||
|
if (!json_object_object_get_ex(args, "key", &key_obj)) {
|
||||||
|
return strdup("Error: missing 'key' argument");
|
||||||
|
}
|
||||||
|
if (!json_object_object_get_ex(args, "value", &value_obj)) {
|
||||||
|
return strdup("Error: missing 'value' argument");
|
||||||
|
}
|
||||||
|
|
||||||
|
db_handle db = db_open(NULL);
|
||||||
|
if (!db) return strdup("Failed to open database.");
|
||||||
|
|
||||||
|
r_status_t status = db_kv_set(db, json_object_get_string(key_obj),
|
||||||
|
json_object_get_string(value_obj));
|
||||||
|
db_close(db);
|
||||||
|
|
||||||
|
if (status != R_SUCCESS) {
|
||||||
|
return strdup("Failed to set value in the database.");
|
||||||
|
}
|
||||||
|
return strdup("Value successfully stored.");
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct json_object *db_set_get_description(void) {
|
||||||
|
struct json_object *root = json_object_new_object();
|
||||||
|
json_object_object_add(root, "type", json_object_new_string("function"));
|
||||||
|
|
||||||
|
struct json_object *function = json_object_new_object();
|
||||||
|
json_object_object_add(function, "name", json_object_new_string("db_set"));
|
||||||
|
json_object_object_add(function, "description",
|
||||||
|
json_object_new_string("Sets a value in the database for a given key."));
|
||||||
|
|
||||||
|
struct json_object *parameters = json_object_new_object();
|
||||||
|
json_object_object_add(parameters, "type", json_object_new_string("object"));
|
||||||
|
|
||||||
|
struct json_object *properties = json_object_new_object();
|
||||||
|
|
||||||
|
struct json_object *key = json_object_new_object();
|
||||||
|
json_object_object_add(key, "type", json_object_new_string("string"));
|
||||||
|
json_object_object_add(key, "description",
|
||||||
|
json_object_new_string("The key to set in the database."));
|
||||||
|
json_object_object_add(properties, "key", key);
|
||||||
|
|
||||||
|
struct json_object *value = json_object_new_object();
|
||||||
|
json_object_object_add(value, "type", json_object_new_string("string"));
|
||||||
|
json_object_object_add(value, "description",
|
||||||
|
json_object_new_string("The value to set for the given key."));
|
||||||
|
json_object_object_add(properties, "value", value);
|
||||||
|
|
||||||
|
json_object_object_add(parameters, "properties", properties);
|
||||||
|
|
||||||
|
struct json_object *required = json_object_new_array();
|
||||||
|
json_object_array_add(required, json_object_new_string("key"));
|
||||||
|
json_object_array_add(required, json_object_new_string("value"));
|
||||||
|
json_object_object_add(parameters, "required", required);
|
||||||
|
json_object_object_add(parameters, "additionalProperties", json_object_new_boolean(0));
|
||||||
|
|
||||||
|
json_object_object_add(function, "parameters", parameters);
|
||||||
|
|
||||||
|
r_config_handle cfg = r_config_get_instance();
|
||||||
|
if (r_config_use_strict(cfg)) {
|
||||||
|
json_object_object_add(function, "strict", json_object_new_boolean(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
json_object_object_add(root, "function", function);
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void db_query_print_action(const char *name, struct json_object *args) {
|
||||||
|
(void)name;
|
||||||
|
if (!args) return;
|
||||||
|
struct json_object *query;
|
||||||
|
if (json_object_object_get_ex(args, "query", &query)) {
|
||||||
|
const char *q = json_object_get_string(query);
|
||||||
|
if (strlen(q) > 60) {
|
||||||
|
fprintf(stderr, " -> Executing SQL: %.60s...\n", q);
|
||||||
|
} else {
|
||||||
|
fprintf(stderr, " -> Executing SQL: %s\n", q);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static char *db_query_execute(tool_t *self, struct json_object *args) {
|
||||||
|
(void)self;
|
||||||
|
struct json_object *query_obj;
|
||||||
|
if (!json_object_object_get_ex(args, "query", &query_obj)) {
|
||||||
|
return strdup("Error: missing 'query' argument");
|
||||||
|
}
|
||||||
|
|
||||||
|
db_handle db = db_open(NULL);
|
||||||
|
if (!db) return strdup("Failed to open database.");
|
||||||
|
|
||||||
|
struct json_object *result = NULL;
|
||||||
|
r_status_t status = db_execute(db, json_object_get_string(query_obj), &result);
|
||||||
|
db_close(db);
|
||||||
|
|
||||||
|
if (status != R_SUCCESS) {
|
||||||
|
return strdup("Failed to execute query on the database.");
|
||||||
|
}
|
||||||
|
|
||||||
|
char *response = strdup(json_object_to_json_string(result));
|
||||||
|
json_object_put(result);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct json_object *db_query_get_description(void) {
|
||||||
|
struct json_object *root = json_object_new_object();
|
||||||
|
json_object_object_add(root, "type", json_object_new_string("function"));
|
||||||
|
|
||||||
|
struct json_object *function = json_object_new_object();
|
||||||
|
json_object_object_add(function, "name", json_object_new_string("db_query"));
|
||||||
|
json_object_object_add(function, "description",
|
||||||
|
json_object_new_string("Executes a query on the database and returns the results."));
|
||||||
|
|
||||||
|
struct json_object *parameters = json_object_new_object();
|
||||||
|
json_object_object_add(parameters, "type", json_object_new_string("object"));
|
||||||
|
|
||||||
|
struct json_object *properties = json_object_new_object();
|
||||||
|
struct json_object *query = json_object_new_object();
|
||||||
|
json_object_object_add(query, "type", json_object_new_string("string"));
|
||||||
|
json_object_object_add(query, "description",
|
||||||
|
json_object_new_string("The SQL query to execute."));
|
||||||
|
json_object_object_add(properties, "query", query);
|
||||||
|
|
||||||
|
json_object_object_add(parameters, "properties", properties);
|
||||||
|
|
||||||
|
struct json_object *required = json_object_new_array();
|
||||||
|
json_object_array_add(required, json_object_new_string("query"));
|
||||||
|
json_object_object_add(parameters, "required", required);
|
||||||
|
json_object_object_add(parameters, "additionalProperties", json_object_new_boolean(0));
|
||||||
|
|
||||||
|
json_object_object_add(function, "parameters", parameters);
|
||||||
|
|
||||||
|
r_config_handle cfg = r_config_get_instance();
|
||||||
|
if (r_config_use_strict(cfg)) {
|
||||||
|
json_object_object_add(function, "strict", json_object_new_boolean(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
json_object_object_add(root, "function", function);
|
||||||
|
return root;
|
||||||
|
}
|
||||||
563
src/tools/tool_file.c
Executable file
563
src/tools/tool_file.c
Executable file
@ -0,0 +1,563 @@
|
|||||||
|
// retoor <retoor@molodetz.nl>
|
||||||
|
|
||||||
|
#include "tool.h"
|
||||||
|
#include "r_config.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <dirent.h>
|
||||||
|
#include <glob.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include <limits.h>
|
||||||
|
|
||||||
|
static char *expand_home_directory(const char *path);
|
||||||
|
static const char *get_file_type(struct stat *st);
|
||||||
|
static void format_time(time_t raw_time, char *buffer, size_t size);
|
||||||
|
|
||||||
|
static struct json_object *read_file_get_description(void);
|
||||||
|
static char *read_file_execute(tool_t *self, struct json_object *args);
|
||||||
|
static void read_file_print_action(const char *name, struct json_object *args);
|
||||||
|
|
||||||
|
static struct json_object *write_file_get_description(void);
|
||||||
|
static char *write_file_execute(tool_t *self, struct json_object *args);
|
||||||
|
static void write_file_print_action(const char *name, struct json_object *args);
|
||||||
|
|
||||||
|
static struct json_object *directory_glob_get_description(void);
|
||||||
|
static char *directory_glob_execute(tool_t *self, struct json_object *args);
|
||||||
|
static void directory_glob_print_action(const char *name, struct json_object *args);
|
||||||
|
|
||||||
|
static struct json_object *mkdir_get_description(void);
|
||||||
|
static char *mkdir_execute(tool_t *self, struct json_object *args);
|
||||||
|
static void mkdir_print_action(const char *name, struct json_object *args);
|
||||||
|
|
||||||
|
static struct json_object *chdir_get_description(void);
|
||||||
|
static char *chdir_execute(tool_t *self, struct json_object *args);
|
||||||
|
static void chdir_print_action(const char *name, struct json_object *args);
|
||||||
|
|
||||||
|
static struct json_object *getpwd_get_description(void);
|
||||||
|
static char *getpwd_execute(tool_t *self, struct json_object *args);
|
||||||
|
static void getpwd_print_action(const char *name, struct json_object *args);
|
||||||
|
|
||||||
|
static const tool_vtable_t read_file_vtable = {
|
||||||
|
.get_description = read_file_get_description,
|
||||||
|
.execute = read_file_execute,
|
||||||
|
.print_action = read_file_print_action
|
||||||
|
};
|
||||||
|
|
||||||
|
static const tool_vtable_t write_file_vtable = {
|
||||||
|
.get_description = write_file_get_description,
|
||||||
|
.execute = write_file_execute,
|
||||||
|
.print_action = write_file_print_action
|
||||||
|
};
|
||||||
|
|
||||||
|
static const tool_vtable_t directory_glob_vtable = {
|
||||||
|
.get_description = directory_glob_get_description,
|
||||||
|
.execute = directory_glob_execute,
|
||||||
|
.print_action = directory_glob_print_action
|
||||||
|
};
|
||||||
|
|
||||||
|
static const tool_vtable_t mkdir_vtable = {
|
||||||
|
.get_description = mkdir_get_description,
|
||||||
|
.execute = mkdir_execute,
|
||||||
|
.print_action = mkdir_print_action
|
||||||
|
};
|
||||||
|
|
||||||
|
static const tool_vtable_t chdir_vtable = {
|
||||||
|
.get_description = chdir_get_description,
|
||||||
|
.execute = chdir_execute,
|
||||||
|
.print_action = chdir_print_action
|
||||||
|
};
|
||||||
|
|
||||||
|
static const tool_vtable_t getpwd_vtable = {
|
||||||
|
.get_description = getpwd_get_description,
|
||||||
|
.execute = getpwd_execute,
|
||||||
|
.print_action = getpwd_print_action
|
||||||
|
};
|
||||||
|
|
||||||
|
static tool_t read_file_tool = { .vtable = &read_file_vtable, .name = "read_file" };
|
||||||
|
static tool_t write_file_tool = { .vtable = &write_file_vtable, .name = "write_file" };
|
||||||
|
static tool_t directory_glob_tool = { .vtable = &directory_glob_vtable, .name = "directory_glob" };
|
||||||
|
static tool_t mkdir_tool = { .vtable = &mkdir_vtable, .name = "mkdir" };
|
||||||
|
static tool_t chdir_tool = { .vtable = &chdir_vtable, .name = "chdir" };
|
||||||
|
static tool_t getpwd_tool = { .vtable = &getpwd_vtable, .name = "getpwd" };
|
||||||
|
|
||||||
|
tool_t *tool_read_file_create(void) { return &read_file_tool; }
|
||||||
|
tool_t *tool_write_file_create(void) { return &write_file_tool; }
|
||||||
|
tool_t *tool_directory_glob_create(void) { return &directory_glob_tool; }
|
||||||
|
tool_t *tool_mkdir_create(void) { return &mkdir_tool; }
|
||||||
|
tool_t *tool_chdir_create(void) { return &chdir_tool; }
|
||||||
|
tool_t *tool_getpwd_create(void) { return &getpwd_tool; }
|
||||||
|
|
||||||
|
static char *expand_home_directory(const char *path) {
|
||||||
|
if (!path) return NULL;
|
||||||
|
if (path[0] != '~') return strdup(path);
|
||||||
|
|
||||||
|
const char *home_dir = getenv("HOME");
|
||||||
|
if (!home_dir) home_dir = getenv("USERPROFILE");
|
||||||
|
if (!home_dir) return strdup(path);
|
||||||
|
|
||||||
|
size_t home_len = strlen(home_dir);
|
||||||
|
size_t path_len = strlen(path);
|
||||||
|
char *expanded = malloc(home_len + path_len);
|
||||||
|
if (!expanded) return NULL;
|
||||||
|
|
||||||
|
strcpy(expanded, home_dir);
|
||||||
|
strcat(expanded, path + 1);
|
||||||
|
return expanded;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char *get_file_type(struct stat *st) {
|
||||||
|
if (S_ISREG(st->st_mode)) return "file";
|
||||||
|
if (S_ISDIR(st->st_mode)) return "directory";
|
||||||
|
return "other";
|
||||||
|
}
|
||||||
|
|
||||||
|
static void format_time(time_t raw_time, char *buffer, size_t size) {
|
||||||
|
struct tm *time_info = localtime(&raw_time);
|
||||||
|
strftime(buffer, size, "%Y-%m-%d %H:%M:%S", time_info);
|
||||||
|
}
|
||||||
|
|
||||||
|
static char *make_dir_recursive(const char *path) {
|
||||||
|
char temp[4096];
|
||||||
|
char *p = NULL;
|
||||||
|
size_t len = strlen(path);
|
||||||
|
|
||||||
|
if (len >= sizeof(temp)) return strdup("Path too long!");
|
||||||
|
|
||||||
|
memcpy(temp, path, len + 1);
|
||||||
|
if (temp[len - 1] == '/') temp[len - 1] = '\0';
|
||||||
|
|
||||||
|
for (p = temp + 1; *p; p++) {
|
||||||
|
if (*p == '/') {
|
||||||
|
*p = '\0';
|
||||||
|
if (mkdir(temp, 0777) != 0 && errno != EEXIST) {
|
||||||
|
return strdup("Failed to create directory!");
|
||||||
|
}
|
||||||
|
*p = '/';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (mkdir(temp, 0777) != 0 && errno != EEXIST) {
|
||||||
|
return strdup("Failed to create directory!");
|
||||||
|
}
|
||||||
|
return strdup("Directory successfully created.");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ensure_parent_directory_exists(const char *path_including_file_name) {
|
||||||
|
if (!path_including_file_name) return;
|
||||||
|
|
||||||
|
char *path = strdup(path_including_file_name);
|
||||||
|
if (!path) return;
|
||||||
|
|
||||||
|
char *last_slash = strrchr(path, '/');
|
||||||
|
if (last_slash) {
|
||||||
|
*last_slash = '\0';
|
||||||
|
char *result = make_dir_recursive(path);
|
||||||
|
free(result);
|
||||||
|
}
|
||||||
|
free(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void read_file_print_action(const char *name, struct json_object *args) {
|
||||||
|
(void)name;
|
||||||
|
if (!args) return;
|
||||||
|
struct json_object *path;
|
||||||
|
if (json_object_object_get_ex(args, "path", &path)) {
|
||||||
|
fprintf(stderr, " -> Reading file: %s\n", json_object_get_string(path));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static char *read_file_execute(tool_t *self, struct json_object *args) {
|
||||||
|
(void)self;
|
||||||
|
struct json_object *path_obj;
|
||||||
|
if (!json_object_object_get_ex(args, "path", &path_obj)) {
|
||||||
|
return strdup("Error: missing 'path' argument");
|
||||||
|
}
|
||||||
|
|
||||||
|
char *expanded_path = expand_home_directory(json_object_get_string(path_obj));
|
||||||
|
if (!expanded_path) return strdup("Failed to expand path!");
|
||||||
|
|
||||||
|
FILE *fp = fopen(expanded_path, "r");
|
||||||
|
free(expanded_path);
|
||||||
|
if (!fp) return strdup("Failed to open file for reading!");
|
||||||
|
|
||||||
|
fseek(fp, 0, SEEK_END);
|
||||||
|
long size = ftell(fp);
|
||||||
|
if (size < 0) {
|
||||||
|
fclose(fp);
|
||||||
|
return strdup("Failed to determine file size!");
|
||||||
|
}
|
||||||
|
rewind(fp);
|
||||||
|
|
||||||
|
char *content = malloc((size_t)size + 1);
|
||||||
|
if (!content) {
|
||||||
|
fclose(fp);
|
||||||
|
return strdup("Memory allocation failed!");
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t read_size = fread(content, 1, (size_t)size, fp);
|
||||||
|
content[read_size] = '\0';
|
||||||
|
fclose(fp);
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct json_object *read_file_get_description(void) {
|
||||||
|
struct json_object *root = json_object_new_object();
|
||||||
|
json_object_object_add(root, "type", json_object_new_string("function"));
|
||||||
|
|
||||||
|
struct json_object *function = json_object_new_object();
|
||||||
|
json_object_object_add(function, "name", json_object_new_string("read_file"));
|
||||||
|
json_object_object_add(function, "description",
|
||||||
|
json_object_new_string("Reads / opens / loads a file and returns its contents as a string."));
|
||||||
|
|
||||||
|
struct json_object *parameters = json_object_new_object();
|
||||||
|
json_object_object_add(parameters, "type", json_object_new_string("object"));
|
||||||
|
|
||||||
|
struct json_object *properties = json_object_new_object();
|
||||||
|
struct json_object *path = json_object_new_object();
|
||||||
|
json_object_object_add(path, "type", json_object_new_string("string"));
|
||||||
|
json_object_object_add(path, "description",
|
||||||
|
json_object_new_string("Path to the file to read / open / load."));
|
||||||
|
json_object_object_add(properties, "path", path);
|
||||||
|
|
||||||
|
json_object_object_add(parameters, "properties", properties);
|
||||||
|
|
||||||
|
struct json_object *required = json_object_new_array();
|
||||||
|
json_object_array_add(required, json_object_new_string("path"));
|
||||||
|
json_object_object_add(parameters, "required", required);
|
||||||
|
json_object_object_add(parameters, "additionalProperties", json_object_new_boolean(0));
|
||||||
|
|
||||||
|
json_object_object_add(function, "parameters", parameters);
|
||||||
|
|
||||||
|
r_config_handle cfg = r_config_get_instance();
|
||||||
|
if (r_config_use_strict(cfg)) {
|
||||||
|
json_object_object_add(function, "strict", json_object_new_boolean(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
json_object_object_add(root, "function", function);
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void write_file_print_action(const char *name, struct json_object *args) {
|
||||||
|
(void)name;
|
||||||
|
if (!args) return;
|
||||||
|
struct json_object *path;
|
||||||
|
if (json_object_object_get_ex(args, "path", &path)) {
|
||||||
|
fprintf(stderr, " -> Writing file: %s\n", json_object_get_string(path));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static char *write_file_execute(tool_t *self, struct json_object *args) {
|
||||||
|
(void)self;
|
||||||
|
struct json_object *path_obj, *content_obj;
|
||||||
|
|
||||||
|
if (!json_object_object_get_ex(args, "path", &path_obj)) {
|
||||||
|
return strdup("Error: missing 'path' argument");
|
||||||
|
}
|
||||||
|
if (!json_object_object_get_ex(args, "content", &content_obj)) {
|
||||||
|
return strdup("Error: missing 'content' argument");
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *path = json_object_get_string(path_obj);
|
||||||
|
const char *content = json_object_get_string(content_obj);
|
||||||
|
|
||||||
|
ensure_parent_directory_exists(path);
|
||||||
|
|
||||||
|
FILE *fp = fopen(path, "w+");
|
||||||
|
if (!fp) return strdup("Failed to open file for writing!");
|
||||||
|
|
||||||
|
fwrite(content, 1, strlen(content), fp);
|
||||||
|
fclose(fp);
|
||||||
|
return strdup("File successfully written.");
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct json_object *write_file_get_description(void) {
|
||||||
|
struct json_object *root = json_object_new_object();
|
||||||
|
json_object_object_add(root, "type", json_object_new_string("function"));
|
||||||
|
|
||||||
|
struct json_object *function = json_object_new_object();
|
||||||
|
json_object_object_add(function, "name", json_object_new_string("write_file"));
|
||||||
|
json_object_object_add(function, "description",
|
||||||
|
json_object_new_string("Writes / saves / stores content to a file. Content should be in plain format, not json encoded."));
|
||||||
|
|
||||||
|
struct json_object *parameters = json_object_new_object();
|
||||||
|
json_object_object_add(parameters, "type", json_object_new_string("object"));
|
||||||
|
|
||||||
|
struct json_object *properties = json_object_new_object();
|
||||||
|
|
||||||
|
struct json_object *path = json_object_new_object();
|
||||||
|
json_object_object_add(path, "type", json_object_new_string("string"));
|
||||||
|
json_object_object_add(path, "description",
|
||||||
|
json_object_new_string("Path to the plain file to write / save / store."));
|
||||||
|
json_object_object_add(properties, "path", path);
|
||||||
|
|
||||||
|
struct json_object *content = json_object_new_object();
|
||||||
|
json_object_object_add(content, "type", json_object_new_string("string"));
|
||||||
|
json_object_object_add(content, "description",
|
||||||
|
json_object_new_string("Plain content to write / save / store into the file."));
|
||||||
|
json_object_object_add(properties, "content", content);
|
||||||
|
|
||||||
|
json_object_object_add(parameters, "properties", properties);
|
||||||
|
|
||||||
|
struct json_object *required = json_object_new_array();
|
||||||
|
json_object_array_add(required, json_object_new_string("path"));
|
||||||
|
json_object_array_add(required, json_object_new_string("content"));
|
||||||
|
json_object_object_add(parameters, "required", required);
|
||||||
|
json_object_object_add(parameters, "additionalProperties", json_object_new_boolean(0));
|
||||||
|
|
||||||
|
json_object_object_add(function, "parameters", parameters);
|
||||||
|
|
||||||
|
r_config_handle cfg = r_config_get_instance();
|
||||||
|
if (r_config_use_strict(cfg)) {
|
||||||
|
json_object_object_add(function, "strict", json_object_new_boolean(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
json_object_object_add(root, "function", function);
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void directory_glob_print_action(const char *name, struct json_object *args) {
|
||||||
|
(void)name;
|
||||||
|
if (!args) return;
|
||||||
|
struct json_object *path;
|
||||||
|
if (json_object_object_get_ex(args, "path", &path)) {
|
||||||
|
fprintf(stderr, " -> Listing: %s\n", json_object_get_string(path));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static char *directory_glob_execute(tool_t *self, struct json_object *args) {
|
||||||
|
(void)self;
|
||||||
|
struct json_object *path_obj;
|
||||||
|
if (!json_object_object_get_ex(args, "path", &path_obj)) {
|
||||||
|
return strdup("Error: missing 'path' argument");
|
||||||
|
}
|
||||||
|
|
||||||
|
char target_dir[PATH_MAX];
|
||||||
|
strncpy(target_dir, json_object_get_string(path_obj), PATH_MAX - 1);
|
||||||
|
target_dir[PATH_MAX - 1] = '\0';
|
||||||
|
|
||||||
|
if (strcmp(target_dir, ".") == 0) {
|
||||||
|
target_dir[0] = '*';
|
||||||
|
target_dir[1] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
glob_t results;
|
||||||
|
struct stat file_stat;
|
||||||
|
char mod_time[20], create_time[20];
|
||||||
|
|
||||||
|
if (glob(target_dir, GLOB_TILDE, NULL, &results) != 0) {
|
||||||
|
return strdup("[]");
|
||||||
|
}
|
||||||
|
|
||||||
|
json_object *json_array = json_object_new_array();
|
||||||
|
|
||||||
|
for (size_t i = 0; i < results.gl_pathc; i++) {
|
||||||
|
const char *file_path = results.gl_pathv[i];
|
||||||
|
|
||||||
|
if (stat(file_path, &file_stat) == -1) continue;
|
||||||
|
|
||||||
|
format_time(file_stat.st_mtime, mod_time, sizeof(mod_time));
|
||||||
|
format_time(file_stat.st_ctime, create_time, sizeof(create_time));
|
||||||
|
|
||||||
|
json_object *json_entry = json_object_new_object();
|
||||||
|
json_object_object_add(json_entry, "name", json_object_new_string(file_path));
|
||||||
|
json_object_object_add(json_entry, "modification_date", json_object_new_string(mod_time));
|
||||||
|
json_object_object_add(json_entry, "creation_date", json_object_new_string(create_time));
|
||||||
|
json_object_object_add(json_entry, "type", json_object_new_string(get_file_type(&file_stat)));
|
||||||
|
json_object_object_add(json_entry, "size_bytes", json_object_new_int64(file_stat.st_size));
|
||||||
|
|
||||||
|
json_object_array_add(json_array, json_entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
globfree(&results);
|
||||||
|
char *result = strdup(json_object_to_json_string_ext(json_array, JSON_C_TO_STRING_PRETTY));
|
||||||
|
json_object_put(json_array);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct json_object *directory_glob_get_description(void) {
|
||||||
|
struct json_object *root = json_object_new_object();
|
||||||
|
json_object_object_add(root, "type", json_object_new_string("function"));
|
||||||
|
|
||||||
|
struct json_object *function = json_object_new_object();
|
||||||
|
json_object_object_add(function, "name", json_object_new_string("directory_glob"));
|
||||||
|
json_object_object_add(function, "description",
|
||||||
|
json_object_new_string("List the contents of a specified directory in glob format. Result is a JSON array containing objects with keys: name, modification_date(iso), creation_date(iso), type, and size_bytes."));
|
||||||
|
|
||||||
|
struct json_object *parameters = json_object_new_object();
|
||||||
|
json_object_object_add(parameters, "type", json_object_new_string("object"));
|
||||||
|
|
||||||
|
struct json_object *properties = json_object_new_object();
|
||||||
|
struct json_object *directory = json_object_new_object();
|
||||||
|
json_object_object_add(directory, "type", json_object_new_string("string"));
|
||||||
|
json_object_object_add(directory, "description",
|
||||||
|
json_object_new_string("Path to the directory to list in glob format."));
|
||||||
|
json_object_object_add(properties, "path", directory);
|
||||||
|
|
||||||
|
json_object_object_add(parameters, "properties", properties);
|
||||||
|
|
||||||
|
struct json_object *required = json_object_new_array();
|
||||||
|
json_object_array_add(required, json_object_new_string("path"));
|
||||||
|
json_object_object_add(parameters, "required", required);
|
||||||
|
json_object_object_add(parameters, "additionalProperties", json_object_new_boolean(0));
|
||||||
|
|
||||||
|
json_object_object_add(function, "parameters", parameters);
|
||||||
|
|
||||||
|
r_config_handle cfg = r_config_get_instance();
|
||||||
|
if (r_config_use_strict(cfg)) {
|
||||||
|
json_object_object_add(function, "strict", json_object_new_boolean(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
json_object_object_add(root, "function", function);
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void mkdir_print_action(const char *name, struct json_object *args) {
|
||||||
|
(void)name;
|
||||||
|
if (!args) return;
|
||||||
|
struct json_object *path;
|
||||||
|
if (json_object_object_get_ex(args, "path", &path)) {
|
||||||
|
fprintf(stderr, " -> Creating directory: %s\n", json_object_get_string(path));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static char *mkdir_execute(tool_t *self, struct json_object *args) {
|
||||||
|
(void)self;
|
||||||
|
struct json_object *path_obj;
|
||||||
|
if (!json_object_object_get_ex(args, "path", &path_obj)) {
|
||||||
|
return strdup("Error: missing 'path' argument");
|
||||||
|
}
|
||||||
|
return make_dir_recursive(json_object_get_string(path_obj));
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct json_object *mkdir_get_description(void) {
|
||||||
|
struct json_object *root = json_object_new_object();
|
||||||
|
json_object_object_add(root, "type", json_object_new_string("function"));
|
||||||
|
|
||||||
|
struct json_object *function = json_object_new_object();
|
||||||
|
json_object_object_add(function, "name", json_object_new_string("mkdir"));
|
||||||
|
json_object_object_add(function, "description",
|
||||||
|
json_object_new_string("Creates a new directory with the specified path."));
|
||||||
|
|
||||||
|
struct json_object *parameters = json_object_new_object();
|
||||||
|
json_object_object_add(parameters, "type", json_object_new_string("object"));
|
||||||
|
|
||||||
|
struct json_object *properties = json_object_new_object();
|
||||||
|
struct json_object *path = json_object_new_object();
|
||||||
|
json_object_object_add(path, "type", json_object_new_string("string"));
|
||||||
|
json_object_object_add(path, "description",
|
||||||
|
json_object_new_string("Path of the directory to create."));
|
||||||
|
json_object_object_add(properties, "path", path);
|
||||||
|
|
||||||
|
json_object_object_add(parameters, "properties", properties);
|
||||||
|
|
||||||
|
struct json_object *required = json_object_new_array();
|
||||||
|
json_object_array_add(required, json_object_new_string("path"));
|
||||||
|
json_object_object_add(parameters, "required", required);
|
||||||
|
json_object_object_add(parameters, "additionalProperties", json_object_new_boolean(0));
|
||||||
|
|
||||||
|
json_object_object_add(function, "parameters", parameters);
|
||||||
|
|
||||||
|
r_config_handle cfg = r_config_get_instance();
|
||||||
|
if (r_config_use_strict(cfg)) {
|
||||||
|
json_object_object_add(function, "strict", json_object_new_boolean(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
json_object_object_add(root, "function", function);
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void chdir_print_action(const char *name, struct json_object *args) {
|
||||||
|
(void)name;
|
||||||
|
if (!args) return;
|
||||||
|
struct json_object *path;
|
||||||
|
if (json_object_object_get_ex(args, "path", &path)) {
|
||||||
|
fprintf(stderr, " -> Changing directory: %s\n", json_object_get_string(path));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static char *chdir_execute(tool_t *self, struct json_object *args) {
|
||||||
|
(void)self;
|
||||||
|
struct json_object *path_obj;
|
||||||
|
if (!json_object_object_get_ex(args, "path", &path_obj)) {
|
||||||
|
return strdup("Error: missing 'path' argument");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (chdir(json_object_get_string(path_obj)) != 0) {
|
||||||
|
return strdup("Failed to change directory!");
|
||||||
|
}
|
||||||
|
return strdup("Directory successfully changed.");
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct json_object *chdir_get_description(void) {
|
||||||
|
struct json_object *root = json_object_new_object();
|
||||||
|
json_object_object_add(root, "type", json_object_new_string("function"));
|
||||||
|
|
||||||
|
struct json_object *function = json_object_new_object();
|
||||||
|
json_object_object_add(function, "name", json_object_new_string("chdir"));
|
||||||
|
json_object_object_add(function, "description",
|
||||||
|
json_object_new_string("Changes the current working directory to the specified path. Call this function when `cd` is prompted."));
|
||||||
|
|
||||||
|
struct json_object *parameters = json_object_new_object();
|
||||||
|
json_object_object_add(parameters, "type", json_object_new_string("object"));
|
||||||
|
|
||||||
|
struct json_object *properties = json_object_new_object();
|
||||||
|
struct json_object *path = json_object_new_object();
|
||||||
|
json_object_object_add(path, "type", json_object_new_string("string"));
|
||||||
|
json_object_object_add(path, "description",
|
||||||
|
json_object_new_string("Path to change the current working directory to."));
|
||||||
|
json_object_object_add(properties, "path", path);
|
||||||
|
|
||||||
|
json_object_object_add(parameters, "properties", properties);
|
||||||
|
|
||||||
|
struct json_object *required = json_object_new_array();
|
||||||
|
json_object_array_add(required, json_object_new_string("path"));
|
||||||
|
json_object_object_add(parameters, "required", required);
|
||||||
|
json_object_object_add(parameters, "additionalProperties", json_object_new_boolean(0));
|
||||||
|
|
||||||
|
json_object_object_add(function, "parameters", parameters);
|
||||||
|
|
||||||
|
r_config_handle cfg = r_config_get_instance();
|
||||||
|
if (r_config_use_strict(cfg)) {
|
||||||
|
json_object_object_add(function, "strict", json_object_new_boolean(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
json_object_object_add(root, "function", function);
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void getpwd_print_action(const char *name, struct json_object *args) {
|
||||||
|
(void)name;
|
||||||
|
(void)args;
|
||||||
|
fprintf(stderr, " -> Getting current directory\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
static char *getpwd_execute(tool_t *self, struct json_object *args) {
|
||||||
|
(void)self;
|
||||||
|
(void)args;
|
||||||
|
|
||||||
|
char *cwd = malloc(PATH_MAX);
|
||||||
|
if (!cwd) return strdup("Failed to allocate memory for current working directory!");
|
||||||
|
|
||||||
|
if (!getcwd(cwd, PATH_MAX)) {
|
||||||
|
free(cwd);
|
||||||
|
return strdup("Failed to get current working directory!");
|
||||||
|
}
|
||||||
|
return cwd;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct json_object *getpwd_get_description(void) {
|
||||||
|
struct json_object *root = json_object_new_object();
|
||||||
|
json_object_object_add(root, "type", json_object_new_string("function"));
|
||||||
|
|
||||||
|
struct json_object *function = json_object_new_object();
|
||||||
|
json_object_object_add(function, "name", json_object_new_string("getpwd"));
|
||||||
|
json_object_object_add(function, "description",
|
||||||
|
json_object_new_string("Returns the current working directory as a string."));
|
||||||
|
|
||||||
|
json_object_object_add(function, "parameters", json_object_new_null());
|
||||||
|
json_object_object_add(root, "function", function);
|
||||||
|
return root;
|
||||||
|
}
|
||||||
236
src/tools/tool_http.c
Executable file
236
src/tools/tool_http.c
Executable file
@ -0,0 +1,236 @@
|
|||||||
|
// retoor <retoor@molodetz.nl>
|
||||||
|
|
||||||
|
#include "tool.h"
|
||||||
|
#include "http_client.h"
|
||||||
|
#include "r_config.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <curl/curl.h>
|
||||||
|
|
||||||
|
static struct json_object *http_fetch_get_description(void);
|
||||||
|
static char *http_fetch_execute(tool_t *self, struct json_object *args);
|
||||||
|
static void http_fetch_print_action(const char *name, struct json_object *args);
|
||||||
|
|
||||||
|
static struct json_object *web_search_get_description(void);
|
||||||
|
static char *web_search_execute(tool_t *self, struct json_object *args);
|
||||||
|
static void web_search_print_action(const char *name, struct json_object *args);
|
||||||
|
|
||||||
|
static struct json_object *web_search_news_get_description(void);
|
||||||
|
static char *web_search_news_execute(tool_t *self, struct json_object *args);
|
||||||
|
|
||||||
|
static const tool_vtable_t http_fetch_vtable = {
|
||||||
|
.get_description = http_fetch_get_description,
|
||||||
|
.execute = http_fetch_execute,
|
||||||
|
.print_action = http_fetch_print_action
|
||||||
|
};
|
||||||
|
|
||||||
|
static const tool_vtable_t web_search_vtable = {
|
||||||
|
.get_description = web_search_get_description,
|
||||||
|
.execute = web_search_execute,
|
||||||
|
.print_action = web_search_print_action
|
||||||
|
};
|
||||||
|
|
||||||
|
static const tool_vtable_t web_search_news_vtable = {
|
||||||
|
.get_description = web_search_news_get_description,
|
||||||
|
.execute = web_search_news_execute,
|
||||||
|
.print_action = web_search_print_action
|
||||||
|
};
|
||||||
|
|
||||||
|
static tool_t http_fetch_tool = { .vtable = &http_fetch_vtable, .name = "http_fetch" };
|
||||||
|
static tool_t web_search_tool = { .vtable = &web_search_vtable, .name = "web_search" };
|
||||||
|
static tool_t web_search_news_tool = { .vtable = &web_search_news_vtable, .name = "web_search_news" };
|
||||||
|
|
||||||
|
tool_t *tool_http_fetch_create(void) { return &http_fetch_tool; }
|
||||||
|
tool_t *tool_web_search_create(void) { return &web_search_tool; }
|
||||||
|
tool_t *tool_web_search_news_create(void) { return &web_search_news_tool; }
|
||||||
|
|
||||||
|
static void http_fetch_print_action(const char *name, struct json_object *args) {
|
||||||
|
(void)name;
|
||||||
|
if (!args) return;
|
||||||
|
struct json_object *url;
|
||||||
|
if (json_object_object_get_ex(args, "url", &url)) {
|
||||||
|
fprintf(stderr, " -> Fetching URL: %s\n", json_object_get_string(url));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static char *http_fetch_execute(tool_t *self, struct json_object *args) {
|
||||||
|
(void)self;
|
||||||
|
struct json_object *url_obj;
|
||||||
|
if (!json_object_object_get_ex(args, "url", &url_obj)) {
|
||||||
|
return strdup("Error: missing 'url' argument");
|
||||||
|
}
|
||||||
|
|
||||||
|
http_client_handle client = http_client_create(NULL);
|
||||||
|
if (!client) return strdup("Failed to create HTTP client.");
|
||||||
|
|
||||||
|
char *response = NULL;
|
||||||
|
r_status_t status = http_get(client, json_object_get_string(url_obj), &response);
|
||||||
|
http_client_destroy(client);
|
||||||
|
|
||||||
|
if (status != R_SUCCESS || !response) {
|
||||||
|
return strdup("Failed to fetch URL.");
|
||||||
|
}
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct json_object *http_fetch_get_description(void) {
|
||||||
|
struct json_object *root = json_object_new_object();
|
||||||
|
json_object_object_add(root, "type", json_object_new_string("function"));
|
||||||
|
|
||||||
|
struct json_object *function = json_object_new_object();
|
||||||
|
json_object_object_add(function, "name", json_object_new_string("http_fetch"));
|
||||||
|
json_object_object_add(function, "description",
|
||||||
|
json_object_new_string("Get the contents of a URL."));
|
||||||
|
|
||||||
|
struct json_object *parameters = json_object_new_object();
|
||||||
|
json_object_object_add(parameters, "type", json_object_new_string("object"));
|
||||||
|
|
||||||
|
struct json_object *properties = json_object_new_object();
|
||||||
|
struct json_object *url = json_object_new_object();
|
||||||
|
json_object_object_add(url, "type", json_object_new_string("string"));
|
||||||
|
json_object_object_add(url, "description",
|
||||||
|
json_object_new_string("Fetch URL contents."));
|
||||||
|
json_object_object_add(properties, "url", url);
|
||||||
|
|
||||||
|
json_object_object_add(parameters, "properties", properties);
|
||||||
|
|
||||||
|
struct json_object *required = json_object_new_array();
|
||||||
|
json_object_array_add(required, json_object_new_string("url"));
|
||||||
|
json_object_object_add(parameters, "required", required);
|
||||||
|
json_object_object_add(parameters, "additionalProperties", json_object_new_boolean(0));
|
||||||
|
|
||||||
|
json_object_object_add(function, "parameters", parameters);
|
||||||
|
|
||||||
|
r_config_handle cfg = r_config_get_instance();
|
||||||
|
if (r_config_use_strict(cfg)) {
|
||||||
|
json_object_object_add(function, "strict", json_object_new_boolean(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
json_object_object_add(root, "function", function);
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void web_search_print_action(const char *name, struct json_object *args) {
|
||||||
|
(void)name;
|
||||||
|
if (!args) return;
|
||||||
|
struct json_object *query;
|
||||||
|
if (json_object_object_get_ex(args, "query", &query)) {
|
||||||
|
fprintf(stderr, " -> Searching web: %s\n", json_object_get_string(query));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static char *do_web_search(const char *query) {
|
||||||
|
if (!query) return strdup("Query cannot be NULL.");
|
||||||
|
|
||||||
|
char *q_encoded = curl_easy_escape(NULL, query, 0);
|
||||||
|
if (!q_encoded) return strdup("Failed to encode query.");
|
||||||
|
|
||||||
|
char url[4096];
|
||||||
|
snprintf(url, sizeof(url), "https://static.molodetz.nl/search.cgi?query=%s", q_encoded);
|
||||||
|
curl_free(q_encoded);
|
||||||
|
|
||||||
|
http_client_handle client = http_client_create(NULL);
|
||||||
|
if (!client) return strdup("Failed to create HTTP client.");
|
||||||
|
|
||||||
|
char *response = NULL;
|
||||||
|
r_status_t status = http_get(client, url, &response);
|
||||||
|
http_client_destroy(client);
|
||||||
|
|
||||||
|
if (status != R_SUCCESS || !response) {
|
||||||
|
return strdup("Failed to search.");
|
||||||
|
}
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
static char *web_search_execute(tool_t *self, struct json_object *args) {
|
||||||
|
(void)self;
|
||||||
|
struct json_object *query_obj;
|
||||||
|
if (!json_object_object_get_ex(args, "query", &query_obj)) {
|
||||||
|
return strdup("Error: missing 'query' argument");
|
||||||
|
}
|
||||||
|
return do_web_search(json_object_get_string(query_obj));
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct json_object *web_search_get_description(void) {
|
||||||
|
struct json_object *root = json_object_new_object();
|
||||||
|
json_object_object_add(root, "type", json_object_new_string("function"));
|
||||||
|
|
||||||
|
struct json_object *function = json_object_new_object();
|
||||||
|
json_object_object_add(function, "name", json_object_new_string("web_search"));
|
||||||
|
json_object_object_add(function, "description",
|
||||||
|
json_object_new_string("Searches for information based on a query using search engines like google."));
|
||||||
|
|
||||||
|
struct json_object *parameters = json_object_new_object();
|
||||||
|
json_object_object_add(parameters, "type", json_object_new_string("object"));
|
||||||
|
|
||||||
|
struct json_object *properties = json_object_new_object();
|
||||||
|
struct json_object *query = json_object_new_object();
|
||||||
|
json_object_object_add(query, "type", json_object_new_string("string"));
|
||||||
|
json_object_object_add(query, "description",
|
||||||
|
json_object_new_string("The url encoded query string to search for information."));
|
||||||
|
json_object_object_add(properties, "query", query);
|
||||||
|
|
||||||
|
json_object_object_add(parameters, "properties", properties);
|
||||||
|
|
||||||
|
struct json_object *required = json_object_new_array();
|
||||||
|
json_object_array_add(required, json_object_new_string("query"));
|
||||||
|
json_object_object_add(parameters, "required", required);
|
||||||
|
json_object_object_add(parameters, "additionalProperties", json_object_new_boolean(0));
|
||||||
|
|
||||||
|
json_object_object_add(function, "parameters", parameters);
|
||||||
|
|
||||||
|
r_config_handle cfg = r_config_get_instance();
|
||||||
|
if (r_config_use_strict(cfg)) {
|
||||||
|
json_object_object_add(function, "strict", json_object_new_boolean(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
json_object_object_add(root, "function", function);
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
static char *web_search_news_execute(tool_t *self, struct json_object *args) {
|
||||||
|
(void)self;
|
||||||
|
struct json_object *query_obj;
|
||||||
|
if (!json_object_object_get_ex(args, "query", &query_obj)) {
|
||||||
|
return strdup("Error: missing 'query' argument");
|
||||||
|
}
|
||||||
|
return do_web_search(json_object_get_string(query_obj));
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct json_object *web_search_news_get_description(void) {
|
||||||
|
struct json_object *root = json_object_new_object();
|
||||||
|
json_object_object_add(root, "type", json_object_new_string("function"));
|
||||||
|
|
||||||
|
struct json_object *function = json_object_new_object();
|
||||||
|
json_object_object_add(function, "name", json_object_new_string("web_search_news"));
|
||||||
|
json_object_object_add(function, "description",
|
||||||
|
json_object_new_string("Searches for news articles based on a query."));
|
||||||
|
|
||||||
|
struct json_object *parameters = json_object_new_object();
|
||||||
|
json_object_object_add(parameters, "type", json_object_new_string("object"));
|
||||||
|
|
||||||
|
struct json_object *properties = json_object_new_object();
|
||||||
|
struct json_object *query = json_object_new_object();
|
||||||
|
json_object_object_add(query, "type", json_object_new_string("string"));
|
||||||
|
json_object_object_add(query, "description",
|
||||||
|
json_object_new_string("The url encoded query string to search for news articles."));
|
||||||
|
json_object_object_add(properties, "query", query);
|
||||||
|
|
||||||
|
json_object_object_add(parameters, "properties", properties);
|
||||||
|
|
||||||
|
struct json_object *required = json_object_new_array();
|
||||||
|
json_object_array_add(required, json_object_new_string("query"));
|
||||||
|
json_object_object_add(parameters, "required", required);
|
||||||
|
json_object_object_add(parameters, "additionalProperties", json_object_new_boolean(0));
|
||||||
|
|
||||||
|
json_object_object_add(function, "parameters", parameters);
|
||||||
|
|
||||||
|
r_config_handle cfg = r_config_get_instance();
|
||||||
|
if (r_config_use_strict(cfg)) {
|
||||||
|
json_object_object_add(function, "strict", json_object_new_boolean(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
json_object_object_add(root, "function", function);
|
||||||
|
return root;
|
||||||
|
}
|
||||||
190
src/tools/tool_indexer.c
Executable file
190
src/tools/tool_indexer.c
Executable file
@ -0,0 +1,190 @@
|
|||||||
|
// retoor <retoor@molodetz.nl>
|
||||||
|
|
||||||
|
#include "tool.h"
|
||||||
|
#include "r_config.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <dirent.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <limits.h>
|
||||||
|
|
||||||
|
static struct json_object *index_source_directory_get_description(void);
|
||||||
|
static char *index_source_directory_execute(tool_t *self, struct json_object *args);
|
||||||
|
static void index_source_directory_print_action(const char *name, struct json_object *args);
|
||||||
|
|
||||||
|
static const tool_vtable_t index_source_directory_vtable = {
|
||||||
|
.get_description = index_source_directory_get_description,
|
||||||
|
.execute = index_source_directory_execute,
|
||||||
|
.print_action = index_source_directory_print_action
|
||||||
|
};
|
||||||
|
|
||||||
|
static tool_t index_source_directory_tool = {
|
||||||
|
.vtable = &index_source_directory_vtable,
|
||||||
|
.name = "index_source_directory"
|
||||||
|
};
|
||||||
|
|
||||||
|
tool_t *tool_index_source_directory_create(void) {
|
||||||
|
return &index_source_directory_tool;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void index_source_directory_print_action(const char *name, struct json_object *args) {
|
||||||
|
(void)name;
|
||||||
|
if (!args) return;
|
||||||
|
struct json_object *path;
|
||||||
|
if (json_object_object_get_ex(args, "path", &path)) {
|
||||||
|
fprintf(stderr, " -> Indexing directory: %s\n", json_object_get_string(path));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int is_source_file(const char *filename) {
|
||||||
|
const char *extensions[] = {
|
||||||
|
".c", ".h", ".cpp", ".hpp", ".cc", ".cxx",
|
||||||
|
".py", ".js", ".ts", ".java", ".go", ".rs",
|
||||||
|
".rb", ".php", ".swift", ".kt", ".scala",
|
||||||
|
".sh", ".bash", ".zsh", ".fish",
|
||||||
|
".json", ".xml", ".yaml", ".yml", ".toml",
|
||||||
|
".md", ".txt", ".rst",
|
||||||
|
".html", ".css", ".scss", ".sass",
|
||||||
|
".sql", ".graphql",
|
||||||
|
".lua", ".pl", ".pm",
|
||||||
|
".r", ".R", ".jl",
|
||||||
|
".vim", ".el", ".clj", ".cljs",
|
||||||
|
".ex", ".exs", ".erl", ".hrl",
|
||||||
|
".hs", ".lhs", ".ml", ".mli",
|
||||||
|
".f", ".f90", ".f95",
|
||||||
|
".asm", ".s",
|
||||||
|
".makefile", ".mk", ".cmake",
|
||||||
|
NULL
|
||||||
|
};
|
||||||
|
|
||||||
|
const char *ext = strrchr(filename, '.');
|
||||||
|
if (!ext) {
|
||||||
|
if (strcmp(filename, "Makefile") == 0 ||
|
||||||
|
strcmp(filename, "makefile") == 0 ||
|
||||||
|
strcmp(filename, "Dockerfile") == 0 ||
|
||||||
|
strcmp(filename, "Jenkinsfile") == 0 ||
|
||||||
|
strcmp(filename, "Vagrantfile") == 0 ||
|
||||||
|
strcmp(filename, "Gemfile") == 0 ||
|
||||||
|
strcmp(filename, "Rakefile") == 0) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; extensions[i]; i++) {
|
||||||
|
if (strcasecmp(ext, extensions[i]) == 0) return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void index_directory_recursive(const char *path, struct json_object *result_array) {
|
||||||
|
DIR *dir = opendir(path);
|
||||||
|
if (!dir) return;
|
||||||
|
|
||||||
|
struct dirent *entry;
|
||||||
|
while ((entry = readdir(dir))) {
|
||||||
|
if (entry->d_name[0] == '.') continue;
|
||||||
|
if (strcmp(entry->d_name, "node_modules") == 0) continue;
|
||||||
|
if (strcmp(entry->d_name, "__pycache__") == 0) continue;
|
||||||
|
if (strcmp(entry->d_name, ".git") == 0) continue;
|
||||||
|
if (strcmp(entry->d_name, "build") == 0) continue;
|
||||||
|
if (strcmp(entry->d_name, "dist") == 0) continue;
|
||||||
|
if (strcmp(entry->d_name, "vendor") == 0) continue;
|
||||||
|
|
||||||
|
char full_path[PATH_MAX];
|
||||||
|
snprintf(full_path, sizeof(full_path), "%s/%s", path, entry->d_name);
|
||||||
|
|
||||||
|
struct stat st;
|
||||||
|
if (stat(full_path, &st) != 0) continue;
|
||||||
|
|
||||||
|
if (S_ISDIR(st.st_mode)) {
|
||||||
|
index_directory_recursive(full_path, result_array);
|
||||||
|
} else if (S_ISREG(st.st_mode) && is_source_file(entry->d_name)) {
|
||||||
|
FILE *fp = fopen(full_path, "r");
|
||||||
|
if (!fp) continue;
|
||||||
|
|
||||||
|
fseek(fp, 0, SEEK_END);
|
||||||
|
long size = ftell(fp);
|
||||||
|
rewind(fp);
|
||||||
|
|
||||||
|
if (size > 100000) {
|
||||||
|
fclose(fp);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *content = malloc(size + 1);
|
||||||
|
if (!content) {
|
||||||
|
fclose(fp);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t read_size = fread(content, 1, size, fp);
|
||||||
|
content[read_size] = '\0';
|
||||||
|
fclose(fp);
|
||||||
|
|
||||||
|
struct json_object *file_obj = json_object_new_object();
|
||||||
|
json_object_object_add(file_obj, "path", json_object_new_string(full_path));
|
||||||
|
json_object_object_add(file_obj, "content", json_object_new_string(content));
|
||||||
|
json_object_array_add(result_array, file_obj);
|
||||||
|
|
||||||
|
free(content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
closedir(dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
static char *index_source_directory_execute(tool_t *self, struct json_object *args) {
|
||||||
|
(void)self;
|
||||||
|
struct json_object *path_obj;
|
||||||
|
if (!json_object_object_get_ex(args, "path", &path_obj)) {
|
||||||
|
return strdup("Error: missing 'path' argument");
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *path = json_object_get_string(path_obj);
|
||||||
|
struct json_object *result_array = json_object_new_array();
|
||||||
|
|
||||||
|
index_directory_recursive(path, result_array);
|
||||||
|
|
||||||
|
char *result = strdup(json_object_to_json_string_ext(result_array, JSON_C_TO_STRING_PRETTY));
|
||||||
|
json_object_put(result_array);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct json_object *index_source_directory_get_description(void) {
|
||||||
|
struct json_object *root = json_object_new_object();
|
||||||
|
json_object_object_add(root, "type", json_object_new_string("function"));
|
||||||
|
|
||||||
|
struct json_object *function = json_object_new_object();
|
||||||
|
json_object_object_add(function, "name", json_object_new_string("index_source_directory"));
|
||||||
|
json_object_object_add(function, "description",
|
||||||
|
json_object_new_string("Returns a JSON array containing every source file with it's contents from given directory. Execute with '.' for current directory."));
|
||||||
|
|
||||||
|
struct json_object *parameters = json_object_new_object();
|
||||||
|
json_object_object_add(parameters, "type", json_object_new_string("object"));
|
||||||
|
|
||||||
|
struct json_object *properties = json_object_new_object();
|
||||||
|
struct json_object *path = json_object_new_object();
|
||||||
|
json_object_object_add(path, "type", json_object_new_string("string"));
|
||||||
|
json_object_object_add(path, "description",
|
||||||
|
json_object_new_string("Path to index and retreive files from."));
|
||||||
|
json_object_object_add(properties, "path", path);
|
||||||
|
|
||||||
|
json_object_object_add(parameters, "properties", properties);
|
||||||
|
|
||||||
|
struct json_object *required = json_object_new_array();
|
||||||
|
json_object_array_add(required, json_object_new_string("path"));
|
||||||
|
json_object_object_add(parameters, "required", required);
|
||||||
|
json_object_object_add(parameters, "additionalProperties", json_object_new_boolean(0));
|
||||||
|
|
||||||
|
json_object_object_add(function, "parameters", parameters);
|
||||||
|
|
||||||
|
r_config_handle cfg = r_config_get_instance();
|
||||||
|
if (r_config_use_strict(cfg)) {
|
||||||
|
json_object_object_add(function, "strict", json_object_new_boolean(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
json_object_object_add(root, "function", function);
|
||||||
|
return root;
|
||||||
|
}
|
||||||
103
src/tools/tool_python.c
Executable file
103
src/tools/tool_python.c
Executable file
@ -0,0 +1,103 @@
|
|||||||
|
// retoor <retoor@molodetz.nl>
|
||||||
|
|
||||||
|
#include "tool.h"
|
||||||
|
#include "r_config.h"
|
||||||
|
#include "bash_executor.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
static struct json_object *python_execute_get_description(void);
|
||||||
|
static char *python_execute_execute(tool_t *self, struct json_object *args);
|
||||||
|
static void python_execute_print_action(const char *name, struct json_object *args);
|
||||||
|
|
||||||
|
static const tool_vtable_t python_execute_vtable = {
|
||||||
|
.get_description = python_execute_get_description,
|
||||||
|
.execute = python_execute_execute,
|
||||||
|
.print_action = python_execute_print_action
|
||||||
|
};
|
||||||
|
|
||||||
|
static tool_t python_execute_tool = {
|
||||||
|
.vtable = &python_execute_vtable,
|
||||||
|
.name = "python_execute"
|
||||||
|
};
|
||||||
|
|
||||||
|
tool_t *tool_python_execute_create(void) {
|
||||||
|
return &python_execute_tool;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void python_execute_print_action(const char *name, struct json_object *args) {
|
||||||
|
(void)name;
|
||||||
|
(void)args;
|
||||||
|
fprintf(stderr, " -> Executing Python code\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
static char *python_execute_execute(tool_t *self, struct json_object *args) {
|
||||||
|
(void)self;
|
||||||
|
|
||||||
|
struct json_object *source_obj;
|
||||||
|
if (!json_object_object_get_ex(args, "source", &source_obj)) {
|
||||||
|
return strdup("Error: missing 'source' argument");
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *source_code = json_object_get_string(source_obj);
|
||||||
|
char *output = NULL;
|
||||||
|
int fd = -1;
|
||||||
|
|
||||||
|
char tmp_file[] = "/tmp/r_python_tool_XXXXXX.py";
|
||||||
|
fd = mkstemps(tmp_file, 3);
|
||||||
|
if (fd == -1) {
|
||||||
|
return strdup("Failed to create temporary file for Python code.");
|
||||||
|
}
|
||||||
|
|
||||||
|
FILE *fp = fdopen(fd, "w");
|
||||||
|
if (!fp) {
|
||||||
|
close(fd);
|
||||||
|
unlink(tmp_file);
|
||||||
|
return strdup("Failed to open temporary file for writing.");
|
||||||
|
}
|
||||||
|
|
||||||
|
fwrite(source_code, 1, strlen(source_code), fp);
|
||||||
|
fclose(fp);
|
||||||
|
|
||||||
|
char command[4096];
|
||||||
|
snprintf(command, sizeof(command), "python3 '%s'", tmp_file);
|
||||||
|
|
||||||
|
output = r_bash_execute(command, false);
|
||||||
|
|
||||||
|
unlink(tmp_file);
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct json_object *python_execute_get_description(void) {
|
||||||
|
struct json_object *root = json_object_new_object();
|
||||||
|
json_object_object_add(root, "type", json_object_new_string("function"));
|
||||||
|
|
||||||
|
struct json_object *function = json_object_new_object();
|
||||||
|
json_object_object_add(function, "name", json_object_new_string("python_execute"));
|
||||||
|
json_object_object_add(function, "description",
|
||||||
|
json_object_new_string("Executes Python source code using the python3 interpreter and returns stdout/stderr."));
|
||||||
|
|
||||||
|
struct json_object *parameters = json_object_new_object();
|
||||||
|
json_object_object_add(parameters, "type", json_object_new_string("object"));
|
||||||
|
|
||||||
|
struct json_object *properties = json_object_new_object();
|
||||||
|
struct json_object *source = json_object_new_object();
|
||||||
|
json_object_object_add(source, "type", json_object_new_string("string"));
|
||||||
|
json_object_object_add(source, "description",
|
||||||
|
json_object_new_string("Python source code to execute."));
|
||||||
|
json_object_object_add(properties, "source", source);
|
||||||
|
|
||||||
|
json_object_object_add(parameters, "properties", properties);
|
||||||
|
|
||||||
|
struct json_object *required = json_object_new_array();
|
||||||
|
json_object_array_add(required, json_object_new_string("source"));
|
||||||
|
json_object_object_add(parameters, "required", required);
|
||||||
|
json_object_object_add(parameters, "additionalProperties", json_object_new_boolean(0));
|
||||||
|
|
||||||
|
json_object_object_add(function, "parameters", parameters);
|
||||||
|
|
||||||
|
json_object_object_add(root, "function", function);
|
||||||
|
return root;
|
||||||
|
}
|
||||||
160
src/tools/tool_terminal.c
Executable file
160
src/tools/tool_terminal.c
Executable file
@ -0,0 +1,160 @@
|
|||||||
|
// retoor <retoor@molodetz.nl>
|
||||||
|
|
||||||
|
#include "tool.h"
|
||||||
|
#include "r_config.h"
|
||||||
|
#include "bash_executor.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
static struct json_object *terminal_get_description(void);
|
||||||
|
static char *terminal_execute(tool_t *self, struct json_object *args);
|
||||||
|
static void terminal_print_action(const char *name, struct json_object *args);
|
||||||
|
|
||||||
|
static struct json_object *terminal_interactive_get_description(void);
|
||||||
|
static char *terminal_interactive_execute(tool_t *self, struct json_object *args);
|
||||||
|
|
||||||
|
static const tool_vtable_t terminal_vtable = {
|
||||||
|
.get_description = terminal_get_description,
|
||||||
|
.execute = terminal_execute,
|
||||||
|
.print_action = terminal_print_action
|
||||||
|
};
|
||||||
|
|
||||||
|
static const tool_vtable_t terminal_interactive_vtable = {
|
||||||
|
.get_description = terminal_interactive_get_description,
|
||||||
|
.execute = terminal_interactive_execute,
|
||||||
|
.print_action = terminal_print_action
|
||||||
|
};
|
||||||
|
|
||||||
|
static tool_t terminal_tool = {
|
||||||
|
.vtable = &terminal_vtable,
|
||||||
|
.name = "linux_terminal_execute"
|
||||||
|
};
|
||||||
|
|
||||||
|
static tool_t terminal_interactive_tool = {
|
||||||
|
.vtable = &terminal_interactive_vtable,
|
||||||
|
.name = "linux_terminal_execute_interactive"
|
||||||
|
};
|
||||||
|
|
||||||
|
tool_t *tool_terminal_create(void) {
|
||||||
|
return &terminal_tool;
|
||||||
|
}
|
||||||
|
|
||||||
|
tool_t *tool_terminal_interactive_create(void) {
|
||||||
|
return &terminal_interactive_tool;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void terminal_print_action(const char *name, struct json_object *args) {
|
||||||
|
if (!args) {
|
||||||
|
fprintf(stderr, " -> %s\n", name);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
struct json_object *cmd;
|
||||||
|
if (json_object_object_get_ex(args, "command", &cmd)) {
|
||||||
|
if (strcmp(name, "linux_terminal_execute_interactive") == 0) {
|
||||||
|
fprintf(stderr, " -> Running interactive: %s\n", json_object_get_string(cmd));
|
||||||
|
} else {
|
||||||
|
fprintf(stderr, " -> Running command: %s\n", json_object_get_string(cmd));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static char *terminal_execute(tool_t *self, struct json_object *args) {
|
||||||
|
(void)self;
|
||||||
|
|
||||||
|
struct json_object *cmd_obj;
|
||||||
|
if (!json_object_object_get_ex(args, "command", &cmd_obj)) {
|
||||||
|
return strdup("Error: missing 'command' argument");
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *command = json_object_get_string(cmd_obj);
|
||||||
|
return r_bash_execute(command, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
static char *terminal_interactive_execute(tool_t *self, struct json_object *args) {
|
||||||
|
(void)self;
|
||||||
|
|
||||||
|
struct json_object *cmd_obj;
|
||||||
|
if (!json_object_object_get_ex(args, "command", &cmd_obj)) {
|
||||||
|
return strdup("Error: missing 'command' argument");
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *command = json_object_get_string(cmd_obj);
|
||||||
|
return r_bash_execute(command, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct json_object *terminal_get_description(void) {
|
||||||
|
struct json_object *root = json_object_new_object();
|
||||||
|
json_object_object_add(root, "type", json_object_new_string("function"));
|
||||||
|
|
||||||
|
struct json_object *function = json_object_new_object();
|
||||||
|
json_object_object_add(function, "name",
|
||||||
|
json_object_new_string("linux_terminal_execute"));
|
||||||
|
json_object_object_add(function, "description",
|
||||||
|
json_object_new_string("Execute a linux_terminal command on user terminal."));
|
||||||
|
|
||||||
|
struct json_object *parameters = json_object_new_object();
|
||||||
|
json_object_object_add(parameters, "type", json_object_new_string("object"));
|
||||||
|
|
||||||
|
struct json_object *properties = json_object_new_object();
|
||||||
|
struct json_object *cmd = json_object_new_object();
|
||||||
|
json_object_object_add(cmd, "type", json_object_new_string("string"));
|
||||||
|
json_object_object_add(cmd, "description",
|
||||||
|
json_object_new_string("Bash command to execute."));
|
||||||
|
json_object_object_add(properties, "command", cmd);
|
||||||
|
|
||||||
|
json_object_object_add(parameters, "properties", properties);
|
||||||
|
|
||||||
|
struct json_object *required = json_object_new_array();
|
||||||
|
json_object_array_add(required, json_object_new_string("command"));
|
||||||
|
json_object_object_add(parameters, "required", required);
|
||||||
|
json_object_object_add(parameters, "additionalProperties", json_object_new_boolean(0));
|
||||||
|
|
||||||
|
json_object_object_add(function, "parameters", parameters);
|
||||||
|
|
||||||
|
r_config_handle cfg = r_config_get_instance();
|
||||||
|
if (r_config_use_strict(cfg)) {
|
||||||
|
json_object_object_add(function, "strict", json_object_new_boolean(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
json_object_object_add(root, "function", function);
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct json_object *terminal_interactive_get_description(void) {
|
||||||
|
struct json_object *root = json_object_new_object();
|
||||||
|
json_object_object_add(root, "type", json_object_new_string("function"));
|
||||||
|
|
||||||
|
struct json_object *function = json_object_new_object();
|
||||||
|
json_object_object_add(function, "name",
|
||||||
|
json_object_new_string("linux_terminal_execute_interactive"));
|
||||||
|
json_object_object_add(function, "description",
|
||||||
|
json_object_new_string("Executes interactive terminal for user. You will not be able to read the result. Do not use if you need to know output."));
|
||||||
|
|
||||||
|
struct json_object *parameters = json_object_new_object();
|
||||||
|
json_object_object_add(parameters, "type", json_object_new_string("object"));
|
||||||
|
|
||||||
|
struct json_object *properties = json_object_new_object();
|
||||||
|
struct json_object *cmd = json_object_new_object();
|
||||||
|
json_object_object_add(cmd, "type", json_object_new_string("string"));
|
||||||
|
json_object_object_add(cmd, "description",
|
||||||
|
json_object_new_string("Executable with parameters to execute interactively."));
|
||||||
|
json_object_object_add(properties, "command", cmd);
|
||||||
|
|
||||||
|
json_object_object_add(parameters, "properties", properties);
|
||||||
|
|
||||||
|
struct json_object *required = json_object_new_array();
|
||||||
|
json_object_array_add(required, json_object_new_string("command"));
|
||||||
|
json_object_object_add(parameters, "required", required);
|
||||||
|
json_object_object_add(parameters, "additionalProperties", json_object_new_boolean(0));
|
||||||
|
|
||||||
|
json_object_object_add(function, "parameters", parameters);
|
||||||
|
|
||||||
|
r_config_handle cfg = r_config_get_instance();
|
||||||
|
if (r_config_use_strict(cfg)) {
|
||||||
|
json_object_object_add(function, "strict", json_object_new_boolean(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
json_object_object_add(root, "function", function);
|
||||||
|
return root;
|
||||||
|
}
|
||||||
56
src/tools/tools_init.c
Executable file
56
src/tools/tools_init.c
Executable file
@ -0,0 +1,56 @@
|
|||||||
|
// retoor <retoor@molodetz.nl>
|
||||||
|
|
||||||
|
#include "tool.h"
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
extern tool_t *tool_terminal_create(void);
|
||||||
|
extern tool_t *tool_terminal_interactive_create(void);
|
||||||
|
extern tool_t *tool_read_file_create(void);
|
||||||
|
extern tool_t *tool_write_file_create(void);
|
||||||
|
extern tool_t *tool_directory_glob_create(void);
|
||||||
|
extern tool_t *tool_mkdir_create(void);
|
||||||
|
extern tool_t *tool_chdir_create(void);
|
||||||
|
extern tool_t *tool_getpwd_create(void);
|
||||||
|
extern tool_t *tool_http_fetch_create(void);
|
||||||
|
extern tool_t *tool_web_search_create(void);
|
||||||
|
extern tool_t *tool_web_search_news_create(void);
|
||||||
|
extern tool_t *tool_db_get_create(void);
|
||||||
|
extern tool_t *tool_db_set_create(void);
|
||||||
|
extern tool_t *tool_db_query_create(void);
|
||||||
|
extern tool_t *tool_python_execute_create(void);
|
||||||
|
extern tool_t *tool_index_source_directory_create(void);
|
||||||
|
|
||||||
|
static tool_registry_t *global_registry = NULL;
|
||||||
|
|
||||||
|
tool_registry_t *tools_get_registry(void) {
|
||||||
|
if (global_registry) return global_registry;
|
||||||
|
|
||||||
|
global_registry = tool_registry_create();
|
||||||
|
if (!global_registry) return NULL;
|
||||||
|
|
||||||
|
tool_registry_register(global_registry, tool_terminal_create());
|
||||||
|
tool_registry_register(global_registry, tool_terminal_interactive_create());
|
||||||
|
tool_registry_register(global_registry, tool_read_file_create());
|
||||||
|
tool_registry_register(global_registry, tool_write_file_create());
|
||||||
|
tool_registry_register(global_registry, tool_directory_glob_create());
|
||||||
|
tool_registry_register(global_registry, tool_mkdir_create());
|
||||||
|
tool_registry_register(global_registry, tool_chdir_create());
|
||||||
|
tool_registry_register(global_registry, tool_getpwd_create());
|
||||||
|
tool_registry_register(global_registry, tool_http_fetch_create());
|
||||||
|
tool_registry_register(global_registry, tool_web_search_create());
|
||||||
|
tool_registry_register(global_registry, tool_web_search_news_create());
|
||||||
|
tool_registry_register(global_registry, tool_db_get_create());
|
||||||
|
tool_registry_register(global_registry, tool_db_set_create());
|
||||||
|
tool_registry_register(global_registry, tool_db_query_create());
|
||||||
|
tool_registry_register(global_registry, tool_python_execute_create());
|
||||||
|
tool_registry_register(global_registry, tool_index_source_directory_create());
|
||||||
|
|
||||||
|
return global_registry;
|
||||||
|
}
|
||||||
|
|
||||||
|
void tools_registry_shutdown(void) {
|
||||||
|
if (global_registry) {
|
||||||
|
tool_registry_destroy(global_registry);
|
||||||
|
global_registry = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user