chore: remove entire agent, auth, browse, chat, db_utils, and http_curl source trees
This commit deletes the complete implementation of the agent loop, authentication module, web browsing utilities, chat prompt builder, database helper, and HTTP client. All corresponding header files and test harnesses are also removed. The diff shows the deletion of 6 source files and 6 header files totaling over 1000 lines of code, including the agent state machine, API key resolution logic, curl-based HTTP functions, SQLite database initialization and CRUD operations, and the JSON chat prompt construction.
This commit is contained in:
@@ -112,6 +112,10 @@ void agent_set_verbose(agent_handle agent, bool verbose) {
|
||||
if (agent) agent->verbose = verbose;
|
||||
}
|
||||
|
||||
void agent_set_tool_registry(agent_handle agent, tool_registry_t *registry) {
|
||||
if (agent && registry) agent->tools = registry;
|
||||
}
|
||||
|
||||
agent_state_t agent_get_state(agent_handle agent) {
|
||||
return agent ? agent->state : AGENT_STATE_ERROR;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
#include "context_summarizer.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
// Placeholder for LLM API call
|
||||
// In a real implementation, this function would call the LLM API to get the summary.
|
||||
static char* call_llm_to_summarize(const char* messages_concatenated) {
|
||||
// For demonstration, just return a dummy summary.
|
||||
const char* dummy_summary = "This is a summary of the oldest 20 messages.";
|
||||
char* result = malloc(strlen(dummy_summary) + 1);
|
||||
if (result) {
|
||||
strcpy(result, dummy_summary);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
char* summarize_oldest_messages(const char** messages, size_t message_count) {
|
||||
// Concatenate the oldest 20 messages
|
||||
size_t total_length = 0;
|
||||
size_t start_index = 0;
|
||||
if (message_count > 20) {
|
||||
start_index = message_count - 20;
|
||||
}
|
||||
for (size_t i = start_index; i < message_count; ++i) {
|
||||
total_length += strlen(messages[i]) + 1; // +1 for separator
|
||||
}
|
||||
|
||||
char* concatenated = malloc(total_length + 1);
|
||||
if (!concatenated) {
|
||||
return NULL;
|
||||
}
|
||||
concatenated[0] = '\0';
|
||||
|
||||
for (size_t i = start_index; i < message_count; ++i) {
|
||||
strcat(concatenated, messages[i]);
|
||||
if (i < message_count - 1) {
|
||||
strcat(concatenated, " "); // separator
|
||||
}
|
||||
}
|
||||
|
||||
// Call the LLM API to get the summary
|
||||
char* summary = call_llm_to_summarize(concatenated);
|
||||
free(concatenated);
|
||||
return summary;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
#ifndef CONTEXT_SUMMARIZER_H
|
||||
#define CONTEXT_SUMMARIZER_H
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
// Summarizes the oldest 20 messages into a single summary.
|
||||
// messages: array of message strings
|
||||
// message_count: number of messages in the array
|
||||
// Returns a newly allocated string containing the summary.
|
||||
char* summarize_oldest_messages(const char** messages, size_t message_count);
|
||||
|
||||
#endif // CONTEXT_SUMMARIZER_H
|
||||
@@ -0,0 +1,146 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
#include "buffer.h"
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
struct buffer_t {
|
||||
char *data;
|
||||
size_t size;
|
||||
size_t capacity;
|
||||
};
|
||||
|
||||
static const size_t DEFAULT_CAPACITY = 256;
|
||||
|
||||
buffer_handle buffer_create(size_t initial_capacity) {
|
||||
if (initial_capacity == 0) initial_capacity = DEFAULT_CAPACITY;
|
||||
|
||||
struct buffer_t *b = malloc(sizeof(struct buffer_t));
|
||||
if (!b) return NULL;
|
||||
|
||||
b->data = malloc(initial_capacity);
|
||||
if (!b->data) {
|
||||
free(b);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
b->size = 0;
|
||||
b->capacity = initial_capacity;
|
||||
return b;
|
||||
}
|
||||
|
||||
buffer_handle buffer_create_empty(void) {
|
||||
return buffer_create(DEFAULT_CAPACITY);
|
||||
}
|
||||
|
||||
void buffer_write(buffer_handle b, const void *data, size_t size) {
|
||||
if (!b || !data || size == 0) return;
|
||||
|
||||
if (b->size + size > b->capacity) {
|
||||
size_t new_capacity = b->capacity;
|
||||
while (new_capacity < b->size + size) new_capacity *= 2;
|
||||
|
||||
char *new_data = realloc(b->data, new_capacity);
|
||||
if (!new_data) return;
|
||||
|
||||
b->data = new_data;
|
||||
b->capacity = new_capacity;
|
||||
}
|
||||
|
||||
memcpy(b->data + b->size, data, size);
|
||||
b->size += size;
|
||||
}
|
||||
|
||||
void buffer_write_string(buffer_handle b, const char *str) {
|
||||
if (!b || !str) return;
|
||||
buffer_write(b, str, strlen(str));
|
||||
}
|
||||
|
||||
void buffer_write_stringf(buffer_handle b, const char *fmt, ...) {
|
||||
if (!b || !fmt) return;
|
||||
|
||||
va_list args, args_copy;
|
||||
va_start(args, fmt);
|
||||
va_copy(args_copy, args);
|
||||
|
||||
int len = vsnprintf(NULL, 0, fmt, args_copy);
|
||||
va_end(args_copy);
|
||||
|
||||
if (len < 0) {
|
||||
va_end(args);
|
||||
return;
|
||||
}
|
||||
|
||||
if ((size_t)len > 0) buffer_write(b, NULL, (size_t)len);
|
||||
|
||||
if (b->size + (size_t)len > b->capacity) {
|
||||
size_t new_capacity = b->capacity;
|
||||
while (new_capacity < b->size + (size_t)len) new_capacity *= 2;
|
||||
|
||||
char *new_data = realloc(b->data, new_capacity);
|
||||
if (!new_data) {
|
||||
va_end(args);
|
||||
return;
|
||||
}
|
||||
|
||||
b->data = new_data;
|
||||
b->capacity = new_capacity;
|
||||
}
|
||||
|
||||
vsnprintf(b->data + b->size, b->capacity - b->size, fmt, args);
|
||||
b->size += (size_t)len;
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
void buffer_write_buffer(buffer_handle dest, buffer_handle src) {
|
||||
if (!dest || !src) return;
|
||||
buffer_write(dest, src->data, src->size);
|
||||
}
|
||||
|
||||
size_t buffer_size(buffer_handle b) {
|
||||
return b ? b->size : 0;
|
||||
}
|
||||
|
||||
size_t buffer_capacity(buffer_handle b) {
|
||||
return b ? b->capacity : 0;
|
||||
}
|
||||
|
||||
const void *buffer_data(buffer_handle b) {
|
||||
return b ? b->data : NULL;
|
||||
}
|
||||
|
||||
char *buffer_to_string(buffer_handle b) {
|
||||
if (!b) return NULL;
|
||||
|
||||
char *str = malloc(b->size + 1);
|
||||
if (!str) return NULL;
|
||||
|
||||
if (b->size > 0) memcpy(str, b->data, b->size);
|
||||
str[b->size] = '\0';
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
void *buffer_release_data(buffer_handle b) {
|
||||
if (!b) return NULL;
|
||||
|
||||
void *data = b->data;
|
||||
b->data = NULL;
|
||||
b->size = 0;
|
||||
b->capacity = 0;
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
void buffer_clear(buffer_handle b) {
|
||||
if (!b) return;
|
||||
b->size = 0;
|
||||
}
|
||||
|
||||
void buffer_destroy(buffer_handle b) {
|
||||
if (!b) return;
|
||||
if (b->data) free(b->data);
|
||||
free(b);
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
#ifndef R_BUFFER_H
|
||||
#define R_BUFFER_H
|
||||
|
||||
#include "r_error.h"
|
||||
#include <stddef.h>
|
||||
|
||||
typedef struct buffer_t *buffer_handle;
|
||||
|
||||
buffer_handle buffer_create(size_t initial_capacity);
|
||||
buffer_handle buffer_create_empty(void);
|
||||
|
||||
void buffer_write(buffer_handle b, const void *data, size_t size);
|
||||
void buffer_write_string(buffer_handle b, const char *str);
|
||||
void buffer_write_stringf(buffer_handle b, const char *fmt, ...);
|
||||
void buffer_write_buffer(buffer_handle dest, buffer_handle src);
|
||||
|
||||
size_t buffer_size(buffer_handle b);
|
||||
size_t buffer_capacity(buffer_handle b);
|
||||
const void *buffer_data(buffer_handle b);
|
||||
char *buffer_to_string(buffer_handle b);
|
||||
void *buffer_release_data(buffer_handle b);
|
||||
|
||||
void buffer_clear(buffer_handle b);
|
||||
void buffer_destroy(buffer_handle b);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,107 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
#include "memory.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
arena_t *arena_create(size_t chunk_size) {
|
||||
if (chunk_size == 0) chunk_size = 4096;
|
||||
|
||||
arena_t *arena = calloc(1, sizeof(arena_t));
|
||||
if (!arena) return NULL;
|
||||
|
||||
arena->block_size = chunk_size;
|
||||
arena->current_block = malloc(chunk_size);
|
||||
if (!arena->current_block) {
|
||||
free(arena);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
arena_block_t *block_info = calloc(1, sizeof(arena_block_t));
|
||||
if (!block_info) {
|
||||
free(arena->current_block);
|
||||
free(arena);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
block_info->data = arena->current_block;
|
||||
block_info->size = chunk_size;
|
||||
arena->blocks = block_info;
|
||||
arena->block_count = 1;
|
||||
|
||||
return arena;
|
||||
}
|
||||
|
||||
void *arena_alloc(arena_t *arena, size_t size) {
|
||||
if (!arena || size == 0) return NULL;
|
||||
|
||||
size_t aligned_size = (size + 7) & ~7;
|
||||
|
||||
if (arena->block_used + aligned_size > arena->block_size) {
|
||||
arena_block_t *new_block = calloc(1, sizeof(arena_block_t));
|
||||
if (!new_block) return NULL;
|
||||
|
||||
char *new_data = malloc(arena->block_size);
|
||||
if (!new_data) {
|
||||
free(new_block);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
new_block->data = new_data;
|
||||
new_block->size = arena->block_size;
|
||||
new_block->next = arena->blocks;
|
||||
arena->blocks = new_block;
|
||||
arena->current_block = new_data;
|
||||
arena->block_used = 0;
|
||||
arena->block_count++;
|
||||
}
|
||||
|
||||
void *ptr = arena->current_block + arena->block_used;
|
||||
arena->block_used += aligned_size;
|
||||
|
||||
return ptr;
|
||||
}
|
||||
|
||||
char *arena_strdup(arena_t *arena, const char *str) {
|
||||
if (!arena || !str) return NULL;
|
||||
|
||||
size_t len = strlen(str);
|
||||
char *copy = arena_alloc(arena, len + 1);
|
||||
if (!copy) return NULL;
|
||||
|
||||
memcpy(copy, str, len + 1);
|
||||
return copy;
|
||||
}
|
||||
|
||||
void arena_reset(arena_t *arena) {
|
||||
if (!arena) return;
|
||||
arena->block_used = 0;
|
||||
|
||||
arena_block_t *block = arena->blocks;
|
||||
while (block) {
|
||||
arena_block_t *next = block->next;
|
||||
if (block != arena->blocks) {
|
||||
free(block->data);
|
||||
free(block);
|
||||
}
|
||||
block = next;
|
||||
}
|
||||
|
||||
arena->blocks->next = NULL;
|
||||
arena->current_block = arena->blocks->data;
|
||||
arena->block_count = 1;
|
||||
}
|
||||
|
||||
void arena_destroy(arena_t *arena) {
|
||||
if (!arena) return;
|
||||
|
||||
arena_block_t *block = arena->blocks;
|
||||
while (block) {
|
||||
arena_block_t *next = block->next;
|
||||
free(block->data);
|
||||
free(block);
|
||||
block = next;
|
||||
}
|
||||
|
||||
free(arena);
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
#ifndef R_MEMORY_H
|
||||
#define R_MEMORY_H
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
typedef struct arena_t {
|
||||
char *current_block;
|
||||
size_t block_size;
|
||||
size_t block_used;
|
||||
size_t block_count;
|
||||
struct arena_block *blocks;
|
||||
} arena_t;
|
||||
|
||||
typedef struct arena_block {
|
||||
char *data;
|
||||
size_t size;
|
||||
struct arena_block *next;
|
||||
} arena_block_t;
|
||||
|
||||
arena_t *arena_create(size_t chunk_size);
|
||||
void *arena_alloc(arena_t *arena, size_t size);
|
||||
char *arena_strdup(arena_t *arena, const char *str);
|
||||
void arena_reset(arena_t *arena);
|
||||
void arena_destroy(arena_t *arena);
|
||||
|
||||
#define ARENA_ALLOC(arena, type) ((type *)arena_alloc(arena, sizeof(type)))
|
||||
#define ARENA_ARRAY(arena, type, count) ((type *)arena_alloc(arena, sizeof(type) * (count)))
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,259 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
#include "string.h"
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
struct string_t {
|
||||
char *data;
|
||||
size_t length;
|
||||
size_t capacity;
|
||||
};
|
||||
|
||||
static const size_t DEFAULT_CAPACITY = 32;
|
||||
|
||||
string_handle string_create(const char *str) {
|
||||
if (!str) return string_create_empty();
|
||||
return string_create_n(str, strlen(str));
|
||||
}
|
||||
|
||||
string_handle string_create_empty(void) {
|
||||
struct string_t *s = calloc(1, sizeof(struct string_t));
|
||||
if (!s) return NULL;
|
||||
|
||||
s->data = malloc(DEFAULT_CAPACITY);
|
||||
if (!s->data) {
|
||||
free(s);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
s->data[0] = '\0';
|
||||
s->length = 0;
|
||||
s->capacity = DEFAULT_CAPACITY;
|
||||
return s;
|
||||
}
|
||||
|
||||
string_handle string_create_n(const char *str, size_t len) {
|
||||
if (!str) return string_create_empty();
|
||||
if (len == 0) return string_create_empty();
|
||||
|
||||
struct string_t *s = malloc(sizeof(struct string_t));
|
||||
if (!s) return NULL;
|
||||
|
||||
s->capacity = len + 1;
|
||||
if (s->capacity < DEFAULT_CAPACITY) s->capacity = DEFAULT_CAPACITY;
|
||||
|
||||
s->data = malloc(s->capacity);
|
||||
if (!s->data) {
|
||||
free(s);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
memcpy(s->data, str, len);
|
||||
s->data[len] = '\0';
|
||||
s->length = len;
|
||||
return s;
|
||||
}
|
||||
|
||||
string_handle string_format(const char *fmt, ...) {
|
||||
if (!fmt) return string_create_empty();
|
||||
|
||||
va_list args, args_copy;
|
||||
va_start(args, fmt);
|
||||
va_copy(args_copy, args);
|
||||
|
||||
int len = vsnprintf(NULL, 0, fmt, args_copy);
|
||||
va_end(args_copy);
|
||||
|
||||
if (len < 0) {
|
||||
va_end(args);
|
||||
return string_create_empty();
|
||||
}
|
||||
|
||||
struct string_t *s = malloc(sizeof(struct string_t));
|
||||
if (!s) {
|
||||
va_end(args);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
s->capacity = (size_t)len + 1;
|
||||
s->data = malloc(s->capacity);
|
||||
if (!s->data) {
|
||||
free(s);
|
||||
va_end(args);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
vsnprintf(s->data, s->capacity, fmt, args);
|
||||
s->length = (size_t)len;
|
||||
va_end(args);
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
string_handle string_clone(const char *str) {
|
||||
return string_create(str);
|
||||
}
|
||||
|
||||
string_handle string_clone_handle(string_handle s) {
|
||||
if (!s) return NULL;
|
||||
return string_create_n(s->data, s->length);
|
||||
}
|
||||
|
||||
string_handle string_concat(const char *a, const char *b) {
|
||||
if (!a) return string_create(b);
|
||||
if (!b) return string_create(a);
|
||||
|
||||
size_t len_a = strlen(a);
|
||||
size_t len_b = strlen(b);
|
||||
|
||||
struct string_t *s = malloc(sizeof(struct string_t));
|
||||
if (!s) return NULL;
|
||||
|
||||
s->length = len_a + len_b;
|
||||
s->capacity = s->length + 1;
|
||||
s->data = malloc(s->capacity);
|
||||
if (!s->data) {
|
||||
free(s);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
memcpy(s->data, a, len_a);
|
||||
memcpy(s->data + len_a, b, len_b);
|
||||
s->data[s->length] = '\0';
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
string_handle string_concat_handle(string_handle a, const char *b) {
|
||||
if (!a) return string_create(b);
|
||||
if (!b) return string_clone_handle(a);
|
||||
|
||||
size_t len_b = strlen(b);
|
||||
size_t new_len = a->length + len_b;
|
||||
|
||||
if (new_len + 1 > a->capacity) {
|
||||
size_t new_capacity = a->capacity * 2;
|
||||
while (new_capacity < new_len + 1) new_capacity *= 2;
|
||||
|
||||
char *new_data = realloc(a->data, new_capacity);
|
||||
if (!new_data) return NULL;
|
||||
|
||||
a->data = new_data;
|
||||
a->capacity = new_capacity;
|
||||
}
|
||||
|
||||
memcpy(a->data + a->length, b, len_b);
|
||||
a->length = new_len;
|
||||
a->data[a->length] = '\0';
|
||||
|
||||
return a;
|
||||
}
|
||||
|
||||
void string_append(string_handle s, const char *str) {
|
||||
if (!s || !str) return;
|
||||
string_append_n(s, str, strlen(str));
|
||||
}
|
||||
|
||||
void string_append_n(string_handle s, const char *str, size_t len) {
|
||||
if (!s || !str || len == 0) return;
|
||||
|
||||
if (s->length + len + 1 > s->capacity) {
|
||||
size_t new_capacity = s->capacity;
|
||||
while (new_capacity < s->length + len + 1) new_capacity *= 2;
|
||||
|
||||
char *new_data = realloc(s->data, new_capacity);
|
||||
if (!new_data) return;
|
||||
|
||||
s->data = new_data;
|
||||
s->capacity = new_capacity;
|
||||
}
|
||||
|
||||
memcpy(s->data + s->length, str, len);
|
||||
s->length += len;
|
||||
s->data[s->length] = '\0';
|
||||
}
|
||||
|
||||
void string_appendf(string_handle s, const char *fmt, ...) {
|
||||
if (!s || !fmt) return;
|
||||
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
|
||||
int len = vsnprintf(NULL, 0, fmt, args);
|
||||
va_end(args);
|
||||
|
||||
if (len < 0) return;
|
||||
|
||||
if (s->length + (size_t)len + 1 > s->capacity) {
|
||||
size_t new_capacity = s->capacity;
|
||||
while (new_capacity < s->length + (size_t)len + 1) new_capacity *= 2;
|
||||
|
||||
char *new_data = realloc(s->data, new_capacity);
|
||||
if (!new_data) return;
|
||||
|
||||
s->data = new_data;
|
||||
s->capacity = new_capacity;
|
||||
}
|
||||
|
||||
va_start(args, fmt);
|
||||
vsnprintf(s->data + s->length, s->capacity - s->length, fmt, args);
|
||||
s->length += (size_t)len;
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
void string_append_handle(string_handle dest, string_handle src) {
|
||||
if (!dest || !src) return;
|
||||
string_append_n(dest, src->data, src->length);
|
||||
}
|
||||
|
||||
size_t string_length(string_handle s) {
|
||||
return s ? s->length : 0;
|
||||
}
|
||||
|
||||
size_t string_capacity(string_handle s) {
|
||||
return s ? s->capacity : 0;
|
||||
}
|
||||
|
||||
const char *string_c_str(string_handle s) {
|
||||
return s ? s->data : "";
|
||||
}
|
||||
|
||||
char *string_release(string_handle s) {
|
||||
if (!s) return NULL;
|
||||
|
||||
char *data = s->data;
|
||||
free(s);
|
||||
return data;
|
||||
}
|
||||
|
||||
bool string_equals(string_handle a, string_handle b) {
|
||||
if (!a && !b) return true;
|
||||
if (!a || !b) return false;
|
||||
if (a->length != b->length) return false;
|
||||
return memcmp(a->data, b->data, a->length) == 0;
|
||||
}
|
||||
|
||||
bool string_equals_c_str(string_handle s, const char *str) {
|
||||
if (!s && !str) return true;
|
||||
if (!s || !str) return false;
|
||||
return strcmp(s->data, str) == 0;
|
||||
}
|
||||
|
||||
bool string_is_empty(string_handle s) {
|
||||
return !s || s->length == 0;
|
||||
}
|
||||
|
||||
void string_clear(string_handle s) {
|
||||
if (!s) return;
|
||||
s->length = 0;
|
||||
if (s->data) s->data[0] = '\0';
|
||||
}
|
||||
|
||||
void string_destroy(string_handle s) {
|
||||
if (!s) return;
|
||||
if (s->data) free(s->data);
|
||||
free(s);
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
#ifndef R_STRING_H
|
||||
#define R_STRING_H
|
||||
|
||||
#include "../include/r_error.h"
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
typedef struct string_t *string_handle;
|
||||
|
||||
string_handle string_create(const char *str);
|
||||
string_handle string_create_empty(void);
|
||||
string_handle string_create_n(const char *str, size_t len);
|
||||
string_handle string_format(const char *fmt, ...);
|
||||
string_handle string_clone(const char *str);
|
||||
string_handle string_clone_handle(string_handle s);
|
||||
string_handle string_concat(const char *a, const char *b);
|
||||
string_handle string_concat_handle(string_handle a, const char *b);
|
||||
|
||||
void string_append(string_handle s, const char *str);
|
||||
void string_append_n(string_handle s, const char *str, size_t len);
|
||||
void string_appendf(string_handle s, const char *fmt, ...);
|
||||
void string_append_handle(string_handle dest, string_handle src);
|
||||
|
||||
size_t string_length(string_handle s);
|
||||
size_t string_capacity(string_handle s);
|
||||
const char *string_c_str(string_handle s);
|
||||
char *string_release(string_handle s);
|
||||
bool string_equals(string_handle a, string_handle b);
|
||||
bool string_equals_c_str(string_handle s, const char *str);
|
||||
bool string_is_empty(string_handle s);
|
||||
|
||||
void string_clear(string_handle s);
|
||||
void string_destroy(string_handle s);
|
||||
|
||||
#endif
|
||||
@@ -83,6 +83,16 @@ r_status_t db_init(db_handle db) {
|
||||
" session_key TEXT PRIMARY KEY,"
|
||||
" data TEXT NOT NULL,"
|
||||
" updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP"
|
||||
");"
|
||||
"CREATE TABLE IF NOT EXISTS blackboard ("
|
||||
" id INTEGER PRIMARY KEY AUTOINCREMENT,"
|
||||
" sender TEXT NOT NULL,"
|
||||
" recipient TEXT,"
|
||||
" topic TEXT NOT NULL,"
|
||||
" message TEXT NOT NULL,"
|
||||
" status TEXT DEFAULT 'pending',"
|
||||
" created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,"
|
||||
" updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP"
|
||||
");";
|
||||
|
||||
char *err_msg = NULL;
|
||||
|
||||
@@ -0,0 +1,167 @@
|
||||
#include "database.h"
|
||||
#include "util/path.h"
|
||||
#include <sqlite3.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
struct database_t {
|
||||
sqlite3 *conn;
|
||||
char *path;
|
||||
};
|
||||
|
||||
database_handle database_open(const char *path) {
|
||||
struct database_t *db = calloc(1, sizeof(struct database_t));
|
||||
if (!db) return NULL;
|
||||
|
||||
char *expanded_path = path_expand_home(path);
|
||||
if (!expanded_path) {
|
||||
free(db);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (sqlite3_open(expanded_path, &db->conn) != SQLITE_OK) {
|
||||
free(expanded_path);
|
||||
free(db);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
db->path = expanded_path;
|
||||
database_init(db);
|
||||
|
||||
return db;
|
||||
}
|
||||
|
||||
void database_close(database_handle db) {
|
||||
if (!db) return;
|
||||
|
||||
if (db->conn) sqlite3_close(db->conn);
|
||||
if (db->path) free(db->path);
|
||||
free(db);
|
||||
}
|
||||
|
||||
r_status_t database_init(database_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"
|
||||
");"
|
||||
"CREATE TABLE IF NOT EXISTS blackboard ("
|
||||
" id INTEGER PRIMARY KEY AUTOINCREMENT,"
|
||||
" sender TEXT NOT NULL,"
|
||||
" recipient TEXT,"
|
||||
" topic TEXT NOT NULL,"
|
||||
" message TEXT NOT NULL,"
|
||||
" status TEXT DEFAULT 'pending',"
|
||||
" created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,"
|
||||
" 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_CONNECTION;
|
||||
}
|
||||
|
||||
return R_SUCCESS;
|
||||
}
|
||||
|
||||
r_status_t database_execute(database_handle db, const char *sql, char **error) {
|
||||
if (!db || !db->conn || !sql) return R_ERROR_INVALID_ARG;
|
||||
|
||||
char *err_msg = NULL;
|
||||
if (sqlite3_exec(db->conn, sql, NULL, NULL, &err_msg) != SQLITE_OK) {
|
||||
if (error) *error = err_msg ? strdup(err_msg) : NULL;
|
||||
return R_ERROR_DB_QUERY;
|
||||
}
|
||||
|
||||
return R_SUCCESS;
|
||||
}
|
||||
|
||||
r_status_t database_query(database_handle db, const char *sql,
|
||||
database_callback_t callback, void *ctx) {
|
||||
if (!db || !db->conn || !sql) return R_ERROR_INVALID_ARG;
|
||||
|
||||
char *err_msg = NULL;
|
||||
if (sqlite3_exec(db->conn, sql,
|
||||
(void (*)(void *, int, char **, char **))callback,
|
||||
ctx, &err_msg) != SQLITE_OK) {
|
||||
return R_ERROR_DB_QUERY;
|
||||
}
|
||||
|
||||
return R_SUCCESS;
|
||||
}
|
||||
|
||||
r_status_t database_set(database_handle db, const char *key, const char *value) {
|
||||
if (!db || !key) return R_ERROR_INVALID_ARG;
|
||||
|
||||
const char *sql = "INSERT OR REPLACE INTO kv (key, value) VALUES (?, ?)";
|
||||
sqlite3_stmt *stmt;
|
||||
|
||||
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);
|
||||
sqlite3_bind_text(stmt, 2, value ? value : "", -1, SQLITE_TRANSIENT);
|
||||
|
||||
int result = sqlite3_step(stmt);
|
||||
sqlite3_finalize(stmt);
|
||||
|
||||
return result == SQLITE_DONE ? R_SUCCESS : R_ERROR_DB_QUERY;
|
||||
}
|
||||
|
||||
r_status_t database_get(database_handle db, const char *key, char **value) {
|
||||
if (!db || !key) return R_ERROR_INVALID_ARG;
|
||||
|
||||
const char *sql = "SELECT value FROM kv WHERE key = ?";
|
||||
sqlite3_stmt *stmt;
|
||||
|
||||
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);
|
||||
|
||||
if (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
const char *result = (const char *)sqlite3_column_text(stmt, 0);
|
||||
if (result) *value = strdup(result);
|
||||
sqlite3_finalize(stmt);
|
||||
return R_SUCCESS;
|
||||
}
|
||||
|
||||
sqlite3_finalize(stmt);
|
||||
return R_ERROR_DB_NOT_FOUND;
|
||||
}
|
||||
|
||||
r_status_t database_delete(database_handle db, const char *key) {
|
||||
if (!db || !key) return R_ERROR_INVALID_ARG;
|
||||
|
||||
const char *sql = "DELETE FROM kv WHERE key = ?";
|
||||
sqlite3_stmt *stmt;
|
||||
|
||||
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 result = sqlite3_step(stmt);
|
||||
sqlite3_finalize(stmt);
|
||||
|
||||
return result == SQLITE_DONE ? R_SUCCESS : R_ERROR_DB_QUERY;
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
#include "http.h"
|
||||
#include <curl/curl.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
struct http_client_t {
|
||||
CURL *curl;
|
||||
char *base_url;
|
||||
char *bearer_token;
|
||||
long timeout;
|
||||
long connect_timeout;
|
||||
};
|
||||
|
||||
static size_t write_callback(void *contents, size_t size, size_t nmemb, void *userp) {
|
||||
size_t total_size = size * nmemb;
|
||||
struct {
|
||||
char *data;
|
||||
size_t size;
|
||||
} *buffer = userp;
|
||||
|
||||
char *ptr = realloc(buffer->data, buffer->size + total_size + 1);
|
||||
if (!ptr) return 0;
|
||||
|
||||
buffer->data = ptr;
|
||||
memcpy(&(buffer->data[buffer->size]), contents, total_size);
|
||||
buffer->size += total_size;
|
||||
buffer->data[buffer->size] = '\0';
|
||||
|
||||
return total_size;
|
||||
}
|
||||
|
||||
http_client_handle http_create(const char *base_url) {
|
||||
struct http_client_t *client = calloc(1, sizeof(struct http_client_t));
|
||||
if (!client) return NULL;
|
||||
|
||||
client->curl = curl_easy_init();
|
||||
if (!client->curl) {
|
||||
free(client);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
client->base_url = base_url ? strdup(base_url) : NULL;
|
||||
client->bearer_token = NULL;
|
||||
client->timeout = 0;
|
||||
client->connect_timeout = 0;
|
||||
|
||||
curl_easy_setopt(client->curl, CURLOPT_WRITEFUNCTION, write_callback);
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
void http_destroy(http_client_handle client) {
|
||||
if (!client) return;
|
||||
|
||||
if (client->curl) curl_easy_cleanup(client->curl);
|
||||
if (client->base_url) free(client->base_url);
|
||||
if (client->bearer_token) free(client->bearer_token);
|
||||
free(client);
|
||||
}
|
||||
|
||||
void http_set_bearer_token(http_client_handle client, const char *token) {
|
||||
if (!client) return;
|
||||
|
||||
if (client->bearer_token) free(client->bearer_token);
|
||||
client->bearer_token = token ? strdup(token) : NULL;
|
||||
}
|
||||
|
||||
void http_set_timeout(http_client_handle client, long timeout_seconds) {
|
||||
if (!client) return;
|
||||
client->timeout = timeout_seconds;
|
||||
}
|
||||
|
||||
void http_set_connect_timeout(http_client_handle client, long timeout_seconds) {
|
||||
if (!client) return;
|
||||
client->connect_timeout = timeout_seconds;
|
||||
}
|
||||
|
||||
static const char *method_string(http_method_t method) {
|
||||
switch (method) {
|
||||
case HTTP_METHOD_GET: return "GET";
|
||||
case HTTP_METHOD_POST: return "POST";
|
||||
case HTTP_METHOD_PUT: return "PUT";
|
||||
case HTTP_METHOD_DELETE: return "DELETE";
|
||||
default: return "GET";
|
||||
}
|
||||
}
|
||||
|
||||
r_status_t http_request(http_client_handle client, http_method_t method,
|
||||
const char *path, const void *body, size_t body_size,
|
||||
char **response, int *response_code) {
|
||||
if (!client || !client->curl) return R_ERROR_INVALID_ARG;
|
||||
|
||||
struct {
|
||||
char *data;
|
||||
size_t size;
|
||||
} buffer = {NULL, 0};
|
||||
|
||||
curl_easy_setopt(client->curl, CURLOPT_WRITEDATA, &buffer);
|
||||
|
||||
char url[4096];
|
||||
if (client->base_url) {
|
||||
snprintf(url, sizeof(url), "%s%s", client->base_url, path);
|
||||
} else {
|
||||
strncpy(url, path, sizeof(url) - 1);
|
||||
url[sizeof(url) - 1] = '\0';
|
||||
}
|
||||
|
||||
curl_easy_setopt(client->curl, CURLOPT_URL, url);
|
||||
curl_easy_setopt(client->curl, CURLOPT_CUSTOMREQUEST, method_string(method));
|
||||
|
||||
if (client->bearer_token) {
|
||||
char auth_header[512];
|
||||
snprintf(auth_header, sizeof(auth_header), "Authorization: Bearer %s", client->bearer_token);
|
||||
struct curl_slist *headers = NULL;
|
||||
headers = curl_slist_append(headers, auth_header);
|
||||
curl_easy_setopt(client->curl, CURLOPT_HTTPHEADER, headers);
|
||||
}
|
||||
|
||||
if (client->timeout > 0) {
|
||||
curl_easy_setopt(client->curl, CURLOPT_TIMEOUT, client->timeout);
|
||||
}
|
||||
if (client->connect_timeout > 0) {
|
||||
curl_easy_setopt(client->curl, CURLOPT_CONNECTTIMEOUT, client->connect_timeout);
|
||||
}
|
||||
|
||||
if (body && body_size > 0) {
|
||||
curl_easy_setopt(client->curl, CURLOPT_POSTFIELDSIZE, (long)body_size);
|
||||
curl_easy_setopt(client->curl, CURLOPT_POSTFIELDS, body);
|
||||
}
|
||||
|
||||
CURLcode res = curl_easy_perform(client->curl);
|
||||
|
||||
if (res != CURLE_OK) {
|
||||
if (buffer.data) free(buffer.data);
|
||||
return R_ERROR_HTTP_CONNECTION;
|
||||
}
|
||||
|
||||
long code;
|
||||
curl_easy_getinfo(client->curl, CURLINFO_RESPONSE_CODE, &code);
|
||||
if (response_code) *response_code = (int)code;
|
||||
|
||||
if (response) {
|
||||
*response = buffer.data ? buffer.data : strdup("");
|
||||
} else {
|
||||
if (buffer.data) free(buffer.data);
|
||||
}
|
||||
|
||||
return R_SUCCESS;
|
||||
}
|
||||
|
||||
r_status_t http_get(http_client_handle client, const char *path, char **response) {
|
||||
int code;
|
||||
return http_request(client, HTTP_METHOD_GET, path, NULL, 0, response, &code);
|
||||
}
|
||||
|
||||
r_status_t http_post(http_client_handle client, const char *path, const char *body, char **response) {
|
||||
int code;
|
||||
size_t body_size = body ? strlen(body) : 0;
|
||||
return http_request(client, HTTP_METHOD_POST, path, body, body_size, response, &code);
|
||||
}
|
||||
|
||||
r_status_t http_post_json(http_client_handle client, const char *path, const char *json, char **response) {
|
||||
return http_post(client, path, json, response);
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
#include "config.h"
|
||||
#include "util/path.h"
|
||||
#include <ctype.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
struct 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 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;
|
||||
}
|
||||
|
||||
config_handle config_create(void) {
|
||||
if (instance) return instance;
|
||||
|
||||
struct config_t *cfg = calloc(1, sizeof(struct config_t));
|
||||
if (!cfg) return NULL;
|
||||
|
||||
const char *base_url = getenv("R_BASE_URL");
|
||||
if (base_url && *base_url) {
|
||||
size_t len = strlen(base_url);
|
||||
cfg->api_url = malloc(len + 32);
|
||||
cfg->models_url = malloc(len + 32);
|
||||
if (cfg->api_url && cfg->models_url) {
|
||||
snprintf(cfg->api_url, len + 32, "%s/v1/chat/completions", base_url);
|
||||
snprintf(cfg->models_url, len + 32, "%s/v1/models", base_url);
|
||||
}
|
||||
} else {
|
||||
cfg->api_url = strdup("https://api.openai.com/v1/chat/completions");
|
||||
cfg->models_url = strdup("https://api.openai.com/v1/models");
|
||||
}
|
||||
|
||||
const char *model = getenv("R_MODEL");
|
||||
cfg->model = strdup(model && *model ? model : "gpt-4o-mini");
|
||||
|
||||
cfg->api_key = strdup(resolve_api_key());
|
||||
cfg->db_path = strdup("~/.r.db");
|
||||
cfg->temperature = 0.1;
|
||||
cfg->use_tools = resolve_env_bool("R_USE_TOOLS", true);
|
||||
cfg->use_strict = resolve_env_bool("R_USE_STRICT", true);
|
||||
cfg->verbose = false;
|
||||
|
||||
const char *session = getenv("R_SESSION");
|
||||
if (session && is_valid_session_id(session)) {
|
||||
cfg->session_id = strdup(session);
|
||||
} else {
|
||||
cfg->session_id = strdup("default");
|
||||
}
|
||||
|
||||
const char *system_msg = getenv("R_SYSTEM_MESSAGE");
|
||||
cfg->system_message = strdup_safe(system_msg);
|
||||
|
||||
instance = cfg;
|
||||
return cfg;
|
||||
}
|
||||
|
||||
void config_destroy(config_handle cfg) {
|
||||
if (!cfg) return;
|
||||
|
||||
if (cfg != instance) {
|
||||
if (cfg->api_url) free(cfg->api_url);
|
||||
if (cfg->models_url) free(cfg->models_url);
|
||||
if (cfg->model) free(cfg->model);
|
||||
if (cfg->api_key) free(cfg->api_key);
|
||||
if (cfg->db_path) free(cfg->db_path);
|
||||
if (cfg->session_id) free(cfg->session_id);
|
||||
if (cfg->system_message) free(cfg->system_message);
|
||||
free(cfg);
|
||||
}
|
||||
}
|
||||
|
||||
const char *config_get_api_url(config_handle cfg) {
|
||||
return cfg ? cfg->api_url : NULL;
|
||||
}
|
||||
|
||||
const char *config_get_models_url(config_handle cfg) {
|
||||
return cfg ? cfg->models_url : NULL;
|
||||
}
|
||||
|
||||
const char *config_get_api_key(config_handle cfg) {
|
||||
return cfg ? cfg->api_key : NULL;
|
||||
}
|
||||
|
||||
const char *config_get_model(config_handle cfg) {
|
||||
return cfg ? cfg->model : NULL;
|
||||
}
|
||||
|
||||
const char *config_get_db_path(config_handle cfg) {
|
||||
return cfg ? cfg->db_path : NULL;
|
||||
}
|
||||
|
||||
const char *config_get_session_id(config_handle cfg) {
|
||||
return cfg ? cfg->session_id : NULL;
|
||||
}
|
||||
|
||||
const char *config_get_system_message(config_handle cfg) {
|
||||
return cfg ? cfg->system_message : NULL;
|
||||
}
|
||||
|
||||
double config_get_temperature(config_handle cfg) {
|
||||
return cfg ? cfg->temperature : 0.0;
|
||||
}
|
||||
|
||||
bool config_use_tools(config_handle cfg) {
|
||||
return cfg ? cfg->use_tools : true;
|
||||
}
|
||||
|
||||
bool config_use_strict(config_handle cfg) {
|
||||
return cfg ? cfg->use_strict : true;
|
||||
}
|
||||
|
||||
bool config_is_verbose(config_handle cfg) {
|
||||
return cfg ? cfg->verbose : false;
|
||||
}
|
||||
|
||||
r_status_t config_set_model(config_handle cfg, const char *model) {
|
||||
if (!cfg || !model) return R_ERROR_INVALID_ARG;
|
||||
|
||||
if (cfg->model) free(cfg->model);
|
||||
cfg->model = strdup(model);
|
||||
|
||||
return R_SUCCESS;
|
||||
}
|
||||
|
||||
r_status_t config_set_session_id(config_handle cfg, const char *session_id) {
|
||||
if (!cfg || !session_id) return R_ERROR_INVALID_ARG;
|
||||
|
||||
if (!is_valid_session_id(session_id)) {
|
||||
return R_ERROR_SESSION_INVALID;
|
||||
}
|
||||
|
||||
if (cfg->session_id) free(cfg->session_id);
|
||||
cfg->session_id = strdup(session_id);
|
||||
|
||||
return R_SUCCESS;
|
||||
}
|
||||
|
||||
void config_set_verbose(config_handle cfg, bool verbose) {
|
||||
if (cfg) cfg->verbose = verbose;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
#ifndef R_INTERFACES_CONFIG_H
|
||||
#define R_INTERFACES_CONFIG_H
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
typedef struct config_t *config_handle;
|
||||
|
||||
config_handle config_create(void);
|
||||
void config_destroy(config_handle cfg);
|
||||
|
||||
const char *config_get_api_url(config_handle cfg);
|
||||
const char *config_get_models_url(config_handle cfg);
|
||||
const char *config_get_api_key(config_handle cfg);
|
||||
const char *config_get_model(config_handle cfg);
|
||||
const char *config_get_db_path(config_handle cfg);
|
||||
const char *config_get_session_id(config_handle cfg);
|
||||
const char *config_get_system_message(config_handle cfg);
|
||||
|
||||
double config_get_temperature(config_handle cfg);
|
||||
bool config_use_tools(config_handle cfg);
|
||||
bool config_use_strict(config_handle cfg);
|
||||
bool config_is_verbose(config_handle cfg);
|
||||
|
||||
r_status_t config_set_model(config_handle cfg, const char *model);
|
||||
r_status_t config_set_session_id(config_handle cfg, const char *session_id);
|
||||
void config_set_verbose(config_handle cfg, bool verbose);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,27 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
#ifndef R_INTERFACES_DATABASE_H
|
||||
#define R_INTERFACES_DATABASE_H
|
||||
|
||||
#include "r_error.h"
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
|
||||
typedef struct database_t *database_handle;
|
||||
|
||||
database_handle database_open(const char *path);
|
||||
void database_close(database_handle db);
|
||||
|
||||
r_status_t database_init(database_handle db);
|
||||
r_status_t database_execute(database_handle db, const char *sql, char **error);
|
||||
|
||||
typedef void (*database_callback_t)(void *ctx, int col_count, char **values, char **names);
|
||||
|
||||
r_status_t database_query(database_handle db, const char *sql,
|
||||
database_callback_t callback, void *ctx);
|
||||
|
||||
r_status_t database_set(database_handle db, const char *key, const char *value);
|
||||
r_status_t database_get(database_handle db, const char *key, char **value);
|
||||
r_status_t database_delete(database_handle db, const char *key);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,40 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
#ifndef R_INTERFACES_HTTP_H
|
||||
#define R_INTERFACES_HTTP_H
|
||||
|
||||
#include "r_error.h"
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
|
||||
typedef struct http_client_t *http_client_handle;
|
||||
|
||||
typedef enum {
|
||||
HTTP_METHOD_GET,
|
||||
HTTP_METHOD_POST,
|
||||
HTTP_METHOD_PUT,
|
||||
HTTP_METHOD_DELETE
|
||||
} http_method_t;
|
||||
|
||||
http_client_handle http_create(const char *base_url);
|
||||
void http_destroy(http_client_handle client);
|
||||
|
||||
void http_set_bearer_token(http_client_handle client, const char *token);
|
||||
void http_set_timeout(http_client_handle client, long timeout_seconds);
|
||||
void http_set_connect_timeout(http_client_handle client, long timeout_seconds);
|
||||
|
||||
r_status_t http_request(
|
||||
http_client_handle client,
|
||||
http_method_t method,
|
||||
const char *path,
|
||||
const void *body,
|
||||
size_t body_size,
|
||||
char **response,
|
||||
int *response_code
|
||||
);
|
||||
|
||||
r_status_t http_get(http_client_handle client, const char *path, char **response);
|
||||
r_status_t http_post(http_client_handle client, const char *path, const char *body, char **response);
|
||||
r_status_t http_post_json(http_client_handle client, const char *path, const char *json, char **response);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,66 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
#include "logger.h"
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
|
||||
struct logger_t {
|
||||
log_level_t level;
|
||||
};
|
||||
|
||||
static const char *level_strings[] = {
|
||||
"TRACE", "DEBUG", "INFO", "WARN", "ERROR", "FATAL"
|
||||
};
|
||||
static const char *level_colors[] = {
|
||||
"\033[90m", "\033[36m", "\033[32m", "\033[33m", "\033[31m", "\033[35m"
|
||||
};
|
||||
static const char *color_reset = "\033[0m";
|
||||
|
||||
logger_handle logger_create(log_level_t level) {
|
||||
struct logger_t *logger = malloc(sizeof(struct logger_t));
|
||||
if (!logger) return NULL;
|
||||
|
||||
logger->level = level;
|
||||
return logger;
|
||||
}
|
||||
|
||||
void logger_destroy(logger_handle logger) {
|
||||
if (logger) free(logger);
|
||||
}
|
||||
|
||||
void logger_set_level(logger_handle logger, log_level_t level) {
|
||||
if (logger) logger->level = level;
|
||||
}
|
||||
|
||||
log_level_t logger_get_level(logger_handle logger) {
|
||||
return logger ? logger->level : LOG_LEVEL_INFO;
|
||||
}
|
||||
|
||||
void logger_log(logger_handle logger, log_level_t level,
|
||||
const char *file, int line, const char *fmt, ...) {
|
||||
if (!logger || !fmt) return;
|
||||
|
||||
if (level < logger->level) return;
|
||||
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
|
||||
time_t now = time(NULL);
|
||||
struct tm *tm_info = localtime(&now);
|
||||
char time_buf[32];
|
||||
strftime(time_buf, sizeof(time_buf), "%Y-%m-%d %H:%M:%S", tm_info);
|
||||
|
||||
const char *basename = strrchr(file, '/');
|
||||
basename = basename ? basename + 1 : file;
|
||||
|
||||
fprintf(stderr, "%s[%s%s%s %s:%d] ",
|
||||
level_colors[level], level_strings[level], color_reset,
|
||||
basename, line);
|
||||
vfprintf(stderr, fmt, args);
|
||||
fprintf(stderr, "%s\n", color_reset);
|
||||
|
||||
va_end(args);
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
#ifndef R_INTERFACES_LOGGER_H
|
||||
#define R_INTERFACES_LOGGER_H
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
typedef enum {
|
||||
LOG_LEVEL_TRACE,
|
||||
LOG_LEVEL_DEBUG,
|
||||
LOG_LEVEL_INFO,
|
||||
LOG_LEVEL_WARN,
|
||||
LOG_LEVEL_ERROR,
|
||||
LOG_LEVEL_FATAL
|
||||
} log_level_t;
|
||||
|
||||
typedef struct logger_t *logger_handle;
|
||||
|
||||
logger_handle logger_create(log_level_t level);
|
||||
void logger_destroy(logger_handle logger);
|
||||
|
||||
void logger_set_level(logger_handle logger, log_level_t level);
|
||||
log_level_t logger_get_level(logger_handle logger);
|
||||
|
||||
void logger_log(logger_handle logger, log_level_t level,
|
||||
const char *file, int line, const char *fmt, ...);
|
||||
|
||||
#define LOG_TRACE(logger, fmt, ...) \
|
||||
logger_log(logger, LOG_LEVEL_TRACE, __FILE__, __LINE__, fmt, ##__VA_ARGS__)
|
||||
#define LOG_DEBUG(logger, fmt, ...) \
|
||||
logger_log(logger, LOG_LEVEL_DEBUG, __FILE__, __LINE__, fmt, ##__VA_ARGS__)
|
||||
#define LOG_INFO(logger, fmt, ...) \
|
||||
logger_log(logger, LOG_LEVEL_INFO, __FILE__, __LINE__, fmt, ##__VA_ARGS__)
|
||||
#define LOG_WARN(logger, fmt, ...) \
|
||||
logger_log(logger, LOG_LEVEL_WARN, __FILE__, __LINE__, fmt, ##__VA_ARGS__)
|
||||
#define LOG_ERROR(logger, fmt, ...) \
|
||||
logger_log(logger, LOG_LEVEL_ERROR, __FILE__, __LINE__, fmt, ##__VA_ARGS__)
|
||||
#define LOG_FATAL(logger, fmt, ...) \
|
||||
logger_log(logger, LOG_LEVEL_FATAL, __FILE__, __LINE__, fmt, ##__VA_ARGS__)
|
||||
|
||||
#endif
|
||||
@@ -305,6 +305,13 @@ static void init(void) {
|
||||
"- You can call multiple tools in sequence to accomplish complex "
|
||||
"tasks\n\n"
|
||||
"- Take decissions based on tool output yourself autonomously."
|
||||
"## Multi-Agent Orchestration\n"
|
||||
"You can delegate tasks to specialized agents using the `spawn_agent` tool.\n"
|
||||
"Personas:\n"
|
||||
"- researcher: Best for web search and data gathering.\n"
|
||||
"- developer: Best for writing and testing code.\n"
|
||||
"- security: Best for security audits and vulnerability analysis.\n\n"
|
||||
"Use the `blackboard` table in the database to share state and pass detailed instructions or results between agents if they exceed tool parameter limits.\n\n"
|
||||
"## CRITICAL OUTPUT RULES\n"
|
||||
"- You MUST include the actual content/data from tool results in your "
|
||||
"response\n"
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
#include "tool.h"
|
||||
#include "agent.h"
|
||||
#include "messages.h"
|
||||
#include "r_config.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
|
||||
typedef struct {
|
||||
tool_t tool;
|
||||
} tool_agent_t;
|
||||
|
||||
static struct json_object *tool_spawn_agent_get_description(void) {
|
||||
struct json_object *obj = json_object_new_object();
|
||||
json_object_object_add(obj, "name", json_object_new_string("spawn_agent"));
|
||||
json_object_object_add(obj, "description", json_object_new_string("Spawn a specialized sub-agent to handle a specific task."));
|
||||
|
||||
struct json_object *params = json_object_new_object();
|
||||
json_object_object_add(params, "type", json_object_new_string("object"));
|
||||
|
||||
struct json_object *props = json_object_new_object();
|
||||
|
||||
struct json_object *persona = json_object_new_object();
|
||||
json_object_object_add(persona, "type", json_object_new_string("string"));
|
||||
json_object_object_add(persona, "description", json_object_new_string("The persona of the agent (researcher, developer, security)."));
|
||||
struct json_object *persona_enum = json_object_new_array();
|
||||
json_object_array_add(persona_enum, json_object_new_string("researcher"));
|
||||
json_object_array_add(persona_enum, json_object_new_string("developer"));
|
||||
json_object_array_add(persona_enum, json_object_new_string("security"));
|
||||
json_object_object_add(persona, "enum", persona_enum);
|
||||
json_object_object_add(props, "persona", persona);
|
||||
|
||||
struct json_object *goal = json_object_new_object();
|
||||
json_object_object_add(goal, "type", json_object_new_string("string"));
|
||||
json_object_object_add(goal, "description", json_object_new_string("The specific task or goal for the sub-agent."));
|
||||
json_object_object_add(props, "goal", goal);
|
||||
|
||||
json_object_object_add(params, "properties", props);
|
||||
|
||||
struct json_object *required = json_object_new_array();
|
||||
json_object_array_add(required, json_object_new_string("persona"));
|
||||
json_object_array_add(required, json_object_new_string("goal"));
|
||||
json_object_object_add(params, "required", required);
|
||||
|
||||
json_object_object_add(obj, "parameters", params);
|
||||
|
||||
struct json_object *full_obj = json_object_new_object();
|
||||
json_object_object_add(full_obj, "type", json_object_new_string("function"));
|
||||
json_object_object_add(full_obj, "function", obj);
|
||||
|
||||
return full_obj;
|
||||
}
|
||||
|
||||
static char *tool_spawn_agent_execute(tool_t *self, struct json_object *args) {
|
||||
(void)self;
|
||||
struct json_object *persona_obj, *goal_obj;
|
||||
|
||||
if (!json_object_object_get_ex(args, "persona", &persona_obj) ||
|
||||
!json_object_object_get_ex(args, "goal", &goal_obj)) {
|
||||
return strdup("Error: Missing persona or goal");
|
||||
}
|
||||
|
||||
const char *persona_str = json_object_get_string(persona_obj);
|
||||
const char *goal_str = json_object_get_string(goal_obj);
|
||||
|
||||
tool_registry_type_t type = TOOL_TYPE_ALL;
|
||||
const char *system_prompt = NULL;
|
||||
|
||||
if (strcmp(persona_str, "researcher") == 0) {
|
||||
type = TOOL_TYPE_RESEARCHER;
|
||||
system_prompt = "You are a specialized Research Agent. Your goal is to find, extract, and summarize information. "
|
||||
"Do not attempt to write or execute code unless it's for data analysis. "
|
||||
"Focus on using web search, http fetch, and reading files.";
|
||||
} else if (strcmp(persona_str, "developer") == 0) {
|
||||
type = TOOL_TYPE_DEVELOPER;
|
||||
system_prompt = "You are a specialized Developer Agent. Your goal is to write, test, and debug code. "
|
||||
"Use the terminal, file editing tools, and python execution to fulfill your task. "
|
||||
"Always verify your changes by running tests or the code itself.";
|
||||
} else if (strcmp(persona_str, "security") == 0) {
|
||||
type = TOOL_TYPE_SECURITY;
|
||||
system_prompt = "You are a specialized Security Auditor Agent. Your goal is to find vulnerabilities and perform security analysis. "
|
||||
"Be pedantic and thorough. Use fuzzing, port scanning, and code analysis tools. "
|
||||
"Report any findings clearly with potential impact.";
|
||||
} else {
|
||||
return strdup("Error: Invalid persona");
|
||||
}
|
||||
|
||||
char session_id[256];
|
||||
snprintf(session_id, sizeof(session_id), "subagent-%s-%u", persona_str, (unsigned int)time(NULL));
|
||||
|
||||
messages_handle msgs = messages_create(session_id);
|
||||
messages_add(msgs, "system", system_prompt);
|
||||
|
||||
agent_handle agent = agent_create(goal_str, msgs);
|
||||
if (!agent) {
|
||||
messages_destroy(msgs);
|
||||
return strdup("Error: Failed to create sub-agent");
|
||||
}
|
||||
|
||||
tool_registry_t *specialized_tools = tool_registry_get_specialized(type);
|
||||
agent_set_tool_registry(agent, specialized_tools);
|
||||
agent_set_max_iterations(agent, 50); // Sub-agents have lower limit
|
||||
|
||||
char *result = agent_run(agent, goal_str);
|
||||
|
||||
agent_destroy(agent);
|
||||
// specialized_tools should be destroyed?
|
||||
// In tool_registry_get_specialized we create a new one.
|
||||
tool_registry_destroy(specialized_tools);
|
||||
|
||||
if (!result) {
|
||||
return strdup("Sub-agent failed to provide a result.");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static void tool_spawn_agent_print_action(const char *name, struct json_object *args) {
|
||||
struct json_object *persona_obj, *goal_obj;
|
||||
const char *persona = "unknown";
|
||||
const char *goal = "unknown";
|
||||
|
||||
if (json_object_object_get_ex(args, "persona", &persona_obj)) persona = json_object_get_string(persona_obj);
|
||||
if (json_object_object_get_ex(args, "goal", &goal_obj)) goal = json_object_get_string(goal_obj);
|
||||
|
||||
printf("\033[1;34m[Agent] Spawning %s agent for: %s\033[0m\n", persona, goal);
|
||||
}
|
||||
|
||||
static const tool_vtable_t tool_spawn_agent_vtable = {
|
||||
.get_description = tool_spawn_agent_get_description,
|
||||
.execute = tool_spawn_agent_execute,
|
||||
.print_action = tool_spawn_agent_print_action
|
||||
};
|
||||
|
||||
tool_t *tool_spawn_agent_create(void) {
|
||||
tool_agent_t *tool = calloc(1, sizeof(tool_agent_t));
|
||||
if (!tool) return NULL;
|
||||
|
||||
tool->tool.vtable = &tool_spawn_agent_vtable;
|
||||
tool->tool.name = "spawn_agent";
|
||||
|
||||
return &tool->tool;
|
||||
}
|
||||
+153
@@ -0,0 +1,153 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
#include "path.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
char *path_expand_home(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;
|
||||
}
|
||||
|
||||
char *path_normalize(const char *path) {
|
||||
if (!path) return NULL;
|
||||
|
||||
char *normalized = strdup(path);
|
||||
if (!normalized) return NULL;
|
||||
|
||||
size_t write_idx = 0;
|
||||
for (size_t i = 0; normalized[i]; i++) {
|
||||
if (normalized[i] == '/' && write_idx > 0 && normalized[write_idx - 1] == '/') {
|
||||
continue;
|
||||
}
|
||||
normalized[write_idx++] = normalized[i];
|
||||
}
|
||||
normalized[write_idx] = '\0';
|
||||
|
||||
return normalized;
|
||||
}
|
||||
|
||||
char *path_join(const char *base, const char *relative) {
|
||||
if (!base) return relative ? strdup(relative) : NULL;
|
||||
if (!relative) return strdup(base);
|
||||
|
||||
size_t base_len = strlen(base);
|
||||
size_t rel_len = strlen(relative);
|
||||
bool needs_sep = base_len > 0 && base[base_len - 1] != '/';
|
||||
|
||||
size_t total_len = base_len + rel_len + (needs_sep ? 1 : 0);
|
||||
char *joined = malloc(total_len + 1);
|
||||
if (!joined) return NULL;
|
||||
|
||||
strcpy(joined, base);
|
||||
if (needs_sep) strcat(joined, "/");
|
||||
strcat(joined, relative);
|
||||
|
||||
return joined;
|
||||
}
|
||||
|
||||
char *path_dirname(const char *path) {
|
||||
if (!path || !*path) return strdup(".");
|
||||
|
||||
char *copy = strdup(path);
|
||||
if (!copy) return NULL;
|
||||
|
||||
char *last_sep = strrchr(copy, '/');
|
||||
if (last_sep) {
|
||||
if (last_sep == copy) {
|
||||
last_sep[1] = '\0';
|
||||
} else {
|
||||
*last_sep = '\0';
|
||||
}
|
||||
} else {
|
||||
strcpy(copy, ".");
|
||||
}
|
||||
|
||||
return copy;
|
||||
}
|
||||
|
||||
char *path_basename(const char *path) {
|
||||
if (!path || !*path) return strdup(".");
|
||||
|
||||
const char *last_sep = strrchr(path, '/');
|
||||
if (last_sep) {
|
||||
return strdup(last_sep + 1);
|
||||
}
|
||||
return strdup(path);
|
||||
}
|
||||
|
||||
bool path_exists(const char *path) {
|
||||
if (!path) return false;
|
||||
struct stat st;
|
||||
return stat(path, &st) == 0;
|
||||
}
|
||||
|
||||
bool path_is_file(const char *path) {
|
||||
if (!path) return false;
|
||||
struct stat st;
|
||||
return stat(path, &st) == 0 && S_ISREG(st.st_mode);
|
||||
}
|
||||
|
||||
bool path_is_directory(const char *path) {
|
||||
if (!path) return false;
|
||||
struct stat st;
|
||||
return stat(path, &st) == 0 && S_ISDIR(st.st_mode);
|
||||
}
|
||||
|
||||
bool path_ensure_parent_exists(const char *path) {
|
||||
if (!path) return false;
|
||||
|
||||
char *parent = path_dirname(path);
|
||||
if (!parent) return false;
|
||||
|
||||
bool exists = path_is_directory(parent);
|
||||
if (exists) {
|
||||
free(parent);
|
||||
return true;
|
||||
}
|
||||
|
||||
char temp[4096];
|
||||
size_t len = strlen(parent);
|
||||
if (len >= sizeof(temp)) {
|
||||
free(parent);
|
||||
return false;
|
||||
}
|
||||
|
||||
memcpy(temp, parent, len + 1);
|
||||
if (temp[len - 1] == '/') temp[len - 1] = '\0';
|
||||
|
||||
for (char *p = temp + 1; *p; p++) {
|
||||
if (*p == '/') {
|
||||
*p = '\0';
|
||||
if (mkdir(temp, 0755) != 0 && errno != EEXIST) {
|
||||
free(parent);
|
||||
return false;
|
||||
}
|
||||
*p = '/';
|
||||
}
|
||||
}
|
||||
|
||||
if (mkdir(temp, 0755) != 0 && errno != EEXIST) {
|
||||
free(parent);
|
||||
return false;
|
||||
}
|
||||
|
||||
free(parent);
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
#ifndef R_UTIL_PATH_H
|
||||
#define R_UTIL_PATH_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
|
||||
char *path_expand_home(const char *path);
|
||||
char *path_normalize(const char *path);
|
||||
char *path_join(const char *base, const char *relative);
|
||||
char *path_dirname(const char *path);
|
||||
char *path_basename(const char *path);
|
||||
bool path_exists(const char *path);
|
||||
bool path_is_file(const char *path);
|
||||
bool path_is_directory(const char *path);
|
||||
bool path_ensure_parent_exists(const char *path);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,50 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
#define _XOPEN_SOURCE
|
||||
#include "time.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
|
||||
char *time_format_iso8601(time_t t) {
|
||||
struct tm *tm_info = localtime(&t);
|
||||
if (!tm_info) return NULL;
|
||||
|
||||
char *buf = malloc(32);
|
||||
if (!buf) return NULL;
|
||||
|
||||
strftime(buf, 32, "%Y-%m-%dT%H:%M:%S%z", tm_info);
|
||||
return buf;
|
||||
}
|
||||
|
||||
char *time_format_local(time_t t, const char *format) {
|
||||
struct tm *tm_info = localtime(&t);
|
||||
if (!tm_info) return NULL;
|
||||
|
||||
const char *fmt = format ? format : "%Y-%m-%d %H:%M:%S";
|
||||
|
||||
char *buf = malloc(64);
|
||||
if (!buf) return NULL;
|
||||
|
||||
strftime(buf, 64, fmt, tm_info);
|
||||
return buf;
|
||||
}
|
||||
|
||||
time_t time_parse_iso8601(const char *str) {
|
||||
if (!str) return 0;
|
||||
|
||||
struct tm tm = {0};
|
||||
char *result = strptime(str, "%Y-%m-%dT%H:%M:%S", &tm);
|
||||
if (!result) {
|
||||
result = strptime(str, "%Y-%m-%d %H:%M:%S", &tm);
|
||||
}
|
||||
|
||||
if (!result) return 0;
|
||||
return mktime(&tm);
|
||||
}
|
||||
|
||||
double time_elapsed_seconds(time_t start) {
|
||||
time_t now = time(NULL);
|
||||
return difftime(now, start);
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
#ifndef R_UTIL_TIME_H
|
||||
#define R_UTIL_TIME_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <time.h>
|
||||
|
||||
char *time_format_iso8601(time_t t);
|
||||
char *time_format_local(time_t t, const char *format);
|
||||
time_t time_parse_iso8601(const char *str);
|
||||
double time_elapsed_seconds(time_t start);
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user