This commit is contained in:
retoor 2026-01-28 19:47:14 +01:00
parent 3f8e1ec53a
commit 5ee81eb58e

View File

@ -43,381 +43,411 @@ static void cleanup(void);
static void handle_sigint(int sig); static void handle_sigint(int sig);
static char *get_env_string(void) { static char *get_env_string(void) {
FILE *fp = popen("env", "r"); FILE *fp = popen("env", "r");
if (!fp) return NULL; if (!fp)
return NULL;
size_t buffer_size = 1024; size_t buffer_size = 1024;
size_t total_size = 0; size_t total_size = 0;
char *output = malloc(buffer_size); char *output = malloc(buffer_size);
if (!output) { if (!output) {
pclose(fp);
return NULL;
}
size_t bytes_read;
while ((bytes_read = fread(output + total_size, 1, buffer_size - total_size,
fp)) > 0) {
total_size += bytes_read;
if (total_size >= buffer_size) {
buffer_size *= 2;
char *temp = realloc(output, buffer_size);
if (!temp) {
free(output);
pclose(fp); pclose(fp);
return NULL; return NULL;
}
output = temp;
} }
}
size_t bytes_read; output[total_size] = '\0';
while ((bytes_read = fread(output + total_size, 1, buffer_size - total_size, fp)) > 0) { pclose(fp);
total_size += bytes_read; return output;
if (total_size >= buffer_size) {
buffer_size *= 2;
char *temp = realloc(output, buffer_size);
if (!temp) {
free(output);
pclose(fp);
return NULL;
}
output = temp;
}
}
output[total_size] = '\0';
pclose(fp);
return output;
} }
static char *get_prompt_from_stdin(char *prompt) { static char *get_prompt_from_stdin(char *prompt) {
int index = 0; int index = 0;
int c; int c;
while ((c = getchar()) != EOF) { while ((c = getchar()) != EOF) {
prompt[index++] = (char)c; prompt[index++] = (char)c;
} }
prompt[index] = '\0'; prompt[index] = '\0';
return prompt; return prompt;
} }
static char *get_prompt_from_args(int argc, char **argv) { static char *get_prompt_from_args(int argc, char **argv) {
r_config_handle cfg = r_config_get_instance(); r_config_handle cfg = r_config_get_instance();
char *prompt = malloc(10 * 1024 * 1024 + 1); char *prompt = malloc(10 * 1024 * 1024 + 1);
char *system_msg = malloc(1024 * 1024); char *system_msg = malloc(1024 * 1024);
if (!prompt || !system_msg) { if (!prompt || !system_msg) {
free(prompt); free(prompt);
free(system_msg); free(system_msg);
return NULL; return NULL;
} }
system_msg[0] = '\0'; system_msg[0] = '\0';
bool get_from_stdin = false; bool get_from_stdin = false;
for (int i = 1; i < argc; i++) { for (int i = 1; i < argc; i++) {
if (strcmp(argv[i], "--stdin") == 0) { if (strcmp(argv[i], "--stdin") == 0) {
fprintf(stderr, "Reading from stdin.\n"); fprintf(stderr, "Reading from stdin.\n");
get_from_stdin = true; get_from_stdin = true;
} else if (strcmp(argv[i], "--verbose") == 0) { } else if (strcmp(argv[i], "--verbose") == 0) {
r_config_set_verbose(cfg, true); r_config_set_verbose(cfg, true);
} else if (strcmp(argv[i], "--py") == 0 && i + 1 < argc) { } else if (strcmp(argv[i], "--py") == 0 && i + 1 < argc) {
char *py_file_path = expand_home_directory(argv[++i]); char *py_file_path = expand_home_directory(argv[++i]);
fprintf(stderr, "Including \"%s\".\n", py_file_path); fprintf(stderr, "Including \"%s\".\n", py_file_path);
include_file(py_file_path); include_file(py_file_path);
free(py_file_path); free(py_file_path);
} else if (strcmp(argv[i], "--context") == 0 && i + 1 < argc) { } else if (strcmp(argv[i], "--context") == 0 && i + 1 < argc) {
char *context_file_path = argv[++i]; char *context_file_path = argv[++i];
fprintf(stderr, "Including \"%s\".\n", context_file_path); fprintf(stderr, "Including \"%s\".\n", context_file_path);
include_file(context_file_path); include_file(context_file_path);
} else if (strcmp(argv[i], "--api") == 0) { } else if (strcmp(argv[i], "--api") == 0) {
api_mode = true; api_mode = true;
} else if (strcmp(argv[i], "--nh") == 0) { } else if (strcmp(argv[i], "--nh") == 0) {
syntax_highlight_enabled = false; syntax_highlight_enabled = false;
fprintf(stderr, "Syntax highlighting disabled.\n"); fprintf(stderr, "Syntax highlighting disabled.\n");
} else if (strncmp(argv[i], "--session=", 10) == 0) { } else if (strncmp(argv[i], "--session=", 10) == 0) {
continue; continue;
} else if (strcmp(argv[i], "-s") == 0 || strcmp(argv[i], "--session") == 0) { } else if (strcmp(argv[i], "-s") == 0 ||
i++; strcmp(argv[i], "--session") == 0) {
continue; i++;
} else { continue;
strcat(system_msg, argv[i]);
strcat(system_msg, (i < argc - 1) ? " " : ".");
}
}
if (get_from_stdin) {
if (*system_msg && global_messages) {
messages_add(global_messages, "system", system_msg);
}
prompt = get_prompt_from_stdin(prompt);
free(system_msg);
} else { } else {
free(prompt); strcat(system_msg, argv[i]);
prompt = system_msg; strcat(system_msg, (i < argc - 1) ? " " : ".");
} }
}
if (!*prompt) { if (get_from_stdin) {
free(prompt); if (*system_msg && global_messages) {
return NULL; messages_add(global_messages, "system", system_msg);
} }
return prompt; prompt = get_prompt_from_stdin(prompt);
free(system_msg);
} else {
free(prompt);
prompt = system_msg;
}
if (!*prompt) {
free(prompt);
return NULL;
}
return prompt;
} }
static bool try_prompt(int argc, char *argv[]) { static bool try_prompt(int argc, char *argv[]) {
char *prompt = get_prompt_from_args(argc, argv); char *prompt = get_prompt_from_args(argc, argv);
if (prompt) { if (prompt) {
char *response = agent_chat(prompt, global_messages); char *response = agent_chat(prompt, global_messages);
if (!response) { if (!response) {
printf("Could not get response from server\n"); printf("Could not get response from server\n");
free(prompt); free(prompt);
return false; return false;
}
render(response);
free(response);
free(prompt);
return true;
} }
return false; render(response);
free(response);
free(prompt);
return true;
}
return false;
} }
static bool include_file(const char *path) { static bool include_file(const char *path) {
char *file_content = read_file(path); char *file_content = read_file(path);
if (!file_content) return false; if (!file_content)
return false;
if (global_messages) { if (global_messages) {
messages_add(global_messages, "system", file_content); messages_add(global_messages, "system", file_content);
} }
free(file_content); free(file_content);
return true; return true;
} }
static void render(const char *content) { static void render(const char *content) {
if (syntax_highlight_enabled) { if (syntax_highlight_enabled) {
parse_markdown_to_ansi(content); parse_markdown_to_ansi(content);
} else { } else {
printf("%s", content); printf("%s", content);
} }
} }
static void repl(void) { static void repl(void) {
r_config_handle cfg = r_config_get_instance(); r_config_handle cfg = r_config_get_instance();
tool_registry_t *tools = tools_get_registry(); tool_registry_t *tools = tools_get_registry();
line_init(); line_init();
char *line = NULL; char *line = NULL;
while (true) { while (true) {
line = line_read("> "); line = line_read("> ");
if (!line || !*line) continue; if (!line || !*line)
continue;
if (!strncmp(line, "!dump", 5)) { if (!strncmp(line, "!dump", 5)) {
char *json = messages_to_string(global_messages); char *json = messages_to_string(global_messages);
if (json) { if (json) {
printf("%s\n", json); printf("%s\n", json);
free(json); free(json);
} }
continue; continue;
}
if (!strncmp(line, "!clear", 6)) {
messages_clear(global_messages);
fprintf(stderr, "Session cleared.\n");
continue;
}
if (!strncmp(line, "!session", 8)) {
printf("Session: %s\n", messages_get_session_id(global_messages));
continue;
}
if (!strncmp(line, "!verbose", 8)) {
bool verbose = !r_config_is_verbose(cfg);
r_config_set_verbose(cfg, verbose);
fprintf(stderr, "%s\n", verbose ? "Verbose mode enabled" : "Verbose mode disabled");
continue;
}
if (line && *line != '\n') {
line_add_history(line);
}
if (!strncmp(line, "!tools", 6)) {
struct json_object *descs = tool_registry_get_descriptions(tools);
printf("Available tools: %s\n", json_object_to_json_string(descs));
continue;
}
if (!strncmp(line, "!models", 7)) {
http_client_handle http = http_client_create(r_config_get_api_key(cfg));
if (http) {
http_client_set_show_spinner(http, false);
char *response = NULL;
if (http_get(http, r_config_get_models_url(cfg), &response) == R_SUCCESS && response) {
printf("Models: %s\n", response);
free(response);
}
http_client_destroy(http);
}
continue;
}
if (!strncmp(line, "!model", 6)) {
if (line[6] == ' ') {
r_config_set_model(cfg, line + 7);
}
printf("Current model: %s\n", r_config_get_model(cfg));
continue;
}
if (!strncmp(line, "exit", 4)) {
exit(0);
}
while (line && *line != '\n') {
char *response = agent_chat(line, global_messages);
if (response) {
render(response);
printf("\n");
if (strstr(response, "_STEP_")) {
line = "continue";
} else {
line = NULL;
}
free(response);
} else {
fprintf(stderr, "Agent returned no response\n");
line = NULL;
}
}
} }
if (!strncmp(line, "!clear", 6)) {
messages_clear(global_messages);
fprintf(stderr, "Session cleared.\n");
continue;
}
if (!strncmp(line, "!session", 8)) {
printf("Session: %s\n", messages_get_session_id(global_messages));
continue;
}
if (!strncmp(line, "!verbose", 8)) {
bool verbose = !r_config_is_verbose(cfg);
r_config_set_verbose(cfg, verbose);
fprintf(stderr, "%s\n",
verbose ? "Verbose mode enabled" : "Verbose mode disabled");
continue;
}
if (line && *line != '\n') {
line_add_history(line);
}
if (!strncmp(line, "!tools", 6)) {
struct json_object *descs = tool_registry_get_descriptions(tools);
printf("Available tools: %s\n", json_object_to_json_string(descs));
continue;
}
if (!strncmp(line, "!models", 7)) {
http_client_handle http = http_client_create(r_config_get_api_key(cfg));
if (http) {
http_client_set_show_spinner(http, false);
char *response = NULL;
if (http_get(http, r_config_get_models_url(cfg), &response) ==
R_SUCCESS &&
response) {
printf("Models: %s\n", response);
free(response);
}
http_client_destroy(http);
}
continue;
}
if (!strncmp(line, "!model", 6)) {
if (line[6] == ' ') {
r_config_set_model(cfg, line + 7);
}
printf("Current model: %s\n", r_config_get_model(cfg));
continue;
}
if (!strncmp(line, "exit", 4)) {
exit(0);
}
while (line && *line != '\n') {
char *response = agent_chat(line, global_messages);
if (response) {
render(response);
printf("\n");
if (strstr(response, "_STEP_")) {
line = "continue";
} else {
line = NULL;
}
free(response);
} else {
fprintf(stderr, "Agent returned no response\n");
line = NULL;
}
}
}
} }
static void init(void) { static void init(void) {
setbuf(stdout, NULL); setbuf(stdout, NULL);
line_init(); line_init();
r_config_handle cfg = r_config_get_instance(); r_config_handle cfg = r_config_get_instance();
global_db = db_open(NULL); global_db = db_open(NULL);
global_messages = messages_create(r_config_get_session_id(cfg)); global_messages = messages_create(r_config_get_session_id(cfg));
char *schema = db_get_schema(global_db); char *schema = db_get_schema(global_db);
char payload[1024 * 1024] = {0}; char payload[1024 * 1024] = {0};
time_t now = time(NULL); time_t now = time(NULL);
struct tm *tm_info = localtime(&now); struct tm *tm_info = localtime(&now);
char datetime[64]; char datetime[64];
strftime(datetime, sizeof(datetime), "%Y-%m-%d %H:%M:%S %Z", tm_info); strftime(datetime, sizeof(datetime), "%Y-%m-%d %H:%M:%S %Z", tm_info);
char cwd[4096]; char cwd[4096];
if (!getcwd(cwd, sizeof(cwd))) { if (!getcwd(cwd, sizeof(cwd))) {
strcpy(cwd, "unknown"); 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"
"5. Do not ask questions, do assumptions autonomously."
"## 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"
"- Take decissions based on tool output yourself autonomously."
"## 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"
"## Reasoning\n"
"Let the user know what you thougt process / reasoning is and why you "
"choose to do things the way you do."
"## Backup\n"
"You have the power to delete and modify files whatever your want to but "
"always make a .bak file with backup before editing unless you've "
"created the file yourself."
"## Terminal execution\n"
"You are in a bash environment, so can also execute bash commands.\n "
"Everything is allowed.\n"
"Be very creative with your bash tool use, use it exessively.\n"
"Prefer commands that do not require root access.\n",
datetime, cwd, schema ? schema : "{}");
free(schema);
fprintf(stderr, "Loading...");
snprintf( if (global_messages) {
payload, sizeof(payload), messages_add(global_messages, "system", payload);
"# AUTONOMOUS AGENT INSTRUCTIONS\n" }
"Current date/time: %s\n"
"Working directory: %s\n\n"
"You are an autonomous AI agent. You operate in a loop: reason about the task, "
"select and execute tools when needed, observe results, and continue until the goal is achieved.\n\n"
"## Reasoning Pattern (ReAct)\n"
"For complex tasks, think step-by-step:\n"
"1. Thought: What do I need to accomplish? What information do I have?\n"
"2. Action: Which tool should I use? With what parameters?\n"
"3. Observation: What did the tool return? What does this tell me?\n"
"4. Repeat until the goal is complete.\n\n"
"## Tool Usage\n"
"- Use tools proactively to gather information and take actions\n"
"- If a tool fails, analyze the error and try a different approach\n"
"- You can call multiple tools in sequence to accomplish complex tasks\n\n"
"## CRITICAL OUTPUT RULES\n"
"- You MUST include the actual content/data from tool results in your response\n"
"- When you search the web, QUOTE the relevant information found\n"
"- When you run a command, SHOW the output\n"
"- NEVER say 'I found information' without showing what you found\n"
"- NEVER say 'task complete' or 'report provided' - SHOW THE ACTUAL DATA\n"
"- The user cannot see tool results - only YOUR response. Include everything relevant.\n\n"
"## Local Database\n"
"You have a local SQLite database accessible via db_query, db_get, and db_set tools.\n"
"Use stemmed, lowercase keys to prevent duplicates.\n"
"Schema: %s\n\n"
"## Response Format\n"
"Your response IS the only thing the user sees. Tool outputs are hidden from them.\n"
"You MUST copy/paste relevant data from tool results into your response.\n"
"Bad: 'I searched and found information about X.'\n"
"Good: 'Here is what I found: [actual content from search results]'\n",
datetime, cwd, schema ? schema : "{}");
free(schema); const char *env_system_msg = r_config_get_system_message(cfg);
fprintf(stderr, "Loading..."); if (env_system_msg && *env_system_msg && global_messages) {
messages_add(global_messages, "system", env_system_msg);
}
if (global_messages) { if (!include_file(".rcontext.txt")) {
messages_add(global_messages, "system", payload); include_file("~/.rcontext.txt");
} }
const char *env_system_msg = r_config_get_system_message(cfg); fprintf(stderr, "\r \r");
if (env_system_msg && *env_system_msg && global_messages) {
messages_add(global_messages, "system", env_system_msg);
}
if (!include_file(".rcontext.txt")) {
include_file("~/.rcontext.txt");
}
fprintf(stderr, "\r \r");
} }
static void cleanup(void) { static void cleanup(void) {
if (global_messages) { if (global_messages) {
messages_destroy(global_messages); messages_destroy(global_messages);
global_messages = NULL; global_messages = NULL;
} }
if (global_db) { if (global_db) {
db_close(global_db); db_close(global_db);
global_db = NULL; global_db = NULL;
} }
tools_registry_shutdown(); tools_registry_shutdown();
r_config_destroy(); r_config_destroy();
} }
static void handle_sigint(int sig) { static void handle_sigint(int sig) {
(void)sig; (void)sig;
time_t current_time = time(NULL); time_t current_time = time(NULL);
printf("\n"); printf("\n");
if (sigint_count == 0) { if (sigint_count == 0) {
first_sigint_time = current_time; first_sigint_time = current_time;
sigint_count++; sigint_count++;
} else {
if (difftime(current_time, first_sigint_time) <= 1) {
cleanup();
exit(0);
} else { } else {
if (difftime(current_time, first_sigint_time) <= 1) { sigint_count = 1;
cleanup(); first_sigint_time = current_time;
exit(0);
} else {
sigint_count = 1;
first_sigint_time = current_time;
}
} }
}
} }
static void parse_session_arg(int argc, char *argv[]) { static void parse_session_arg(int argc, char *argv[]) {
r_config_handle cfg = r_config_get_instance(); r_config_handle cfg = r_config_get_instance();
for (int i = 1; i < argc; i++) { for (int i = 1; i < argc; i++) {
if (strncmp(argv[i], "--session=", 10) == 0) { if (strncmp(argv[i], "--session=", 10) == 0) {
const char *name = argv[i] + 10; const char *name = argv[i] + 10;
if (!r_config_set_session_id(cfg, name)) { if (!r_config_set_session_id(cfg, name)) {
fprintf(stderr, "Error: Invalid session name '%s'\n", name); fprintf(stderr, "Error: Invalid session name '%s'\n", name);
exit(1); exit(1);
} }
return; return;
}
if ((strcmp(argv[i], "-s") == 0 || strcmp(argv[i], "--session") == 0) && i + 1 < argc) {
const char *name = argv[++i];
if (!r_config_set_session_id(cfg, name)) {
fprintf(stderr, "Error: Invalid session name '%s'\n", name);
exit(1);
}
return;
}
} }
if ((strcmp(argv[i], "-s") == 0 || strcmp(argv[i], "--session") == 0) &&
i + 1 < argc) {
const char *name = argv[++i];
if (!r_config_set_session_id(cfg, name)) {
fprintf(stderr, "Error: Invalid session name '%s'\n", name);
exit(1);
}
return;
}
}
} }
int main(int argc, char *argv[]) { int main(int argc, char *argv[]) {
signal(SIGINT, handle_sigint); signal(SIGINT, handle_sigint);
atexit(cleanup); atexit(cleanup);
parse_session_arg(argc, argv); parse_session_arg(argc, argv);
init(); init();
char *env_string = get_env_string(); char *env_string = get_env_string();
if (env_string && *env_string && global_messages) { if (env_string && *env_string && global_messages) {
messages_add(global_messages, "system", env_string); messages_add(global_messages, "system", env_string);
free(env_string); free(env_string);
} }
messages_load(global_messages); messages_load(global_messages);
if (try_prompt(argc, argv)) { if (try_prompt(argc, argv)) {
return 0;
}
repl();
return 0; return 0;
}
repl();
return 0;
} }