Update.
This commit is contained in:
parent
c641d0835a
commit
66f17da391
381
agent.h
381
agent.h
@ -1,381 +0,0 @@
|
|||||||
// retoor <retoor@molodetz.nl>
|
|
||||||
|
|
||||||
#ifndef R_AGENT_H
|
|
||||||
#define R_AGENT_H
|
|
||||||
|
|
||||||
#include "chat.h"
|
|
||||||
#include "http_curl.h"
|
|
||||||
#include "messages.h"
|
|
||||||
#include "r.h"
|
|
||||||
#include "tools.h"
|
|
||||||
#include <stdbool.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <time.h>
|
|
||||||
|
|
||||||
#define AGENT_MAX_ITERATIONS 300
|
|
||||||
#define AGENT_MAX_TOOL_RETRIES 3
|
|
||||||
|
|
||||||
typedef enum {
|
|
||||||
AGENT_STATUS_RUNNING,
|
|
||||||
AGENT_STATUS_COMPLETED,
|
|
||||||
AGENT_STATUS_MAX_ITERATIONS,
|
|
||||||
AGENT_STATUS_ERROR,
|
|
||||||
AGENT_STATUS_TOOL_ERROR
|
|
||||||
} agent_status_t;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
char *goal;
|
|
||||||
int iteration_count;
|
|
||||||
int max_iterations;
|
|
||||||
int tool_retry_count;
|
|
||||||
int max_tool_retries;
|
|
||||||
agent_status_t status;
|
|
||||||
time_t start_time;
|
|
||||||
char *last_error;
|
|
||||||
bool verbose;
|
|
||||||
} agent_state_t;
|
|
||||||
|
|
||||||
static agent_state_t *current_agent = NULL;
|
|
||||||
|
|
||||||
agent_state_t *agent_create(const char *goal) {
|
|
||||||
agent_state_t *agent = (agent_state_t *)malloc(sizeof(agent_state_t));
|
|
||||||
if (agent == NULL) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
agent->goal = goal ? strdup(goal) : NULL;
|
|
||||||
agent->iteration_count = 0;
|
|
||||||
agent->max_iterations = AGENT_MAX_ITERATIONS;
|
|
||||||
agent->tool_retry_count = 0;
|
|
||||||
agent->max_tool_retries = AGENT_MAX_TOOL_RETRIES;
|
|
||||||
agent->status = AGENT_STATUS_RUNNING;
|
|
||||||
agent->start_time = time(NULL);
|
|
||||||
agent->last_error = NULL;
|
|
||||||
agent->verbose = is_verbose;
|
|
||||||
|
|
||||||
return agent;
|
|
||||||
}
|
|
||||||
|
|
||||||
void agent_destroy(agent_state_t *agent) {
|
|
||||||
if (agent == NULL) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (agent->goal) {
|
|
||||||
free(agent->goal);
|
|
||||||
}
|
|
||||||
if (agent->last_error) {
|
|
||||||
free(agent->last_error);
|
|
||||||
}
|
|
||||||
free(agent);
|
|
||||||
}
|
|
||||||
|
|
||||||
void agent_set_error(agent_state_t *agent, const char *error) {
|
|
||||||
if (agent == NULL) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (agent->last_error) {
|
|
||||||
free(agent->last_error);
|
|
||||||
}
|
|
||||||
agent->last_error = error ? strdup(error) : NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
static struct json_object *agent_process_response(const char *api_url,
|
|
||||||
const char *json_data) {
|
|
||||||
char *response = curl_post(api_url, json_data);
|
|
||||||
if (!response) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct json_object *parsed_json = json_tokener_parse(response);
|
|
||||||
free(response);
|
|
||||||
|
|
||||||
if (!parsed_json) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct json_object *error_object;
|
|
||||||
if (json_object_object_get_ex(parsed_json, "error", &error_object)) {
|
|
||||||
const char *err_str = json_object_to_json_string(error_object);
|
|
||||||
fprintf(stderr, "API Error: %s\n", err_str);
|
|
||||||
json_object_put(parsed_json);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct json_object *choices_array;
|
|
||||||
if (!json_object_object_get_ex(parsed_json, "choices", &choices_array)) {
|
|
||||||
json_object_put(parsed_json);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct json_object *first_choice = json_object_array_get_idx(choices_array, 0);
|
|
||||||
if (!first_choice) {
|
|
||||||
json_object_put(parsed_json);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
return first_choice;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool agent_has_tool_calls(struct json_object *choice) {
|
|
||||||
struct json_object *message_obj;
|
|
||||||
if (!json_object_object_get_ex(choice, "message", &message_obj)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct json_object *tool_calls;
|
|
||||||
if (!json_object_object_get_ex(message_obj, "tool_calls", &tool_calls)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return json_object_array_length(tool_calls) > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static const char *agent_get_finish_reason(struct json_object *choice) {
|
|
||||||
struct json_object *finish_reason_obj;
|
|
||||||
if (json_object_object_get_ex(choice, "finish_reason", &finish_reason_obj)) {
|
|
||||||
return json_object_get_string(finish_reason_obj);
|
|
||||||
}
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
static char *agent_get_content(struct json_object *choice) {
|
|
||||||
struct json_object *message_obj;
|
|
||||||
if (!json_object_object_get_ex(choice, "message", &message_obj)) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct json_object *content_obj;
|
|
||||||
if (!json_object_object_get_ex(message_obj, "content", &content_obj)) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
const char *content = json_object_get_string(content_obj);
|
|
||||||
return content ? strdup(content) : NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
static struct json_object *agent_get_tool_calls(struct json_object *choice) {
|
|
||||||
struct json_object *message_obj;
|
|
||||||
if (!json_object_object_get_ex(choice, "message", &message_obj)) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct json_object *tool_calls;
|
|
||||||
if (!json_object_object_get_ex(message_obj, "tool_calls", &tool_calls)) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
return tool_calls;
|
|
||||||
}
|
|
||||||
|
|
||||||
static struct json_object *agent_get_message(struct json_object *choice) {
|
|
||||||
struct json_object *message_obj;
|
|
||||||
if (json_object_object_get_ex(choice, "message", &message_obj)) {
|
|
||||||
return message_obj;
|
|
||||||
}
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool agent_response_indicates_incomplete(const char *content) {
|
|
||||||
if (content == NULL) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const char *incomplete_phrases[] = {
|
|
||||||
"I'll ", "I will ", "Let me ", "I'm going to ",
|
|
||||||
"Next, I", "Now I'll", "Now I will", "I'll now",
|
|
||||||
"I need to", "I should", "I can ", "Going to ",
|
|
||||||
"Will now", "Proceeding", "Starting to", "About to ",
|
|
||||||
"First, I", "Then I", "After that", "Following that",
|
|
||||||
NULL
|
|
||||||
};
|
|
||||||
|
|
||||||
for (int i = 0; incomplete_phrases[i] != NULL; i++) {
|
|
||||||
if (strstr(content, incomplete_phrases[i]) != NULL) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const char *incomplete_endings[] = {
|
|
||||||
"...", ":", "files:", "content:", "implementation:",
|
|
||||||
NULL
|
|
||||||
};
|
|
||||||
|
|
||||||
size_t len = strlen(content);
|
|
||||||
if (len > 3) {
|
|
||||||
for (int i = 0; incomplete_endings[i] != NULL; i++) {
|
|
||||||
size_t end_len = strlen(incomplete_endings[i]);
|
|
||||||
if (len >= end_len && strcmp(content + len - end_len, incomplete_endings[i]) == 0) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
char *agent_run(agent_state_t *agent, const char *user_message) {
|
|
||||||
if (agent == NULL) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
current_agent = agent;
|
|
||||||
agent->status = AGENT_STATUS_RUNNING;
|
|
||||||
agent->iteration_count = 0;
|
|
||||||
agent->tool_retry_count = 0;
|
|
||||||
|
|
||||||
if (user_message == NULL || *user_message == '\0') {
|
|
||||||
agent->status = AGENT_STATUS_ERROR;
|
|
||||||
agent_set_error(agent, "Empty user message");
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
char *json_data = chat_json("user", user_message);
|
|
||||||
if (json_data == NULL) {
|
|
||||||
agent->status = AGENT_STATUS_ERROR;
|
|
||||||
agent_set_error(agent, "Failed to create chat JSON");
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
char *final_response = NULL;
|
|
||||||
|
|
||||||
while (agent->status == AGENT_STATUS_RUNNING) {
|
|
||||||
agent->iteration_count++;
|
|
||||||
|
|
||||||
if (agent->iteration_count > agent->max_iterations) {
|
|
||||||
agent->status = AGENT_STATUS_MAX_ITERATIONS;
|
|
||||||
agent_set_error(agent, "Maximum iterations reached");
|
|
||||||
if (agent->verbose) {
|
|
||||||
fprintf(stderr, "[Agent] Max iterations (%d) reached\n",
|
|
||||||
agent->max_iterations);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (agent->verbose) {
|
|
||||||
fprintf(stderr, "[Agent] Iteration %d/%d\n", agent->iteration_count,
|
|
||||||
agent->max_iterations);
|
|
||||||
}
|
|
||||||
|
|
||||||
struct json_object *choice =
|
|
||||||
agent_process_response(get_completions_api_url(), json_data);
|
|
||||||
|
|
||||||
if (choice == NULL) {
|
|
||||||
agent->tool_retry_count++;
|
|
||||||
if (agent->tool_retry_count >= agent->max_tool_retries) {
|
|
||||||
agent->status = AGENT_STATUS_ERROR;
|
|
||||||
agent_set_error(agent, "API request failed after retries");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (agent->verbose) {
|
|
||||||
fprintf(stderr, "[Agent] API error, retry %d/%d\n",
|
|
||||||
agent->tool_retry_count, agent->max_tool_retries);
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
agent->tool_retry_count = 0;
|
|
||||||
|
|
||||||
struct json_object *message_obj = agent_get_message(choice);
|
|
||||||
if (message_obj) {
|
|
||||||
message_add_object(json_object_get(message_obj));
|
|
||||||
}
|
|
||||||
|
|
||||||
const char *finish_reason = agent_get_finish_reason(choice);
|
|
||||||
bool has_tools = agent_has_tool_calls(choice);
|
|
||||||
|
|
||||||
if (agent->verbose) {
|
|
||||||
fprintf(stderr, "[Agent] finish_reason=%s, has_tool_calls=%s\n",
|
|
||||||
finish_reason ? finish_reason : "null",
|
|
||||||
has_tools ? "true" : "false");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (has_tools) {
|
|
||||||
struct json_object *tool_calls = agent_get_tool_calls(choice);
|
|
||||||
|
|
||||||
if (agent->verbose) {
|
|
||||||
int num_tools = json_object_array_length(tool_calls);
|
|
||||||
fprintf(stderr, "[Agent] Executing %d tool(s)\n", num_tools);
|
|
||||||
}
|
|
||||||
|
|
||||||
struct json_object *tool_results = tools_execute(tool_calls);
|
|
||||||
|
|
||||||
int results_count = json_object_array_length(tool_results);
|
|
||||||
for (int i = 0; i < results_count; i++) {
|
|
||||||
struct json_object *tool_result =
|
|
||||||
json_object_array_get_idx(tool_results, i);
|
|
||||||
message_add_tool_call(json_object_get(tool_result));
|
|
||||||
}
|
|
||||||
|
|
||||||
json_data = chat_json(NULL, NULL);
|
|
||||||
if (json_data == NULL) {
|
|
||||||
agent->status = AGENT_STATUS_ERROR;
|
|
||||||
agent_set_error(agent, "Failed to create follow-up JSON");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
char *content = agent_get_content(choice);
|
|
||||||
|
|
||||||
if (content && agent_response_indicates_incomplete(content)) {
|
|
||||||
if (agent->verbose) {
|
|
||||||
fprintf(stderr, "[Agent] Response indicates incomplete work, auto-continuing\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
free(content);
|
|
||||||
json_data = chat_json("user", "Continue. Execute the necessary actions to complete the task.");
|
|
||||||
if (json_data == NULL) {
|
|
||||||
agent->status = AGENT_STATUS_ERROR;
|
|
||||||
agent_set_error(agent, "Failed to create continue JSON");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
final_response = content;
|
|
||||||
agent->status = AGENT_STATUS_COMPLETED;
|
|
||||||
|
|
||||||
if (agent->verbose) {
|
|
||||||
fprintf(stderr, "[Agent] Completed in %d iteration(s)\n",
|
|
||||||
agent->iteration_count);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
current_agent = NULL;
|
|
||||||
return final_response;
|
|
||||||
}
|
|
||||||
|
|
||||||
char *agent_chat(const char *user_message) {
|
|
||||||
agent_state_t *agent = agent_create(user_message);
|
|
||||||
if (agent == NULL) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
char *response = agent_run(agent, user_message);
|
|
||||||
|
|
||||||
if (agent->verbose && agent->status != AGENT_STATUS_COMPLETED && agent->last_error) {
|
|
||||||
fprintf(stderr, "[Agent] Error: %s\n", agent->last_error);
|
|
||||||
}
|
|
||||||
|
|
||||||
agent_destroy(agent);
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
|
|
||||||
char *agent_chat_with_limit(const char *user_message, int max_iterations) {
|
|
||||||
agent_state_t *agent = agent_create(user_message);
|
|
||||||
if (agent == NULL) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
agent->max_iterations = max_iterations;
|
|
||||||
char *response = agent_run(agent, user_message);
|
|
||||||
|
|
||||||
if (agent->verbose && agent->status != AGENT_STATUS_COMPLETED && agent->last_error) {
|
|
||||||
fprintf(stderr, "[Agent] Error: %s\n", agent->last_error);
|
|
||||||
}
|
|
||||||
|
|
||||||
agent_destroy(agent);
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
75
auth.h
75
auth.h
@ -1,75 +0,0 @@
|
|||||||
// Written by retoor@molodetz.nl
|
|
||||||
|
|
||||||
// This source code retrieves an API key from environment variables or defaults
|
|
||||||
// to a hardcoded key if none are found.
|
|
||||||
|
|
||||||
// Uses the C standard library functions from stdlib.h for environment
|
|
||||||
// management and stdio.h for error handling.
|
|
||||||
|
|
||||||
// MIT License
|
|
||||||
|
|
||||||
#ifndef R_AUTH_H
|
|
||||||
#define R_AUTH_H
|
|
||||||
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
|
|
||||||
enum AUTH_TYPE { AUTH_TYPE_NONE, AUTH_TYPE_API_KEY, AUTH_TYPE_FREE };
|
|
||||||
|
|
||||||
int auth_type = AUTH_TYPE_NONE;
|
|
||||||
|
|
||||||
void auth_free() { auth_type = AUTH_TYPE_FREE; }
|
|
||||||
|
|
||||||
void auth_init() {
|
|
||||||
|
|
||||||
char *api_key = NULL;
|
|
||||||
if (auth_type != AUTH_TYPE_FREE) {
|
|
||||||
|
|
||||||
api_key = getenv("R_KEY");
|
|
||||||
if (api_key) {
|
|
||||||
auth_type = AUTH_TYPE_API_KEY;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
api_key = getenv("OPENROUTER_API_KEY");
|
|
||||||
if (api_key) {
|
|
||||||
auth_type = AUTH_TYPE_API_KEY;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
api_key = getenv("OPENAI_API_KEY");
|
|
||||||
if (api_key) {
|
|
||||||
auth_type = AUTH_TYPE_API_KEY;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
auth_type = AUTH_TYPE_FREE;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const char *resolve_api_key() {
|
|
||||||
static char *api_key = NULL;
|
|
||||||
if (auth_type != AUTH_TYPE_FREE) {
|
|
||||||
|
|
||||||
api_key = getenv("R_KEY");
|
|
||||||
if (api_key) {
|
|
||||||
auth_type = AUTH_TYPE_API_KEY;
|
|
||||||
return api_key;
|
|
||||||
}
|
|
||||||
api_key = getenv("OPENROUTER_API_KEY");
|
|
||||||
if (api_key) {
|
|
||||||
auth_type = AUTH_TYPE_API_KEY;
|
|
||||||
return api_key;
|
|
||||||
}
|
|
||||||
api_key = getenv("OPENAI_API_KEY");
|
|
||||||
if (api_key) {
|
|
||||||
auth_type = AUTH_TYPE_API_KEY;
|
|
||||||
return api_key;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
auth_type = AUTH_TYPE_FREE;
|
|
||||||
api_key = "sk-proj-d798HLfWYBeB9HT_o7isaY0s88631IaYhhOR5IVAd4D_fF-"
|
|
||||||
"SQ5z46BCr8iDi1ang1rUmlagw55T3BlbkFJ6IOsqhAxNN9Zt6ERDBnv2p2HCc2fDgc"
|
|
||||||
"5DsNhPxdOzYb009J6CNd4wILPsFGEoUdWo4QrZ1eOkA";
|
|
||||||
return api_key;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
17
browse.c
17
browse.c
@ -1,17 +0,0 @@
|
|||||||
#include "browse.h"
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
|
|
||||||
int main() {
|
|
||||||
char *query = "example";
|
|
||||||
char *result = get_news(query);
|
|
||||||
|
|
||||||
if (result) {
|
|
||||||
printf("Result: %s\n", result);
|
|
||||||
free(result); // Free the allocated memory for the result
|
|
||||||
} else {
|
|
||||||
fprintf(stderr, "Error: Failed to fetch news.\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
23
browse.h
23
browse.h
@ -1,23 +0,0 @@
|
|||||||
#include "http_curl.h"
|
|
||||||
#include <curl/curl.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
char *url_encode(char *s) { return curl_easy_escape(NULL, s, 0); }
|
|
||||||
|
|
||||||
char *web_search(char *q) {
|
|
||||||
char url[4096];
|
|
||||||
char *q_encoded = url_encode(q);
|
|
||||||
if (!q_encoded) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
snprintf(url, sizeof(url), "https://static.molodetz.nl/search.cgi?query=%s", q_encoded);
|
|
||||||
free(q_encoded);
|
|
||||||
char *ret = curl_get(url);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
char *web_search_news(char *q) {
|
|
||||||
return web_search(q);
|
|
||||||
}
|
|
||||||
71
chat.h
71
chat.h
@ -1,71 +0,0 @@
|
|||||||
// Written by retoor@molodetz.nl
|
|
||||||
|
|
||||||
// This code defines functionality for creating and managing JSON-based chat
|
|
||||||
// prompts using a specific AI model configuration, providing easy integration
|
|
||||||
// with message handling and HTTP communication for dynamic applications.
|
|
||||||
|
|
||||||
// Non-standard imports: json-c library for handling JSON objects.
|
|
||||||
|
|
||||||
// MIT License
|
|
||||||
//
|
|
||||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
// of this software and associated documentation files (the "Software"), to deal
|
|
||||||
// in the Software without restriction, including without limitation the rights
|
|
||||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
// copies of the Software, and to permit persons to whom the Software is
|
|
||||||
// furnished to do so, subject to the following conditions:
|
|
||||||
//
|
|
||||||
// The above copyright notice and this permission notice shall be included in
|
|
||||||
// all copies or substantial portions of the Software.
|
|
||||||
//
|
|
||||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
// SOFTWARE.
|
|
||||||
|
|
||||||
#ifndef R_PROMPT_H
|
|
||||||
#define R_PROMPT_H
|
|
||||||
|
|
||||||
#include "auth.h"
|
|
||||||
#include "messages.h"
|
|
||||||
#include "r.h"
|
|
||||||
#include "tools.h"
|
|
||||||
#include <json-c/json.h>
|
|
||||||
static json_object *_prompt = NULL;
|
|
||||||
|
|
||||||
void chat_free() {
|
|
||||||
if (_prompt == NULL)
|
|
||||||
return;
|
|
||||||
json_object_put(_prompt);
|
|
||||||
_prompt = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
char *chat_json(const char *role, const char *message) {
|
|
||||||
chat_free();
|
|
||||||
json_object *root_object = json_object_new_object();
|
|
||||||
if (root_object == NULL) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
json_object_object_add(root_object, "model",
|
|
||||||
json_object_new_string(get_prompt_model()));
|
|
||||||
|
|
||||||
if (role != NULL && message != NULL) {
|
|
||||||
message_add(role, message);
|
|
||||||
if (use_tools()) {
|
|
||||||
json_object_object_add(root_object, "tools", tools_descriptions());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
json_object_object_add(root_object, "messages", json_object_get(message_list()));
|
|
||||||
json_object_object_add(root_object, "temperature",
|
|
||||||
json_object_new_double(PROMPT_TEMPERATURE));
|
|
||||||
|
|
||||||
_prompt = root_object;
|
|
||||||
return (char *)json_object_to_json_string_ext(root_object,
|
|
||||||
JSON_C_TO_STRING_PRETTY);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
64
db_utils.c
64
db_utils.c
@ -1,64 +0,0 @@
|
|||||||
#include "db_utils.h"
|
|
||||||
#include <sqlite3.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
|
|
||||||
void db_initialize() {
|
|
||||||
sqlite3 *db;
|
|
||||||
char *err_msg = 0;
|
|
||||||
int rc = sqlite3_open("database.db", &db);
|
|
||||||
|
|
||||||
if (rc != SQLITE_OK) {
|
|
||||||
fprintf(stderr, "Error: Cannot open database: %s\n", sqlite3_errmsg(db));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const char *sql =
|
|
||||||
"CREATE TABLE IF NOT EXISTS kv_store (key TEXT PRIMARY KEY, value TEXT);";
|
|
||||||
rc = sqlite3_exec(db, sql, 0, 0, &err_msg);
|
|
||||||
|
|
||||||
if (rc != SQLITE_OK) {
|
|
||||||
fprintf(stderr, "SQL error: %s\n", err_msg);
|
|
||||||
sqlite3_free(err_msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
sqlite3_close(db);
|
|
||||||
}
|
|
||||||
|
|
||||||
void test_db_set() {
|
|
||||||
json_object *result = db_set("test_key", "test_value");
|
|
||||||
if (result) {
|
|
||||||
printf("db_set: %s\n", json_object_get_string(result));
|
|
||||||
json_object_put(result);
|
|
||||||
} else {
|
|
||||||
printf("db_set failed\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void test_db_get() {
|
|
||||||
json_object *result = db_get("test_key");
|
|
||||||
if (result) {
|
|
||||||
printf("db_get: %s\n", json_object_to_json_string(result));
|
|
||||||
json_object_put(result);
|
|
||||||
} else {
|
|
||||||
printf("db_get failed\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void test_db_query() {
|
|
||||||
json_object *result = db_query("SELECT * FROM kv_store");
|
|
||||||
if (result) {
|
|
||||||
printf("db_query: %s\n", json_object_to_json_string(result));
|
|
||||||
json_object_put(result);
|
|
||||||
} else {
|
|
||||||
printf("db_query failed\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int main() {
|
|
||||||
db_initialize();
|
|
||||||
test_db_set();
|
|
||||||
test_db_get();
|
|
||||||
test_db_query();
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
236
db_utils.h
236
db_utils.h
@ -1,236 +0,0 @@
|
|||||||
#ifndef DB_UTILS_H
|
|
||||||
#define DB_UTILS_H
|
|
||||||
|
|
||||||
#include "r.h"
|
|
||||||
#include "utils.h"
|
|
||||||
#include <json-c/json.h>
|
|
||||||
#include <sqlite3.h>
|
|
||||||
|
|
||||||
json_object *db_execute(const char *query);
|
|
||||||
|
|
||||||
char *db_file_expanded() {
|
|
||||||
char *expanded = expand_home_directory(DB_FILE);
|
|
||||||
static char result[4096];
|
|
||||||
result[0] = 0;
|
|
||||||
strcpy(result, expanded);
|
|
||||||
free(expanded);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
void db_initialize();
|
|
||||||
json_object *db_set(const char *key, const char *value);
|
|
||||||
json_object *db_get(const char *key);
|
|
||||||
json_object *db_query(const char *query);
|
|
||||||
|
|
||||||
void db_initialize() {
|
|
||||||
sqlite3 *db;
|
|
||||||
int rc = sqlite3_open(db_file_expanded(), &db);
|
|
||||||
|
|
||||||
if (rc != SQLITE_OK) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
db_execute("CREATE TABLE IF NOT EXISTS kv_store (key TEXT PRIMARY KEY, value "
|
|
||||||
"TEXT);");
|
|
||||||
db_execute("CREATE TABLE IF NOT EXISTS file_version_history ( id INTEGER "
|
|
||||||
"PRIMARY KEY AUTOINCREMENT,"
|
|
||||||
"path TEXT NOT NULL,"
|
|
||||||
"content TEXT,"
|
|
||||||
"date DATETIME DEFAULT CURRENT_TIMESTAMP"
|
|
||||||
");");
|
|
||||||
|
|
||||||
sqlite3_close(db);
|
|
||||||
}
|
|
||||||
|
|
||||||
json_object *db_set(const char *key, const char *value) {
|
|
||||||
sqlite3 *db;
|
|
||||||
char *err_msg = 0;
|
|
||||||
int rc = sqlite3_open(db_file_expanded(), &db);
|
|
||||||
|
|
||||||
if (rc != SQLITE_OK) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
char *sql =
|
|
||||||
sqlite3_mprintf("INSERT INTO kv_store (key, value) VALUES (%Q, %Q) ON "
|
|
||||||
"CONFLICT(key) DO UPDATE SET value = %Q WHERE key = %Q",
|
|
||||||
key, value, value, key);
|
|
||||||
rc = sqlite3_exec(db, sql, 0, 0, &err_msg);
|
|
||||||
sqlite3_free(sql);
|
|
||||||
|
|
||||||
if (rc != SQLITE_OK) {
|
|
||||||
sqlite3_free(err_msg);
|
|
||||||
sqlite3_close(db);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
sqlite3_close(db);
|
|
||||||
return json_object_new_string("Success");
|
|
||||||
}
|
|
||||||
|
|
||||||
json_object *db_get(const char *key) {
|
|
||||||
sqlite3 *db;
|
|
||||||
sqlite3_stmt *stmt;
|
|
||||||
json_object *result = json_object_new_object();
|
|
||||||
if (result == NULL) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
int rc = sqlite3_open(db_file_expanded(), &db);
|
|
||||||
if (rc != SQLITE_OK) {
|
|
||||||
json_object_put(result);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
const char *sql = "SELECT value FROM kv_store WHERE key = ?";
|
|
||||||
rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
|
|
||||||
if (rc != SQLITE_OK) {
|
|
||||||
sqlite3_close(db);
|
|
||||||
json_object_put(result);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
sqlite3_bind_text(stmt, 1, key, -1, SQLITE_STATIC);
|
|
||||||
|
|
||||||
if (sqlite3_step(stmt) == SQLITE_ROW) {
|
|
||||||
const char *value = (const char *)sqlite3_column_text(stmt, 0);
|
|
||||||
if (value) {
|
|
||||||
json_object_object_add(result, "value", json_object_new_string(value));
|
|
||||||
} else {
|
|
||||||
json_object_object_add(result, "error",
|
|
||||||
json_object_new_string("Key not found"));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
json_object_object_add(result, "error",
|
|
||||||
json_object_new_string("Key not found"));
|
|
||||||
}
|
|
||||||
|
|
||||||
sqlite3_finalize(stmt);
|
|
||||||
sqlite3_close(db);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
json_object *db_query(const char *query) {
|
|
||||||
sqlite3 *db;
|
|
||||||
sqlite3_stmt *stmt;
|
|
||||||
|
|
||||||
if (strncmp(query, "SELECT", 6)) {
|
|
||||||
return db_execute(query);
|
|
||||||
}
|
|
||||||
|
|
||||||
json_object *result = json_object_new_array();
|
|
||||||
if (result == NULL) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
int rc = sqlite3_open(db_file_expanded(), &db);
|
|
||||||
if (rc != SQLITE_OK) {
|
|
||||||
json_object_put(result);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
rc = sqlite3_prepare_v2(db, query, -1, &stmt, NULL);
|
|
||||||
if (rc != SQLITE_OK) {
|
|
||||||
sqlite3_close(db);
|
|
||||||
json_object_put(result);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
while (sqlite3_step(stmt) == SQLITE_ROW) {
|
|
||||||
json_object *row = json_object_new_object();
|
|
||||||
for (int i = 0; i < sqlite3_column_count(stmt); i++) {
|
|
||||||
const char *col_name = sqlite3_column_name(stmt, i);
|
|
||||||
switch (sqlite3_column_type(stmt, i)) {
|
|
||||||
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_BLOB:
|
|
||||||
json_object_object_add(row, col_name,
|
|
||||||
json_object_new_string_len(
|
|
||||||
(const char *)sqlite3_column_blob(stmt, i),
|
|
||||||
sqlite3_column_bytes(stmt, i)));
|
|
||||||
break;
|
|
||||||
case SQLITE_NULL:
|
|
||||||
default:
|
|
||||||
json_object_object_add(row, col_name, json_object_new_string("NULL"));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
json_object_array_add(result, row);
|
|
||||||
}
|
|
||||||
|
|
||||||
sqlite3_finalize(stmt);
|
|
||||||
sqlite3_close(db);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
json_object *db_execute(const char *query) {
|
|
||||||
sqlite3 *db;
|
|
||||||
char *err_msg = 0;
|
|
||||||
int rc = sqlite3_open(db_file_expanded(), &db);
|
|
||||||
json_object *result = json_object_new_object();
|
|
||||||
|
|
||||||
if (rc != SQLITE_OK) {
|
|
||||||
json_object_object_add(result, "error",
|
|
||||||
json_object_new_string("Cannot open database"));
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
rc = sqlite3_exec(db, query, 0, 0, &err_msg);
|
|
||||||
if (rc != SQLITE_OK) {
|
|
||||||
json_object_object_add(result, "error", json_object_new_string(err_msg));
|
|
||||||
sqlite3_free(err_msg);
|
|
||||||
} else {
|
|
||||||
json_object_object_add(
|
|
||||||
result, "success",
|
|
||||||
json_object_new_string("Query executed successfully"));
|
|
||||||
}
|
|
||||||
|
|
||||||
sqlite3_close(db);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
void db_store_file_version(const char *path) {
|
|
||||||
char *expanded = expand_home_directory(path);
|
|
||||||
if (expanded == NULL) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
char *content = read_file(expanded);
|
|
||||||
if (!content) {
|
|
||||||
free(expanded);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
fprintf(stderr, "Creating backup:: %s\n", expanded);
|
|
||||||
char *formatted = sqlite3_mprintf(
|
|
||||||
"INSERT INTO file_version_history (path, content) VALUES (%Q, %Q)",
|
|
||||||
expanded, content);
|
|
||||||
if (formatted) {
|
|
||||||
db_execute(formatted);
|
|
||||||
sqlite3_free(formatted);
|
|
||||||
}
|
|
||||||
free(content);
|
|
||||||
free(expanded);
|
|
||||||
}
|
|
||||||
char *db_get_schema() {
|
|
||||||
json_object *tables =
|
|
||||||
db_query("SELECT * FROM sqlite_master WHERE type='table'");
|
|
||||||
if (tables == NULL) {
|
|
||||||
return strdup("[]");
|
|
||||||
}
|
|
||||||
const char *str = json_object_to_json_string(tables);
|
|
||||||
char *result = str ? strdup(str) : strdup("[]");
|
|
||||||
json_object_put(tables);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
232
http_curl.h
232
http_curl.h
@ -1,232 +0,0 @@
|
|||||||
// Written by retoor@molodetz.nl
|
|
||||||
|
|
||||||
// This code defines a simple HTTP client using libcurl in C. It provides
|
|
||||||
// functions for executing POST and GET HTTP requests with JSON data, including
|
|
||||||
// authorization via a bearer token. The functions `curl_post` and `curl_get`
|
|
||||||
// handle these operations and return the server's response as a string.
|
|
||||||
|
|
||||||
// Uses libcurl for HTTP requests and includes a custom "auth.h" for API key
|
|
||||||
// resolution.
|
|
||||||
|
|
||||||
// MIT License
|
|
||||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
// of this software and associated documentation files (the "Software"), to deal
|
|
||||||
// in the Software without restriction, including without limitation the rights
|
|
||||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
// copies of the Software, and to permit persons to whom the Software is
|
|
||||||
// furnished to do so, subject to the following conditions: the above copyright
|
|
||||||
// notice and this permission notice shall be included in all copies or
|
|
||||||
// substantial portions of the Software. The Software is provided "as is",
|
|
||||||
// without warranty of any kind, express or implied, including but not limited
|
|
||||||
// to the warranties of merchantability, fitness for a particular purpose and
|
|
||||||
// noninfringement. In no event shall the authors or copyright holders be liable
|
|
||||||
// for any claim, damages or other liability, whether in an action of contract,
|
|
||||||
// tort or otherwise, arising from, out of or in connection with the software or
|
|
||||||
// the use or other dealings in the Software.
|
|
||||||
|
|
||||||
#ifndef HTTP_CURL
|
|
||||||
#define HTTP_CURL
|
|
||||||
|
|
||||||
#include "auth.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>
|
|
||||||
|
|
||||||
struct ResponseBuffer {
|
|
||||||
char *data;
|
|
||||||
size_t size;
|
|
||||||
};
|
|
||||||
|
|
||||||
static size_t WriteCallback(void *contents, size_t size, size_t nmemb,
|
|
||||||
void *userp) {
|
|
||||||
size_t total_size = size * nmemb;
|
|
||||||
struct ResponseBuffer *response = (struct ResponseBuffer *)userp;
|
|
||||||
|
|
||||||
if (total_size > SIZE_MAX - response->size - 1) {
|
|
||||||
fprintf(stderr, "Response too large\n");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
char *ptr = realloc(response->data, response->size + total_size + 1);
|
|
||||||
if (ptr == NULL) {
|
|
||||||
fprintf(stderr, "Failed to allocate memory for response\n");
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
#define HTTP_MAX_RETRIES 3
|
|
||||||
#define HTTP_RETRY_DELAY_MS 2000
|
|
||||||
|
|
||||||
static struct timespec spinner_start_time = {0, 0};
|
|
||||||
static volatile int spinner_running = 0;
|
|
||||||
|
|
||||||
static double get_elapsed_seconds() {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
char *curl_post(const char *url, const char *data) {
|
|
||||||
CURL *curl;
|
|
||||||
CURLcode res;
|
|
||||||
struct ResponseBuffer response = {NULL, 0};
|
|
||||||
int retry_count = 0;
|
|
||||||
pthread_t spinner_tid;
|
|
||||||
|
|
||||||
clock_gettime(CLOCK_MONOTONIC, &spinner_start_time);
|
|
||||||
spinner_running = 1;
|
|
||||||
pthread_create(&spinner_tid, NULL, spinner_thread, NULL);
|
|
||||||
|
|
||||||
while (retry_count < HTTP_MAX_RETRIES) {
|
|
||||||
if (response.data) {
|
|
||||||
free(response.data);
|
|
||||||
}
|
|
||||||
response.data = malloc(1);
|
|
||||||
response.size = 0;
|
|
||||||
if (!response.data) {
|
|
||||||
spinner_running = 0;
|
|
||||||
pthread_join(spinner_tid, NULL);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
response.data[0] = '\0';
|
|
||||||
|
|
||||||
curl = curl_easy_init();
|
|
||||||
if (!curl) {
|
|
||||||
free(response.data);
|
|
||||||
spinner_running = 0;
|
|
||||||
pthread_join(spinner_tid, NULL);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct curl_slist *headers = NULL;
|
|
||||||
curl_easy_setopt(curl, CURLOPT_URL, url);
|
|
||||||
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 10L);
|
|
||||||
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 300L);
|
|
||||||
headers = curl_slist_append(headers, "Content-Type: application/json");
|
|
||||||
char bearer_header[1337];
|
|
||||||
snprintf(bearer_header, sizeof(bearer_header), "Authorization: Bearer %s",
|
|
||||||
resolve_api_key());
|
|
||||||
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, WriteCallback);
|
|
||||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&response);
|
|
||||||
|
|
||||||
res = curl_easy_perform(curl);
|
|
||||||
curl_slist_free_all(headers);
|
|
||||||
curl_easy_cleanup(curl);
|
|
||||||
|
|
||||||
if (res == CURLE_OK) {
|
|
||||||
spinner_running = 0;
|
|
||||||
pthread_join(spinner_tid, NULL);
|
|
||||||
fprintf(stderr, "\r \r");
|
|
||||||
fflush(stderr);
|
|
||||||
return response.data;
|
|
||||||
}
|
|
||||||
|
|
||||||
retry_count++;
|
|
||||||
spinner_running = 0;
|
|
||||||
pthread_join(spinner_tid, NULL);
|
|
||||||
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);
|
|
||||||
clock_gettime(CLOCK_MONOTONIC, &spinner_start_time);
|
|
||||||
spinner_running = 1;
|
|
||||||
pthread_create(&spinner_tid, NULL, spinner_thread, NULL);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fprintf(stderr, "Failed after %d attempts.\n", HTTP_MAX_RETRIES);
|
|
||||||
free(response.data);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
char *curl_get(const char *url) {
|
|
||||||
CURL *curl;
|
|
||||||
CURLcode res;
|
|
||||||
struct ResponseBuffer response = {NULL, 0};
|
|
||||||
int retry_count = 0;
|
|
||||||
|
|
||||||
while (retry_count < HTTP_MAX_RETRIES) {
|
|
||||||
if (response.data) {
|
|
||||||
free(response.data);
|
|
||||||
}
|
|
||||||
response.data = malloc(1);
|
|
||||||
response.size = 0;
|
|
||||||
if (!response.data) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
response.data[0] = '\0';
|
|
||||||
|
|
||||||
curl = curl_easy_init();
|
|
||||||
if (!curl) {
|
|
||||||
free(response.data);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct curl_slist *headers = NULL;
|
|
||||||
curl_easy_setopt(curl, CURLOPT_URL, url);
|
|
||||||
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 10L);
|
|
||||||
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 60L);
|
|
||||||
headers = curl_slist_append(headers, "Content-Type: application/json");
|
|
||||||
char bearer_header[1337];
|
|
||||||
snprintf(bearer_header, sizeof(bearer_header), "Authorization: Bearer %s",
|
|
||||||
resolve_api_key());
|
|
||||||
headers = curl_slist_append(headers, bearer_header);
|
|
||||||
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
|
|
||||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
|
|
||||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&response);
|
|
||||||
|
|
||||||
res = curl_easy_perform(curl);
|
|
||||||
curl_slist_free_all(headers);
|
|
||||||
curl_easy_cleanup(curl);
|
|
||||||
|
|
||||||
if (res == CURLE_OK) {
|
|
||||||
return response.data;
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fprintf(stderr, "Failed after %d attempts.\n", HTTP_MAX_RETRIES);
|
|
||||||
free(response.data);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
147
indexer.h
147
indexer.h
@ -1,147 +0,0 @@
|
|||||||
#include <dirent.h>
|
|
||||||
#include <json-c/json.h>
|
|
||||||
#include <limits.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <sys/stat.h>
|
|
||||||
#include <time.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
|
|
||||||
#define MAX_FILES 20000
|
|
||||||
#define MAX_PATH 4096
|
|
||||||
|
|
||||||
static const char *extensions[] = {
|
|
||||||
".c", ".cpp", ".h", ".py", ".java", ".js",
|
|
||||||
".mk", ".html", "Makefile", ".css", ".json", ".cs",
|
|
||||||
".csproj", ".sln", ".toml", ".rs", ".go", ".rb",
|
|
||||||
".swift", ".php", ".pl", ".sh", ".bash", ".sql",
|
|
||||||
".xml", ".yaml", ".yml", ".kt", ".dart", ".scala",
|
|
||||||
".clj", ".asm", ".m", ".r", ".lua", ".groovy",
|
|
||||||
".v", ".pas", ".d", ".f90", ".f95", ".for",
|
|
||||||
".s", ".tcl", ".vhdl", ".verilog", ".coffee", ".less",
|
|
||||||
".scss", ".ps1", ".psm1", ".cmd", ".bat", ".json5",
|
|
||||||
".cxx", ".cc", ".hpp", ".hxx", ".inc", ".nsi",
|
|
||||||
".ninja", ".cmake", ".cmake.in", ".mk.in", ".make", ".makefile",
|
|
||||||
".gyp", ".gypi", ".pro", ".qml", ".ui", ".wxs",
|
|
||||||
".wxl", ".wxi", ".wxl", ".wxs", ".wxi", ".wxl",
|
|
||||||
".wxs", ".wxi"};
|
|
||||||
static const size_t ext_count = sizeof(extensions) / sizeof(extensions[0]);
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
char name[MAX_PATH];
|
|
||||||
char modification_date[20];
|
|
||||||
char creation_date[20];
|
|
||||||
char type[10];
|
|
||||||
size_t size_bytes;
|
|
||||||
} FileInfo;
|
|
||||||
|
|
||||||
static FileInfo file_list[MAX_FILES];
|
|
||||||
static size_t file_count = 0;
|
|
||||||
|
|
||||||
static int is_valid_extension(const char *filename) {
|
|
||||||
const char *dot = strrchr(filename, '.');
|
|
||||||
if (!dot)
|
|
||||||
dot = filename;
|
|
||||||
for (size_t i = 0; i < ext_count; i++) {
|
|
||||||
if (strcmp(dot, extensions[i]) == 0)
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int is_ignored_directory(const char *dir_name) {
|
|
||||||
const char *ignored_dirs[] = {"env", ".venv", "node_modules", "venv",
|
|
||||||
"virtualenv"};
|
|
||||||
for (size_t i = 0; i < sizeof(ignored_dirs) / sizeof(ignored_dirs[0]); i++) {
|
|
||||||
if (strcmp(dir_name, ignored_dirs[i]) == 0)
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int get_file_info(const char *path) {
|
|
||||||
if (file_count >= MAX_FILES) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
struct stat file_stat;
|
|
||||||
if (stat(path, &file_stat) == 0) {
|
|
||||||
FileInfo info;
|
|
||||||
strncpy(info.name, path, MAX_PATH - 1);
|
|
||||||
info.name[MAX_PATH - 1] = '\0';
|
|
||||||
strftime(info.modification_date, sizeof(info.modification_date),
|
|
||||||
"%Y-%m-%d %H:%M:%S", localtime(&file_stat.st_mtime));
|
|
||||||
strftime(info.creation_date, sizeof(info.creation_date),
|
|
||||||
"%Y-%m-%d %H:%M:%S", localtime(&file_stat.st_ctime));
|
|
||||||
strncpy(info.type, S_ISDIR(file_stat.st_mode) ? "directory" : "file",
|
|
||||||
sizeof(info.type) - 1);
|
|
||||||
info.type[sizeof(info.type) - 1] = '\0';
|
|
||||||
info.size_bytes = file_stat.st_size;
|
|
||||||
file_list[file_count++] = info;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
char *index_directory(const char *dir_path) {
|
|
||||||
DIR *dir = opendir(dir_path);
|
|
||||||
if (!dir) {
|
|
||||||
perror("Failed to open directory");
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct dirent *entry;
|
|
||||||
json_object *jarray = json_object_new_array();
|
|
||||||
if (jarray == NULL) {
|
|
||||||
closedir(dir);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
while ((entry = readdir(dir)) != NULL) {
|
|
||||||
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
|
|
||||||
continue;
|
|
||||||
if (entry->d_name[0] == '.' || is_ignored_directory(entry->d_name))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
char full_path[MAX_PATH];
|
|
||||||
snprintf(full_path, sizeof(full_path), "%s/%s", dir_path, entry->d_name);
|
|
||||||
|
|
||||||
if (entry->d_type == DT_DIR) {
|
|
||||||
char *subdir_json = index_directory(full_path);
|
|
||||||
if (subdir_json) {
|
|
||||||
json_object *jsubdir = json_object_new_string(subdir_json);
|
|
||||||
json_object_array_add(jarray, jsubdir);
|
|
||||||
free(subdir_json);
|
|
||||||
}
|
|
||||||
} else if (is_valid_extension(entry->d_name)) {
|
|
||||||
if (get_file_info(full_path) == 0 && file_count > 0) {
|
|
||||||
json_object *jfile = json_object_new_object();
|
|
||||||
if (jfile == NULL) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
json_object_object_add(
|
|
||||||
jfile, "file_name",
|
|
||||||
json_object_new_string(file_list[file_count - 1].name));
|
|
||||||
json_object_object_add(
|
|
||||||
jfile, "modification_date",
|
|
||||||
json_object_new_string(file_list[file_count - 1].modification_date));
|
|
||||||
json_object_object_add(
|
|
||||||
jfile, "creation_date",
|
|
||||||
json_object_new_string(file_list[file_count - 1].creation_date));
|
|
||||||
json_object_object_add(
|
|
||||||
jfile, "type",
|
|
||||||
json_object_new_string(file_list[file_count - 1].type));
|
|
||||||
json_object_object_add(
|
|
||||||
jfile, "size_bytes",
|
|
||||||
json_object_new_int64(file_list[file_count - 1].size_bytes));
|
|
||||||
|
|
||||||
json_object_array_add(jarray, jfile);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
closedir(dir);
|
|
||||||
|
|
||||||
char *result = strdup(json_object_to_json_string(jarray));
|
|
||||||
json_object_put(jarray);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
123
line.h
123
line.h
@ -1,123 +0,0 @@
|
|||||||
// Written by retoor@molodetz.nl
|
|
||||||
|
|
||||||
// This source code provides command-line input functionalities with
|
|
||||||
// autocomplete and history features using the readline library. It allows users
|
|
||||||
// to complete commands and manage input history.
|
|
||||||
|
|
||||||
// External includes:
|
|
||||||
// - <readline/readline.h>
|
|
||||||
// - <readline/history.h>
|
|
||||||
// - <glob.h>
|
|
||||||
|
|
||||||
// MIT License: Permission is hereby granted, free of charge, to any person
|
|
||||||
// obtaining a copy of this software and associated documentation files (the
|
|
||||||
// "Software"), to deal in the Software without restriction.
|
|
||||||
|
|
||||||
#include "utils.h"
|
|
||||||
#include <glob.h>
|
|
||||||
#include <readline/history.h>
|
|
||||||
#include <readline/readline.h>
|
|
||||||
#include <stdbool.h>
|
|
||||||
#include <string.h>
|
|
||||||
#define HISTORY_FILE "~/.r_history"
|
|
||||||
|
|
||||||
bool line_initialized = false;
|
|
||||||
|
|
||||||
char *get_history_file() {
|
|
||||||
static char result[4096];
|
|
||||||
result[0] = '\0';
|
|
||||||
|
|
||||||
char *expanded = expand_home_directory(HISTORY_FILE);
|
|
||||||
if (expanded == NULL) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
strncpy(result, expanded, sizeof(result) - 1);
|
|
||||||
result[sizeof(result) - 1] = '\0';
|
|
||||||
free(expanded);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
char *line_command_generator(const char *text, int state) {
|
|
||||||
static int list_index, len = 0;
|
|
||||||
const char *commands[] = {"help", "exit", "list", "review",
|
|
||||||
"refactor", "obfuscate", "!verbose", "!dump",
|
|
||||||
"!model", "!debug", NULL};
|
|
||||||
|
|
||||||
if (!state) {
|
|
||||||
list_index = 0;
|
|
||||||
len = strlen(text);
|
|
||||||
}
|
|
||||||
|
|
||||||
while (commands[list_index]) {
|
|
||||||
const char *command = commands[list_index++];
|
|
||||||
if (strncmp(command, text, len) == 0) {
|
|
||||||
return strdup(command);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
char *line_file_generator(const char *text, int state) {
|
|
||||||
static int list_index;
|
|
||||||
static glob_t glob_result;
|
|
||||||
static int glob_valid = 0;
|
|
||||||
char pattern[1024];
|
|
||||||
|
|
||||||
if (!state) {
|
|
||||||
if (glob_valid) {
|
|
||||||
globfree(&glob_result);
|
|
||||||
glob_valid = 0;
|
|
||||||
}
|
|
||||||
list_index = 0;
|
|
||||||
snprintf(pattern, sizeof(pattern), "%s*", text);
|
|
||||||
if (glob(pattern, GLOB_NOSORT, NULL, &glob_result) == 0) {
|
|
||||||
glob_valid = 1;
|
|
||||||
} else {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (glob_valid && (size_t)list_index < glob_result.gl_pathc) {
|
|
||||||
return strdup(glob_result.gl_pathv[list_index++]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (glob_valid) {
|
|
||||||
globfree(&glob_result);
|
|
||||||
glob_valid = 0;
|
|
||||||
}
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
char **line_command_completion(const char *text, int start, int end) {
|
|
||||||
rl_attempted_completion_over = 1;
|
|
||||||
|
|
||||||
// Check if the input is a file path
|
|
||||||
if (start > 0 && text[0] != ' ') {
|
|
||||||
return rl_completion_matches(text, line_file_generator);
|
|
||||||
}
|
|
||||||
|
|
||||||
return rl_completion_matches(text, line_command_generator);
|
|
||||||
}
|
|
||||||
|
|
||||||
void line_init() {
|
|
||||||
if (!line_initialized) {
|
|
||||||
rl_attempted_completion_function = line_command_completion;
|
|
||||||
line_initialized = true;
|
|
||||||
read_history(get_history_file());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
char *line_read(char *prefix) {
|
|
||||||
char *data = readline(prefix);
|
|
||||||
if (!(data && *data)) {
|
|
||||||
free(data);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
void line_add_history(char *data) {
|
|
||||||
add_history(data);
|
|
||||||
write_history(get_history_file());
|
|
||||||
}
|
|
||||||
387
main.c
387
main.c
@ -1,387 +0,0 @@
|
|||||||
#include "agent.h"
|
|
||||||
#include "db_utils.h"
|
|
||||||
#include "line.h"
|
|
||||||
#include "markdown.h"
|
|
||||||
#include "openai.h"
|
|
||||||
#include "r.h"
|
|
||||||
#include "tools.h"
|
|
||||||
#include "utils.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>
|
|
||||||
|
|
||||||
volatile sig_atomic_t sigint_count = 0;
|
|
||||||
time_t first_sigint_time = 0;
|
|
||||||
bool SYNTAX_HIGHLIGHT_ENABLED = true;
|
|
||||||
bool API_MODE = false;
|
|
||||||
|
|
||||||
static void render(const char *content);
|
|
||||||
static bool openai_include(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 handle_sigint(int sig);
|
|
||||||
|
|
||||||
char *get_env_string() {
|
|
||||||
FILE *fp = popen("env", "r");
|
|
||||||
if (fp == NULL) {
|
|
||||||
perror("popen failed");
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t buffer_size = 1024;
|
|
||||||
size_t total_size = 0;
|
|
||||||
char *output = malloc(buffer_size);
|
|
||||||
if (output == NULL) {
|
|
||||||
perror("malloc failed");
|
|
||||||
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 == NULL) {
|
|
||||||
perror("realloc failed");
|
|
||||||
free(output);
|
|
||||||
pclose(fp);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
output = temp;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Null-terminate the output
|
|
||||||
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) {
|
|
||||||
char *prompt = malloc(10 * 1024 * 1024 + 1);
|
|
||||||
char *system = malloc(1024 * 1024);
|
|
||||||
if (!prompt || !system) {
|
|
||||||
fprintf(stderr, "Error: Memory allocation failed.\n");
|
|
||||||
free(prompt);
|
|
||||||
free(system);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool get_from_std_in = false;
|
|
||||||
|
|
||||||
for (int i = 1; i < argc; i++) {
|
|
||||||
if (strcmp(argv[i], "--stdin") == 0) {
|
|
||||||
fprintf(stderr, "Reading from stdin.\n");
|
|
||||||
get_from_std_in = true;
|
|
||||||
} else if (strcmp(argv[i], "--verbose") == 0) {
|
|
||||||
is_verbose = 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);
|
|
||||||
openai_include(py_file_path);
|
|
||||||
free(py_file_path);
|
|
||||||
} else if (strcmp(argv[i], "--free") == 0) {
|
|
||||||
auth_free();
|
|
||||||
} else if (strcmp(argv[i], "--context") == 0 && i + 1 < argc) {
|
|
||||||
char *context_file_path = argv[++i];
|
|
||||||
fprintf(stderr, "Including \"%s\".\n", context_file_path);
|
|
||||||
openai_include(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, argv[i]);
|
|
||||||
strcat(system, (i < argc - 1) ? " " : ".");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (get_from_std_in) {
|
|
||||||
if (*system)
|
|
||||||
openai_system(system);
|
|
||||||
prompt = get_prompt_from_stdin(prompt);
|
|
||||||
} else {
|
|
||||||
free(prompt);
|
|
||||||
prompt = system;
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
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 openai_include(const char *path) {
|
|
||||||
char *file_content = read_file(path);
|
|
||||||
if (!file_content)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
openai_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) {
|
|
||||||
line_init();
|
|
||||||
char *line = NULL;
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
line = line_read("> ");
|
|
||||||
if (!line || !*line)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (!strncmp(line, "!dump", 5)) {
|
|
||||||
printf("%s\n", message_json());
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if(!strncmp(line,"!debug",6))
|
|
||||||
{
|
|
||||||
printf("%s\n", R_BASE_URL);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (!strncmp(line, "!clear", 6)) {
|
|
||||||
messages_remove();
|
|
||||||
fprintf(stderr, "Session cleared.\n");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (!strncmp(line, "!session", 8)) {
|
|
||||||
init_session_id();
|
|
||||||
printf("Session: %s\n", get_session_id());
|
|
||||||
printf("DB Key: %s\n", get_session_db_key());
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (!strncmp(line, "!verbose", 8)) {
|
|
||||||
is_verbose = !is_verbose;
|
|
||||||
fprintf(stderr, "%s\n",
|
|
||||||
is_verbose ? "Verbose mode enabled" : "Verbose mode disabled");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (line && *line != '\n')
|
|
||||||
line_add_history(line);
|
|
||||||
if (!strncmp(line, "!tools", 6)) {
|
|
||||||
printf("Available tools: %s\n",
|
|
||||||
json_object_to_json_string(tools_descriptions()));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!strncmp(line, "!models", 7)) {
|
|
||||||
printf("Current model: %s\n", openai_fetch_models());
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (!strncmp(line, "!model", 6)) {
|
|
||||||
if (line[6] == ' ') {
|
|
||||||
set_prompt_model(line + 7);
|
|
||||||
}
|
|
||||||
printf("Current model: %s\n", get_prompt_model());
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (!strncmp(line, "exit", 4))
|
|
||||||
exit(0);
|
|
||||||
|
|
||||||
while (line && *line != '\n') {
|
|
||||||
char *response = agent_chat(line);
|
|
||||||
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();
|
|
||||||
auth_init();
|
|
||||||
db_initialize();
|
|
||||||
|
|
||||||
char *schema = db_get_schema();
|
|
||||||
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);
|
|
||||||
|
|
||||||
free(schema);
|
|
||||||
fprintf(stderr, "Loading...");
|
|
||||||
openai_system(payload);
|
|
||||||
|
|
||||||
char *env_system_message = get_env_system_message();
|
|
||||||
if (env_system_message && *env_system_message) {
|
|
||||||
openai_system(env_system_message);
|
|
||||||
free(env_system_message);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!openai_include(".rcontext.txt")) {
|
|
||||||
openai_include("~/.rcontext.txt");
|
|
||||||
}
|
|
||||||
|
|
||||||
fprintf(stderr, "\r \r");
|
|
||||||
}
|
|
||||||
|
|
||||||
static void handle_sigint(int 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) {
|
|
||||||
exit(0);
|
|
||||||
} else {
|
|
||||||
sigint_count = 1;
|
|
||||||
first_sigint_time = current_time;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void parse_session_arg(int argc, char *argv[]) {
|
|
||||||
for (int i = 1; i < argc; i++) {
|
|
||||||
if (strncmp(argv[i], "--session=", 10) == 0) {
|
|
||||||
const char *name = argv[i] + 10;
|
|
||||||
if (!set_session_id(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 (!set_session_id(name)) {
|
|
||||||
fprintf(stderr, "Error: Invalid session name '%s'\n", name);
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
char *env_session = get_env_session();
|
|
||||||
if (env_session) {
|
|
||||||
if (!set_session_id(env_session)) {
|
|
||||||
fprintf(stderr, "Warning: Invalid R_SESSION '%s', using default\n", env_session);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(int argc, char *argv[]) {
|
|
||||||
signal(SIGINT, handle_sigint);
|
|
||||||
|
|
||||||
parse_session_arg(argc, argv);
|
|
||||||
init();
|
|
||||||
char *env_string = get_env_string();
|
|
||||||
if (env_string && *env_string) {
|
|
||||||
openai_system(env_string);
|
|
||||||
free(env_string);
|
|
||||||
}
|
|
||||||
|
|
||||||
messages_load_conversation();
|
|
||||||
|
|
||||||
if (try_prompt(argc, argv))
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
repl();
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
351
markdown.h
351
markdown.h
@ -1,351 +0,0 @@
|
|||||||
#include <ctype.h>
|
|
||||||
#include <stdbool.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
// --- ANSI Escape Codes ---
|
|
||||||
#define RESET "\033[0m"
|
|
||||||
#define BOLD "\033[1m"
|
|
||||||
#define ITALIC "\033[3m"
|
|
||||||
#define STRIKETHROUGH "\033[9m"
|
|
||||||
|
|
||||||
#define FG_YELLOW "\033[33m"
|
|
||||||
#define FG_BLUE "\033[34m"
|
|
||||||
#define FG_CYAN "\033[36m"
|
|
||||||
#define FG_MAGENTA "\033[35m"
|
|
||||||
|
|
||||||
#define BG_YELLOW_FG_BLACK "\033[43;30m"
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Checks if a given word is a programming language keyword.
|
|
||||||
* * @param word The word to check.
|
|
||||||
* @return int 1 if it's a keyword, 0 otherwise.
|
|
||||||
*/
|
|
||||||
int is_keyword(const char *word) {
|
|
||||||
// A comprehensive list of keywords from various popular languages.
|
|
||||||
const char *keywords[] = {
|
|
||||||
// C keywords
|
|
||||||
"int", "float", "double", "char", "void", "if", "else", "while", "for",
|
|
||||||
"return", "struct", "printf",
|
|
||||||
// Rust keywords
|
|
||||||
"let", "fn", "impl", "match", "enum", "trait", "use", "mod", "pub",
|
|
||||||
"const", "static",
|
|
||||||
// Python keywords
|
|
||||||
"def", "class", "import", "from", "as", "with", "try", "except",
|
|
||||||
"finally", "lambda", "async", "await",
|
|
||||||
// Java keywords
|
|
||||||
"public", "private", "protected", "class", "interface", "extends",
|
|
||||||
"implements", "new", "static", "final", "synchronized",
|
|
||||||
// JavaScript keywords
|
|
||||||
"var", "let", "const", "function", "async", "await", "if", "else",
|
|
||||||
"switch", "case", "break", "continue", "return",
|
|
||||||
// C++ keywords
|
|
||||||
"namespace", "template", "typename", "class", "public", "private",
|
|
||||||
"protected", "virtual", "override", "friend", "new",
|
|
||||||
// Go keywords
|
|
||||||
"package", "import", "func", "var", "const", "type", "interface",
|
|
||||||
"struct", "go", "defer", "select",
|
|
||||||
// Bash keywords
|
|
||||||
"if", "then", "else", "elif", "fi", "case", "esac", "for", "while",
|
|
||||||
"until", "do", "done", "function",
|
|
||||||
// C# keywords
|
|
||||||
"namespace", "using", "class", "interface", "public", "private",
|
|
||||||
"protected", "static", "void", "new", "override"};
|
|
||||||
|
|
||||||
for (size_t i = 0; i < sizeof(keywords) / sizeof(keywords[0]); i++) {
|
|
||||||
if (strcmp(word, keywords[i]) == 0) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Applies basic syntax highlighting to a string of code.
|
|
||||||
* * @param code The code string to highlight.
|
|
||||||
*/
|
|
||||||
void highlight_code(const char *code) {
|
|
||||||
const char *ptr = code;
|
|
||||||
char buffer[4096];
|
|
||||||
size_t index = 0;
|
|
||||||
|
|
||||||
while (*ptr) {
|
|
||||||
// Highlight keywords
|
|
||||||
if (isalpha((unsigned char)*ptr) || *ptr == '_') {
|
|
||||||
while (isalnum((unsigned char)*ptr) || *ptr == '_') {
|
|
||||||
buffer[index++] = *ptr++;
|
|
||||||
}
|
|
||||||
buffer[index] = '\0';
|
|
||||||
|
|
||||||
if (is_keyword(buffer)) {
|
|
||||||
printf(FG_BLUE "%s" RESET FG_YELLOW, buffer);
|
|
||||||
} else {
|
|
||||||
printf("%s", buffer);
|
|
||||||
}
|
|
||||||
index = 0;
|
|
||||||
// Highlight numbers
|
|
||||||
} else if (isdigit((unsigned char)*ptr)) {
|
|
||||||
while (isdigit((unsigned char)*ptr)) {
|
|
||||||
buffer[index++] = *ptr++;
|
|
||||||
}
|
|
||||||
buffer[index] = '\0';
|
|
||||||
printf(FG_CYAN "%s" RESET FG_YELLOW, buffer);
|
|
||||||
index = 0;
|
|
||||||
// Print other characters as-is
|
|
||||||
} else {
|
|
||||||
putchar(*ptr);
|
|
||||||
ptr++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Parses a Markdown string and prints it to the console with ANSI color
|
|
||||||
* codes.
|
|
||||||
*
|
|
||||||
* This version supports a wide range of Markdown features, including:
|
|
||||||
* - Headers (H1-H6)
|
|
||||||
* - Bold (**, __) and Italic (*, _) text
|
|
||||||
* - Strikethrough (~~) and Highlight (==)
|
|
||||||
* - Blockquotes (>), Nested Ordered (1.) and Unordered lists (*, -, +)
|
|
||||||
* - Inline code (`) and full code blocks (```) with syntax highlighting
|
|
||||||
* - Links ([text](url)) and Horizontal rules (---, ***)
|
|
||||||
* * @param markdown The raw Markdown string to parse.
|
|
||||||
*/
|
|
||||||
void parse_markdown_to_ansi(const char *markdown) {
|
|
||||||
const char *ptr = markdown;
|
|
||||||
bool is_start_of_line = true;
|
|
||||||
|
|
||||||
while (*ptr) {
|
|
||||||
// --- Code Blocks (```) ---
|
|
||||||
if (is_start_of_line && strncmp(ptr, "```", 3) == 0) {
|
|
||||||
ptr += 3;
|
|
||||||
while (*ptr && *ptr != '\n')
|
|
||||||
ptr++;
|
|
||||||
if (*ptr)
|
|
||||||
ptr++;
|
|
||||||
|
|
||||||
const char *code_start = ptr;
|
|
||||||
const char *code_end = strstr(code_start, "```");
|
|
||||||
|
|
||||||
if (code_end) {
|
|
||||||
char block_buffer[code_end - code_start + 1];
|
|
||||||
strncpy(block_buffer, code_start, code_end - code_start);
|
|
||||||
block_buffer[code_end - code_start] = '\0';
|
|
||||||
|
|
||||||
printf(FG_YELLOW);
|
|
||||||
highlight_code(block_buffer);
|
|
||||||
printf(RESET);
|
|
||||||
|
|
||||||
ptr = code_end + 3;
|
|
||||||
if (*ptr == '\n')
|
|
||||||
ptr++;
|
|
||||||
is_start_of_line = true;
|
|
||||||
continue;
|
|
||||||
} else {
|
|
||||||
printf(FG_YELLOW);
|
|
||||||
highlight_code(code_start);
|
|
||||||
printf(RESET);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Block-level Elements (checked at the start of a line) ---
|
|
||||||
if (is_start_of_line) {
|
|
||||||
const char *line_start_ptr = ptr;
|
|
||||||
int indent_level = 0;
|
|
||||||
while (*ptr == ' ') {
|
|
||||||
indent_level++;
|
|
||||||
ptr++;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool block_processed = true;
|
|
||||||
if (strncmp(ptr, "###### ", 7) == 0) {
|
|
||||||
printf(BOLD FG_YELLOW);
|
|
||||||
ptr += 7;
|
|
||||||
} else if (strncmp(ptr, "##### ", 6) == 0) {
|
|
||||||
printf(BOLD FG_YELLOW);
|
|
||||||
ptr += 6;
|
|
||||||
} else if (strncmp(ptr, "#### ", 5) == 0) {
|
|
||||||
printf(BOLD FG_YELLOW);
|
|
||||||
ptr += 5;
|
|
||||||
} else if (strncmp(ptr, "### ", 4) == 0) {
|
|
||||||
printf(BOLD FG_YELLOW);
|
|
||||||
ptr += 4;
|
|
||||||
} else if (strncmp(ptr, "## ", 3) == 0) {
|
|
||||||
printf(BOLD FG_YELLOW);
|
|
||||||
ptr += 3;
|
|
||||||
} else if (strncmp(ptr, "# ", 2) == 0) {
|
|
||||||
printf(BOLD FG_YELLOW);
|
|
||||||
ptr += 2;
|
|
||||||
} else if ((strncmp(ptr, "---", 3) == 0 || strncmp(ptr, "***", 3) == 0) &&
|
|
||||||
(*(ptr + 3) == '\n' || *(ptr + 3) == '\0')) {
|
|
||||||
printf(FG_CYAN "───────────────────────────────────────────────────────"
|
|
||||||
"──────────" RESET "\n");
|
|
||||||
ptr += 3;
|
|
||||||
if (*ptr == '\n')
|
|
||||||
ptr++;
|
|
||||||
is_start_of_line = true;
|
|
||||||
continue;
|
|
||||||
} else if (strncmp(ptr, "> ", 2) == 0) {
|
|
||||||
for (int i = 0; i < indent_level; i++)
|
|
||||||
putchar(' ');
|
|
||||||
printf(ITALIC FG_CYAN "▎ " RESET);
|
|
||||||
ptr += 2;
|
|
||||||
is_start_of_line = false;
|
|
||||||
continue;
|
|
||||||
} else if ((*ptr == '*' || *ptr == '-' || *ptr == '+') &&
|
|
||||||
*(ptr + 1) == ' ') {
|
|
||||||
for (int i = 0; i < indent_level; i++)
|
|
||||||
putchar(' ');
|
|
||||||
printf(FG_MAGENTA "• " RESET);
|
|
||||||
ptr += 2;
|
|
||||||
is_start_of_line = false;
|
|
||||||
continue;
|
|
||||||
} else {
|
|
||||||
const char *temp_ptr = ptr;
|
|
||||||
while (isdigit((unsigned char)*temp_ptr))
|
|
||||||
temp_ptr++;
|
|
||||||
if (temp_ptr > ptr && *temp_ptr == '.' && *(temp_ptr + 1) == ' ') {
|
|
||||||
for (int i = 0; i < indent_level; i++)
|
|
||||||
putchar(' ');
|
|
||||||
printf(FG_MAGENTA);
|
|
||||||
fwrite(ptr, 1, (temp_ptr - ptr) + 1, stdout);
|
|
||||||
printf(" " RESET);
|
|
||||||
ptr = temp_ptr + 2;
|
|
||||||
is_start_of_line = false;
|
|
||||||
continue;
|
|
||||||
} else {
|
|
||||||
block_processed = false;
|
|
||||||
ptr = line_start_ptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (block_processed) {
|
|
||||||
while (*ptr && *ptr != '\n')
|
|
||||||
putchar(*ptr++);
|
|
||||||
printf(RESET "\n");
|
|
||||||
if (*ptr == '\n')
|
|
||||||
ptr++;
|
|
||||||
is_start_of_line = true;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Inline Elements (order is important) ---
|
|
||||||
if (strncmp(ptr, "***", 3) == 0 || strncmp(ptr, "___", 3) == 0) {
|
|
||||||
const char *marker = strncmp(ptr, "***", 3) == 0 ? "***" : "___";
|
|
||||||
printf(BOLD ITALIC);
|
|
||||||
ptr += 3;
|
|
||||||
const char *end = strstr(ptr, marker);
|
|
||||||
if (end) {
|
|
||||||
fwrite(ptr, 1, end - ptr, stdout);
|
|
||||||
ptr = end + 3;
|
|
||||||
} else {
|
|
||||||
fputs(ptr, stdout);
|
|
||||||
ptr += strlen(ptr);
|
|
||||||
}
|
|
||||||
printf(RESET);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (strncmp(ptr, "**", 2) == 0 || strncmp(ptr, "__", 2) == 0) {
|
|
||||||
const char *marker = strncmp(ptr, "**", 2) == 0 ? "**" : "__";
|
|
||||||
printf(BOLD);
|
|
||||||
ptr += 2;
|
|
||||||
const char *end = strstr(ptr, marker);
|
|
||||||
if (end) {
|
|
||||||
fwrite(ptr, 1, end - ptr, stdout);
|
|
||||||
ptr = end + 2;
|
|
||||||
} else {
|
|
||||||
fputs(ptr, stdout);
|
|
||||||
ptr += strlen(ptr);
|
|
||||||
}
|
|
||||||
printf(RESET);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if ((*ptr == '*' || *ptr == '_') && !isspace(*(ptr - 1)) &&
|
|
||||||
!isspace(*(ptr + 1))) {
|
|
||||||
char marker = *ptr;
|
|
||||||
printf(ITALIC);
|
|
||||||
ptr++;
|
|
||||||
const char *start = ptr;
|
|
||||||
while (*ptr && *ptr != marker)
|
|
||||||
ptr++;
|
|
||||||
if (*ptr == marker) {
|
|
||||||
fwrite(start, 1, ptr - start, stdout);
|
|
||||||
ptr++;
|
|
||||||
} else {
|
|
||||||
putchar(marker);
|
|
||||||
ptr = start;
|
|
||||||
}
|
|
||||||
printf(RESET);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (strncmp(ptr, "~~", 2) == 0) {
|
|
||||||
printf(STRIKETHROUGH);
|
|
||||||
ptr += 2;
|
|
||||||
const char *end = strstr(ptr, "~~");
|
|
||||||
if (end) {
|
|
||||||
fwrite(ptr, 1, end - ptr, stdout);
|
|
||||||
ptr = end + 2;
|
|
||||||
} else {
|
|
||||||
fputs(ptr, stdout);
|
|
||||||
ptr += strlen(ptr);
|
|
||||||
}
|
|
||||||
printf(RESET);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (strncmp(ptr, "==", 2) == 0) {
|
|
||||||
printf(BG_YELLOW_FG_BLACK);
|
|
||||||
ptr += 2;
|
|
||||||
const char *end = strstr(ptr, "==");
|
|
||||||
if (end) {
|
|
||||||
fwrite(ptr, 1, end - ptr, stdout);
|
|
||||||
ptr = end + 2;
|
|
||||||
} else {
|
|
||||||
fputs(ptr, stdout);
|
|
||||||
ptr += strlen(ptr);
|
|
||||||
}
|
|
||||||
printf(RESET);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (*ptr == '`' && *(ptr + 1) != '`') {
|
|
||||||
printf(FG_YELLOW);
|
|
||||||
ptr++;
|
|
||||||
const char *start = ptr;
|
|
||||||
while (*ptr && *ptr != '`')
|
|
||||||
ptr++;
|
|
||||||
fwrite(start, 1, ptr - start, stdout);
|
|
||||||
if (*ptr == '`')
|
|
||||||
ptr++;
|
|
||||||
printf(RESET);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (*ptr == '[') {
|
|
||||||
const char *text_start = ptr + 1;
|
|
||||||
const char *text_end = strchr(text_start, ']');
|
|
||||||
if (text_end && *(text_end + 1) == '(') {
|
|
||||||
const char *url_start = text_end + 2;
|
|
||||||
const char *url_end = strchr(url_start, ')');
|
|
||||||
if (url_end) {
|
|
||||||
printf(FG_BLUE);
|
|
||||||
fwrite(text_start, 1, text_end - text_start, stdout);
|
|
||||||
printf(RESET " (");
|
|
||||||
printf(ITALIC FG_CYAN);
|
|
||||||
fwrite(url_start, 1, url_end - url_start, stdout);
|
|
||||||
printf(RESET ")");
|
|
||||||
ptr = url_end + 1;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Default Character ---
|
|
||||||
if (*ptr == '\n') {
|
|
||||||
is_start_of_line = true;
|
|
||||||
} else if (!isspace((unsigned char)*ptr)) {
|
|
||||||
is_start_of_line = false;
|
|
||||||
}
|
|
||||||
putchar(*ptr);
|
|
||||||
ptr++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
260
messages.h
260
messages.h
@ -1,260 +0,0 @@
|
|||||||
// Written by retoor@molodetz.nl
|
|
||||||
|
|
||||||
// This code manages a collection of messages using JSON objects. It provides
|
|
||||||
// functions to retrieve all messages as a JSON array, add a new message with a
|
|
||||||
// specified role and content, and free the allocated resources.
|
|
||||||
|
|
||||||
// Uses the external library <json-c/json.h> for JSON manipulation
|
|
||||||
|
|
||||||
// MIT License
|
|
||||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
// of this software and associated documentation files (the "Software"), to deal
|
|
||||||
// in the Software without restriction, including without limitation the rights
|
|
||||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
// copies of the Software, and to permit persons to whom the Software is
|
|
||||||
// furnished to do so, subject to the following conditions: The above copyright
|
|
||||||
// notice and this permission notice shall be included in all copies or
|
|
||||||
// substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS",
|
|
||||||
// WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
|
||||||
// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
||||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
|
|
||||||
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
|
||||||
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR
|
|
||||||
// THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
|
|
||||||
#ifndef R_MESSAGES_H
|
|
||||||
#define R_MESSAGES_H
|
|
||||||
|
|
||||||
#include "db_utils.h"
|
|
||||||
#include "json-c/json.h"
|
|
||||||
#include "tools.h"
|
|
||||||
#include "utils.h"
|
|
||||||
#include <string.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
|
|
||||||
struct json_object *message_array = NULL;
|
|
||||||
static char session_id[256] = {0};
|
|
||||||
static char override_session_id[256] = {0};
|
|
||||||
|
|
||||||
struct json_object *message_list();
|
|
||||||
|
|
||||||
bool set_session_id(const char *name) {
|
|
||||||
if (name == NULL || *name == '\0') {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
size_t len = strlen(name);
|
|
||||||
if (len > 200) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
for (const char *p = name; *p; p++) {
|
|
||||||
if (*p == '/' || *p == '\\') {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
strncpy(override_session_id, name, sizeof(override_session_id) - 1);
|
|
||||||
override_session_id[sizeof(override_session_id) - 1] = '\0';
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const char *get_session_id() {
|
|
||||||
if (session_id[0] == '\0') {
|
|
||||||
if (override_session_id[0] != '\0') {
|
|
||||||
strncpy(session_id, override_session_id, sizeof(session_id) - 1);
|
|
||||||
session_id[sizeof(session_id) - 1] = '\0';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return session_id;
|
|
||||||
}
|
|
||||||
|
|
||||||
void init_session_id() {
|
|
||||||
if (session_id[0] != '\0') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (override_session_id[0] != '\0') {
|
|
||||||
strncpy(session_id, override_session_id, sizeof(session_id) - 1);
|
|
||||||
session_id[sizeof(session_id) - 1] = '\0';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
char *tty = ttyname(STDIN_FILENO);
|
|
||||||
if (tty) {
|
|
||||||
unsigned long h = 5381;
|
|
||||||
for (char *p = tty; *p; p++) {
|
|
||||||
h = ((h << 5) + h) + (unsigned char)*p;
|
|
||||||
}
|
|
||||||
snprintf(session_id, sizeof(session_id), "%lu", h);
|
|
||||||
} else {
|
|
||||||
snprintf(session_id, sizeof(session_id), "%d", getppid());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
char *get_session_db_key() {
|
|
||||||
static char key[512];
|
|
||||||
init_session_id();
|
|
||||||
snprintf(key, sizeof(key), "session:%s", session_id);
|
|
||||||
return key;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool messages_save() {
|
|
||||||
char *key = get_session_db_key();
|
|
||||||
const char *json_str = json_object_to_json_string_ext(message_list(), JSON_C_TO_STRING_PLAIN);
|
|
||||||
if (!json_str) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
json_object *result = db_set(key, json_str);
|
|
||||||
if (result) {
|
|
||||||
json_object_put(result);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool messages_load_conversation() {
|
|
||||||
char *key = get_session_db_key();
|
|
||||||
json_object *result = db_get(key);
|
|
||||||
if (!result) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
json_object *value_obj;
|
|
||||||
if (!json_object_object_get_ex(result, "value", &value_obj)) {
|
|
||||||
json_object_put(result);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const char *json_str = json_object_get_string(value_obj);
|
|
||||||
if (!json_str) {
|
|
||||||
json_object_put(result);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
struct json_object *loaded = json_tokener_parse(json_str);
|
|
||||||
json_object_put(result);
|
|
||||||
if (!loaded || !json_object_is_type(loaded, json_type_array)) {
|
|
||||||
if (loaded) {
|
|
||||||
json_object_put(loaded);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
int len = json_object_array_length(loaded);
|
|
||||||
int added = 0;
|
|
||||||
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(message_list(), json_object_get(msg));
|
|
||||||
added++;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
json_object_array_add(message_list(), json_object_get(msg));
|
|
||||||
added++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
json_object_put(loaded);
|
|
||||||
return added > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct json_object *message_list() {
|
|
||||||
if (!message_array) {
|
|
||||||
message_array = json_object_new_array();
|
|
||||||
}
|
|
||||||
return message_array;
|
|
||||||
}
|
|
||||||
bool messages_remove_last() {
|
|
||||||
struct json_object *messages = message_list();
|
|
||||||
int size = json_object_array_length(messages);
|
|
||||||
if (size) {
|
|
||||||
json_object_array_del_idx(messages, size - 1, 1);
|
|
||||||
messages_save();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void messages_remove() {
|
|
||||||
if (message_array) {
|
|
||||||
json_object_put(message_array);
|
|
||||||
message_array = NULL;
|
|
||||||
}
|
|
||||||
message_list();
|
|
||||||
messages_save();
|
|
||||||
}
|
|
||||||
|
|
||||||
struct json_object *message_add_tool_call(struct json_object *message) {
|
|
||||||
struct json_object *messages = message_list();
|
|
||||||
json_object_array_add(messages, message);
|
|
||||||
messages_save();
|
|
||||||
return message;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct json_object *message_add_tool_result(const char *tool_call_id,
|
|
||||||
char *tool_result) {
|
|
||||||
struct json_object *messages = message_list();
|
|
||||||
struct json_object *message = json_object_new_object();
|
|
||||||
if (message == NULL) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
json_object_object_add(message, "tool_call_id",
|
|
||||||
json_object_new_string(tool_call_id));
|
|
||||||
|
|
||||||
size_t result_len = strlen(tool_result);
|
|
||||||
if (result_len > 104000) {
|
|
||||||
result_len = 104000;
|
|
||||||
}
|
|
||||||
json_object_object_add(message, "tool_result",
|
|
||||||
json_object_new_string_len(tool_result, (int)result_len));
|
|
||||||
|
|
||||||
json_object_array_add(messages, message);
|
|
||||||
messages_save();
|
|
||||||
return message;
|
|
||||||
}
|
|
||||||
|
|
||||||
void message_add_object(json_object *message) {
|
|
||||||
struct json_object *messages = message_list();
|
|
||||||
json_object_array_add(messages, message);
|
|
||||||
messages_save();
|
|
||||||
}
|
|
||||||
|
|
||||||
struct json_object *message_add(const char *role, const char *content);
|
|
||||||
|
|
||||||
struct json_object *message_add(const char *role, const char *content) {
|
|
||||||
struct json_object *messages = message_list();
|
|
||||||
struct json_object *message = json_object_new_object();
|
|
||||||
if (message == NULL) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
json_object_object_add(message, "role", json_object_new_string(role));
|
|
||||||
|
|
||||||
if (content) {
|
|
||||||
size_t content_len = strlen(content);
|
|
||||||
if (content_len > 1048570) {
|
|
||||||
content_len = 1048570;
|
|
||||||
}
|
|
||||||
json_object_object_add(message, "content",
|
|
||||||
json_object_new_string_len(content, (int)content_len));
|
|
||||||
}
|
|
||||||
if (!strcmp(role, "user")) {
|
|
||||||
json_object_object_add(message, "tools", tools_descriptions());
|
|
||||||
json_object_object_add(message, "parallel_tool_calls",
|
|
||||||
json_object_new_boolean(true));
|
|
||||||
}
|
|
||||||
|
|
||||||
json_object_array_add(messages, message);
|
|
||||||
if (strcmp(role, "system") != 0) {
|
|
||||||
messages_save();
|
|
||||||
}
|
|
||||||
return message;
|
|
||||||
}
|
|
||||||
|
|
||||||
char *message_json() {
|
|
||||||
return (char *)json_object_to_json_string_ext(message_list(),
|
|
||||||
JSON_C_TO_STRING_PRETTY);
|
|
||||||
}
|
|
||||||
|
|
||||||
void message_free() {
|
|
||||||
if (message_array) {
|
|
||||||
json_object_put(message_array);
|
|
||||||
message_array = NULL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
144
openai.h
144
openai.h
@ -1,144 +0,0 @@
|
|||||||
// Written by retoor@molodetz.nl
|
|
||||||
|
|
||||||
// This code interacts with OpenAI's API to perform various tasks such as
|
|
||||||
// fetching models, sending chat messages, and processing responses.
|
|
||||||
|
|
||||||
// Uncommon imports include "http.h", "chat.h", and "http_curl.h". These may be
|
|
||||||
// internal or external libraries providing HTTP and JSON communication
|
|
||||||
// capabilities required to interact with APIs.
|
|
||||||
|
|
||||||
// MIT License
|
|
||||||
//
|
|
||||||
// Copyright (c) 2023
|
|
||||||
//
|
|
||||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
// of this software and associated documentation files (the "Software"), to deal
|
|
||||||
// in the Software without restriction, including without limitation the rights
|
|
||||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
// copies of the Software, and to permit persons to whom the Software is
|
|
||||||
// furnished to do so, subject to the following conditions:
|
|
||||||
//
|
|
||||||
// The above copyright notice and this permission notice shall be included in
|
|
||||||
// all copies or substantial portions of the Software.
|
|
||||||
//
|
|
||||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
// SOFTWARE.
|
|
||||||
|
|
||||||
#ifndef R_OPENAI_H
|
|
||||||
#define R_OPENAI_H
|
|
||||||
#include "chat.h"
|
|
||||||
#include "http_curl.h"
|
|
||||||
#include "r.h"
|
|
||||||
#include <stdbool.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
char *openai_fetch_models() { return curl_get(get_models_api_url()); }
|
|
||||||
|
|
||||||
bool openai_system(char *message_content) {
|
|
||||||
chat_json("system", message_content);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct json_object *openai_process_chat_message(const char *api_url,
|
|
||||||
const char *json_data) {
|
|
||||||
char *response = curl_post(api_url, json_data);
|
|
||||||
if (!response) {
|
|
||||||
fprintf(stderr, "Failed to get response.\n");
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
struct json_object *parsed_json = json_tokener_parse(response);
|
|
||||||
if (!parsed_json) {
|
|
||||||
fprintf(stderr, "Failed to parse JSON.\nContent: \"%s\"\n", response);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
struct json_object *error_object;
|
|
||||||
if (json_object_object_get_ex(parsed_json, "error", &error_object) &&
|
|
||||||
message_array) {
|
|
||||||
|
|
||||||
const char *all_messages = json_object_to_json_string(message_array);
|
|
||||||
|
|
||||||
fprintf(stderr, "Messages: ");
|
|
||||||
if (all_messages) {
|
|
||||||
fwrite(all_messages, strlen(all_messages), 1, stderr);
|
|
||||||
}
|
|
||||||
fprintf(stderr, "\n");
|
|
||||||
|
|
||||||
fprintf(stderr, "%s\n", json_object_to_json_string(parsed_json));
|
|
||||||
|
|
||||||
json_object_put(parsed_json);
|
|
||||||
messages_remove_last();
|
|
||||||
messages_remove_last();
|
|
||||||
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct json_object *choices_array;
|
|
||||||
if (!json_object_object_get_ex(parsed_json, "choices", &choices_array)) {
|
|
||||||
fprintf(stderr, "Failed to get 'choices' array.\n%s\n", response);
|
|
||||||
fprintf(stderr, "%s\n", json_object_to_json_string(parsed_json));
|
|
||||||
json_object_put(parsed_json);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
struct json_object *first_choice =
|
|
||||||
json_object_array_get_idx(choices_array, 0);
|
|
||||||
if (!first_choice) {
|
|
||||||
fprintf(stderr, "Failed to get the first element of 'choices'.\n");
|
|
||||||
json_object_put(parsed_json);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
struct json_object *message_object;
|
|
||||||
if (!json_object_object_get_ex(first_choice, "message", &message_object)) {
|
|
||||||
fprintf(stderr, "Failed to get 'message' object.\n");
|
|
||||||
json_object_put(parsed_json);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
return message_object;
|
|
||||||
}
|
|
||||||
|
|
||||||
char *openai_chat(const char *user_role, const char *message_content) {
|
|
||||||
if (message_content == NULL || *message_content == '\0' ||
|
|
||||||
*message_content == '\n') {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
char *json_data = chat_json(user_role, message_content);
|
|
||||||
|
|
||||||
struct json_object *message_object =
|
|
||||||
openai_process_chat_message(get_completions_api_url(), json_data);
|
|
||||||
|
|
||||||
if (message_object == NULL) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
message_add_object(message_object);
|
|
||||||
struct json_object *tool_calls;
|
|
||||||
json_object_object_get_ex(message_object, "tool_calls", &tool_calls);
|
|
||||||
if (tool_calls) {
|
|
||||||
// message_add_tool_call(message_object);
|
|
||||||
struct json_object *tool_call_results = tools_execute(tool_calls);
|
|
||||||
int results_count = json_object_array_length(tool_call_results);
|
|
||||||
for (int i = 0; i < results_count; i++) {
|
|
||||||
struct json_object *tool_call_result =
|
|
||||||
json_object_array_get_idx(tool_call_results, i);
|
|
||||||
message_add_tool_call(tool_call_result);
|
|
||||||
}
|
|
||||||
char *tool_calls_result_str = chat_json(NULL, NULL);
|
|
||||||
message_object = openai_process_chat_message(get_completions_api_url(),
|
|
||||||
tool_calls_result_str);
|
|
||||||
if (message_object == NULL) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
message_add_object(message_object);
|
|
||||||
// message_add_tool_call(message_object);
|
|
||||||
}
|
|
||||||
const char *content_str =
|
|
||||||
json_object_get_string(json_object_object_get(message_object, "content"));
|
|
||||||
return strdup(content_str);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
62
plugin.h
62
plugin.h
@ -1,62 +0,0 @@
|
|||||||
// Written by retoor@molodetz.nl
|
|
||||||
|
|
||||||
// This source code initializes a Python interpreter within a plugin, executes a
|
|
||||||
// provided Python script with some basic imports, and finalizes the Python
|
|
||||||
// environment when done.
|
|
||||||
|
|
||||||
// This code does not use any non-standard imports or includes aside from
|
|
||||||
// Python.h and structmember.h which are part of Python's C API.
|
|
||||||
|
|
||||||
// MIT License
|
|
||||||
|
|
||||||
#include <Python.h>
|
|
||||||
#include <stdbool.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <structmember.h>
|
|
||||||
|
|
||||||
bool plugin_initialized = false;
|
|
||||||
|
|
||||||
bool plugin_construct() {
|
|
||||||
if (plugin_initialized)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
Py_Initialize();
|
|
||||||
if (!Py_IsInitialized()) {
|
|
||||||
fprintf(stderr, "Failed to initialize the Python interpreter\n");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
plugin_initialized = true;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void plugin_run(char *src) {
|
|
||||||
plugin_construct();
|
|
||||||
/*const char *basics =
|
|
||||||
"import sys\n"
|
|
||||||
"import os\n"
|
|
||||||
"from os import *\n"
|
|
||||||
"import math\n"
|
|
||||||
"import pathlib\n"
|
|
||||||
"from pathlib import Path\n"
|
|
||||||
"import re\n"
|
|
||||||
"import subprocess\n"
|
|
||||||
"from subprocess import *\n"
|
|
||||||
"import time\n"
|
|
||||||
"from datetime import datetime\n"
|
|
||||||
"%s";
|
|
||||||
*/
|
|
||||||
const char *basics = "\n\n";
|
|
||||||
size_t length = strlen(basics) + strlen(src);
|
|
||||||
char *script = malloc(length + 1);
|
|
||||||
sprintf(script, basics, src);
|
|
||||||
script[length] = '\0';
|
|
||||||
PyRun_SimpleString(script);
|
|
||||||
free(script);
|
|
||||||
}
|
|
||||||
|
|
||||||
void plugin_destruct() {
|
|
||||||
if (plugin_initialized)
|
|
||||||
Py_Finalize();
|
|
||||||
}
|
|
||||||
126
r.h
126
r.h
@ -1,126 +0,0 @@
|
|||||||
#ifndef R_H
|
|
||||||
#define R_H
|
|
||||||
#include "auth.h"
|
|
||||||
#include "utils.h"
|
|
||||||
#include <stdbool.h>
|
|
||||||
#include <string.h>
|
|
||||||
bool is_verbose = false;
|
|
||||||
|
|
||||||
char *models_api_url = "https://api.openai.com/v1/models";
|
|
||||||
char *completions_api_url = "https://api.openai.com/v1/chat/completions";
|
|
||||||
char *advanced_model = "gpt-4o-mini";
|
|
||||||
char *fast_model = "gpt-3.5-turbo";
|
|
||||||
|
|
||||||
// char *models_api_url = "https://api.openai.com/v1/models";
|
|
||||||
// char *completions_api_url = "https://api.anthropic.com/v1/chat/completions";
|
|
||||||
// char *advanced_model = "claude-3-5-haiku-20241022";
|
|
||||||
// char *advanced_model = "meta-llama/Meta-Llama-3.1-8B-Instruct";
|
|
||||||
// char *advanced_model = "google/gemini-1.5-flash";
|
|
||||||
// char *fast_model = "claude-3-5-haiku-20241022";
|
|
||||||
|
|
||||||
// #endif
|
|
||||||
// #ifdef OLLAMA
|
|
||||||
// char *models_api_url = "https://ollama.molodetz.nl/v1/models";
|
|
||||||
// char *completions_api_url = "https://ollama.molodetz.nl/v1/chat/completions";
|
|
||||||
// char *advanced_model = "qwen2.5:3b";
|
|
||||||
// char *advanced_model = "qwen2.5-coder:0.5b";
|
|
||||||
// char *fast_model = "qwen2.5:0.5b";
|
|
||||||
// #endif
|
|
||||||
|
|
||||||
char *_model = NULL;
|
|
||||||
|
|
||||||
#define DB_FILE "~/.r.db"
|
|
||||||
#define PROMPT_TEMPERATURE 0.1
|
|
||||||
|
|
||||||
bool use_tools() {
|
|
||||||
if (getenv("R_USE_TOOLS") != NULL) {
|
|
||||||
const char *value = getenv("R_USE_TOOLS");
|
|
||||||
if (!strcmp(value, "true")) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (!strcmp(value, "false")) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!strcmp(value, "1")) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (!strcmp(value, "0")) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
char *get_env_system_message() {
|
|
||||||
if (getenv("R_SYSTEM_MESSAGE") != NULL) {
|
|
||||||
return strdup(getenv("R_SYSTEM_MESSAGE"));
|
|
||||||
}
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
char *get_env_session() {
|
|
||||||
char *session = getenv("R_SESSION");
|
|
||||||
return (session && *session) ? session : NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool get_use_strict() {
|
|
||||||
if (getenv("R_USE_STRICT") != NULL) {
|
|
||||||
const char *value = getenv("R_USE_STRICT");
|
|
||||||
if (!strcmp(value, "true")) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (!strcmp(value, "false")) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!strcmp(value, "1")) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (!strcmp(value, "0")) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
char *get_completions_api_url() {
|
|
||||||
if (getenv("R_BASE_URL") != NULL) {
|
|
||||||
char * path = joinpath(getenv("R_BASE_URL"), "v1/chat/completions");
|
|
||||||
printf("%s\n",path);
|
|
||||||
|
|
||||||
return path;
|
|
||||||
}
|
|
||||||
printf("%s\n",completions_api_url);
|
|
||||||
return completions_api_url;
|
|
||||||
}
|
|
||||||
char *get_models_api_url() {
|
|
||||||
if (getenv("R_BASE_URL") != NULL) {
|
|
||||||
return joinpath(getenv("R_BASE_URL"), "v1/models");
|
|
||||||
}
|
|
||||||
return models_api_url;
|
|
||||||
}
|
|
||||||
|
|
||||||
void set_prompt_model(const char *model) {
|
|
||||||
if (_model != NULL) {
|
|
||||||
free(_model);
|
|
||||||
}
|
|
||||||
_model = strdup(model);
|
|
||||||
}
|
|
||||||
|
|
||||||
const char *get_prompt_model() {
|
|
||||||
if (_model == NULL && getenv("R_MODEL") != NULL) {
|
|
||||||
_model = strdup(getenv("R_MODEL"));
|
|
||||||
}
|
|
||||||
if (_model) {
|
|
||||||
return _model;
|
|
||||||
}
|
|
||||||
if (auth_type != AUTH_TYPE_API_KEY) {
|
|
||||||
if (_model == NULL) {
|
|
||||||
_model = strdup(fast_model);
|
|
||||||
}
|
|
||||||
} else if (_model == NULL) {
|
|
||||||
_model = strdup(advanced_model);
|
|
||||||
}
|
|
||||||
return _model;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
112
rpylib.c
112
rpylib.c
@ -1,112 +0,0 @@
|
|||||||
/* Written by retoor@molodetz.nl */
|
|
||||||
|
|
||||||
/*
|
|
||||||
This C extension for Python provides a simple API for communication with an
|
|
||||||
OpenAI service. It includes functions to return a "Hello World" string, conduct
|
|
||||||
a chat session through OpenAI, and reset the message history.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
Summary of used imports:
|
|
||||||
- <Python.h>: Includes necessary Python headers to create a C extension.
|
|
||||||
- "openai.h": Assumes an external library for OpenAI interaction.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
MIT License
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#define PY_SSIZE_T_CLEAN
|
|
||||||
#include "auth.h"
|
|
||||||
#include "openai.h"
|
|
||||||
#include <Python.h>
|
|
||||||
|
|
||||||
static PyObject *rpylib_reset(PyObject *self, PyObject *args) {
|
|
||||||
return PyUnicode_FromString("True");
|
|
||||||
}
|
|
||||||
|
|
||||||
static PyObject *rpylib_chat(PyObject *self, PyObject *args) {
|
|
||||||
const char *role, *message;
|
|
||||||
|
|
||||||
if (!PyArg_ParseTuple(args, "ss", &role, &message)) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
char *result = openai_chat(role, message);
|
|
||||||
if (!result) {
|
|
||||||
PyErr_SetString(PyExc_RuntimeError, "Failed to get response from OpenAI.");
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
PyObject *py_result = PyUnicode_FromString(result);
|
|
||||||
free(result);
|
|
||||||
return py_result;
|
|
||||||
}
|
|
||||||
|
|
||||||
static PyObject *rpylib_prompt(PyObject *self, PyObject *args) {
|
|
||||||
const char *role = "user";
|
|
||||||
const char *message;
|
|
||||||
if (!PyArg_ParseTuple(args, "s", &message)) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
char *result = openai_chat(role, message);
|
|
||||||
if (!result) {
|
|
||||||
PyErr_SetString(PyExc_RuntimeError, "Failed to get response from OpenAI.");
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
PyObject *py_result = PyUnicode_FromString(result);
|
|
||||||
free(result);
|
|
||||||
return py_result;
|
|
||||||
}
|
|
||||||
|
|
||||||
static PyObject *rpylib_system(PyObject *self, PyObject *args) {
|
|
||||||
const char *role = "system";
|
|
||||||
const char *message;
|
|
||||||
if (!PyArg_ParseTuple(args, "s", &message)) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
char *result = openai_chat(role, message);
|
|
||||||
if (!result) {
|
|
||||||
PyErr_SetString(PyExc_RuntimeError, "Failed to get response from OpenAI.");
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
PyObject *py_result = PyUnicode_FromString(result);
|
|
||||||
free(result);
|
|
||||||
return py_result;
|
|
||||||
}
|
|
||||||
|
|
||||||
static PyMethodDef MyModuleMethods[] = {
|
|
||||||
{"chat", rpylib_chat, METH_VARARGS, "Chat with OpenAI."},
|
|
||||||
{"reset", rpylib_reset, METH_NOARGS, "Reset message history."},
|
|
||||||
{NULL, NULL, 0, NULL}};
|
|
||||||
|
|
||||||
static struct PyModuleDef rpylib = {
|
|
||||||
PyModuleDef_HEAD_INIT, "rpylib",
|
|
||||||
"R - the power of R in Python, made by retoor.", -1, MyModuleMethods};
|
|
||||||
|
|
||||||
PyMODINIT_FUNC PyInit_rpylib(void) {
|
|
||||||
auth_init();
|
|
||||||
return PyModule_Create(&rpylib);
|
|
||||||
}
|
|
||||||
@ -112,6 +112,10 @@ void agent_set_verbose(agent_handle agent, bool verbose) {
|
|||||||
if (agent) agent->verbose = 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) {
|
agent_state_t agent_get_state(agent_handle agent) {
|
||||||
return agent ? agent->state : AGENT_STATE_ERROR;
|
return agent ? agent->state : AGENT_STATE_ERROR;
|
||||||
}
|
}
|
||||||
|
|||||||
46
src/context_summarizer.c
Normal file
46
src/context_summarizer.c
Normal file
@ -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;
|
||||||
|
}
|
||||||
12
src/context_summarizer.h
Normal file
12
src/context_summarizer.h
Normal file
@ -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
|
||||||
146
src/core/buffer.c
Normal file
146
src/core/buffer.c
Normal file
@ -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);
|
||||||
|
}
|
||||||
28
src/core/buffer.h
Normal file
28
src/core/buffer.h
Normal file
@ -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
|
||||||
107
src/core/memory.c
Normal file
107
src/core/memory.c
Normal file
@ -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);
|
||||||
|
}
|
||||||
31
src/core/memory.h
Normal file
31
src/core/memory.h
Normal file
@ -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
|
||||||
259
src/core/string.c
Normal file
259
src/core/string.c
Normal file
@ -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);
|
||||||
|
}
|
||||||
37
src/core/string.h
Normal file
37
src/core/string.h
Normal file
@ -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
|
||||||
10
src/db.c
10
src/db.c
@ -83,6 +83,16 @@ r_status_t db_init(db_handle db) {
|
|||||||
" session_key TEXT PRIMARY KEY,"
|
" session_key TEXT PRIMARY KEY,"
|
||||||
" data TEXT NOT NULL,"
|
" data TEXT NOT NULL,"
|
||||||
" updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP"
|
" 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;
|
char *err_msg = NULL;
|
||||||
|
|||||||
167
src/impl/db_sqlite.c
Normal file
167
src/impl/db_sqlite.c
Normal file
@ -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;
|
||||||
|
}
|
||||||
164
src/impl/http_curl.c
Normal file
164
src/impl/http_curl.c
Normal file
@ -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);
|
||||||
|
}
|
||||||
188
src/interfaces/config.c
Normal file
188
src/interfaces/config.c
Normal file
@ -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;
|
||||||
|
}
|
||||||
30
src/interfaces/config.h
Normal file
30
src/interfaces/config.h
Normal file
@ -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
|
||||||
27
src/interfaces/database.h
Normal file
27
src/interfaces/database.h
Normal file
@ -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
|
||||||
40
src/interfaces/http.h
Normal file
40
src/interfaces/http.h
Normal file
@ -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
|
||||||
66
src/interfaces/logger.c
Normal file
66
src/interfaces/logger.c
Normal file
@ -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);
|
||||||
|
}
|
||||||
41
src/interfaces/logger.h
Normal file
41
src/interfaces/logger.h
Normal file
@ -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 "
|
"- You can call multiple tools in sequence to accomplish complex "
|
||||||
"tasks\n\n"
|
"tasks\n\n"
|
||||||
"- Take decissions based on tool output yourself autonomously."
|
"- 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"
|
"## CRITICAL OUTPUT RULES\n"
|
||||||
"- You MUST include the actual content/data from tool results in your "
|
"- You MUST include the actual content/data from tool results in your "
|
||||||
"response\n"
|
"response\n"
|
||||||
|
|||||||
146
src/tools/tool_agent.c
Normal file
146
src/tools/tool_agent.c
Normal file
@ -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
src/util/path.c
Normal file
153
src/util/path.c
Normal file
@ -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;
|
||||||
|
}
|
||||||
19
src/util/path.h
Normal file
19
src/util/path.h
Normal file
@ -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
|
||||||
50
src/util/time.c
Normal file
50
src/util/time.c
Normal file
@ -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);
|
||||||
|
}
|
||||||
14
src/util/time.h
Normal file
14
src/util/time.h
Normal file
@ -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
|
||||||
102
url.h
102
url.h
@ -1,102 +0,0 @@
|
|||||||
// Written by retoor@molodetz.nl
|
|
||||||
|
|
||||||
// This code defines a URL parser in C that extracts components such as the
|
|
||||||
// scheme, hostname, port, path, and query from a given URL.
|
|
||||||
|
|
||||||
// Includes:
|
|
||||||
// - <stdio.h> for standard I/O operations
|
|
||||||
// - <string.h> for string manipulation functions
|
|
||||||
// - <stdlib.h> for memory allocation and management
|
|
||||||
|
|
||||||
// MIT License
|
|
||||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
// of this software and associated documentation files (the "Software"), to deal
|
|
||||||
// in the Software without restriction, including without limitation the rights
|
|
||||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
// copies of the Software, and to permit persons to whom the Software is
|
|
||||||
// furnished to do so, subject to the following conditions: The above copyright
|
|
||||||
// notice and this permission notice shall be included in all copies or
|
|
||||||
// substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS",
|
|
||||||
// WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
|
||||||
// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
||||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
|
|
||||||
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
|
||||||
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR
|
|
||||||
// THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
|
|
||||||
#ifndef R_URL_H
|
|
||||||
#define R_URL_H
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
char scheme[16];
|
|
||||||
char hostname[256];
|
|
||||||
char port[8];
|
|
||||||
char path[512];
|
|
||||||
char query[512];
|
|
||||||
} url_t;
|
|
||||||
|
|
||||||
int parse_url(const char *url, url_t *parsed_url) {
|
|
||||||
memset(parsed_url, 0, sizeof(url_t));
|
|
||||||
|
|
||||||
const char *scheme_end = strstr(url, "://");
|
|
||||||
if (!scheme_end) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
strncpy(parsed_url->scheme, url, scheme_end - url);
|
|
||||||
parsed_url->scheme[scheme_end - url] = '\0';
|
|
||||||
|
|
||||||
const char *hostname_start = scheme_end + 3;
|
|
||||||
const char *port_start = strchr(hostname_start, ':');
|
|
||||||
const char *path_start = strchr(hostname_start, '/');
|
|
||||||
const char *query_start = strchr(hostname_start, '?');
|
|
||||||
|
|
||||||
if (port_start && (!path_start || port_start < path_start)) {
|
|
||||||
size_t hostname_length = port_start - hostname_start;
|
|
||||||
if (hostname_length >= sizeof(parsed_url->hostname)) {
|
|
||||||
fprintf(stderr, "Hostname is too long\n");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
strncpy(parsed_url->hostname, hostname_start, hostname_length);
|
|
||||||
parsed_url->hostname[hostname_length] = '\0';
|
|
||||||
|
|
||||||
size_t port_length = query_start ? (size_t)(query_start - port_start - 1)
|
|
||||||
: strlen(port_start + 1);
|
|
||||||
if (port_length >= sizeof(parsed_url->port)) {
|
|
||||||
fprintf(stderr, "Port value is too long\n");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
strncpy(parsed_url->port, port_start + 1, port_length);
|
|
||||||
parsed_url->port[port_length] = '\0';
|
|
||||||
} else {
|
|
||||||
size_t hostname_length =
|
|
||||||
path_start ? (size_t)(path_start - hostname_start)
|
|
||||||
: (query_start ? (size_t)(query_start - hostname_start)
|
|
||||||
: strlen(hostname_start));
|
|
||||||
if (hostname_length >= sizeof(parsed_url->hostname)) {
|
|
||||||
fprintf(stderr, "Hostname is too long\n");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
strncpy(parsed_url->hostname, hostname_start, hostname_length);
|
|
||||||
parsed_url->hostname[hostname_length] = '\0';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (path_start) {
|
|
||||||
if (query_start) {
|
|
||||||
strncpy(parsed_url->path, path_start, query_start - path_start);
|
|
||||||
parsed_url->path[query_start - path_start] = '\0';
|
|
||||||
} else {
|
|
||||||
strcpy(parsed_url->path, path_start);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (query_start) {
|
|
||||||
strcpy(parsed_url->query, query_start + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
146
utils.h
146
utils.h
@ -1,146 +0,0 @@
|
|||||||
// Written by retoor@molodetz.nl
|
|
||||||
|
|
||||||
// This header file contains utility functions for manipulating file system
|
|
||||||
// paths, focusing primarily on expanding paths starting with '~' to the home
|
|
||||||
// directory.
|
|
||||||
|
|
||||||
// This code uses standard libraries: stdio.h, stdlib.h, and string.h, and
|
|
||||||
// conditionally includes the posix libraries pwd.h and unistd.h when expanding
|
|
||||||
// the home directory manually.
|
|
||||||
|
|
||||||
// MIT License
|
|
||||||
//
|
|
||||||
// Permission is granted to use, copy, modify, merge, distribute, sublicense,
|
|
||||||
// and/or sell copies of the Software. The license includes conditions about
|
|
||||||
// providing a copy of the license and the limitation of liability and warranty.
|
|
||||||
|
|
||||||
#ifndef UTILS_H
|
|
||||||
#define UTILS_H
|
|
||||||
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
#include <windows.h>
|
|
||||||
#else
|
|
||||||
#include <limits.h>
|
|
||||||
#include <pwd.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
void get_current_directory() {
|
|
||||||
char buffer[PATH_MAX];
|
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
DWORD length = GetCurrentDirectory(PATH_MAX, buffer);
|
|
||||||
if (length > 0 && length < PATH_MAX) {
|
|
||||||
printf("Current Directory: %s\n", buffer);
|
|
||||||
} else {
|
|
||||||
printf("Error getting current directory.\n");
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
if (getcwd(buffer, sizeof(buffer)) != NULL) {
|
|
||||||
printf("Current Directory: %s\n", buffer);
|
|
||||||
} else {
|
|
||||||
perror("Error getting current directory");
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
char *expand_home_directory(const char *path) {
|
|
||||||
if (path[0] != '~') {
|
|
||||||
return strdup(path); // Return the original path if it doesn't start with ~
|
|
||||||
}
|
|
||||||
|
|
||||||
char *home_dir;
|
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
home_dir = getenv("USERPROFILE"); // Get home directory on Windows
|
|
||||||
#else
|
|
||||||
struct passwd *pw = getpwuid(getuid());
|
|
||||||
home_dir = pw->pw_dir; // Get home directory on Linux
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if (home_dir == NULL) {
|
|
||||||
return NULL; // Error getting home directory
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t home_len = strlen(home_dir);
|
|
||||||
size_t path_len = strlen(path + 1);
|
|
||||||
size_t expanded_size = home_len + path_len + 1;
|
|
||||||
char *expanded_path = (char *)malloc(expanded_size);
|
|
||||||
if (expanded_path == NULL) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
memcpy(expanded_path, home_dir, home_len);
|
|
||||||
memcpy(expanded_path + home_len, path + 1, path_len);
|
|
||||||
expanded_path[home_len + path_len] = '\0';
|
|
||||||
return expanded_path;
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned long hash(const char *str) {
|
|
||||||
unsigned long hash = 5381; // Starting value
|
|
||||||
int c;
|
|
||||||
|
|
||||||
while ((c = *str++)) {
|
|
||||||
hash = ((hash << 5) + hash) + c; // hash * 33 + c
|
|
||||||
}
|
|
||||||
|
|
||||||
return hash;
|
|
||||||
}
|
|
||||||
|
|
||||||
char *joinpath(const char *base_url, const char *path) {
|
|
||||||
static char result[4096];
|
|
||||||
size_t base_len = strlen(base_url);
|
|
||||||
size_t pos = 0;
|
|
||||||
|
|
||||||
if (base_len >= sizeof(result) - 2) {
|
|
||||||
base_len = sizeof(result) - 2;
|
|
||||||
}
|
|
||||||
memcpy(result, base_url, base_len);
|
|
||||||
pos = base_len;
|
|
||||||
|
|
||||||
if (pos > 0 && result[pos - 1] != '/') {
|
|
||||||
result[pos++] = '/';
|
|
||||||
}
|
|
||||||
if (path[0] == '/') {
|
|
||||||
path++;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t path_len = strlen(path);
|
|
||||||
if (pos + path_len >= sizeof(result)) {
|
|
||||||
path_len = sizeof(result) - pos - 1;
|
|
||||||
}
|
|
||||||
memcpy(result + pos, path, path_len);
|
|
||||||
result[pos + path_len] = '\0';
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
char *read_file(const char *path) {
|
|
||||||
char *expanded_path = expand_home_directory(path);
|
|
||||||
if (expanded_path == NULL) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
FILE *file = fopen(expanded_path, "r");
|
|
||||||
free(expanded_path);
|
|
||||||
if (file == NULL) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
fseek(file, 0, SEEK_END);
|
|
||||||
long size = ftell(file);
|
|
||||||
fseek(file, 0, SEEK_SET);
|
|
||||||
|
|
||||||
char *buffer = (char *)malloc(size + 1);
|
|
||||||
size_t read = fread(buffer, 1, size, file);
|
|
||||||
if (read == 0) {
|
|
||||||
free(buffer);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
fclose(file);
|
|
||||||
buffer[read] = '\0';
|
|
||||||
return buffer;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
Loading…
Reference in New Issue
Block a user