Update.
This commit is contained in:
parent
bd31abcbdb
commit
1057624ab8
3
Makefile
3
Makefile
@ -1,7 +1,7 @@
|
||||
# retoor <retoor@molodetz.nl>
|
||||
|
||||
CC = gcc
|
||||
CFLAGS = -Ofast -Werror -Wall -I./include
|
||||
CFLAGS = -O3 -Werror -Wall -Wextra -Wshadow -I./include
|
||||
LDFLAGS = -lreadline -lncurses -lcurl -ljson-c -lsqlite3 -lm -lpthread -lssl -lcrypto
|
||||
|
||||
SRCDIR = src
|
||||
@ -21,6 +21,7 @@ SRC_CORE = $(SRCDIR)/r_error.c \
|
||||
$(SRCDIR)/context_manager.c \
|
||||
$(SRCDIR)/markdown.c \
|
||||
$(SRCDIR)/r_diff.c \
|
||||
$(SRCDIR)/json_repair.c \
|
||||
$(SRCDIR)/main.c
|
||||
|
||||
SRC_TOOLS = $(TOOLSDIR)/tools_init.c \
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
#ifndef R_MESSAGES_H
|
||||
#define R_MESSAGES_H
|
||||
|
||||
#include "db.h"
|
||||
#include "r_error.h"
|
||||
#include <json-c/json.h>
|
||||
#include <stdbool.h>
|
||||
@ -37,4 +38,6 @@ int messages_count(messages_handle msgs);
|
||||
|
||||
char *messages_generate_session_id(void);
|
||||
|
||||
db_handle messages_get_db(messages_handle msgs);
|
||||
|
||||
#endif
|
||||
|
||||
@ -24,7 +24,6 @@ bool r_config_is_verbose(r_config_handle cfg);
|
||||
void r_config_set_verbose(r_config_handle cfg, bool verbose);
|
||||
|
||||
double r_config_get_temperature(r_config_handle cfg);
|
||||
int r_config_get_max_tokens(r_config_handle cfg);
|
||||
|
||||
const char *r_config_get_session_id(r_config_handle cfg);
|
||||
bool r_config_set_session_id(r_config_handle cfg, const char *session_id);
|
||||
|
||||
108
src/agent.c
108
src/agent.c
@ -27,6 +27,7 @@ struct agent_t {
|
||||
int refusal_retry_count;
|
||||
bool tools_were_used;
|
||||
int goal_verification_count;
|
||||
int consecutive_tool_error_iterations;
|
||||
agent_state_t state;
|
||||
time_t start_time;
|
||||
char *last_error;
|
||||
@ -85,29 +86,29 @@ static const char *refusal_phrases[] = {
|
||||
};
|
||||
extern tool_registry_t *tools_get_registry(void);
|
||||
static void agent_update_heartbeat(agent_handle agent) {
|
||||
if (!agent || !agent->agent_id) return;
|
||||
db_handle db = db_open(NULL);
|
||||
if (!agent || !agent->agent_id || !agent->messages) return;
|
||||
db_handle db = messages_get_db(agent->messages);
|
||||
if (!db) return;
|
||||
char *sql = sqlite3_mprintf("UPDATE agents SET last_heartbeat = CURRENT_TIMESTAMP WHERE agent_id = %Q", agent->agent_id);
|
||||
struct json_object *res = NULL;
|
||||
db_execute(db, sql, &res);
|
||||
sqlite3_free(sql);
|
||||
if (res) json_object_put(res);
|
||||
db_close(db);
|
||||
}
|
||||
static bool agent_check_budget(agent_handle agent) {
|
||||
if (!agent || agent->budget_limit <= 0) return true;
|
||||
return agent->used_tokens < agent->budget_limit;
|
||||
}
|
||||
static void agent_add_tokens(agent_handle agent, long tokens) {
|
||||
if (!agent || !agent->agent_id) return;
|
||||
if (!agent || !agent->agent_id || !agent->messages) return;
|
||||
agent->used_tokens += tokens;
|
||||
db_handle db = db_open(NULL);
|
||||
db_handle db = messages_get_db(agent->messages);
|
||||
if (!db) return;
|
||||
char *sql = sqlite3_mprintf("UPDATE agents SET used_tokens = used_tokens + %ld WHERE agent_id = %Q", tokens, agent->agent_id);
|
||||
struct json_object *res = NULL;
|
||||
db_execute(db, sql, &res);
|
||||
sqlite3_free(sql);
|
||||
if (res) json_object_put(res);
|
||||
db_close(db);
|
||||
}
|
||||
static void agent_set_error(agent_handle agent, const char *error) {
|
||||
if (!agent) return;
|
||||
@ -132,8 +133,6 @@ static char *agent_build_request(agent_handle agent, const char *role, const cha
|
||||
json_object_get(messages_to_json(agent->messages)));
|
||||
json_object_object_add(root, "temperature",
|
||||
json_object_new_double(r_config_get_temperature(cfg)));
|
||||
json_object_object_add(root, "max_tokens",
|
||||
json_object_new_int(r_config_get_max_tokens(cfg)));
|
||||
char *result = strdup(json_object_to_json_string_ext(root, JSON_C_TO_STRING_PRETTY));
|
||||
if (agent->verbose && !agent->is_subagent) {
|
||||
fprintf(stderr, "\n[LLM Request]\n%s\n", result);
|
||||
@ -190,6 +189,28 @@ static struct json_object *agent_process_response(agent_handle agent, const char
|
||||
json_object_put(parsed);
|
||||
return NULL;
|
||||
}
|
||||
struct json_object *finish_reason_obj;
|
||||
if (json_object_object_get_ex(first_choice, "finish_reason", &finish_reason_obj)) {
|
||||
const char *finish_reason = json_object_get_string(finish_reason_obj);
|
||||
if (finish_reason && strcmp(finish_reason, "length") == 0) {
|
||||
struct json_object *msg_obj;
|
||||
if (json_object_object_get_ex(first_choice, "message", &msg_obj)) {
|
||||
struct json_object *tc_obj;
|
||||
if (json_object_object_get_ex(msg_obj, "tool_calls", &tc_obj) &&
|
||||
json_object_array_length(tc_obj) > 0) {
|
||||
if (agent->verbose) {
|
||||
fprintf(stderr, "[Agent] Output truncated (finish_reason=length) with pending tool_calls, stripping truncated calls\n");
|
||||
}
|
||||
json_object_object_del(msg_obj, "tool_calls");
|
||||
json_object_object_add(msg_obj, "content",
|
||||
json_object_new_string(
|
||||
"My previous response was truncated due to output length limits. "
|
||||
"I will split large file writes into smaller sections or use "
|
||||
"linux_terminal_execute with heredoc for large content."));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return first_choice;
|
||||
}
|
||||
static bool agent_has_tool_calls(struct json_object *choice) {
|
||||
@ -273,14 +294,6 @@ agent_handle agent_create(const char *goal, messages_handle messages) {
|
||||
agent->agent_id = strdup("Executive-Apex");
|
||||
agent->role = strdup("Executive");
|
||||
agent->budget_limit = 1000000;
|
||||
db_handle db = db_open(NULL);
|
||||
char *sql = sqlite3_mprintf("INSERT OR IGNORE INTO agents (agent_id, role, budget_limit_tokens) VALUES (%Q, %Q, %ld)",
|
||||
agent->agent_id, agent->role, agent->budget_limit);
|
||||
struct json_object *res = NULL;
|
||||
db_execute(db, sql, &res);
|
||||
sqlite3_free(sql);
|
||||
if (res) json_object_put(res);
|
||||
db_close(db);
|
||||
if (messages) {
|
||||
agent->messages = messages;
|
||||
agent->owns_messages = false;
|
||||
@ -311,6 +324,15 @@ agent_handle agent_create(const char *goal, messages_handle messages) {
|
||||
messages_add(agent->messages, "system", system_msg);
|
||||
}
|
||||
}
|
||||
db_handle db = messages_get_db(agent->messages);
|
||||
if (db) {
|
||||
char *sql = sqlite3_mprintf("INSERT OR IGNORE INTO agents (agent_id, role, budget_limit_tokens) VALUES (%Q, %Q, %ld)",
|
||||
agent->agent_id, agent->role, agent->budget_limit);
|
||||
struct json_object *res = NULL;
|
||||
db_execute(db, sql, &res);
|
||||
sqlite3_free(sql);
|
||||
if (res) json_object_put(res);
|
||||
}
|
||||
agent->http = http_client_create(r_config_get_api_key(cfg));
|
||||
if (!agent->http) {
|
||||
if (agent->owns_messages) {
|
||||
@ -325,14 +347,26 @@ agent_handle agent_create(const char *goal, messages_handle messages) {
|
||||
}
|
||||
void agent_destroy(agent_handle agent) {
|
||||
if (!agent) return;
|
||||
if (agent->http) http_client_destroy(agent->http);
|
||||
if (agent->messages && agent->owns_messages) messages_destroy(agent->messages);
|
||||
if (agent->http) {
|
||||
http_client_destroy(agent->http);
|
||||
agent->http = NULL;
|
||||
}
|
||||
if (agent->messages && agent->owns_messages) {
|
||||
messages_destroy(agent->messages);
|
||||
agent->messages = NULL;
|
||||
}
|
||||
free(agent->agent_id);
|
||||
agent->agent_id = NULL;
|
||||
free(agent->role);
|
||||
agent->role = NULL;
|
||||
free(agent->manager_id);
|
||||
agent->manager_id = NULL;
|
||||
free(agent->department);
|
||||
agent->department = NULL;
|
||||
free(agent->goal);
|
||||
agent->goal = NULL;
|
||||
free(agent->last_error);
|
||||
agent->last_error = NULL;
|
||||
free(agent);
|
||||
}
|
||||
void agent_set_max_iterations(agent_handle agent, int max) {
|
||||
@ -388,6 +422,7 @@ char *agent_run(agent_handle agent, const char *user_message) {
|
||||
agent->refusal_retry_count = 0;
|
||||
agent->tools_were_used = false;
|
||||
agent->goal_verification_count = 0;
|
||||
agent->consecutive_tool_error_iterations = 0;
|
||||
if (!user_message || !*user_message) {
|
||||
agent->state = AGENT_STATE_ERROR;
|
||||
agent_set_error(agent, "Empty user message");
|
||||
@ -473,11 +508,15 @@ char *agent_run(agent_handle agent, const char *user_message) {
|
||||
if (new_acc) {
|
||||
accumulated_response = new_acc;
|
||||
if (accumulated_len > 0) {
|
||||
strcat(accumulated_response, "\n");
|
||||
accumulated_response[accumulated_len] = '\n';
|
||||
accumulated_len += 1;
|
||||
}
|
||||
strcpy(accumulated_response + accumulated_len, content);
|
||||
memcpy(accumulated_response + accumulated_len, content, content_len + 1);
|
||||
accumulated_len += content_len;
|
||||
} else {
|
||||
free(accumulated_response);
|
||||
accumulated_response = NULL;
|
||||
accumulated_len = 0;
|
||||
}
|
||||
}
|
||||
bool has_tools = agent_has_tool_calls(choice);
|
||||
@ -494,12 +533,39 @@ char *agent_run(agent_handle agent, const char *user_message) {
|
||||
}
|
||||
struct json_object *results = tool_registry_execute(agent->tools, tool_calls, agent->verbose);
|
||||
int count = json_object_array_length(results);
|
||||
bool all_errors = count > 0;
|
||||
for (int i = 0; i < count; i++) {
|
||||
struct json_object *result = json_object_array_get_idx(results, i);
|
||||
messages_add_tool_call(agent->messages, json_object_get(result));
|
||||
struct json_object *content_obj;
|
||||
if (all_errors && json_object_object_get_ex(result, "content", &content_obj)) {
|
||||
const char *content_str = json_object_get_string(content_obj);
|
||||
if (!content_str || strncmp(content_str, "Error", 5) != 0) {
|
||||
all_errors = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (all_errors) {
|
||||
agent->consecutive_tool_error_iterations++;
|
||||
} else {
|
||||
agent->consecutive_tool_error_iterations = 0;
|
||||
}
|
||||
agent->state = AGENT_STATE_RUNNING;
|
||||
json_data = agent_build_request(agent, NULL, NULL);
|
||||
if (agent->consecutive_tool_error_iterations >= 2) {
|
||||
agent->consecutive_tool_error_iterations = 0;
|
||||
json_data = agent_build_request(agent, "user",
|
||||
"Your last tool calls failed repeatedly. The most likely cause is "
|
||||
"output truncation: your tool call arguments were cut off mid-JSON "
|
||||
"because the content was too large. To fix this:\n"
|
||||
"1. For large file writes, use linux_terminal_execute with cat and "
|
||||
"heredoc (cat << 'HEREDOC_EOF' > file.html) instead of write_file.\n"
|
||||
"2. If you must use write_file, keep the content argument under 4000 "
|
||||
"characters. Split large files into multiple write operations using "
|
||||
"file_edit or append mode.\n"
|
||||
"3. Re-read any error messages carefully and verify argument types.");
|
||||
} else {
|
||||
json_data = agent_build_request(agent, NULL, NULL);
|
||||
}
|
||||
if (!json_data) {
|
||||
agent->state = AGENT_STATE_ERROR;
|
||||
agent_set_error(agent, "Failed to create follow-up JSON");
|
||||
|
||||
@ -33,7 +33,8 @@ r_process_result_t *r_bash_execute_ext(const char *command, int timeout_seconds,
|
||||
char tmp_script[] = "/tmp/r_bash_XXXXXX.sh";
|
||||
int script_fd = mkstemps(tmp_script, 3);
|
||||
if (script_fd == -1) {
|
||||
res->output = strdup("Error: failed to create temp script");
|
||||
char *msg = strdup("Error: failed to create temp script");
|
||||
res->output = msg ? msg : NULL;
|
||||
return res;
|
||||
}
|
||||
dprintf(script_fd, "%s\n", command);
|
||||
@ -41,7 +42,8 @@ r_process_result_t *r_bash_execute_ext(const char *command, int timeout_seconds,
|
||||
int pipe_fds[2];
|
||||
if (pipe(pipe_fds) == -1) {
|
||||
unlink(tmp_script);
|
||||
res->output = strdup("Error: pipe failed");
|
||||
char *msg = strdup("Error: pipe failed");
|
||||
res->output = msg ? msg : NULL;
|
||||
return res;
|
||||
}
|
||||
pid_t pid = fork();
|
||||
@ -49,7 +51,8 @@ r_process_result_t *r_bash_execute_ext(const char *command, int timeout_seconds,
|
||||
close(pipe_fds[0]);
|
||||
close(pipe_fds[1]);
|
||||
unlink(tmp_script);
|
||||
res->output = strdup("Error: fork failed");
|
||||
char *msg = strdup("Error: fork failed");
|
||||
res->output = msg ? msg : NULL;
|
||||
return res;
|
||||
}
|
||||
if (pid == 0) {
|
||||
@ -143,9 +146,10 @@ r_process_result_t *r_bash_execute_ext(const char *command, int timeout_seconds,
|
||||
return res;
|
||||
}
|
||||
char *r_bash_execute(const char *command, bool interactive, int timeout_seconds) {
|
||||
// Legacy support wrapper
|
||||
(void)interactive;
|
||||
r_process_result_t *res = r_bash_execute_ext(command, timeout_seconds, false);
|
||||
char *out = strdup(res->output);
|
||||
if (!res) return NULL;
|
||||
char *out = res->output ? strdup(res->output) : strdup("");
|
||||
r_process_result_free(res);
|
||||
return out;
|
||||
}
|
||||
|
||||
@ -16,10 +16,13 @@ static char *ensure_shebang(const char *text) {
|
||||
}
|
||||
// Heuristic: if it has multiple lines, add shebang
|
||||
if (strchr(text, '\n')) {
|
||||
char *result = malloc(strlen(text) + 32);
|
||||
const char *shebang = "#!/usr/bin/env bash\n";
|
||||
size_t shebang_len = strlen(shebang);
|
||||
size_t text_len = strlen(text);
|
||||
char *result = malloc(shebang_len + text_len + 1);
|
||||
if (!result) return strdup(text);
|
||||
strcpy(result, "#!/usr/bin/env bash\n");
|
||||
strcat(result, text);
|
||||
memcpy(result, shebang, shebang_len);
|
||||
memcpy(result + shebang_len, text, text_len + 1);
|
||||
return result;
|
||||
}
|
||||
return strdup(text);
|
||||
@ -121,8 +124,14 @@ static char *fix_line_issues(const char *src) {
|
||||
else if (line_buf[i] == '\'') single++;
|
||||
else if (line_buf[i] == '"') double_q++;
|
||||
}
|
||||
if (single % 2 == 1 && double_q == 0) strcat(line_buf, "'");
|
||||
else if (double_q % 2 == 1 && single == 0) strcat(line_buf, "\"");
|
||||
size_t buf_len = strlen(line_buf);
|
||||
if (single % 2 == 1 && double_q == 0 && buf_len + 1 < sizeof(line_buf)) {
|
||||
line_buf[buf_len] = '\'';
|
||||
line_buf[buf_len + 1] = '\0';
|
||||
} else if (double_q % 2 == 1 && single == 0 && buf_len + 1 < sizeof(line_buf)) {
|
||||
line_buf[buf_len] = '"';
|
||||
line_buf[buf_len + 1] = '\0';
|
||||
}
|
||||
// 3. Fix trailing operators
|
||||
size_t cur_len = strlen(line_buf);
|
||||
const char *ops[] = {"||", "&&", ">>", "|&", "|", ">", "<", NULL};
|
||||
@ -130,24 +139,26 @@ static char *fix_line_issues(const char *src) {
|
||||
size_t op_len = strlen(ops[i]);
|
||||
if (cur_len >= op_len) {
|
||||
if (strcmp(line_buf + cur_len - op_len, ops[i]) == 0) {
|
||||
// Comment it
|
||||
char temp[4096];
|
||||
strcpy(temp, line_buf);
|
||||
temp[cur_len - op_len] = '\0';
|
||||
strcat(temp, "# ");
|
||||
strcat(temp, ops[i]);
|
||||
strcpy(line_buf, temp);
|
||||
size_t prefix_len = cur_len - op_len;
|
||||
memcpy(temp, line_buf, prefix_len);
|
||||
int written = snprintf(temp + prefix_len, sizeof(temp) - prefix_len, "# %s", ops[i]);
|
||||
if (written > 0 && prefix_len + (size_t)written < sizeof(line_buf)) {
|
||||
memcpy(line_buf, temp, prefix_len + (size_t)written + 1);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// 4. Dangerous rm -rf check
|
||||
if (strstr(line_buf, "sudo rm -rf /") || strstr(line_buf, "rm -rf / ")) {
|
||||
strcpy(dst, "# WARNING: potentially destructive command detected\n");
|
||||
dst += strlen(dst);
|
||||
const char *warning = "# WARNING: potentially destructive command detected\n";
|
||||
size_t warn_len = strlen(warning);
|
||||
memcpy(dst, warning, warn_len);
|
||||
dst += warn_len;
|
||||
}
|
||||
strcpy(dst, line_buf);
|
||||
dst += strlen(dst);
|
||||
size_t final_len = strlen(line_buf);
|
||||
memcpy(dst, line_buf, final_len);
|
||||
dst += final_len;
|
||||
if (next_line) *dst++ = '\n';
|
||||
if (next_line) line = next_line + 1;
|
||||
else break;
|
||||
@ -183,13 +194,14 @@ static char *collapse_nested_bash_c(const char *src) {
|
||||
size_t prefix_len = (size_t)(p - s1);
|
||||
memcpy(new_s, s1, prefix_len);
|
||||
char *d = new_s + prefix_len;
|
||||
strcpy(d, "bash -c ");
|
||||
memcpy(d, "bash -c ", 8);
|
||||
d += 8;
|
||||
*d++ = inner;
|
||||
memcpy(d, p + 19, cmd_len);
|
||||
d += cmd_len;
|
||||
*d++ = inner;
|
||||
strcpy(d, inner_end + 2);
|
||||
size_t tail_len = strlen(inner_end + 2);
|
||||
memcpy(d, inner_end + 2, tail_len + 1);
|
||||
|
||||
free(s1);
|
||||
s1 = new_s;
|
||||
|
||||
@ -61,12 +61,14 @@ static r_status_t perform_truncate(messages_handle msgs, int index, double ratio
|
||||
if (target_len >= len) return R_SUCCESS;
|
||||
size_t keep_each = target_len / 2;
|
||||
|
||||
char *new_content = malloc(keep_each * 2 + strlen(TRUNCATE_MARKER) + 1);
|
||||
size_t marker_len = strlen(TRUNCATE_MARKER);
|
||||
char *new_content = malloc(keep_each * 2 + marker_len + 1);
|
||||
if (!new_content) return R_ERROR_OUT_OF_MEMORY;
|
||||
strncpy(new_content, content, keep_each);
|
||||
new_content[keep_each] = '\0';
|
||||
strcat(new_content, TRUNCATE_MARKER);
|
||||
strcat(new_content, content + len - keep_each);
|
||||
memcpy(new_content, content, keep_each);
|
||||
memcpy(new_content + keep_each, TRUNCATE_MARKER, marker_len);
|
||||
size_t offset = keep_each + marker_len;
|
||||
memcpy(new_content + offset, content + len - keep_each, keep_each);
|
||||
new_content[offset + keep_each] = '\0';
|
||||
struct json_object *new_msg = json_tokener_parse(json_object_to_json_string(msg));
|
||||
if (is_tool_result) {
|
||||
json_object_object_add(new_msg, "tool_result", json_object_new_string(new_content));
|
||||
|
||||
@ -6,12 +6,7 @@
|
||||
// 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;
|
||||
return strdup("This is a summary of the oldest 20 messages.");
|
||||
}
|
||||
char* summarize_oldest_messages(const char** messages, size_t message_count) {
|
||||
// Concatenate the oldest 20 messages
|
||||
@ -27,13 +22,16 @@ char* summarize_oldest_messages(const char** messages, size_t message_count) {
|
||||
if (!concatenated) {
|
||||
return NULL;
|
||||
}
|
||||
concatenated[0] = '\0';
|
||||
size_t write_offset = 0;
|
||||
for (size_t i = start_index; i < message_count; ++i) {
|
||||
strcat(concatenated, messages[i]);
|
||||
size_t msg_len = strlen(messages[i]);
|
||||
memcpy(concatenated + write_offset, messages[i], msg_len);
|
||||
write_offset += msg_len;
|
||||
if (i < message_count - 1) {
|
||||
strcat(concatenated, " "); // separator
|
||||
concatenated[write_offset++] = ' ';
|
||||
}
|
||||
}
|
||||
concatenated[write_offset] = '\0';
|
||||
// Call the LLM API to get the summary
|
||||
char* summary = call_llm_to_summarize(concatenated);
|
||||
free(concatenated);
|
||||
|
||||
6
src/db.c
6
src/db.c
@ -18,10 +18,10 @@ static char *expand_home_directory(const char *path) {
|
||||
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);
|
||||
char *expanded = malloc(home_len + path_len + 1);
|
||||
if (!expanded) return NULL;
|
||||
strcpy(expanded, home_dir);
|
||||
strcat(expanded, path + 1);
|
||||
memcpy(expanded, home_dir, home_len);
|
||||
memcpy(expanded + home_len, path + 1, path_len);
|
||||
return expanded;
|
||||
}
|
||||
db_handle db_open(const char *path) {
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
#include "http_client.h"
|
||||
#include <curl/curl.h>
|
||||
#include <pthread.h>
|
||||
#include <stdatomic.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
@ -21,7 +22,7 @@ struct response_buffer_t {
|
||||
size_t size;
|
||||
};
|
||||
static struct timespec spinner_start_time = {0, 0};
|
||||
static volatile int spinner_running = 0;
|
||||
static atomic_int spinner_running = 0;
|
||||
static double get_elapsed_seconds(void) {
|
||||
struct timespec now;
|
||||
clock_gettime(CLOCK_MONOTONIC, &now);
|
||||
@ -32,7 +33,7 @@ static void *spinner_thread(void *arg) {
|
||||
(void)arg;
|
||||
const char *frames[] = {"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"};
|
||||
int frame = 0;
|
||||
while (spinner_running) {
|
||||
while (atomic_load(&spinner_running)) {
|
||||
double elapsed = get_elapsed_seconds();
|
||||
fprintf(stderr, "\r%s Querying AI... (%.1fs) ", frames[frame % 10], elapsed);
|
||||
fflush(stderr);
|
||||
@ -99,8 +100,11 @@ r_status_t http_post(http_client_handle client, const char *url,
|
||||
bool actually_show_spinner = client->show_spinner && isatty(STDERR_FILENO);
|
||||
if (actually_show_spinner) {
|
||||
clock_gettime(CLOCK_MONOTONIC, &spinner_start_time);
|
||||
spinner_running = 1;
|
||||
pthread_create(&spinner_tid, NULL, spinner_thread, NULL);
|
||||
atomic_store(&spinner_running, 1);
|
||||
if (pthread_create(&spinner_tid, NULL, spinner_thread, NULL) != 0) {
|
||||
spinner_tid = 0;
|
||||
actually_show_spinner = false;
|
||||
}
|
||||
}
|
||||
while (retry_count < HTTP_MAX_RETRIES) {
|
||||
free(resp.data);
|
||||
@ -142,8 +146,8 @@ r_status_t http_post(http_client_handle client, const char *url,
|
||||
goto cleanup;
|
||||
}
|
||||
retry_count++;
|
||||
if (actually_show_spinner) {
|
||||
spinner_running = 0;
|
||||
if (actually_show_spinner && spinner_tid) {
|
||||
atomic_store(&spinner_running, 0);
|
||||
pthread_join(spinner_tid, NULL);
|
||||
spinner_tid = 0;
|
||||
fprintf(stderr, "\r \r");
|
||||
@ -155,15 +159,17 @@ r_status_t http_post(http_client_handle client, const char *url,
|
||||
usleep(HTTP_RETRY_DELAY_MS * 1000);
|
||||
if (actually_show_spinner) {
|
||||
clock_gettime(CLOCK_MONOTONIC, &spinner_start_time);
|
||||
spinner_running = 1;
|
||||
pthread_create(&spinner_tid, NULL, spinner_thread, NULL);
|
||||
atomic_store(&spinner_running, 1);
|
||||
if (pthread_create(&spinner_tid, NULL, spinner_thread, NULL) != 0) {
|
||||
spinner_tid = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
status = R_ERROR_HTTP_TIMEOUT;
|
||||
cleanup:
|
||||
if (actually_show_spinner && spinner_tid) {
|
||||
spinner_running = 0;
|
||||
atomic_store(&spinner_running, 0);
|
||||
pthread_join(spinner_tid, NULL);
|
||||
fprintf(stderr, "\r \r");
|
||||
fflush(stderr);
|
||||
|
||||
@ -35,11 +35,11 @@ static const char *resolve_api_key(void) {
|
||||
if (key && *key) return key;
|
||||
key = getenv("OPENAI_API_KEY");
|
||||
if (key && *key) return key;
|
||||
return "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";
|
||||
return NULL;
|
||||
}
|
||||
static bool is_valid_session_id(const char *session_id) {
|
||||
if (!session_id || !*session_id) return false;
|
||||
if (strlen(session_id) > 255) return false;
|
||||
if (strlen(session_id) > 200) return false;
|
||||
for (const char *p = session_id; *p; p++) {
|
||||
if (!isalnum((unsigned char)*p) && *p != '-' && *p != '_' && *p != '.') {
|
||||
return false;
|
||||
@ -66,7 +66,8 @@ config_handle config_create(void) {
|
||||
}
|
||||
const char *model = getenv("R_MODEL");
|
||||
cfg->model = strdup(model && *model ? model : "gpt-4o-mini");
|
||||
cfg->api_key = strdup(resolve_api_key());
|
||||
const char *resolved_key = resolve_api_key();
|
||||
cfg->api_key = resolved_key ? strdup(resolved_key) : NULL;
|
||||
cfg->db_path = strdup("~/.r.db");
|
||||
cfg->temperature = 0.1;
|
||||
cfg->use_tools = resolve_env_bool("R_USE_TOOLS", true);
|
||||
|
||||
@ -256,6 +256,13 @@ static char *balance_brackets(const char *src) {
|
||||
}
|
||||
*dst++ = *p++;
|
||||
}
|
||||
if (in_string) {
|
||||
if (escaped) {
|
||||
*(dst - 1) = '"';
|
||||
} else {
|
||||
*dst++ = '"';
|
||||
}
|
||||
}
|
||||
while (top > 0) {
|
||||
char opener = stack[--top];
|
||||
*dst++ = (opener == '{') ? '}' : ']';
|
||||
|
||||
@ -1,5 +1,8 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
#ifndef LINE_H
|
||||
#define LINE_H
|
||||
|
||||
#include "utils.h"
|
||||
#include <glob.h>
|
||||
#include <readline/history.h>
|
||||
@ -81,6 +84,7 @@ char *line_file_generator(const char *text, int state) {
|
||||
}
|
||||
|
||||
char **line_command_completion(const char *text, int start, int end) {
|
||||
(void)end;
|
||||
rl_attempted_completion_over = 1;
|
||||
|
||||
// Check if the input is a file path
|
||||
@ -254,3 +258,5 @@ void line_add_history(char *data) {
|
||||
add_history(data);
|
||||
write_history(get_history_file());
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
115
src/main.c
115
src/main.c
@ -20,7 +20,7 @@
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
static volatile sig_atomic_t sigint_count = 0;
|
||||
static time_t first_sigint_time = 0;
|
||||
static struct timespec first_sigint_ts = {0, 0};
|
||||
static bool syntax_highlight_enabled = true;
|
||||
static bool api_mode = false;
|
||||
static db_handle global_db = NULL;
|
||||
@ -35,40 +35,51 @@ static void repl(void);
|
||||
static void init(void);
|
||||
static void cleanup(void);
|
||||
static void handle_sigint(int sig);
|
||||
extern char **environ;
|
||||
static const char *safe_env_prefixes[] = {
|
||||
"LANG=", "LC_", "TERM=", "SHELL=", "USER=", "HOME=",
|
||||
"PATH=", "HOSTNAME=", "EDITOR=", "VISUAL=", "TZ=",
|
||||
"R_", "OPENROUTER_", "XDG_",
|
||||
NULL
|
||||
};
|
||||
static char *get_env_string(void) {
|
||||
FILE *fp = popen("env", "r");
|
||||
if (!fp)
|
||||
return NULL;
|
||||
size_t buffer_size = 1024;
|
||||
size_t total_size = 0;
|
||||
size_t buffer_size = 4096;
|
||||
size_t offset = 0;
|
||||
char *output = malloc(buffer_size);
|
||||
if (!output) {
|
||||
pclose(fp);
|
||||
return NULL;
|
||||
}
|
||||
size_t bytes_read;
|
||||
while ((bytes_read = fread(output + total_size, 1, buffer_size - total_size,
|
||||
fp)) > 0) {
|
||||
total_size += bytes_read;
|
||||
if (total_size >= buffer_size) {
|
||||
if (!output) return NULL;
|
||||
output[0] = '\0';
|
||||
for (char **env = environ; *env; env++) {
|
||||
bool allowed = false;
|
||||
for (int i = 0; safe_env_prefixes[i]; i++) {
|
||||
if (strncmp(*env, safe_env_prefixes[i], strlen(safe_env_prefixes[i])) == 0) {
|
||||
allowed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!allowed) continue;
|
||||
size_t entry_len = strlen(*env);
|
||||
if (offset + entry_len + 2 >= buffer_size) {
|
||||
buffer_size *= 2;
|
||||
char *temp = realloc(output, buffer_size);
|
||||
if (!temp) {
|
||||
char *expanded = realloc(output, buffer_size);
|
||||
if (!expanded) {
|
||||
free(output);
|
||||
pclose(fp);
|
||||
return NULL;
|
||||
}
|
||||
output = temp;
|
||||
output = expanded;
|
||||
}
|
||||
memcpy(output + offset, *env, entry_len);
|
||||
offset += entry_len;
|
||||
output[offset++] = '\n';
|
||||
}
|
||||
output[total_size] = '\0';
|
||||
pclose(fp);
|
||||
output[offset] = '\0';
|
||||
return output;
|
||||
}
|
||||
static char *get_prompt_from_stdin(char *prompt) {
|
||||
int index = 0;
|
||||
int c;
|
||||
int max_size = 10 * 1024 * 1024;
|
||||
while ((c = getchar()) != EOF) {
|
||||
if (index >= max_size - 1) break;
|
||||
prompt[index++] = (char)c;
|
||||
}
|
||||
prompt[index] = '\0';
|
||||
@ -112,8 +123,15 @@ static char *get_prompt_from_args(int argc, char **argv) {
|
||||
i++;
|
||||
continue;
|
||||
} else {
|
||||
strcat(system_msg, argv[i]);
|
||||
strcat(system_msg, (i < argc - 1) ? " " : ".");
|
||||
size_t remaining = (1024 * 1024) - strlen(system_msg) - 2;
|
||||
size_t arg_len = strlen(argv[i]);
|
||||
if (arg_len < remaining) {
|
||||
size_t offset = strlen(system_msg);
|
||||
memcpy(system_msg + offset, argv[i], arg_len);
|
||||
const char *sep = (i < argc - 1) ? " " : ".";
|
||||
system_msg[offset + arg_len] = sep[0];
|
||||
system_msg[offset + arg_len + 1] = '\0';
|
||||
}
|
||||
}
|
||||
}
|
||||
if (get_from_stdin) {
|
||||
@ -141,7 +159,6 @@ static bool try_prompt(int argc, char *argv[]) {
|
||||
free(prompt);
|
||||
return false;
|
||||
}
|
||||
// response is already printed inside agent_run
|
||||
free(response);
|
||||
free(prompt);
|
||||
return true;
|
||||
@ -164,6 +181,8 @@ static void repl(void) {
|
||||
line_init();
|
||||
char *line = NULL;
|
||||
while (true) {
|
||||
free(line);
|
||||
line = NULL;
|
||||
line = line_read(line_build_prompt());
|
||||
if (!line || !*line)
|
||||
continue;
|
||||
@ -209,7 +228,7 @@ static void repl(void) {
|
||||
verbose ? "Verbose mode enabled" : "Verbose mode disabled");
|
||||
continue;
|
||||
}
|
||||
if (line && *line != '\n') {
|
||||
if (*line != '\n') {
|
||||
line_add_history(line);
|
||||
}
|
||||
if (!strncmp(line, "!tools", 6)) {
|
||||
@ -240,18 +259,17 @@ static void repl(void) {
|
||||
continue;
|
||||
}
|
||||
if (!strncmp(line, "exit", 4)) {
|
||||
free(line);
|
||||
line = NULL;
|
||||
exit(0);
|
||||
}
|
||||
while (line && *line != '\n') {
|
||||
if (*line != '\n') {
|
||||
char *response = agent_chat(line, global_messages);
|
||||
if (response) {
|
||||
// response is already printed inside agent_run via
|
||||
// parse_markdown_to_ansi
|
||||
free(response);
|
||||
} else {
|
||||
fprintf(stderr, "Agent returned no response\n");
|
||||
}
|
||||
line = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -263,17 +281,22 @@ static void init(void) {
|
||||
global_db = db_open(NULL);
|
||||
global_messages = messages_create(r_config_get_session_id(cfg));
|
||||
char *schema = db_get_schema(global_db);
|
||||
char payload[1024 * 1024] = {0};
|
||||
char *payload = calloc(1, 1024 * 1024);
|
||||
if (!payload) {
|
||||
free(schema);
|
||||
return;
|
||||
}
|
||||
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(cwd, sizeof(cwd), "unknown");
|
||||
}
|
||||
size_t payload_size = 1024 * 1024;
|
||||
snprintf(
|
||||
payload, sizeof(payload),
|
||||
payload, payload_size,
|
||||
"# AUTONOMOUS AGENT INSTRUCTIONS\n"
|
||||
"Current date/time: %s\n"
|
||||
"Working directory: %s\n\n"
|
||||
@ -419,6 +442,7 @@ static void init(void) {
|
||||
if (global_messages) {
|
||||
messages_add(global_messages, "system", payload);
|
||||
}
|
||||
free(payload);
|
||||
const char *env_system_msg = r_config_get_system_message(cfg);
|
||||
if (env_system_msg && *env_system_msg && global_messages) {
|
||||
messages_add(global_messages, "system", env_system_msg);
|
||||
@ -441,20 +465,24 @@ static void cleanup(void) {
|
||||
spawn_tracker_destroy();
|
||||
r_config_destroy();
|
||||
}
|
||||
static volatile sig_atomic_t exit_requested = 0;
|
||||
static void handle_sigint(int sig) {
|
||||
(void)sig;
|
||||
time_t current_time = time(NULL);
|
||||
printf("\n");
|
||||
const char nl = '\n';
|
||||
(void)!write(STDERR_FILENO, &nl, 1);
|
||||
struct timespec now_ts;
|
||||
clock_gettime(CLOCK_MONOTONIC, &now_ts);
|
||||
if (sigint_count == 0) {
|
||||
first_sigint_time = current_time;
|
||||
sigint_count++;
|
||||
first_sigint_ts = now_ts;
|
||||
sigint_count = 1;
|
||||
} else {
|
||||
if (difftime(current_time, first_sigint_time) <= 1) {
|
||||
cleanup();
|
||||
exit(0);
|
||||
long elapsed = now_ts.tv_sec - first_sigint_ts.tv_sec;
|
||||
if (elapsed <= 1) {
|
||||
exit_requested = 1;
|
||||
_exit(0);
|
||||
} else {
|
||||
sigint_count = 1;
|
||||
first_sigint_time = current_time;
|
||||
first_sigint_ts = now_ts;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -486,9 +514,12 @@ int main(int argc, char *argv[]) {
|
||||
parse_session_arg(argc, argv);
|
||||
init();
|
||||
char *env_string = get_env_string();
|
||||
if (env_string && *env_string && global_messages) {
|
||||
messages_add(global_messages, "system", env_string);
|
||||
if (env_string) {
|
||||
if (*env_string && global_messages) {
|
||||
messages_add(global_messages, "system", env_string);
|
||||
}
|
||||
free(env_string);
|
||||
env_string = NULL;
|
||||
}
|
||||
messages_load(global_messages);
|
||||
if (try_prompt(argc, argv)) {
|
||||
|
||||
@ -2,12 +2,14 @@
|
||||
#include "messages.h"
|
||||
#include "db.h"
|
||||
#include <ctype.h>
|
||||
#include <limits.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#define MAX_CONTENT_LENGTH 1048570
|
||||
#define MAX_TOOL_RESULT_LENGTH 104000
|
||||
#define MAX_SESSION_ID_LENGTH 200
|
||||
#define SESSION_EXPIRY_SECONDS 86400
|
||||
struct messages_t {
|
||||
struct json_object *array;
|
||||
@ -17,7 +19,7 @@ struct messages_t {
|
||||
};
|
||||
static bool is_valid_session_id(const char *session_id) {
|
||||
if (!session_id || !*session_id) return false;
|
||||
if (strlen(session_id) > 200) return false;
|
||||
if (strlen(session_id) > MAX_SESSION_ID_LENGTH) return false;
|
||||
for (const char *p = session_id; *p; p++) {
|
||||
if (!isalnum((unsigned char)*p) && *p != '-' && *p != '_' && *p != '.') {
|
||||
return false;
|
||||
@ -104,8 +106,9 @@ r_status_t messages_add(messages_handle msgs, const char *role, const char *cont
|
||||
if (content) {
|
||||
size_t len = strlen(content);
|
||||
if (len > MAX_CONTENT_LENGTH) len = MAX_CONTENT_LENGTH;
|
||||
int safe_len = (len <= (size_t)INT_MAX) ? (int)len : INT_MAX;
|
||||
json_object_object_add(message, "content",
|
||||
json_object_new_string_len(content, (int)len));
|
||||
json_object_new_string_len(content, safe_len));
|
||||
}
|
||||
json_object_array_add(msgs->array, message);
|
||||
if (strcmp(role, "system") != 0) {
|
||||
@ -132,8 +135,9 @@ r_status_t messages_add_tool_result(messages_handle msgs, const char *tool_call_
|
||||
json_object_object_add(message, "tool_call_id", json_object_new_string(tool_call_id));
|
||||
size_t len = strlen(result);
|
||||
if (len > MAX_TOOL_RESULT_LENGTH) len = MAX_TOOL_RESULT_LENGTH;
|
||||
int safe_len = (len <= (size_t)INT_MAX) ? (int)len : INT_MAX;
|
||||
json_object_object_add(message, "tool_result",
|
||||
json_object_new_string_len(result, (int)len));
|
||||
json_object_new_string_len(result, safe_len));
|
||||
json_object_array_add(msgs->array, message);
|
||||
messages_save(msgs);
|
||||
return R_SUCCESS;
|
||||
@ -240,3 +244,6 @@ int messages_count(messages_handle msgs) {
|
||||
if (!msgs || !msgs->array) return 0;
|
||||
return json_object_array_length(msgs->array);
|
||||
}
|
||||
db_handle messages_get_db(messages_handle msgs) {
|
||||
return msgs ? msgs->db : NULL;
|
||||
}
|
||||
|
||||
@ -43,7 +43,9 @@ static char *dedent_code(const char *src) {
|
||||
dst += line_len;
|
||||
curr = next_line + 1;
|
||||
} else {
|
||||
strcpy(dst, curr);
|
||||
size_t rest_len = strlen(curr);
|
||||
memcpy(dst, curr, rest_len + 1);
|
||||
dst += rest_len;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@ -14,7 +14,6 @@ struct r_config_t {
|
||||
char *system_message;
|
||||
char *current_prompt;
|
||||
double temperature;
|
||||
int max_tokens;
|
||||
int max_spawn_depth;
|
||||
int max_total_spawns;
|
||||
bool use_tools;
|
||||
@ -39,7 +38,7 @@ static const char *resolve_api_key(void) {
|
||||
if (key && *key) return key;
|
||||
key = getenv("OPENAI_API_KEY");
|
||||
if (key && *key) return key;
|
||||
return "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";
|
||||
return NULL;
|
||||
}
|
||||
static bool is_valid_session_id(const char *session_id) {
|
||||
if (!session_id || !*session_id) return false;
|
||||
@ -70,17 +69,23 @@ r_config_handle r_config_get_instance(void) {
|
||||
}
|
||||
const char *model = getenv("R_MODEL");
|
||||
instance->model = strdup(model && *model ? model : "gpt-4o-mini");
|
||||
instance->api_key = strdup(resolve_api_key());
|
||||
const char *resolved_key = resolve_api_key();
|
||||
if (!resolved_key) {
|
||||
fprintf(stderr, "Warning: No API key found. Set R_KEY, OPENROUTER_API_KEY, or OPENAI_API_KEY.\n");
|
||||
}
|
||||
instance->api_key = resolved_key ? strdup(resolved_key) : NULL;
|
||||
instance->db_path = strdup("~/.r.db");
|
||||
instance->temperature = 0.1;
|
||||
const char *max_tokens_env = getenv("R_MAX_TOKENS");
|
||||
instance->max_tokens = max_tokens_env ? atoi(max_tokens_env) : 4096;
|
||||
const char *spawn_depth_env = getenv("R_MAX_SPAWN_DEPTH");
|
||||
instance->max_spawn_depth = spawn_depth_env ? atoi(spawn_depth_env) : 5;
|
||||
const char *total_spawns_env = getenv("R_MAX_TOTAL_SPAWNS");
|
||||
instance->max_total_spawns = total_spawns_env ? atoi(total_spawns_env) : 20;
|
||||
instance->use_tools = resolve_env_bool("R_USE_TOOLS", true);
|
||||
instance->use_strict = resolve_env_bool("R_USE_STRICT", true);
|
||||
if (instance->use_strict && instance->api_url &&
|
||||
!strstr(instance->api_url, "api.openai.com")) {
|
||||
instance->use_strict = false;
|
||||
}
|
||||
instance->verbose = false;
|
||||
const char *session = getenv("R_SESSION");
|
||||
if (session && is_valid_session_id(session)) {
|
||||
@ -139,9 +144,6 @@ void r_config_set_verbose(r_config_handle cfg, bool verbose) {
|
||||
double r_config_get_temperature(r_config_handle cfg) {
|
||||
return cfg ? cfg->temperature : 0.1;
|
||||
}
|
||||
int r_config_get_max_tokens(r_config_handle cfg) {
|
||||
return cfg ? cfg->max_tokens : 4096;
|
||||
}
|
||||
const char *r_config_get_session_id(r_config_handle cfg) {
|
||||
return cfg ? cfg->session_id : NULL;
|
||||
}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
#include "tool.h"
|
||||
#include "json_repair.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
@ -8,6 +9,8 @@ typedef struct {
|
||||
tool_t *tool;
|
||||
struct json_object *args;
|
||||
char *output;
|
||||
const char *name;
|
||||
const char *args_json;
|
||||
} tool_thread_args_t;
|
||||
static void *tool_thread_func(void *ptr) {
|
||||
tool_thread_args_t *args = (tool_thread_args_t *)ptr;
|
||||
@ -95,15 +98,55 @@ struct json_object *tool_registry_execute(tool_registry_t *registry,
|
||||
continue;
|
||||
}
|
||||
const char *name = json_object_get_string(json_object_object_get(function_obj, "name"));
|
||||
const char *args_json = json_object_get_string(json_object_object_get(function_obj, "arguments"));
|
||||
// DEDUPLICATION LOGIC: Check if this exact call (name + args) appeared earlier in this batch
|
||||
|
||||
struct json_object *args_field = json_object_object_get(function_obj, "arguments");
|
||||
struct json_object *args = NULL;
|
||||
const char *args_json = NULL;
|
||||
|
||||
if (args_field) {
|
||||
enum json_type atype = json_object_get_type(args_field);
|
||||
if (atype == json_type_object) {
|
||||
args = json_object_get(args_field);
|
||||
args_json = json_object_to_json_string(args);
|
||||
} else if (atype == json_type_string) {
|
||||
const char *raw = json_object_get_string(args_field);
|
||||
args_json = raw;
|
||||
if (raw) {
|
||||
args = json_tokener_parse(raw);
|
||||
if (!args) {
|
||||
char *repaired = json_repair_string(raw);
|
||||
if (repaired) {
|
||||
args = json_tokener_parse(repaired);
|
||||
free(repaired);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!args) {
|
||||
if (args_json) {
|
||||
size_t args_len = strlen(args_json);
|
||||
fprintf(stderr, "\033[1;31m[Registry] Failed to parse args for %s (len=%zu): %.200s...\033[0m\n",
|
||||
name ? name : "unknown", args_len, args_json);
|
||||
}
|
||||
json_object_object_add(result_objs[i], "content",
|
||||
json_object_new_string(
|
||||
"Error: failed to parse tool arguments (invalid JSON). "
|
||||
"This is likely caused by output truncation — the arguments were cut off. "
|
||||
"For large file content, use linux_terminal_execute with: "
|
||||
"cat << 'HEREDOC_EOF' > filename\n...content...\nHEREDOC_EOF"));
|
||||
continue;
|
||||
}
|
||||
|
||||
for (int j = 0; j < i; j++) {
|
||||
struct json_object *prev_call = json_object_array_get_idx(tool_calls, j);
|
||||
struct json_object *prev_func;
|
||||
json_object_object_get_ex(prev_call, "function", &prev_func);
|
||||
const char *prev_name = json_object_get_string(json_object_object_get(prev_func, "name"));
|
||||
const char *prev_args = json_object_get_string(json_object_object_get(prev_func, "arguments"));
|
||||
if (strcmp(name, prev_name) == 0 && strcmp(args_json, prev_args) == 0) {
|
||||
const char *prev_args = t_args[j].args_json;
|
||||
if (name && prev_name && args_json && prev_args &&
|
||||
strcmp(name, prev_name) == 0 && strcmp(args_json, prev_args) == 0) {
|
||||
is_duplicate[i] = true;
|
||||
if (verbose) {
|
||||
fprintf(stderr, " \033[1;33m[Registry] Redundant call to %s prevented.\033[0m\n", name);
|
||||
@ -111,63 +154,87 @@ struct json_object *tool_registry_execute(tool_registry_t *registry,
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (is_duplicate[i]) continue;
|
||||
if (is_duplicate[i]) {
|
||||
json_object_put(args);
|
||||
continue;
|
||||
}
|
||||
tool_t *tool = tool_registry_find(registry, name);
|
||||
if (!tool) {
|
||||
json_object_object_add(result_objs[i], "content", json_object_new_string("Error: tool not found"));
|
||||
json_object_put(args);
|
||||
continue;
|
||||
}
|
||||
struct json_object *args = json_tokener_parse(args_json);
|
||||
if (tool->vtable->print_action) {
|
||||
tool->vtable->print_action(tool->name, args);
|
||||
}
|
||||
if (verbose && args) {
|
||||
if (verbose) {
|
||||
fprintf(stderr, " \033[2m[parallel] launching %s\033[0m\n", tool->name);
|
||||
}
|
||||
t_args[i].tool = tool;
|
||||
t_args[i].args = args;
|
||||
t_args[i].output = NULL;
|
||||
pthread_create(&threads[i], NULL, tool_thread_func, &t_args[i]);
|
||||
t_args[i].name = name;
|
||||
t_args[i].args_json = args_json;
|
||||
if (pthread_create(&threads[i], NULL, tool_thread_func, &t_args[i]) != 0) {
|
||||
threads[i] = 0;
|
||||
t_args[i].output = strdup("Error: failed to create thread for tool execution");
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < len; i++) {
|
||||
if (is_duplicate[i]) {
|
||||
// Find the original result to copy it
|
||||
struct json_object *curr_func;
|
||||
json_object_object_get_ex(json_object_array_get_idx(tool_calls, i), "function", &curr_func);
|
||||
const char *name = json_object_get_string(json_object_object_get(curr_func, "name"));
|
||||
const char *args_json = json_object_get_string(json_object_object_get(curr_func, "arguments"));
|
||||
|
||||
for (int j = 0; j < i; j++) {
|
||||
struct json_object *prev_func;
|
||||
json_object_object_get_ex(json_object_array_get_idx(tool_calls, j), "function", &prev_func);
|
||||
if (strcmp(name, json_object_get_string(json_object_object_get(prev_func, "name"))) == 0 &&
|
||||
strcmp(args_json, json_object_get_string(json_object_object_get(prev_func, "arguments"))) == 0) {
|
||||
|
||||
|
||||
for (int i = 0; i < len; i++) {
|
||||
if (!is_duplicate[i] && threads[i]) {
|
||||
pthread_join(threads[i], NULL);
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < len; i++) {
|
||||
if (is_duplicate[i]) {
|
||||
struct json_object *curr_func;
|
||||
json_object_object_get_ex(json_object_array_get_idx(tool_calls, i), "function", &curr_func);
|
||||
const char *name = json_object_get_string(json_object_object_get(curr_func, "name"));
|
||||
const char *dup_args = t_args[i].args_json;
|
||||
|
||||
for (int j = 0; j < i; j++) {
|
||||
const char *prev_name = t_args[j].name;
|
||||
const char *prev_args = t_args[j].args_json;
|
||||
if (name && prev_name && dup_args && prev_args &&
|
||||
strcmp(name, prev_name) == 0 && strcmp(dup_args, prev_args) == 0) {
|
||||
struct json_object *orig_content;
|
||||
if (json_object_object_get_ex(result_objs[j], "content", &orig_content)) {
|
||||
json_object_object_add(result_objs[i], "content", json_object_get(orig_content));
|
||||
} else {
|
||||
// Original hasn't finished yet or failed
|
||||
json_object_object_add(result_objs[i], "content", json_object_new_string("Result mirrored from previous parallel call."));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (threads[i]) {
|
||||
pthread_join(threads[i], NULL);
|
||||
}
|
||||
|
||||
char *output = t_args[i].output ? t_args[i].output : "";
|
||||
json_object_object_add(result_objs[i], "content", json_object_new_string(output));
|
||||
|
||||
if (output && strncmp(output, "Error:", 6) == 0) {
|
||||
fprintf(stderr, "\033[1;31m[Tool Error] %s\033[0m\n", output);
|
||||
|
||||
if (output[0] != '\0' && strncmp(output, "Error:", 6) == 0) {
|
||||
const char *tool_name = t_args[i].name ? t_args[i].name : "unknown";
|
||||
const char *received_args = t_args[i].args_json ? t_args[i].args_json : "{}";
|
||||
size_t enriched_len = strlen(output) + strlen(tool_name) + strlen(received_args) + 64;
|
||||
char *enriched = malloc(enriched_len);
|
||||
if (enriched) {
|
||||
snprintf(enriched, enriched_len,
|
||||
"Error in tool '%s': %s. Arguments received: %s",
|
||||
tool_name, output + 7, received_args);
|
||||
json_object_object_add(result_objs[i], "content", json_object_new_string(enriched));
|
||||
fprintf(stderr, "\033[1;31m[Tool Error] %s\033[0m\n", enriched);
|
||||
free(enriched);
|
||||
} else {
|
||||
json_object_object_add(result_objs[i], "content", json_object_new_string(output));
|
||||
fprintf(stderr, "\033[1;31m[Tool Error] %s\033[0m\n", output);
|
||||
}
|
||||
} else {
|
||||
json_object_object_add(result_objs[i], "content", json_object_new_string(output));
|
||||
}
|
||||
|
||||
|
||||
free(t_args[i].output);
|
||||
if (t_args[i].args) json_object_put(t_args[i].args);
|
||||
t_args[i].output = NULL;
|
||||
if (t_args[i].args) {
|
||||
json_object_put(t_args[i].args);
|
||||
t_args[i].args = NULL;
|
||||
}
|
||||
}
|
||||
json_object_array_add(results, result_objs[i]);
|
||||
}
|
||||
|
||||
@ -343,7 +343,7 @@ static char *merge_search_results(char **results, int count) {
|
||||
|
||||
struct json_object *result_array;
|
||||
if (json_object_object_get_ex(result_json, "results", &result_array)) {
|
||||
for (int j = 0; j < json_object_array_length(result_array); j++) {
|
||||
for (size_t j = 0; j < json_object_array_length(result_array); j++) {
|
||||
struct json_object *item = json_object_array_get_idx(result_array, j);
|
||||
json_object_array_add(results_array, json_object_get(item));
|
||||
}
|
||||
|
||||
@ -70,8 +70,8 @@ static void ChangetoDnsNameFormat(unsigned char* dns, unsigned char* host) {
|
||||
size_t len = strlen((char*)host);
|
||||
if (len > 250) len = 250;
|
||||
memcpy(temp_host, host, len);
|
||||
temp_host[len] = '\0';
|
||||
strcat(temp_host, ".");
|
||||
temp_host[len] = '.';
|
||||
temp_host[len + 1] = '\0';
|
||||
|
||||
for(i = 0; i < (int)strlen(temp_host); i++) {
|
||||
if(temp_host[i] == '.') {
|
||||
@ -363,7 +363,7 @@ static char *dns_lookup_execute(tool_t *self, struct json_object *args) {
|
||||
fflush(stderr);
|
||||
char sub[512];
|
||||
snprintf(sub, sizeof(sub), "%s.%s", common_subdomains[i], final_hostname);
|
||||
int prev_len = json_object_array_length(results);
|
||||
size_t prev_len = json_object_array_length(results);
|
||||
perform_single_query(sub, T_A, dns_server, results);
|
||||
if (json_object_array_length(results) > prev_len) {
|
||||
fprintf(stderr, "[\033[32mFOUND\033[0m] \n");
|
||||
@ -388,7 +388,7 @@ static char *dns_lookup_execute(tool_t *self, struct json_object *args) {
|
||||
struct json_object *ip_entry = json_object_array_get_idx(ip_results, 0);
|
||||
struct json_object *ip_val;
|
||||
if (json_object_object_get_ex(ip_entry, "value", &ip_val)) {
|
||||
int p_len = json_object_array_length(results);
|
||||
size_t p_len = json_object_array_length(results);
|
||||
perform_axfr(final_hostname, json_object_get_string(ip_val), results);
|
||||
if (json_object_array_length(results) > p_len) {
|
||||
fprintf(stderr, "[\033[32mSUCCESS\033[0m]\n");
|
||||
@ -412,7 +412,7 @@ static char *dns_lookup_execute(tool_t *self, struct json_object *args) {
|
||||
for (size_t i = 0; i < sizeof(types)/sizeof(types[0]); i++) {
|
||||
fprintf(stderr, " -> Querying %s... ", type_names[i]);
|
||||
fflush(stderr);
|
||||
int p_len = json_object_array_length(results);
|
||||
size_t p_len = json_object_array_length(results);
|
||||
perform_single_query(final_hostname, types[i], dns_server, results);
|
||||
if (json_object_array_length(results) > p_len) {
|
||||
fprintf(stderr, "[\033[32mOK\033[0m]\n");
|
||||
|
||||
@ -17,7 +17,7 @@ static void compute_hash(const char *input, char *output) {
|
||||
unsigned char hash[SHA256_DIGEST_LENGTH];
|
||||
SHA256((unsigned char*)input, strlen(input), hash);
|
||||
for(int i = 0; i < SHA256_DIGEST_LENGTH; i++) {
|
||||
sprintf(output + (i * 2), "%02x", hash[i]);
|
||||
snprintf(output + (i * 2), 3, "%02x", hash[i]);
|
||||
}
|
||||
output[64] = '\0';
|
||||
}
|
||||
|
||||
@ -102,11 +102,11 @@ static char *expand_home_directory(const char *path) {
|
||||
|
||||
size_t home_len = strlen(home_dir);
|
||||
size_t path_len = strlen(path);
|
||||
char *expanded = malloc(home_len + path_len);
|
||||
char *expanded = malloc(home_len + path_len + 1);
|
||||
if (!expanded) return NULL;
|
||||
|
||||
strcpy(expanded, home_dir);
|
||||
strcat(expanded, path + 1);
|
||||
memcpy(expanded, home_dir, home_len);
|
||||
memcpy(expanded + home_len, path + 1, path_len);
|
||||
return expanded;
|
||||
}
|
||||
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
static struct json_object *file_line_replace_get_description(void);
|
||||
static char *file_line_replace_execute(tool_t *self, struct json_object *args);
|
||||
@ -82,7 +83,9 @@ static char *file_line_replace_execute(tool_t *self, struct json_object *args) {
|
||||
char *saveptr;
|
||||
char *line = strtok_r(copy, "\n", &saveptr);
|
||||
while (line) {
|
||||
lines = realloc(lines, sizeof(char *) * (count + 1));
|
||||
char **expanded = realloc(lines, sizeof(char *) * (size_t)(count + 1));
|
||||
if (!expanded) break;
|
||||
lines = expanded;
|
||||
lines[count++] = strdup(line);
|
||||
line = strtok_r(NULL, "\n", &saveptr);
|
||||
}
|
||||
@ -113,11 +116,15 @@ static char *file_line_replace_execute(tool_t *self, struct json_object *args) {
|
||||
size_t add_len = strlen(to_add);
|
||||
if (current_len + add_len + 2 >= new_size) {
|
||||
new_size *= 2;
|
||||
new_content = realloc(new_content, new_size);
|
||||
char *expanded_content = realloc(new_content, new_size);
|
||||
if (!expanded_content) break;
|
||||
new_content = expanded_content;
|
||||
}
|
||||
strcat(new_content, to_add);
|
||||
strcat(new_content, "\n");
|
||||
current_len += add_len + 1;
|
||||
memcpy(new_content + current_len, to_add, add_len);
|
||||
current_len += add_len;
|
||||
new_content[current_len] = '\n';
|
||||
current_len += 1;
|
||||
new_content[current_len] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
@ -170,9 +177,16 @@ static char *file_apply_patch_execute(tool_t *self, struct json_object *args) {
|
||||
return strdup("Error: could not write full patch to temp file");
|
||||
}
|
||||
|
||||
char cmd[1024];
|
||||
snprintf(cmd, sizeof(cmd), "patch %s %s", path, patch_file);
|
||||
int res = system(cmd);
|
||||
pid_t pid = fork();
|
||||
int res = -1;
|
||||
if (pid == 0) {
|
||||
execl("/usr/bin/patch", "patch", path, patch_file, (char *)NULL);
|
||||
_exit(127);
|
||||
} else if (pid > 0) {
|
||||
int status = 0;
|
||||
waitpid(pid, &status, 0);
|
||||
res = WIFEXITED(status) ? WEXITSTATUS(status) : -1;
|
||||
}
|
||||
unlink(patch_file);
|
||||
|
||||
if (res != 0) {
|
||||
|
||||
@ -19,7 +19,7 @@ static void compute_url_hash(const char *url, char *output) {
|
||||
unsigned char hash[SHA256_DIGEST_LENGTH];
|
||||
SHA256((unsigned char*)url, strlen(url), hash);
|
||||
for(int i = 0; i < SHA256_DIGEST_LENGTH; i++) {
|
||||
sprintf(output + (i * 2), "%02x", hash[i]);
|
||||
snprintf(output + (i * 2), 3, "%02x", hash[i]);
|
||||
}
|
||||
output[64] = '\0';
|
||||
}
|
||||
|
||||
@ -21,6 +21,24 @@ static char *snapshot_resolve_session_id(void) {
|
||||
return messages_generate_session_id();
|
||||
}
|
||||
|
||||
static int mkdirs_recursive(const char *path) {
|
||||
if (!path || !*path) return -1;
|
||||
char temp[4096];
|
||||
size_t len = strlen(path);
|
||||
if (len >= sizeof(temp)) return -1;
|
||||
memcpy(temp, path, 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) return -1;
|
||||
*p = '/';
|
||||
}
|
||||
}
|
||||
if (mkdir(temp, 0755) != 0 && errno != EEXIST) return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int snapshot_mkdirs(const char *filepath) {
|
||||
char *path_copy = strdup(filepath);
|
||||
if (!path_copy) return -1;
|
||||
@ -29,9 +47,7 @@ static int snapshot_mkdirs(const char *filepath) {
|
||||
free(path_copy);
|
||||
return 0;
|
||||
}
|
||||
char mkdir_cmd[4096];
|
||||
snprintf(mkdir_cmd, sizeof(mkdir_cmd), "mkdir -p '%s'", dir);
|
||||
int rc = system(mkdir_cmd);
|
||||
int rc = mkdirs_recursive(dir);
|
||||
free(path_copy);
|
||||
return rc;
|
||||
}
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
#include "tool.h"
|
||||
#include "r_config.h"
|
||||
#include "bash_executor.h"
|
||||
#include <ctype.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
@ -58,22 +59,40 @@ static char *process_monitor_execute(tool_t *self, struct json_object *args) {
|
||||
(void)self;
|
||||
struct json_object *action_obj;
|
||||
if (!json_object_object_get_ex(args, "action", &action_obj)) {
|
||||
return strdup("Error: missing 'action' argument (list or kill)");
|
||||
char *msg = strdup("Error: missing 'action' argument (list or kill)");
|
||||
return msg;
|
||||
}
|
||||
const char *action = json_object_get_string(action_obj);
|
||||
|
||||
if (strcmp(action, "list") == 0) {
|
||||
struct json_object *filter_obj;
|
||||
if (json_object_object_get_ex(args, "filter", &filter_obj)) {
|
||||
const char *raw_filter = json_object_get_string(filter_obj);
|
||||
if (!raw_filter || !*raw_filter) {
|
||||
return r_bash_execute("ps aux --sort=-%cpu | head -n 20", false, 300);
|
||||
}
|
||||
char sanitized[256];
|
||||
size_t j = 0;
|
||||
for (size_t i = 0; raw_filter[i] && j < sizeof(sanitized) - 1; i++) {
|
||||
unsigned char ch = (unsigned char)raw_filter[i];
|
||||
if (isalnum(ch) || ch == ' ' || ch == '.' || ch == '-' || ch == '_') {
|
||||
sanitized[j++] = (char)ch;
|
||||
}
|
||||
}
|
||||
sanitized[j] = '\0';
|
||||
if (!sanitized[0]) {
|
||||
return r_bash_execute("ps aux --sort=-%cpu | head -n 20", false, 300);
|
||||
}
|
||||
char cmd[512];
|
||||
snprintf(cmd, sizeof(cmd), "ps aux | grep -i '%s' | grep -v grep", json_object_get_string(filter_obj));
|
||||
snprintf(cmd, sizeof(cmd), "ps aux | grep -i '%s' | grep -v grep", sanitized);
|
||||
return r_bash_execute(cmd, false, 300);
|
||||
}
|
||||
return r_bash_execute("ps aux --sort=-%cpu | head -n 20", false, 300);
|
||||
} else if (strcmp(action, "kill") == 0) {
|
||||
struct json_object *pid_obj;
|
||||
if (!json_object_object_get_ex(args, "pid", &pid_obj)) {
|
||||
return strdup("Error: missing 'pid' for kill action");
|
||||
char *msg = strdup("Error: missing 'pid' for kill action");
|
||||
return msg;
|
||||
}
|
||||
char cmd[256];
|
||||
snprintf(cmd, sizeof(cmd), "kill -9 %d 2>&1", json_object_get_int(pid_obj));
|
||||
|
||||
@ -17,11 +17,11 @@ char *path_expand_home(const char *path) {
|
||||
|
||||
size_t home_len = strlen(home_dir);
|
||||
size_t path_len = strlen(path);
|
||||
char *expanded = malloc(home_len + path_len);
|
||||
char *expanded = malloc(home_len + path_len + 1);
|
||||
if (!expanded) return NULL;
|
||||
|
||||
strcpy(expanded, home_dir);
|
||||
strcat(expanded, path + 1);
|
||||
memcpy(expanded, home_dir, home_len);
|
||||
memcpy(expanded + home_len, path + 1, path_len);
|
||||
return expanded;
|
||||
}
|
||||
|
||||
@ -55,9 +55,9 @@ char *path_join(const char *base, const char *relative) {
|
||||
char *joined = malloc(total_len + 1);
|
||||
if (!joined) return NULL;
|
||||
|
||||
strcpy(joined, base);
|
||||
if (needs_sep) strcat(joined, "/");
|
||||
strcat(joined, relative);
|
||||
memcpy(joined, base, base_len);
|
||||
if (needs_sep) joined[base_len++] = '/';
|
||||
memcpy(joined + base_len, relative, rel_len + 1);
|
||||
|
||||
return joined;
|
||||
}
|
||||
@ -76,7 +76,8 @@ char *path_dirname(const char *path) {
|
||||
*last_sep = '\0';
|
||||
}
|
||||
} else {
|
||||
strcpy(copy, ".");
|
||||
copy[0] = '.';
|
||||
copy[1] = '\0';
|
||||
}
|
||||
|
||||
return copy;
|
||||
|
||||
101
src/utils.h
101
src/utils.h
@ -1,22 +1,9 @@
|
||||
// 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.
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
#ifndef UTILS_H
|
||||
#define UTILS_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
@ -50,20 +37,25 @@ void get_current_directory() {
|
||||
|
||||
char *expand_home_directory(const char *path) {
|
||||
if (path[0] != '~') {
|
||||
return strdup(path); // Return the original path if it doesn't start with ~
|
||||
return strdup(path);
|
||||
}
|
||||
|
||||
char *home_dir;
|
||||
const char *home_dir = NULL;
|
||||
|
||||
#ifdef _WIN32
|
||||
home_dir = getenv("USERPROFILE"); // Get home directory on Windows
|
||||
home_dir = getenv("USERPROFILE");
|
||||
#else
|
||||
struct passwd *pw = getpwuid(getuid());
|
||||
home_dir = pw->pw_dir; // Get home directory on Linux
|
||||
if (pw) {
|
||||
home_dir = pw->pw_dir;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (home_dir == NULL) {
|
||||
return NULL; // Error getting home directory
|
||||
if (!home_dir) {
|
||||
home_dir = getenv("HOME");
|
||||
}
|
||||
if (!home_dir) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
size_t home_len = strlen(home_dir);
|
||||
@ -81,66 +73,71 @@ char *expand_home_directory(const char *path) {
|
||||
}
|
||||
|
||||
unsigned long hash(const char *str) {
|
||||
unsigned long hash = 5381; // Starting value
|
||||
int c;
|
||||
unsigned long result = 5381;
|
||||
unsigned char c;
|
||||
|
||||
while ((c = *str++)) {
|
||||
hash = ((hash << 5) + hash) + c; // hash * 33 + c
|
||||
while ((c = (unsigned char)*str++)) {
|
||||
result = ((result << 5) + result) + c;
|
||||
}
|
||||
|
||||
return hash;
|
||||
return result;
|
||||
}
|
||||
|
||||
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++] = '/';
|
||||
}
|
||||
size_t path_len = strlen(path);
|
||||
if (path[0] == '/') {
|
||||
path++;
|
||||
path_len--;
|
||||
}
|
||||
|
||||
size_t path_len = strlen(path);
|
||||
if (pos + path_len >= sizeof(result)) {
|
||||
path_len = sizeof(result) - pos - 1;
|
||||
bool need_slash = (base_len > 0 && base_url[base_len - 1] != '/');
|
||||
size_t total = base_len + (need_slash ? 1 : 0) + path_len + 1;
|
||||
char *result = malloc(total);
|
||||
if (!result) return NULL;
|
||||
size_t pos = 0;
|
||||
memcpy(result, base_url, base_len);
|
||||
pos = base_len;
|
||||
if (need_slash) {
|
||||
result[pos++] = '/';
|
||||
}
|
||||
memcpy(result + pos, path, path_len);
|
||||
result[pos + path_len] = '\0';
|
||||
pos += path_len;
|
||||
result[pos] = '\0';
|
||||
return result;
|
||||
}
|
||||
|
||||
char *read_file(const char *path) {
|
||||
char *expanded_path = expand_home_directory(path);
|
||||
if (expanded_path == NULL) {
|
||||
if (!expanded_path) {
|
||||
return NULL;
|
||||
}
|
||||
FILE *file = fopen(expanded_path, "r");
|
||||
free(expanded_path);
|
||||
if (file == NULL) {
|
||||
expanded_path = NULL;
|
||||
if (!file) {
|
||||
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);
|
||||
if (size < 0) {
|
||||
fclose(file);
|
||||
return NULL;
|
||||
}
|
||||
fseek(file, 0, SEEK_SET);
|
||||
|
||||
char *buffer = malloc((size_t)size + 1);
|
||||
if (!buffer) {
|
||||
fclose(file);
|
||||
return NULL;
|
||||
}
|
||||
size_t bytes_read = fread(buffer, 1, (size_t)size, file);
|
||||
fclose(file);
|
||||
buffer[read] = '\0';
|
||||
if (bytes_read == 0 && size > 0) {
|
||||
free(buffer);
|
||||
buffer = NULL;
|
||||
return NULL;
|
||||
}
|
||||
buffer[bytes_read] = '\0';
|
||||
return buffer;
|
||||
}
|
||||
#endif
|
||||
|
||||
Loading…
Reference in New Issue
Block a user